方面建议其他方面
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>
我想让 PerformanceAdvice
的 log()
方法在 ValidationAdvice
的 validate()
方法上调用。 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
但定义了一个新的切入点。然后,在 AspectJ
中 IS 可能 (与 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>
我目前正在开发两个使用 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>
我想让 PerformanceAdvice
的 log()
方法在 ValidationAdvice
的 validate()
方法上调用。 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
但定义了一个新的切入点。然后,在 AspectJ
中 IS 可能 (与 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>