繁体   English   中英

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

[英]Spring AOP aspect doesn't work in multi-module project

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

我有一个名为 project-parent 的 Java Maven 项目,其中我有多个子模块项目。

其中一个项目叫做project-common,我把整个项目用到的所有自定义Spring AOP Aspects都放在那里。 我已经在 project-common 中编写了单元测试,并且该方面正如我在单元测试中所期望的那样正常工作。

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

我使用 Aspect 的服务如下:

  public class RequestServiceImpl implements RequestService {
      ...

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

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

根本原因

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

    @Configuration
    public class ServiceConfig {
        ...

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

    }

RequestServiceSupportAspect 在用 project-common 编写的单元测试中工作的原因是我们在方面定义上使用 @Component 并且在 project-common 中,有一个由 Spring 管理的 RequestServiceSupportAspect bean,因此方面工作。

但是在另一个子模块project-service中,使用@Component注解的Aspect Class默认不会创建Spring管理的Bean,因为它不在SpringBoot应用程序扫描的路径中。 您需要在 Config 类中手动声明一个 Bean 定义,或者您需要在 project-common 中定义一个 Aspect bean 并导入配置文件或让 project-commmon 通过配置 resources/META-INF/spring.factories 来公开它,如下所示:

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxxConfiguration

AOP 背后的工作原理

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

  1. 我首先开始检查我的服务 bean 是否已被代理。 答案是否定的,我只是看到一个原始 bean,所以我开始思考方面在运行时如何工作并代理对真实服务的直接调用,以便方面可以在其上添加其操作。
  2. 经过一番挖掘,我发现 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添加aspect代理的地方。 我在这里设置一个断点并检查出了什么问题。

  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 方法,您还会发现 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 Aspect 不起作用,我们如何调试问题

如果您发现您的 Aspect 不起作用,请在以下位置创建一个断点:

  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]

Aop.class

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

配置.class

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

应用.class

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM