Mig*_*ing 5 spring aspectj spring-aop
是否可以在应用程序启动并初始化上下文后以编程方式注册 AOP 建议?
当我尝试时,这些建议不起作用,据说是因为它们需要在 bean 在上下文中可用之前对其进行包装。
像这样(它不起作用):
@Bean
private AspectJExpressionPointcutAdvisor createPointcutAdvisor(AWSXRayRecorder awsxRayRecorder, String name, String pointcut) {
AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
advisor.setExpression("execution ...()");
advisor.setAdvice(new CustomAdvice("custom bean"));
return advisor;
}
Run Code Online (Sandbox Code Playgroud)
说明:我需要从配置文件中读取建议列表,并相应地注册切入点。我需要标签以用于预订目的。文件内容在编译时未知。
label: execution(* com.my.ns.OtherClass(..))
label2: execution(* com.my.ns.Class(..))
Run Code Online (Sandbox Code Playgroud)
也许根据 Spring AOP 手册以编程方式创建 @AspectJ 代理可以满足您的需求。引用那里的内容是因为只有外部链接的答案在 SO 上是不受欢迎的:
// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);
// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);
// you can also add existing aspect instances, the type of the
// object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);
// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();
Run Code Online (Sandbox Code Playgroud)
更新:
所以实际上我玩了一下,不是一个 Spring 用户,而是一个 AspectJ 专家。但无论如何,我找到了一种使用自定义切入点动态注册顾问的方法。但问题是,您需要知道要将其应用到哪些 Bean,并小心区分已代理的 Bean 和未代理的 Bean。
问题:在您的应用程序生命周期中,您希望将顾问程序添加到哪些 bean 中?您的其他 bean 是否已经实例化并连接(注入)到其他 bean 中?我问这个问题是因为将顾问程序注册到您直接引用的bean、将它们包装到代理中或将顾问程序添加到现有代理中非常容易。但是没有明显的方法来包装已经注入其他 Bean 且尚未代理的 Bean。因此,解决方案的简单或困难取决于您的要求。
PS:我仍然想知道为什么你的切入点位于属性文件中,而不是仅位于 Spring XML 配置文件中,这将是标准方式。该 XML 文件也会在应用程序启动期间加载。使用另一个文件的要求从何而来?两者基本上都是可编辑的(文本)资源文件。
更新 2:繁琐的手动解决方案,改编自另一个示例项目
好的,我已经为您创建了一个GitHub 存储库。只需使用 Maven 构建并使用该方法运行该类即可main(..)。它看起来像这样:
package de.scrum_master.performancemonitor;
import org.aopalliance.aop.Advice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class PerformanceApp {
public static DefaultPointcutAdvisor createAdvisor(String pointcutExpression, Advice advice) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(pointcutExpression);
return new DefaultPointcutAdvisor(pointcut, advice);
}
public static Object adviseIfNecessary(Object bean, DefaultPointcutAdvisor advisor) {
final String pointcutExpression = advisor.getPointcut().toString().replaceAll(".*\\(\\) ", "");
if (!advisor.getPointcut().getClassFilter().matches(bean.getClass())) {
System.out.println("Pointcut " + pointcutExpression + " does not match class " + bean.getClass());
return bean;
}
System.out.println("Pointcut " + pointcutExpression + " matches class " + bean.getClass() + ", advising");
Advised advisedBean = createProxy(bean);
advisedBean.addAdvisor(advisor);
return advisedBean;
}
public static Advised createProxy(Object bean) {
if (bean instanceof Advised) {
System.out.println("Bean " + bean + " is already an advised proxy, doing nothing");
return (Advised) bean;
}
System.out.println("Creating proxy for bean " + bean);
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(bean);
return (Advised) proxyFactory.getProxy();
}
public static void main(String[] args) {
DefaultPointcutAdvisor advisor = createAdvisor(
// Just load this from your YAML file as needed
"execution(public int de.scrum_master.performancemonitor.PersonService.getAge(..))",
new MyPerformanceMonitorInterceptor(true)
);
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfiguration.class);
Person person = (Person) adviseIfNecessary(context.getBean("person"), advisor);
PersonService personService = (PersonService) adviseIfNecessary(context.getBean("personService"), advisor);
System.out.println();
System.out.println("Name: " + personService.getFullName(person));
System.out.println("Age: " + personService.getAge(person));
System.out.println();
// BTW, you can also unadvise a bean like this.
// Write your own utility method for it if you need it.
((Advised) personService).removeAdvisor(advisor);
System.out.println("Name: " + personService.getFullName(person));
System.out.println("Age: " + personService.getAge(person));
}
}
Run Code Online (Sandbox Code Playgroud)
控制台日志如下所示:
Pointcut execution(public int de.scrum_master.performancemonitor.PersonService.getAge(..)) does not match class class de.scrum_master.performancemonitor.Person
Pointcut execution(public int de.scrum_master.performancemonitor.PersonService.getAge(..)) matches class class de.scrum_master.performancemonitor.PersonService$$EnhancerBySpringCGLIB$$965d1d14, advising
Bean de.scrum_master.performancemonitor.PersonService@2fd1433e is already an advised proxy, doing nothing
web - 2018-03-10 09:14:29,229 [main] TRACE d.s.performancemonitor.PersonService - StopWatch 'de.scrum_master.performancemonitor.PersonService.getFullName': running time (millis) = 2
Name: Albert Einstein
web - 2018-03-10 09:14:29,235 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution started at: Sat Mar 10 09:14:29 ICT 2018
web - 2018-03-10 09:14:29,332 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution lasted: 100 ms
web - 2018-03-10 09:14:29,332 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution ended at: Sat Mar 10 09:14:29 ICT 2018
web - 2018-03-10 09:14:29,332 [main] WARN d.s.performancemonitor.PersonService - Method execution longer than 10 ms!
Age: 146
web - 2018-03-10 09:14:29,334 [main] TRACE d.s.performancemonitor.PersonService - StopWatch 'de.scrum_master.performancemonitor.PersonService.getFullName': running time (millis) = 0
Name: Albert Einstein
Age: 146
Run Code Online (Sandbox Code Playgroud)
您可以清楚地看到顾问程序的日志输出是如何打印的。再次分离顾问程序后,日志输出将消失,仅AopConfiguration保留类中定义的顾问程序的日志输出。即,您可以将 Spring 配置与您自己的动态附加顾问程序混合使用。
顺便说一句,如果你像这样注释掉@Bean注释AopConfiguration
//@Bean
public Advisor performanceMonitorAdvisor() {
Run Code Online (Sandbox Code Playgroud)
PersonService那么当您附加动态顾问程序时,类将不会被代理,并且控制台输出更改为:
Pointcut execution(public int de.scrum_master.performancemonitor.PersonService.getAge(..)) does not match class class de.scrum_master.performancemonitor.Person
Pointcut execution(public int de.scrum_master.performancemonitor.PersonService.getAge(..)) matches class class de.scrum_master.performancemonitor.PersonService, advising
Creating proxy for bean de.scrum_master.performancemonitor.PersonService@6a03bcb1
Name: Albert Einstein
web - 2018-03-10 09:43:04,633 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution started at: Sat Mar 10 09:43:04 ICT 2018
web - 2018-03-10 09:43:04,764 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution lasted: 136 ms
web - 2018-03-10 09:43:04,769 [main] INFO d.s.performancemonitor.PersonService - Method de.scrum_master.performancemonitor.PersonService.getAge execution ended at: Sat Mar 10 09:43:04 ICT 2018
web - 2018-03-10 09:43:04,769 [main] WARN d.s.performancemonitor.PersonService - Method execution longer than 10 ms!
Age: 146
Name: Albert Einstein
Age: 146
Run Code Online (Sandbox Code Playgroud)
请注意,不仅 Spring 配置的顾问程序生成的日志行会按预期消失,而且该行也会消失
Bean de.scrum_master.performancemonitor.PersonService@2fd1433e is already an advised proxy, doing nothing
Run Code Online (Sandbox Code Playgroud)
更改为
Creating proxy for bean de.scrum_master.performancemonitor.PersonService@6a03bcb1
Run Code Online (Sandbox Code Playgroud)
更新 3:根据 James W 的回答,更优雅的解决方案
根据James W 的回答,我修改了我的解决方案,以便让 Spring 自动创建代理、顾问并让它添加顾问,请参阅commit @ff53e57。这完全归功于詹姆斯!正如我之前所说,我不是 Spring 用户,并且不知道方便的基类MethodInterceptor是此解决方案的关键,就像 James 所建议的那样。
作为参考,我保留了按需手动取消通知服务的技巧,只需修改代码即可获取对顾问的引用,因为现在它是由 Spring 创建的。更新后的主程序如下所示:
public class PerformanceApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AopConfiguration.class);
Person person = (Person) context.getBean("person");
PersonService personService = (PersonService) context.getBean("personService");
System.out.println("Name: " + personService.getFullName(person));
System.out.println("Age: " + personService.getAge(person));
System.out.println();
System.out.println("Unadvising PersonService bean");
Arrays.stream(((Advised) personService).getAdvisors())
.filter(advisor -> advisor.getAdvice() instanceof MyPerformanceMonitorInterceptor)
.findFirst()
.ifPresent(((Advised) personService)::removeAdvisor);
System.out.println("Name: " + personService.getFullName(person));
System.out.println("Age: " + personService.getAge(person));
}
}
Run Code Online (Sandbox Code Playgroud)
它产生以下输出:
web - 2021-01-11 10:18:09,277 [main] INFO d.s.p.MyPerformanceMonitorInterceptor - Method public java.lang.String de.scrum_master.performancemonitor.PersonService.getFullName(de.scrum_master.performancemonitor.Person) execution started at: Mon Jan 11 10:18:09 ICT 2021
web - 2021-01-11 10:18:09,293 [main] INFO d.s.p.MyPerformanceMonitorInterceptor - Method public java.lang.String de.scrum_master.performancemonitor.PersonService.getFullName(de.scrum_master.performancemonitor.Person) execution lasted: 18 ms
web - 2021-01-11 10:18:09,293 [main] INFO d.s.p.MyPerformanceMonitorInterceptor - Method public java.lang.String de.scrum_master.performancemonitor.PersonService.getFullName(de.scrum_master.performancemonitor.Person) execution ended at: Mon Jan 11 10:18:09 ICT 2021
web - 2021-01-11 10:18:09,293 [main] WARN d.s.p.MyPerformanceMonitorInterceptor - Method execution longer than 10 ms!
Name: Albert Einstein
Age: 149
Unadvising PersonService bean
Name: Albert Einstein
Age: 149
Run Code Online (Sandbox Code Playgroud)
小智 6
之前的解决方案太具有侵入性,因为它不仅即时创建建议,而且还处理建议 bean。这复制了 Spring 的 AbstractAdvisorAutoProxyCreator 的功能,特别是 getAdvicesAndAdvisorsForBean 方法,其中 Spring 将定位合格的顾问并将其应用于每个 bean。更好的方法是简单地以编程方式创建顾问,让 Spring 处理通知 bean、创建代理等的其余管道。
创建 Advisor 的一种简单方法是使用 @Bean 注释创建一个 Advisor bean:
@Bean
public Advisor advisorBean() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.testit.MyAspectedService.*(..))");
return new DefaultPointcutAdvisor(pointcut, new MyMethodInterceptor());
}
Run Code Online (Sandbox Code Playgroud)
其中 MyMethodInterceptor 类实现 MethodInterceptor 接口:
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import java.lang.reflect.Method;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("in interceptor");
//get the method and arguments we are intercepting
Method method = invocation.getMethod();
Object[] arguments = invocation.getArguments();
//... do stuff before the method
//call the underlying method
return invocation.proceed();
}
}
Run Code Online (Sandbox Code Playgroud)
这样做是为所有对声明为 Spring bean MyAspectedService 的方法调用创建一个名为“advisorBean”的环绕建议顾问 bean
@Service
public class MyAspectedService {
//various service methods
}
Run Code Online (Sandbox Code Playgroud)
这种方法专注于只创建必要的顾问和拦截实现,并将方面的编织委托给 Spring 框架。
| 归档时间: |
|
| 查看次数: |
3208 次 |
| 最近记录: |