Spriing Aop中动态Pointcut的实践

为什么需要AspectJ中具有动态值的Pointcut

工作中需要抽离出公司项目的一些监控组件打成一个公共包,需要对一些Aspect作动态适配(根据引入项目的ComponentScan进行特定package的Pointcut/配置PointCut)

@Pointcut的用法

  • 格式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?) 

修饰符匹配(modifier-pattern?)
返回值匹配(ret-type-pattern)可以为*表示任何返回值,全路径的类名等
类路径匹配(declaring-type-pattern?)
方法名匹配(name-pattern)可以指定方法名 或者 *代表所有, set* 代表以set开头的所有方法
参数匹配((param-pattern))可以指定具体的参数类型,多个参数间用“,”隔开,各个参数也可以用“*”来表示匹配任意类型的参数,如(String)表示匹配一个String参数的方法;(*,String) 表示匹配有两个参数的方法,第一个参数可以是任意类型,而第二个参数是String类型;可以用(..)表示零个或多个任意参数
异常类型匹配(throws-pattern?)
其中后面跟着“?”的是可选项

1)execution(* *(..))  
//表示匹配所有方法  
2)execution(public * com. savage.service.UserService.*(..))  
//表示匹配com.savage.server.UserService中所有的公有方法  
3)execution(* com.savage.server..*.*(..))  
//表示匹配com.savage.server包及其子包下的所有方法 
  • Spring中的Pointcut(Pointcut表示式(expression)和Pointcut签名(signature))
//Pointcut表示式(可以使用&& || ! 这三个运算)
@Pointcut("execution(* com.savage.aop.MessageSender.*(..))")
//Point签名
private void log(){} 

javassist实现动态Pointcut

  • 关于SpringAop

AspectJ方式织入的核心,是一个BeanPostProcess(会扫描所有的Pointcut与遍历所有Bean,并对需要的Bean进行织入-自动代理,当对象实例化的时候,为其生成代理对象并返回)

  • 思路

在Aop的BeanPostProcess执行之前( springApplication.run之前),使用javassist修改目标Aop类字节码,动态设置@Pointcut,设置 value为我们自己动态查询到的值。

  • 其他

由于Spring boot的类加载机制,运行时javassist会扫描不到包,要通过insertClassPath添加扫描路径

修改@Pointcut的切点值后,通过toClass覆盖原有类,需要通过类加载器重新加载。

  • 实现
@SpringBootApplication
public class UidServerApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        AspectPoincutScan();
        SpringApplication springApplication = new SpringApplication(UidServerApplication.class);
        springApplication.addInitializers(new UidApplicationContextInitializer());
        springApplication.run(args);
    }

    @Override
    protected SpringApplicationBuilder configure(
            SpringApplicationBuilder builder) {
        AspectPoincutScan();
        return builder.sources(UidServerApplication.class)
                .initializers(new UidApplicationContextInitializer())
                .listeners(new UidApplicationRefreshedListener())
                .listeners(new UidApplicationCloseListener());
    }


    private static void AspectPoincutScan() {
        try {
            ClassPool pool = ClassPool.getDefault();
            // 添加包的扫描路径
            ClassClassPath classPath = new ClassClassPath(UidServerApplication.class);
            pool.insertClassPath(classPath);
            //获取要修改的Class
            CtClass ct = pool.get("mobi.meishuo.uidserver.monitor.springaop.ServiceMonitorAop");
            CtMethod[] cms = ct.getDeclaredMethods();
            for (CtMethod cm : cms) {
                //找到@pointcut 注解的方法
                if (cm.getName().equals("pointcut")) {

                    MethodInfo methodInfo = cm.getMethodInfo();
                    ConstPool cPool = methodInfo.getConstPool();

                    AnnotationsAttribute attribute = new AnnotationsAttribute(cPool, AnnotationsAttribute.visibleTag);
                    //获取@pointcut 注解,修改其value值
                    Annotation annotation = new Annotation("org.aspectj.lang.annotation.Pointcut", cPool);
                    annotation.addMemberValue("value", new StringMemberValue("execution(xxxx", cPool));
                    attribute.setAnnotation(annotation);
                    methodInfo.addAttribute(attribute);

                    //覆盖原有类
                    ct.toClass();
                    //使用类加载器重新加载Aop类
                    pool.getClassLoader().loadClass("mobi.meishuo.uidserver.monitor.springaop.ServiceMonitorAop");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

编程式AOP实现动态PointCut

  • PonitCut
 public static Pointcut getAdapterServicePointcut(){
        AspectJExpressionPointcut adapterPointcut = new AspectJExpressionPointcut();
        //从配置文件中获取PointCut表达式
        adapterPointcut.setExpression(MonitorPropertyConfig.getPoinitcutAdapter());
        return adapterPointcut;}
        
//扩展Spring中AbstractBeanFactoryPointcutAdvisor

public class AdapterServiceAdvisor extends AbstractBeanFactoryPointcutAdvisor {

    @Override
    public Pointcut getPointcut() {
        return getAdapterServicePointcut();
    }

}
  • Advice
public class AdapterServiceMonitorInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
       //做一些操作...
    }
}
  • 配置Advisor Bean
@Configuration
public class MonitorProxyConfiguration {

    @Bean(name = "adapterServiceAdvisor")
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public AdapterServiceAdvisor adapterServiceAdvisor() {
        AdapterServiceAdvisor advisor = new AdapterServiceAdvisor();
        advisor.setAdviceBeanName("adapterServiceAdvice");
        advisor.setAdvice(new AdapterServiceMonitorInterceptor());
        advisor.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return advisor;
    }
}