是否可以通过编程方式注册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;
}
澄清:我需要从配置文件中读取建议列表,并相应地注册切入点。我需要标签用于书目目的。文件内容在编译时是未知的。
label: execution(* com.my.ns.OtherClass(..))
label2: execution(* com.my.ns.Class(..))
答案 0 :(得分:3)
先前的解决方案过于具有侵入性,因为它不仅可以即时创建建议,而且还可以为豆提供建议。这将复制Spring的AbstractAdvisorAutoProxyCreator的功能,特别是getAdvicesAndAdvisorsForBean方法,Spring将在其中定位合格的Advisor并将其应用于每个bean。更好的方法是简单地以编程方式创建Advisor,并让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());
}
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();
}
}
这是为所有声明为Spring bean MyAspectedService的方法调用创建一个名为“ advisorBean”的周围建议顾问豆
@Service
public class MyAspectedService {
//various service methods
}
此方法仅专注于创建必要的Advisor和拦截实现,并将方面的编织委托给Spring框架。
答案 1 :(得分:1)
根据Spring AOP手册,也许programmatic creation of @AspectJ Proxies可以做你想要的。从那里引用,因为只有外部链接的答案在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();
<强>更新强>
所以实际上我玩了一下,不是Spring用户,而是AspectJ专家。但无论如何,我找到了一种通过自定义切入点动态注册顾问程序的方法。但问题是,您需要知道要将其应用于哪些bean,并且要小心区分已经代理的bean和不代理的bean。
问题:在您的应用程序生命周期中以及您想要添加顾问程序的bean?您的其他bean是否已经实例化并连接(注入)其他bean?我问,因为很容易将顾问注册到您直接引用的bean,将它们包装到代理中或将顾问添加到现有代理中。但是没有明显的方法来包装已经注入其他bean但尚未代理的bean。因此,解决方案的难易程度取决于您的要求。
P.S。:我仍然想知道为什么你的切入点在属性文件中,而不仅仅是在Spring XML配置文件中,这将是标准方式。在应用程序启动期间也会加载该XML文件。使用其他文件的要求来自哪里?两者基本上都是可编辑的(文本)资源文件。
更新2:
好的,我为你创建了一个GitHub repo。只需使用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));
}
}
控制台日志如下所示:
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
您可以很好地了解顾问程序的日志输出是如何打印的。再次分离顾问程序后,日志输出消失,只有类AopConfiguration
中定义的顾问程序的日志输出仍然存在。即您可以将Spring配置与您自己动态连接的顾问程序混合使用。
顺便说一句,如果您在@Bean
中注释AopConfiguration
这样的注释
//@Bean
public Advisor performanceMonitorAdvisor() {
然后,当您附加动态顾问程序并且控制台输出更改为:
时,类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
请注意,由Spring配置的顾问程序产生的日志行不仅会按预期消失,而且行也会消失
Bean de.scrum_master.performancemonitor.PersonService@2fd1433e is already an advised proxy, doing nothing
更改为
Creating proxy for bean de.scrum_master.performancemonitor.PersonService@6a03bcb1