春季:在运行时以编程方式注册建议 [英] Spring: register advice programmatically at runtime

查看:97
本文介绍了春季:在运行时以编程方式注册建议的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否可以以编程方式在应用程序启动且上下文已初始化之后注册AOP建议?

Is it possible to register AOP advices programmatically, after the application has booted and the context has been initialized?

当我尝试时,这些建议没有用,可能是因为它们需要在Bean在上下文中可用之前包装Bean.

When I tried, the advices didn't work, supposedly because they need to wrap the bean BEFORE it gets available in the context.

类似的东西(不起作用):

Something like this (it doesn't work):

@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;
  }


说明:我需要从配置文件中读取建议列表,并相应地注册切入点.我需要此标签以用于保管.文件内容在编译时未知.


Clarification: I need to read a list of advice from a config file, and register the pointcuts accordingly. I need the label for bookeeping purposes. The file contents are unknown at compile time.

label: execution(* com.my.ns.OtherClass(..))
label2: execution(* com.my.ns.Class(..)) 

推荐答案

也许

Maybe programmatic creation of @AspectJ Proxies according to the Spring AOP manual does what you want. Quoting from there because answers with external links only are frowned upon on 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.

So actually I played around a bit, not being a Spring user but rather an AspectJ expert. But anyway I found a way to dynamically register an advisor with a custom pointcut. The thing is, though, you need to know which beans you want to apply it to, and be careful to differentiate between beans which are already proxied and ones which are not.

问题:在应用程序生命周期中以及何时要将顾问添加到哪些bean?您的其他bean是否已经实例化并连接(注入)到其他bean中?我之所以问是因为,将顾问直接注册到您直接引用的bean,将它们包装到代理中或将顾问添加到现有代理中是非常容易的.但是,没有明显的方法可以包装已经注入到其他bean中并且尚未被代理的bean.因此,解决方案的难易程度取决于您的要求.

Question: When in your application lifecycle and to which beans do you want to add the advisors? Have your other beans already been instantiated and wired (injected) into others? I am asking because it is quite easy to register advisors to beans you have direct references to, wrapping them into proxies or adding the advisors to existing proxies. But there is no obvious way to wrap a bean which has already been injected into other beans and not proxied yet. So how easy or difficult the solution is depends on your requirements.

P.S .:我仍然想知道为什么您的切入点是在属性文件中而不是仅在Spring XML配置文件中,这是标准方法.该XML文件也在应用程序启动期间加载.使用另一个文件的要求从何而来?两者基本上都是可编辑的(文本)资源文件.

P.S.: I am still wondering why your pointcuts are in a properties file instead of just in a Spring XML config file, which would be the standard way. That XML file is also loaded during application start-up. Where does the requirement to use another file come from? Both are basically editable (text) resource files.

更新2:

好的,我已经为您创建了一个 GitHub存储库.只需使用Maven构建并使用main(..)方法运行该类.看起来像这样:

Okay, I have created a GitHub repo for you. Just build with Maven and run the class with the main(..) method. It looks like this:

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中定义的顾问程序的日志输出. IE.您可以将Spring配置与您自己的动态附加的顾问程序混合使用.

You can nicely see how log output from the advisor gets printed. After detaching the advisor again, the log output goes away and only the log output from the advisor defined in class AopConfiguration remains. I.e. you can mix Spring configuration with your own dynamically attached advisors.

顺便说一句,如果您这样注释掉AopConfiguration中的@Bean批注

BTW, if you comment out the @Bean annotation in AopConfiguration like this

//@Bean
public Advisor performanceMonitorAdvisor() {

然后,当您附加动态顾问程序且控制台输出更改为:

then class PersonService will not be proxied already by the time you attach your dynamic advisor and the console output changes to:

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配置的顾问程序生成的日志行会按预期消失,而且该行也将消失

Please note that not only the log lines produces by the Spring-configured advisor go away as expected but that also the line

Bean de.scrum_master.performancemonitor.PersonService@2fd1433e is already an advised proxy, doing nothing

更改为

Creating proxy for bean de.scrum_master.performancemonitor.PersonService@6a03bcb1

这篇关于春季:在运行时以编程方式注册建议的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆