当前位置:   article > 正文

SpringBoot 实现热插拔AOP,非常实用!

我们要做的就是自定义一个advice的实现类然后,在用户想要开启日志的时候就把advic

戳上方蓝字“Java知音”关注我

现在有这么一个需求:就是我们日志的开与关是交给使用人员来控制的,而不是由我们开发人员固定写死的。大家都知道可以用aop来实现日志管理,但是如何动态的来实现日志管理呢?

aop源码中的实现逻辑中有这么一个步骤,就是会依次扫描Advice的实现类,然后执行。我们要做的就是自定义一个advice的实现类然后,在用户想要开启日志的时候就把advice加到项目中来,关闭日志的时候就把advice剔除就行了。

1前置知识

  • Advice:

org.aopalliance.aop.Advice

“通知”,表示 Aspect 在特定的 Join point 采取的操作。包括 “around”, “before” and “after 等 Advice,大体上分为了三类:BeforeAdviceMethodInterceptorAfterAdvice

  • Advisor:

org.springframework.aop.Advisor

“通知者”,它持有 Advice,是 Spring AOP 的一个基础接口。它的子接口 PointcutAdvisor 是一个功能完善接口,它涵盖了绝大部分的 Advisor。

  • Advised:

org.springframework.aop.framework.Advised

AOP 代理工厂配置类接口。提供了操作和管理 Advice 和 Advisor 的能力。它的实现类 ProxyFactory 是 Spring AOP 主要用于创建 AOP 代理类的核心类。

2热插拔AOP执行核心逻辑

b182bd17cc46f3ceb923e4d339f4841d.png

3核心实现代码

1、动态管理advice端点实现
  1. @RestControllerEndpoint(id = "proxy")  
  2. @RequiredArgsConstructor  
  3. public class ProxyMetaDefinitionControllerEndPoint {  
  4.   
  5.     private final ProxyMetaDefinitionRepository proxyMetaDefinitionRepository;  
  6.   
  7.   
  8.     @GetMapping("listMeta")  
  9.     public List<ProxyMetaDefinition> getProxyMetaDefinitions(){  
  10.        return proxyMetaDefinitionRepository.getProxyMetaDefinitions();  
  11.     }  
  12.   
  13.     @GetMapping("{id}")  
  14.     public ProxyMetaDefinition getProxyMetaDefinition(@PathVariable("id") String proxyMetaDefinitionId){  
  15.         return proxyMetaDefinitionRepository.getProxyMetaDefinition(proxyMetaDefinitionId);  
  16.     }  
  17.   
  18.     @PostMapping("save")  
  19.     public String save(@RequestBody ProxyMetaDefinition definition){  
  20.   
  21.         try {  
  22.             proxyMetaDefinitionRepository.save(definition);  
  23.             return "success";  
  24.         } catch (Exception e) {  
  25.   
  26.         }  
  27.         return "fail";  
  28.   
  29.     }  
  30.   
  31.     @PostMapping("delete/{id}")  
  32.     public String delete(@PathVariable("id")String proxyMetaDefinitionId){  
  33.         try {  
  34.             proxyMetaDefinitionRepository.delete(proxyMetaDefinitionId);  
  35.             return "success";  
  36.         } catch (Exception e) {  
  37.   
  38.         }  
  39.         return "fail";  
  40.     }  
  41.   
  42. }
2、利用事件监听机制捕获安装或者卸载插件
  1. @RequiredArgsConstructor  
  2. public class ProxyMetaDefinitionChangeListener {  
  3.   
  4.     private final AopPluginFactory aopPluginFactory;  
  5.   
  6.     @EventListener  
  7.     public void listener(ProxyMetaDefinitionChangeEvent proxyMetaDefinitionChangeEvent){  
  8.         ProxyMetaInfo proxyMetaInfo = aopPluginFactory.getProxyMetaInfo(proxyMetaDefinitionChangeEvent.getProxyMetaDefinition());  
  9.         switch (proxyMetaDefinitionChangeEvent.getOperateEventEnum()){  
  10.             case ADD:  
  11.                 aopPluginFactory.installPlugin(proxyMetaInfo);  
  12.                 break;  
  13.             case DEL:  
  14.                 aopPluginFactory.uninstallPlugin(proxyMetaInfo.getId());  
  15.                 break;  
  16.         }  
  17.   
  18.     }  
  19. }
3、安装插件
  1. public void installPlugin(ProxyMetaInfo proxyMetaInfo){  
  2.         if(StringUtils.isEmpty(proxyMetaInfo.getId())){  
  3.             proxyMetaInfo.setId(proxyMetaInfo.getProxyUrl() + SPIILT + proxyMetaInfo.getProxyClassName());  
  4.         }  
  5.         AopUtil.registerProxy(defaultListableBeanFactory,proxyMetaInfo);  
  6.     }
4、安装插件核心实现
  1. public static void registerProxy(DefaultListableBeanFactory beanFactory,ProxyMetaInfo proxyMetaInfo){  
  2.         AspectJExpressionPointcutAdvisor advisor = getAspectJExpressionPointcutAdvisor(beanFactory, proxyMetaInfo);  
  3.         addOrDelAdvice(beanFactory,OperateEventEnum.ADD,advisor);  
  4.   
  5.     }  
  6.   
  7.     private static AspectJExpressionPointcutAdvisor getAspectJExpressionPointcutAdvisor(DefaultListableBeanFactory beanFactory, ProxyMetaInfo proxyMetaInfo) {  
  8.         BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();  
  9.         GenericBeanDefinition beanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();  
  10.         beanDefinition.setBeanClass(AspectJExpressionPointcutAdvisor.class);  
  11.         AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();  
  12.         advisor.setExpression(proxyMetaInfo.getPointcut());  
  13.         advisor.setAdvice(Objects.requireNonNull(getMethodInterceptor(proxyMetaInfo.getProxyUrl(), proxyMetaInfo.getProxyClassName())));  
  14.         beanDefinition.setInstanceSupplier((Supplier<AspectJExpressionPointcutAdvisor>) () -> advisor);  
  15.         beanFactory.registerBeanDefinition(PROXY_PLUGIN_PREFIX + proxyMetaInfo.getId(),beanDefinition);  
  16.   
  17.         return advisor;  
  18.     }
5、卸载插件
  1. public void uninstallPlugin(String id){  
  2.         String beanName = PROXY_PLUGIN_PREFIX + id;  
  3.         if(defaultListableBeanFactory.containsBean(beanName)){  
  4.            AopUtil.destoryProxy(defaultListableBeanFactory,id);  
  5.         }else{  
  6.             throw new NoSuchElementException("Plugin not found: " + id);  
  7.         }  
  8.     }
6、卸载插件核心实现
  1. public static void destoryProxy(DefaultListableBeanFactory beanFactory,String id){  
  2.         String beanName = PROXY_PLUGIN_PREFIX + id;  
  3.         if(beanFactory.containsBean(beanName)){  
  4.             AspectJExpressionPointcutAdvisor advisor = beanFactory.getBean(beanName,AspectJExpressionPointcutAdvisor.class);  
  5.             addOrDelAdvice(beanFactory,OperateEventEnum.DEL,advisor);  
  6.             beanFactory.destroyBean(beanFactory.getBean(beanName));  
  7.         }  
  8.     }
7、操作advice实现
  1. public static void addOrDelAdvice(DefaultListableBeanFactory beanFactory, OperateEventEnum operateEventEnum,AspectJExpressionPointcutAdvisor advisor){  
  2.         AspectJExpressionPointcut pointcut = (AspectJExpressionPointcut) advisor.getPointcut();  
  3.         for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {  
  4.             Object bean = beanFactory.getBean(beanDefinitionName);  
  5.             if(!(bean instanceof Advised)){  
  6.                 if(operateEventEnum == OperateEventEnum.ADD){  
  7.                     buildCandidateAdvised(beanFactory,advisor,bean,beanDefinitionName);  
  8.                 }  
  9.                 continue;  
  10.             }  
  11.             Advised advisedBean = (Advised) bean;  
  12.             boolean isFindMatchAdvised = findMatchAdvised(advisedBean.getClass(),pointcut);  
  13.             if(operateEventEnum == OperateEventEnum.DEL){  
  14.                 if(isFindMatchAdvised){  
  15.                     advisedBean.removeAdvice(advisor.getAdvice());  
  16.                     log.info("########################################## Remove Advice -->【{}】 For Bean -->【{}】 SUCCESS !",advisor.getAdvice().getClass().getName(),bean.getClass().getName());  
  17.                 }  
  18.             }else if(operateEventEnum == OperateEventEnum.ADD){  
  19.                 if(isFindMatchAdvised){  
  20.                     advisedBean.addAdvice(advisor.getAdvice());  
  21.                     log.info("########################################## Add Advice -->【{}】 For Bean -->【{}】 SUCCESS !",advisor.getAdvice().getClass().getName(),bean.getClass().getName());  
  22.                 }  
  23.             }  
  24.   
  25.   
  26.         }  
  27.     }

4热插拔AOP演示示例

1、创建一个service
  1. @Service  
  2. @Slf4j  
  3. public class HelloService implements BeanNameAware, BeanFactoryAware {  
  4.     private BeanFactory beanFactory;  
  5.   
  6.     private String beanName;  
  7.   
  8.     @SneakyThrows  
  9.     public String sayHello(String message) {  
  10.         Object bean = beanFactory.getBean(beanName);  
  11.         log.info("============================ {} is Advised : {}",bean, bean instanceof Advised);  
  12.         TimeUnit.SECONDS.sleep(new Random().nextInt(3));  
  13.         log.info("============================ hello:{}",message);  
  14.   
  15.         return "hello:" + message;  
  16.   
  17.     }  
  18.   
  19.     @Override  
  20.     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {  
  21.         this.beanFactory = beanFactory;  
  22.     }  
  23.   
  24.     @Override  
  25.     public void setBeanName(String name) {  
  26.         this.beanName = name;  
  27.     }  
  28. }
2、创建一个controller
  1. @RestController  
  2. @RequestMapping("hello")  
  3. @RequiredArgsConstructor  
  4. public class HelloController {  
  5.   
  6.     private final HelloService helloService;  
  7.   
  8.     @GetMapping("{message}")  
  9.     public String sayHello(@PathVariable("message")String message){  
  10.         return helloService.sayHello(message);  
  11.     }  
  12. }
3、准备一个日志切面jar
1214a44321aa652f63889b6127873a11.png

切面内容为

  1. @Slf4j  
  2. public class LogMethodInterceptor implements MethodInterceptor {  
  3.     @Override  
  4.     public Object invoke(MethodInvocation invocation) throws Throwable {  
  5.         Object result;  
  6.         try {  
  7.             result = invocation.proceed();  
  8.         } finally {  
  9.            log.info(">>>>>>>>>>>>>>>>>>>>>>>>TargetClass:【{}】,method:【{}】,args:【{}】",invocation.getThis().getClass().getName(),invocation.getMethod().getName(), Arrays.toString(invocation.getArguments()));  
  10.         }  
  11.   
  12.         return result;  
  13.     }  
  14. }
4、测试

场景一:未添加切面时 浏览器访问:http://localhost:8080/hello/zhangsan 观察控制台

bb5c058d80bc15c512dc05d10ec53557.png

场景二:通过postman动态操作代理

1、新增代理
18e7ada20ebaf61a08406c00c415eaab.png
图片

观察控制台

  1. ########################################## 
  2. BuildCandidateAdvised 
  3. -->【com.github.lybgeek.aop.test.hello.service.HelloService】 With Advice 
  4. -->【com.github.lybgeek.interceptor.LogMethodInterceptor】 SUCCESS !

此时浏览器访问:ttp://localhost:8080/hello/zhangsan

再次观察控制台

38a0b0d8cf90e7ab1acda9825aa6b671.png

出现了切面日志信息,说明代理生效

2、删除代理
2de2c298ab9908bec7aac5d513bfc206.png

观察控制台

  1. ########################################## 
  2. Remove Advice 
  3. -->【com.github.lybgeek.interceptor.LogMethodInterceptor】 For Bean 
  4. -->【com.github.lybgeek.aop.test.hello.service.HelloService$$EnhancerBySpringCGLIB$$7bc75aa3】 SUCCESS !

此时浏览器访问:ttp://localhost:8080/hello/zhangsan

再次观察控制台

ccf79cb8b7aa7386c05d1409adf6d66a.png

此时没有出现切面日志信息,说明代理删除成功

5总结

本文实现热插拔AOP就在于对advice、advised、advisor、pointcut概念的理解,这是实现热插拔AOP的前提,其次就是对自定义classloader也需要有一定的了解,因为我们jar不一定从classpath底下加载,也有可能来源其他地方,比如远程链接啥的,最后就是把原先spring自动帮我们实现aop。

我们利用相关的api,自己手动实现一遍,示例代码的api只是利用spring api其中一种实现方式,它还有多种实现方式,比如可以利用TargetSource,感兴趣的朋友,也可以自己实现一把。

至于那个代理增删改查端点contoller,是我之前看springcloud gateway的路由定位器端点源码,一直没找到机会实现一下,就把他搬来这个示例实现一把,加深一下印象!

感谢阅读,希望对你有所帮助 :)   

来源:JAVA日知录

  1. 后端专属技术群
  2. 构建高质量的技术交流社群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!
  3. 文明发言,以交流技术、职位内推、行业探讨为主
  4. 广告人士勿入,切勿轻信私聊,防止被骗
  5. 加我好友,拉你进群
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/86667?site
推荐阅读
相关标签
  

闽ICP备14008679号