使用spring的方式,避免proxy类

背景:项目中有这种写法:

@Slf4j
@Component
public class MoaServiceProxy {
...
    public void appendRecommond(List<LiveFrameAnchorRecommondDTO> dtos){
        recommondService.appendRecommond(dtos);
        BizLogUtils.bizLog("rank-moa-appendRecommond", JsonUtils.toJSON(dtos));
    }
    @Cacheable(value = CaffeineConfig.MoaProxy, key = "'getAnchorGradeMedalLabelByUserIds'.concat(#userIds)")
    public Map<String, Label> getAnchorGradeMedalLabelByUserIds(List<String> userIds){
        return gradeService.getAnchorGradeMedalLabelByUserIds(userIds);
    }

作用是通过把rpc方法调用套写一遍,添加日志或缓存的功能。
但这样写,加深了编码层级,调用时也不清楚调用的哪个服务了。亲手套写方法,也浪费了spring的能力。

下面展示一下,使用spring自定义注解的方式,在保持直接注入rpc接口对象的前提下,添加日志或缓存的功能。
最终使用效果:

    /**
     * 成就服务 - 带业务日志和缓存
     * 替代原来的: moaServiceProxy.completeAchievement(userId, achievementId, expireSeconds)
     */
    @Resource
    @BizLogUtils("'rank-moa-'.concat(#methodName)")
    @CacheableAll(value = CaffeineConfig.MoaProxy, key = "#methodName.concat(#arg0).concat(#arg1)")
    private AchievementMoaService achievementMoaService;

1、核心代码BeanPostProcessor,作用是在注入接口对象时,对需要扩展功能的接口加一层代理:

/**
 * MOA服务字段注解处理器
 * 自动处理带有@BizLogUtils和@CacheableAll注解的字段
 */
@Slf4j
@Component
public class MoaServiceFieldProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> clazz = bean.getClass();
        
        // 遍历所有字段
        for (Field field : clazz.getDeclaredFields()) {
            BizLogUtils bizLogAnnotation = field.getAnnotation(BizLogUtils.class);
            CacheableAll cacheableAnnotation = field.getAnnotation(CacheableAll.class);
            
            // 如果字段有这两个注解,则创建代理
            if (bizLogAnnotation != null || cacheableAnnotation != null) {
                try {
                    field.setAccessible(true);
                    Object originalService = field.get(bean);
                    
                    if (originalService != null) {
                        // 创建代理对象
                        Object proxy = MoaServiceProxyFactory.createProxy(
                            originalService, 
                            bizLogAnnotation, 
                            cacheableAnnotation
                        );
                        
                        // 替换原始对象
                        field.set(bean, proxy);
                        
                        log.info("Created MOA service proxy for field: {}.{}", 
                                clazz.getSimpleName(), field.getName());
                    }
                } catch (Exception e) {
                    log.error("Failed to create MOA service proxy for field: {}.{}", 
                            clazz.getSimpleName(), field.getName(), e);
                }
            }
        }
        
        return bean;
    }
}

2、代理工厂:

/**
 * MOA服务代理工厂
 * 用于创建带有缓存和日志功能的MOA服务代理
 */
@Slf4j
public class MoaServiceProxyFactory {

    private static final ExpressionParser parser = new SpelExpressionParser();
    
    @Resource
    private CacheManager cacheManager;

    /**
     * 创建MOA服务代理
     * @param originalService 原始服务
     * @param bizLogAnnotation 业务日志注解
     * @param cacheableAnnotation 缓存注解
     * @return 代理对象
     */
    public static Object createProxy(Object originalService, 
                                   BizLogUtils bizLogAnnotation, 
                                   CacheableAll cacheableAnnotation) {
        
        return Proxy.newProxyInstance(
            originalService.getClass().getClassLoader(),
            originalService.getClass().getInterfaces(),
            new MoaServiceInvocationHandler(originalService, bizLogAnnotation, cacheableAnnotation)
        );
    }

    /**
     * MOA服务调用处理器
     */
    private static class MoaServiceInvocationHandler implements InvocationHandler {
        
        private final Object originalService;
        private final BizLogUtils bizLogAnnotation;
        private final CacheableAll cacheableAnnotation;
        private final CacheManager cacheManager;

        public MoaServiceInvocationHandler(Object originalService, 
                                         BizLogUtils bizLogAnnotation, 
                                         CacheableAll cacheableAnnotation) {
            this.originalService = originalService;
            this.bizLogAnnotation = bizLogAnnotation;
            this.cacheableAnnotation = cacheableAnnotation;
            this.cacheManager = SpringContextUtil.getBean(CacheManager.class);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            
            // 构建缓存key
            String cacheKey = null;
            if (cacheableAnnotation != null) {
                cacheKey = buildCacheKey(cacheableAnnotation.key(), methodName, args);
            }

            
            // 尝试从缓存获取
            if (cacheableAnnotation != null) {
                Cache cache = cacheManager.getCache(cacheableAnnotation.value());
                if (cache != null) {
                    Cache.ValueWrapper cachedValue = cache.get(cacheKey);
                    if (cachedValue != null) {
                        // 记录业务日志
                        if (bizLogAnnotation != null) {
                            // 构建业务日志key
                            String bizLogKey = buildBizLogKey(bizLogAnnotation.value(), methodName, args);
                            logBizLog(bizLogKey, args, cachedValue.get(), false);
                        }
                        return cachedValue.get();
                    }
                }
            }
            
            // 执行原始方法
            Object result = method.invoke(originalService, args);
            
            // 记录业务日志
            if (bizLogAnnotation != null) {
                // 构建业务日志key
                String bizLogKey = buildBizLogKey(bizLogAnnotation.value(), methodName, args);
                logBizLog(bizLogKey, args, result, false);
            }

            // 缓存结果
            if (cacheableAnnotation != null && result != null) {
                Cache cache = cacheManager.getCache(cacheableAnnotation.value());
                if (cache != null) {
                    cache.put(cacheKey, result);
                }
            }
            
            return result;
        }

        private String buildCacheKey(String cacheKeyExpression, String methodName, Object[] args) {
            try {
                Expression expression = parser.parseExpression(cacheKeyExpression);
                EvaluationContext context = new StandardEvaluationContext();
                
                // 设置方法名
                context.setVariable("methodName", methodName);
                
                // 设置参数
                for (int i = 0; i < args.length; i++) {
                    context.setVariable("arg" + i, args[i]);
                }
                
                return expression.getValue(context, String.class);
            } catch (Exception e) {
                log.warn("Failed to build cache key for expression: {}", cacheKeyExpression, e);
                return methodName;
            }
        }

        private String buildBizLogKey(String bizLogExpression, String methodName, Object[] args) {
            try {
                Expression expression = parser.parseExpression(bizLogExpression);
                EvaluationContext context = new StandardEvaluationContext();
                
                // 设置方法名
                context.setVariable("methodName", methodName);
                
                // 设置参数
                for (int i = 0; i < args.length; i++) {
                    context.setVariable("arg" + i, args[i]);
                }
                
                return expression.getValue(context, String.class);
            } catch (Exception e) {
                log.warn("Failed to build biz log key for expression: {}", bizLogExpression, e);
                return "rank-moa-" + methodName;
            }
        }

        private void logBizLog(String bizLogKey, Object[] args, Object result, boolean fromCache) {
            try {
                String logContent = String.join(",", Arrays.toString(args));
                if (result != null) {
                    logContent += "," + result.toString();
                }
                if (fromCache) {
                    logContent += ",fromCache=true";
                }
                com.immomo.jplive.base.util.BizLogUtils.bizLog(bizLogKey, logContent);
            } catch (Exception e) {
                log.warn("Failed to log biz log: {}", bizLogKey, e);
            }
        }
    }
}

3、定义扩展功能注解:

/**
 * 字段级别缓存注解
 * 用于字段级别的缓存配置
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheableAll {
    
    /**
     * 缓存配置名称
     */
    String value();
    
    /**
     * 缓存key表达式,支持SpEL表达式
     * 例如: "#methodName.concat(#userIds)"
     */
    String key();
}
/**
 * 业务日志注解
 * 用于字段级别的业务日志记录
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BizLogUtils {
    
    /**
     * 业务日志key表达式,支持SpEL表达式
     * 例如: "'rank-moa-'.concat(#methodName)"
     */
    String value();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容