springAOP之调用失效与解决方案

2022-08-15 13:06:31

前言

在spring 中使用 @Transactional 、 @Cacheable 或 自定义 AOP
注解时,会发现个问题:在对象内部的方法中调用该对象的其他使用AOP注解的方法,被调用方法的AOP注解失效。

问题描述
前些天做一个第三方服务的开关,为了灵活应用,我设计了一个自定义注解,通过注解属性去匹配数据库记录查找配置是否开启。然而调用的时候出了问题。
原代码如下:

@Transactional(propagation= Propagation.NOT_SUPPORTED)publicbooleansaveUserThirdLevelRisk(String userId, String userVersionType, String riskType)throws ServicesException{.........insertIziWhatsAppDetail(userId, tUser.getPhone());returnfalse;}@ThirdPartyRequestSwitch(name="IziWhatsAppDetail")publicvoidinsertIziWhatsAppDetail(String userId, String phone){//保存TIziWhatsappDetailLog
    TIziWhatsappDetailLog tIziWhatsappDetailLog= tIziWhatsappDetailLogManager.getOneByMasterId(userId);if(tIziWhatsappDetailLog== null){
        tIziWhatsappDetailLog=newTIziWhatsappDetailLog();
        tIziWhatsappDetailLog.setId(IdGen.get().nextId());
        tIziWhatsappDetailLog.setPhone(phone);
        tIziWhatsappDetailLog.setUserId(userId);
        tIziWhatsappDetailLogManager.save(tIziWhatsappDetailLog);//过1分钟开始跑WhatsApp详版的第三方接口
        DelayJob job=newDelayJob();
        job.setAClass(WhatsAppDetailJob.class);
        Map jobParam=newHashMap();
        jobParam.put("userId", userId);
        jobParam.put("type","izi");
        job.setJobParams(jobParam);
        delayJobProducer.submitJob(job, Long.valueOf(1), TimeUnit.MINUTES);}}@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interfaceThirdPartyRequestSwitch{booleanqueryDBConfig()defaulttrue;booleanopen()defaultfalse;
    Stringname()default"";}@Slf4j@Aspect@ComponentpublicclassThirdPartyRequestSwitchAspect{@Pointcut("@annotation(com.yf.function.thirdParty.config.ThirdPartyRequestSwitch)")publicvoidpointCut(){}@Autowiredprivate TThirdPartySwitchConfigManager tThirdPartySwitchConfigManager;@Around("pointCut()")publicvoiddoAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
        ThirdPartyRequestSwitch thirdPartyRequestSwitch=((MethodSignature)proceedingJoinPoint.getSignature()).getMethod().getAnnotation(ThirdPartyRequestSwitch.class);if(thirdPartyRequestSwitch.queryDBConfig()&&!StringUtils.isEmpty(thirdPartyRequestSwitch.name())){
            String value= tThirdPartySwitchConfigManager.findValueByName(thirdPartyRequestSwitch.name());if(CommonConstant.ONE.equals(value)){
                proceedingJoinPoint.proceed();}}}}

经过测试,我单独去跑insertIziWhatsAppDetail这个方法的时候是可以断点到ThirdPartyRequestSwitchAspect
.doAround方法,证明AOP是有效的。但是通过跑风控任务调用saveUserThirdLevelRisk方法时却不能走到ThirdPartyRequestSwitchAspect
.doAround方法断点里。

查找原因
在springaop调用时是生成了代理对象去调用目标方法。而在该次代码编写中insertIziWhatsAppDetail方法的Aop没有作用,是因为没有用SpringAop的代理对象去调用目标方法而是用原对象本身调用目标方法,SpringAop最终执行的invoke方法是代理对象($xxxx类)调用的。

解决方法
1.避免方法内部调用(将需要代理的目标方法写到不同的类里)
通过注入的方式注入whatsAppDetailService,使之托付于Spring管理,这样SpringAop会生成代理对象去调用insertIziWhatsAppDetail方法相关的切面方法。

whatsAppDetailService.insertIziWhatsAppDetail(userId, tUser.getPhone());

2.Spring解决方案
通过AopContext.currentProxy获取当前代理对象,通过代理对象调用方法。最好的方法是避免在方法内部调用。
修改XML 新增如下语句;先开启cglib代理,开启 exposeProxy = true,暴露代理对象

<aop:aspectj-autoproxyproxy-target-class="true"expose-proxy="true"/>

如果写到同一个类中,insertIziWhatsAppDetail是该类(XXXService)的私有方法,在该类(XXXService)中调用可以采用以下方式。

(XXXService)AopContext.currentProxy().insertIziWhatsAppDetail();

3.Springboot解决方法

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(AspectJAutoProxyRegistrar.class)public @interfaceEnableAspectJAutoProxy{booleanproxyTargetClass()defaultfalse;}

通过实现ApplicationContext获取代理对象。新建获取代理对象的工具类SpringUtil

@ComponentpublicclassSpringUtilimplementsApplicationContextAware{privatestatic ApplicationContext applicationContext;@OverridepublicvoidsetApplicationContext(ApplicationContext applicationContext)throws BeansException{if(SpringUtil.applicationContext== null){
            SpringUtil.applicationContext= applicationContext;}}//获取applicationContextpublicstatic ApplicationContextgetApplicationContext(){return applicationContext;}//通过name获取 Bean.publicstatic ObjectgetBean(String name){returngetApplicationContext().getBean(name);}//通过class获取Bean.publicstatic<T> TgetBean(Class<T> clazz){returngetApplicationContext().getBean(clazz);}//通过name,以及Clazz返回指定的Beanpublicstatic<T> TgetBean(String name,Class<T> clazz){returngetApplicationContext().getBean(name, clazz);}

调用方式(利用ApplicationContext 生成代理对象调用):

SpringUtil.getBean(this.getClass()).insertIziWhatsAppDetail()
  • 作者:代码没写完哪有脸睡觉
  • 原文链接:https://blog.csdn.net/qq_37205597/article/details/104446377
    更新时间:2022-08-15 13:06:31