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



让我把我的问题简化如下:

我有一个名为projectparent的JavaMaven项目,其中有多个子模块项目。

其中一个项目被称为公共项目,我把整个项目中使用的所有自定义的SpringAOP特性都放在那里。我已经在公共项目中编写了单元测试,该方面在单元测试中可以正常工作。

然后,我想在其他模块中应用这些特性。其中一个子模块称为项目服务。我应用于服务中方法的方面应该在服务方法之前和之后进行身份验证管理。但是,我发现这些特性在服务运行时不起作用。此外,项目服务对公共项目有一个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) {
...
}
}

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

根本原因

根本原因是方面不是Spring在项目服务中管理的Bean。只需在项目服务的Config类中添加以下内容即可解决问题:

@Configuration
public class ServiceConfig {
...
@Bean
public RequestServiceSupportAspect requestServiceSupportAspect() {
return new RequestServiceSupportAspect();
}
}

RequestServiceSupportAspect在以公共项目编写的单元测试中工作的原因是,我们在方面定义上使用@Component,在公共项目中,有一个由Spring管理的RequestServiceSupportAspect bean,因此方面工作。

然而,在另一个子模块项目服务中,用@Component注释的AspectClass默认情况下不会创建由Spring管理的Bean,因为它不在SpringBoot应用程序扫描的路径中。您需要在Config类中手动声明Bean定义,或者在项目公共中定义Aspectbean并导入Config文件,或者通过配置resources/META-INF/spring.factories让项目commmon公开它,如下所示:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxxConfiguration

AOP如何在幕后工作

以上的解释应该已经解决了问题。但如果你对我如何到达那里感兴趣,下面可能会提供一些提示。

  1. 我首先开始检查我的服务bean是否已被代理。答案是否定的,我只看到了一个原始的bean,所以我开始思考这个方面在运行时是如何工作的,并将直接调用代理到真正的服务,这样这个方面就可以在上面添加它的操作
  2. 经过一番挖掘,我发现BeanPostProcessor是一个重要的切入点。首先,我们可以深入到以下注释链:
@EnableAspectJAutoProxy
--> AspectJAutoProxyRegistrar
--> AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary
--> AnnotationAwareAspectJAutoProxyCreator.class

如果您看到AnnotationwareAspectJAutoProxyCreator的层次结构,它实现了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. 我开始阅读AnnotationwareAspectJAutoProxyCreator是如何实现该方法的,我发现它是它的基类AbstractAutoProxyCreater,它实现如下:
@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;
}

很明显,wrapIfNenecessary是向Bean添加方面代理的地方。我在这里做一个转折点,检查出了什么问题。

  1. 在wrapIfNenecessary方法中,我发现当创建服务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的地方。

它使用singleton模式中常见的代码初始化了aspectNames一次,稍后将在BeanNameAutoProxyCreator::getAdvicesAndAdvisorsForBean中使用该代码来获取您创建的方面。

然后我发现,正是这个项目中没有包含的AspectBean使我的Aspect无法工作。

  1. 如果您查看wrapIfNenecessary方法,您还会发现Spring AOP将为其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添加条件断点筛选器,一步一步执行将找到根本原因。

摘要

尽管调查过程需要我一些时间,但最终,根本原因很简单。然而,在我们的日常工作中,我们中的一些人可能很容易忽视这一点。这就是为什么我在这里发布我的答案,以便将来如果有人遇到类似的问题,该帖子可能会为他们节省一些时间。

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

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

我遇到了这个错误,并用这种方法解决了它项目结构如下

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

A级

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

配置类

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

应用程序.类

@SpringBootApplication
@Import(value = [Config::class])
class Application
fun main(args: Array<String>) {
runApplication<TagApplication>(*args)
}

最新更新