方面建议其他方面

Aspect advising other aspects

我目前正在开发两个使用 Spring-AOP 的 Spring 应用程序。我有一个允许简单性能日志记录的方面,它定义如下:

@Aspect
final class PerformanceAdvice {
    private Logger logger = LoggerFactory.getLogger("perfLogger");

    public Object log(final ProceedingJoinPoint call) throws Throwable {
        logger.info("Logging statistics.");
    }
}

然后可以通过 Spring AOP 配置使用以下 XML 创建此建议:

<bean id="performanceAdvice" class="com.acme.PerformanceAdvice" />

<aop:config>
    <aop:aspect ref="performanceAdvice">
        <aop:around pointcut="execution(* com.acme..*(..))" method="log"/>
    </aop:aspect>
</aop:config>

这适用于由 Spring 创建的 classes,例如用 @Service 注释的 classes。但我希望这方面也能在二级项目中建议其他方面。我知道 Spring 不支持这一点,如 their docs:

中所述

In Spring AOP, it is not possible to have aspects themselves be the target of advice from other aspects. The @Aspect annotation on a class marks it as an aspect, and hence excludes it from auto-proxying.

因此我可能需要更强大的东西,例如 AspectJ。或者是否可以让 Spring 意识到这个方面并仍然允许提供建议?从 Whosebug 上的许多其他问题(与这个特定问题没有直接关系)我尝试制作方面 @Configurable,通过将它们定义为 @Component 使它们 Spring-aware 并播放周围有各种 XML 和插件设置,例如:

<context:spring-configured />
<context:annotation-config/>
<context:load-time-weaver/>
<aop:aspectj-autoproxy/>

我现在 运行 没主意了。我是否需要编写成熟的 AspectJ 方面?如果是这样,它是否可以使用相同的配置,例如 Spring,引用现有方面并定义新的切入点?这将很有用,因此我不必为 Project 1 重新处理 PerformanceAdvice,但仍会在 Project 2.

中引用和使用它

编辑 : 为了让自己更清楚,我有以下示例。

我在 Project 2 有服务。

@Service
public class TargetSpringServiceImpl implements TargetSpringService {
    @Override
    public String doSomeComplexThings(String parameter) {
        return "Complex stuff";
    }
}

调用此方法时,我有一个方面会进行一些验证。

@Aspect
public class ValidationAdvice {
    @Autowired
    ValidationService validationService

    public void validate(JoinPoint jp) throws Throwable {
        //Calls the validationService to validate the parameters
    }
}

使用以下切入点作为执行:

<bean id="validationAdvice" class="com.acme.advice.ValidationAdvice" />

<aop:config>
    <aop:aspect ref="validationAdvice">
        <aop:before pointcut="execution(* com.acme.service..*(..))" method="validate"/>
    </aop:aspect>
</aop:config>

我想让 PerformanceAdvicelog() 方法在 ValidationAdvicevalidate() 方法上调用。 NOT TargetSpringService class 的 doSomeComplexThings() 方法。因为这只是一个额外的切入点。问题出在建议另一方面的方法上。

首先Spring不使用aspectJ实现AOP而是JDK或者cglib动态代理。这里的aspectJ风格只是指语法。

Aspectj 方面是静态的,使用在编译或 class 加载时应用的字节码注入。

然后spring只能在它管理的对象上应用代理,因为在依赖注入期间应用了动态代理。此外,如果您有 2 个不同的项目,您必须确保它们共享相同的 spring 上下文,否则它们将被隔离并且将第一个项目的方面应用到第二个项目的 beans 将不起作用。

是的,您可能必须在这里使用真正的 aspectJ 方面。出于性能记录的目的,它更适合,因为几乎不会影响性能。

所以(据我了解)您想在第二个项目中重复使用第一个项目的建议。但此外,您还想在建议中添加更多逻辑。这可以通过将 project-1 的建议用 project-2 中的自定义实现。 你可以通过包装额外的建议来做到这一点(参见 Advice ordering):

项目 1 需要一些小的修改(或者实现 Ordered 你可以使用 @Order 注释 / 当然你也可以注入顺序而不是硬编码):

public class LoggingPerformanceAdvice implements org.springframework.core.Ordered {

    Object log(final ProceedingJoinPoint pjp) throws Throwable {
       // does the logging/statistics
       ...
       Object result = pjp.proceed(pjp.getArgs());
       ...
       return result;
    }

    @Override
    public int getOrder() {
       return Ordered.LOWEST_PRECEDENCE;
    }
}

在您的 项目 2

中创建自定义建议实施
public class AdditionalPerformanceAdvice implements Ordered {

   Object log(final ProceedingJoinPoint pjp) throws Throwable {
      ...
      Object result = pjp.proceed(pjp.getArgs());
      ...
      return result;
   }

    @Override
    public int getOrder() {
       return Ordered.HIGHEST_PRECEDENCE;
    }
}

将组件连接在一起:

<!-- component of project 1 -->
<bean id="loggingPerformanceAdvice" class="com.acme.project1.LoggingPerformanceAdvice"/>
<!-- component of project 2 -->
<bean id="additionalPerformanceAdvice" class="com.acme.project2.AdditionalPerformanceAdvice"/>

<aop:config>
   <aop:aspect ref="loggingPerformanceAdvice">
      <aop:around pointcut="execution(* com.acme..*(..))" method="log"/>
   </aop:aspect>
   <aop:aspect ref="additionalPerformanceAdvice">
      <aop:around pointcut="execution(* com.acme..*(..))" method="log"/>
   </aop:aspect>
</aop:config> 

首先调用 Ordered.HIGHEST_PRECEDENCE 方面。

如果我知道你想对方面建议执行操作,我会做类似的事情(使用 aspectj 可以更改为 spring 注释):

public abstract aspect AspectPerformanceLogging {

    private static Logger LOG = LoggerFactory.getLogger(AspectPerformanceLogging.class);

    before() : methodExecution() {
        LOG.info("Loggin stat");
        doBefore(thisJoinPoint);
        LOG.info("Loggin stat end");
    }

    after() : methodExecution() {
        LOG.info("Loggin stat");
        doAfter(thisJoinPoint);
        LOG.info("Loggin stat end");
    }

    Object around() : methodExecution() {
        LOG.info("Loggin stat");
        Object result = doAround((ProceedingJoinPoint)thisJoinPoint);
        LOG.info("Loggin stat end");
        return result;
    }

    protected abstract pointcut methodExecution();

    protected abstract Object doBefore(JoinPoint joinPoint);

    protected abstract Object doAfter(JoinPoint joinPoint);

    protected abstract Object doAround(ProceedingJoinPoint joinPoint);
}

然后我创建其他方面来扩展这个方面。

我找到了两个可能的解决方案来解决我的问题。一个实际上是在建议方面,另一个解决了这个问题,但实际上更优雅。

方案一:建议方面

AspectJ中几乎可以编织任何东西。借助 AspectJ LTW documentation 中所述的 META-INF/aop.xml 文件,我可以引用方面并按以下方式定义新的切入点。

项目 1 的更改

PerformanceAdvice

为了允许 AspectJ 定义一个新的切入点,通知必须是 abstract 并且有一个抽象的 pointcut 方法可以挂钩。

@Aspect
final class PerformanceAdvice extends AbstractPerformanceAdvice {
    @Override
    void externalPointcut(){}
}

@Aspect
public abstract class AbstractPerformanceAdvice {
    private Logger logger = LoggerFactory.getLogger("perfLogger");

    @Pointcut
    abstract void externalPointcut();

    @Around("externalPointcut()")
    public Object log(final ProceedingJoinPoint call) throws Throwable {
        logger.info("Logging statistics.");
    }
}

项目 2 的更改

META-INF/aop.xml

aop.xml 文件定义了一个名为 ConcretePerformanceAdvice 的新方面。它也扩展了 AbstractPerformanceAdvice 但定义了一个新的切入点。然后,在 AspectJIS 可能 (与 Spring-AOP 不同) 定义另一个方面的切入点。

<aspectj>
    <aspects>
        <concrete-aspect name="com.example.project2.ConcretePerformanceAdvice" extends="com.example.project1.AbstractPerformanceAdvice">
            <pointcut name="externalPointcut" expression="execution(* com.example.project2.ValidationAdvice.validate(..))"/>
        </concrete-aspect>
    </aspects>    
    <weaver options="-verbose"/>
</aspectj>

pom.xml

编织方面需要一些仪器。这需要依赖项和插件来执行它。至于依赖,是这样的:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-instrument</artifactId>
    <version>${org.springframework.version}</version>
</dependency>

目前,在测试期间,我通过 surefire-plugin 进行检测。这需要以下位:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.8</version>
            <configuration>
                <forkMode>once</forkMode>
                <argLine>
                    -javaagent:"${settings.localRepository}/org/springframework/spring-instrument/${org.springframework.version}/spring-instrument-${org.springframework.version}.jar"
                </argLine>
                <useSystemClassloader>true</useSystemClassloader>
            </configuration>
        </plugin>
    </plugins>
</build>

Spring 上下文

要启用加载时编织,还需要激活编织。因此必须将以下内容添加到 Spring 上下文中。

<context:load-time-weaver/>

解决方案 2:委托给 Spring bean

Spring-AOP不允许方面建议其他方面。但它确实允许 运行 当然是 Spring @Component 的建议。因此,另一个解决方案是将建议中完成的验证移动到另一个 Spring bean。这个 Spring bean 然后被自动连接到建议中并执行,但是 PerformanceAdvice 在验证 component 而不是验证 上有它的切入点方面。所以它看起来像下面这样:

项目 1 的更改

None!

项目 2 的更改

建议自动装配 Spring @Component 并将其逻辑委托给组件。

@Aspect
public class ValidationAdvice {
    @Autowired
    private ValidatorDefault validatorDefault;

    public void validate(JoinPoint jp) throws Throwable {
        validatorDefault.validate(jp);
    }
}

@Component
public class ValidatorDefault {
    @Autowired
    ValidationService validationService

    public void validate(JoinPoint jp) throws Throwable {
        //Calls the validationService to validate the parameters
    }
}

然后在 Spring 上下文中可以在 @Component 上定义切入点,而 ValidationAdvice 自动装配 @Component.

<!-- Scan the package to find the ValidatorDefault component for autowiring -->
<context:component-scan base-package="com.example.project2" />

<bean id="validationAdvice" class="com.example.project2.ValidationAdvice" />
<bean id="performanceAdvice" class="com.example.project1.PerformanceAdvice" />

<aop:config>
    <aop:aspect ref="validationAdvice">
        <aop:before pointcut="execution(* com.acme.service..*.*(..))" method="validate"/>
    </aop:aspect>
    <aop:aspect ref="performanceAdvice">
        <aop:around pointcut="execution(* com.example.project2.ValidatorDefault.validate(..))" method="log"/>
    </aop:aspect>
</aop:config>