java Spring AOP方面在多模块项目中不起作用

5jvtdoz2  于 2023-01-19  发布在  Java
关注(0)|答案(3)|浏览(384)

让我把问题简化如下:
我有一个名为project-parent的Java Maven项目,其中有多个子模块项目。
其中一个项目叫做project-common,我把整个项目中使用的所有自定义的Spring AOP方面都放在里面,我用project-common编写了单元测试,方面在单元测试中工作正常。
然后,我想将这些方面应用到其他模块中,其中一个子模块叫做project-service,我应用到服务中方法的方面应该在服务方法之前和之后进行auth管理,但是我发现这些方面在服务运行时不工作,而且project-service对project-common有一个maven依赖。
项目结构如下

project-parent
  -- project-common (in which define the aspect)
  -- project-service (where my aspect is used)
  ...
  -- other submodules omitted for simplicity

我的方面定义如下:

@Aspect
    @Component
    public class RequestServiceSupportAspect {
        @Pointcut(value = "@annotation(RequestServiceSupport)")
        public void matchMethod() {
            // pointcut
        }

        @Around("matchMethod()")
        public Object basicAuthSupport(ProceedingJoinPoint joinPoint) {
            ...
        }
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface RequestServiceSupport {
    }

我使用方面的服务如下:

public class RequestServiceImpl implements RequestService {
      ...

      @RequestServiceSupport
      public Request addComment(Comment comment) {
          ...
      }
  }
kuuvgm7e

kuuvgm7e1#

我最终解决了这个问题,并有机会了解Spring AOP在后台是如何工作的,以及如果它不工作,我们如何调试这种AOP方面的问题。

根本原因

根本原因是该方面不是由Spring在project-service中管理的Bean,只需在project-service的Config类中添加以下内容即可解决该问题:

@Configuration
    public class ServiceConfig {
        ...

        @Bean
        public RequestServiceSupportAspect requestServiceSupportAspect() {
            return new RequestServiceSupportAspect();
        }

    }

RequestServiceSupportAspect在用project-common编写的单元测试中工作的原因是,我们在方面定义上使用了@Component,并且在project-common中,有一个由Spring管理的RequestServiceSupportAspect bean,因此方面可以工作。
但是在另一个子模块项目服务中,标注了@Component的Aspect类默认不会创建Spring管理的Bean。因为它不在SpringBoot应用程序扫描的路径中。您需要在Config类中手动声明Bean定义,或者需要在project-common中定义Aspect Bean并导入Config文件,或者让project-commmon通过配置resources/META-INF/spring来公开它。工厂如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxxConfiguration

AOP如何在后面工作

上面的解释应该已经解决了这个问题。但是如果你对我如何到达那里感兴趣,下面可能会提供一些提示。
1.我首先开始检查我的服务bean是否被代理了,答案是否定的,我只看到了一个原始bean,所以我开始思考方面在运行时是如何工作的,并代理对真实的服务的直接调用,以便方面可以在服务上添加它的动作。
1.经过一番挖掘,我发现BeanPostProcessor是一个需要深入研究的关键入口点。首先,我们可以深入下面的注解链:

@EnableAspectJAutoProxy
    --> AspectJAutoProxyRegistrar
    --> AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary
    --> AnnotationAwareAspectJAutoProxyCreator.class

如果你看到AnnotationAwareAspectJAutoProxyCreator的层次结构,它实现了BeanPostProcessor接口,这是合理的,因为这样Spring就可以将代理添加到绑定了方面的类中。它有两个方法要实现:

public interface BeanPostProcessor {
  
  @Nullable
  default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }
  
  @Nullable
  default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }
}

1.我开始阅读AnnotationAwareAspectJAutoProxyCreator是如何实现该方法的,我发现是它的基类AbstractAutoProxyCreator实现了该方法,如下所示:

@Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
    throws BeansException {
      if (bean != null) {
         Object cacheKey = getCacheKey(bean.getClass(), beanName);
         if (!this.earlyProxyReferences.contains(cacheKey)) {
            return **wrapIfNecessary**(bean, beanName, cacheKey);
         }
      }
      return bean;
    }

很明显wrapIfNecessary是方面代理被添加到Bean的地方,我在这里做了一个断点并检查哪里出错了。
1.在wrapIfNecessary方法中,我发现当我的服务bean被创建时,它进入DO_NOT_PROXY的分支。

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
  if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
      return bean;
  }
  if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
  }
  if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
  }

  // Create proxy if we have advice.
  Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
  if (specificInterceptors != DO_NOT_PROXY) {
      this.advisedBeans.put(cacheKey, Boolean.TRUE);
      Object proxy = createProxy(
          bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
  }

  this.advisedBeans.put(cacheKey, Boolean.FALSE);
  return bean;
}

原因是getAdvicesAndAdvisorsForBean没有返回我想要的方面。
我深入研究getAdvicesAndAdvisorsForBean,发现BeanFactoryAspectJAdvisorsBuilder::buildAspectJAdvisors是导入所有候选Bean的地方。
它使用单例模式中常见的代码对aspectNames进行了一次初始化,这些代码稍后将在BeanNameAutoProxyCreator::getAdvicesAndAdvisorsForBean中使用,以获取您创建的方面。
然后我发现是这个项目中没有包含的Aspect Bean导致我的Aspect无法工作。
1.如果您查看wrapIfNecessary方法,您还会发现SpringAOP将为其bean类创建的不同代理

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

    ...
  }

如果AOP方面不工作,我们如何调试问题

如果您发现方面不工作,请在以下位置设置断点:

AbstractAutoProxyCreator::postProcessAfterInitialization() -> wrapIfNecessary

为您想要添加方面的服务bean添加条件断点过滤器,逐步执行将引导您找到根本原因。

摘要

虽然调查过程需要一些时间,但最后的根本原因是相当简单和直接的。但在我们的日常工作中,有些人很容易忽略它。这就是为什么我把我的答案张贴在这里,以便将来有人遇到类似的问题,张贴可以保存一些时间。

h9a6wy2h

h9a6wy2h2#

您可以只添加(或扩展)@ComponentScan来使方面组件可访问,而不是显式地提供@Bean方面。

@ComponentScan("my.aspect.package") // << use relevant package from 'project-common'
@Configuration
public class ServiceConfig {
   ...
}
83qze16e

83qze16e3#

我遇到了这个bug,用这个方法解决了它。项目结构如下

parent-project
----common[Aop.class ,Config.class]
----service[Application]

Aop.class

@Aspect
@Component
class Aop {    
@Around("@annotation(aop package)")
fun around(pjp: ProceedingJoinPoint):Any?{
    do something...
    }
}

Config.class

@ComponentScan("Aop package")
@Configuration
class Config {
@Bean
fun createAop(): Aop {        
    return Aop()
}
}

Application.class

@SpringBootApplication
@Import(value = [Config::class])
class Application

fun main(args: Array<String>) {
runApplication<TagApplication>(*args)
}

相关问题