使用spring的方式,避免proxy類

背景:項(xiàng)目中有這種寫法:

@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方法調(diào)用套寫一遍,添加日志或緩存的功能。
但這樣寫,加深了編碼層級(jí),調(diào)用時(shí)也不清楚調(diào)用的哪個(gè)服務(wù)了。親手套寫方法,也浪費(fèi)了spring的能力。

下面展示一下,使用spring自定義注解的方式,在保持直接注入rpc接口對(duì)象的前提下,添加日志或緩存的功能。
最終使用效果:

    /**
     * 成就服務(wù) - 帶業(yè)務(wù)日志和緩存
     * 替代原來的: 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,作用是在注入接口對(duì)象時(shí),對(duì)需要擴(kuò)展功能的接口加一層代理:

/**
 * MOA服務(wù)字段注解處理器
 * 自動(dòng)處理帶有@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);
            
            // 如果字段有這兩個(gè)注解,則創(chuàng)建代理
            if (bizLogAnnotation != null || cacheableAnnotation != null) {
                try {
                    field.setAccessible(true);
                    Object originalService = field.get(bean);
                    
                    if (originalService != null) {
                        // 創(chuàng)建代理對(duì)象
                        Object proxy = MoaServiceProxyFactory.createProxy(
                            originalService, 
                            bizLogAnnotation, 
                            cacheableAnnotation
                        );
                        
                        // 替換原始對(duì)象
                        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服務(wù)代理工廠
 * 用于創(chuàng)建帶有緩存和日志功能的MOA服務(wù)代理
 */
@Slf4j
public class MoaServiceProxyFactory {

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

    /**
     * 創(chuàng)建MOA服務(wù)代理
     * @param originalService 原始服務(wù)
     * @param bizLogAnnotation 業(yè)務(wù)日志注解
     * @param cacheableAnnotation 緩存注解
     * @return 代理對(duì)象
     */
    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服務(wù)調(diào)用處理器
     */
    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();
            
            // 構(gòu)建緩存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) {
                        // 記錄業(yè)務(wù)日志
                        if (bizLogAnnotation != null) {
                            // 構(gòu)建業(yè)務(wù)日志key
                            String bizLogKey = buildBizLogKey(bizLogAnnotation.value(), methodName, args);
                            logBizLog(bizLogKey, args, cachedValue.get(), false);
                        }
                        return cachedValue.get();
                    }
                }
            }
            
            // 執(zhí)行原始方法
            Object result = method.invoke(originalService, args);
            
            // 記錄業(yè)務(wù)日志
            if (bizLogAnnotation != null) {
                // 構(gòu)建業(yè)務(wù)日志key
                String bizLogKey = buildBizLogKey(bizLogAnnotation.value(), methodName, args);
                logBizLog(bizLogKey, args, result, false);
            }

            // 緩存結(jié)果
            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();
                
                // 設(shè)置方法名
                context.setVariable("methodName", methodName);
                
                // 設(shè)置參數(shù)
                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();
                
                // 設(shè)置方法名
                context.setVariable("methodName", methodName);
                
                // 設(shè)置參數(shù)
                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、定義擴(kuò)展功能注解:

/**
 * 字段級(jí)別緩存注解
 * 用于字段級(jí)別的緩存配置
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheableAll {
    
    /**
     * 緩存配置名稱
     */
    String value();
    
    /**
     * 緩存key表達(dá)式,支持SpEL表達(dá)式
     * 例如: "#methodName.concat(#userIds)"
     */
    String key();
}
/**
 * 業(yè)務(wù)日志注解
 * 用于字段級(jí)別的業(yè)務(wù)日志記錄
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BizLogUtils {
    
    /**
     * 業(yè)務(wù)日志key表達(dá)式,支持SpEL表達(dá)式
     * 例如: "'rank-moa-'.concat(#methodName)"
     */
    String value();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容