Spring:在运行时以编程方式注册建议

时间:2018-03-02 15:48:59

标签: 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;
  }

澄清:我需要从配置文件中读取建议列表,并相应地注册切入点。我需要标签用于书目目的。文件内容在编译时是未知的。

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

2 个答案:

答案 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