首页 > 其他分享 >SpringBoot 实现热插拔AOP,非常实用!

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

时间:2024-02-20 09:26:24浏览次数:36  
标签:SpringBoot beanFactory 热插拔 AOP id advisor proxyMetaInfo public String

现在有这么一个需求:就是我们日志的开与关是交给使用人员来控制的,而不是由我们开发人员固定写死的。大家都知道可以用aop来实现日志管理,但是如何动态的来实现日志管理呢?aop源码中的实现逻辑中有这么一个步骤,就是会依次扫描Advice的实现类,然后执行。我们要做的就是自定义一个advice的实现类然后,在用户想要开启日志的时候就把advice加到项目中来,关闭日志的时候就把advice剔除就行了。

前置知识

  • Advice:

org.aopalliance.aop.Advice

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

  • Advisor:

org.springframework.aop.Advisor

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

  • Advised:

org.springframework.aop.framework.Advised

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

热插拔AOP执行核心逻辑

image

图片

核心实现代码

1、动态管理advice端点实现

@RestControllerEndpoint(id = "proxy")
@RequiredArgsConstructor
public class ProxyMetaDefinitionControllerEndPoint {

    private final ProxyMetaDefinitionRepository proxyMetaDefinitionRepository;


    @GetMapping("listMeta")
    public List<ProxyMetaDefinition> getProxyMetaDefinitions(){
       return proxyMetaDefinitionRepository.getProxyMetaDefinitions();
    }

    @GetMapping("{id}")
    public ProxyMetaDefinition getProxyMetaDefinition(@PathVariable("id") String proxyMetaDefinitionId){
        return proxyMetaDefinitionRepository.getProxyMetaDefinition(proxyMetaDefinitionId);
    }

    @PostMapping("save")
    public String save(@RequestBody ProxyMetaDefinition definition){

        try {
            proxyMetaDefinitionRepository.save(definition);
            return "success";
        } catch (Exception e) {

        }
        return "fail";

    }

    @PostMapping("delete/{id}")
    public String delete(@PathVariable("id")String proxyMetaDefinitionId){
        try {
            proxyMetaDefinitionRepository.delete(proxyMetaDefinitionId);
            return "success";
        } catch (Exception e) {

        }
        return "fail";
    }

}

2、利用事件监听机制捕获安装或者卸载插件

@RequiredArgsConstructor
public class ProxyMetaDefinitionChangeListener {

    private final AopPluginFactory aopPluginFactory;

    @EventListener
    public void listener(ProxyMetaDefinitionChangeEvent proxyMetaDefinitionChangeEvent){
        ProxyMetaInfo proxyMetaInfo = aopPluginFactory.getProxyMetaInfo(proxyMetaDefinitionChangeEvent.getProxyMetaDefinition());
        switch (proxyMetaDefinitionChangeEvent.getOperateEventEnum()){
            case ADD:
                aopPluginFactory.installPlugin(proxyMetaInfo);
                break;
            case DEL:
                aopPluginFactory.uninstallPlugin(proxyMetaInfo.getId());
                break;
        }

    }
}

3、安装插件

public void installPlugin(ProxyMetaInfo proxyMetaInfo){
        if(StringUtils.isEmpty(proxyMetaInfo.getId())){
            proxyMetaInfo.setId(proxyMetaInfo.getProxyUrl() + SPIILT + proxyMetaInfo.getProxyClassName());
        }
        AopUtil.registerProxy(defaultListableBeanFactory,proxyMetaInfo);
    }

4、安装插件核心实现

public static void registerProxy(DefaultListableBeanFactory beanFactory,ProxyMetaInfo proxyMetaInfo){
        AspectJExpressionPointcutAdvisor advisor = getAspectJExpressionPointcutAdvisor(beanFactory, proxyMetaInfo);
        addOrDelAdvice(beanFactory,OperateEventEnum.ADD,advisor);

    }

    private static AspectJExpressionPointcutAdvisor getAspectJExpressionPointcutAdvisor(DefaultListableBeanFactory beanFactory, ProxyMetaInfo proxyMetaInfo) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        GenericBeanDefinition beanDefinition = (GenericBeanDefinition) builder.getBeanDefinition();
        beanDefinition.setBeanClass(AspectJExpressionPointcutAdvisor.class);
        AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
        advisor.setExpression(proxyMetaInfo.getPointcut());
        advisor.setAdvice(Objects.requireNonNull(getMethodInterceptor(proxyMetaInfo.getProxyUrl(), proxyMetaInfo.getProxyClassName())));
        beanDefinition.setInstanceSupplier((Supplier<AspectJExpressionPointcutAdvisor>) () -> advisor);
        beanFactory.registerBeanDefinition(PROXY_PLUGIN_PREFIX + proxyMetaInfo.getId(),beanDefinition);

        return advisor;
    }

5、卸载插件

public void uninstallPlugin(String id){
        String beanName = PROXY_PLUGIN_PREFIX + id;
        if(defaultListableBeanFactory.containsBean(beanName)){
           AopUtil.destoryProxy(defaultListableBeanFactory,id);
        }else{
            throw new NoSuchElementException("Plugin not found: " + id);
        }
    }

6、卸载插件核心实现

public static void destoryProxy(DefaultListableBeanFactory beanFactory,String id){
        String beanName = PROXY_PLUGIN_PREFIX + id;
        if(beanFactory.containsBean(beanName)){
            AspectJExpressionPointcutAdvisor advisor = beanFactory.getBean(beanName,AspectJExpressionPointcutAdvisor.class);
            addOrDelAdvice(beanFactory,OperateEventEnum.DEL,advisor);
            beanFactory.destroyBean(beanFactory.getBean(beanName));
        }
    }

7、操作advice实现

public static void addOrDelAdvice(DefaultListableBeanFactory beanFactory, OperateEventEnum operateEventEnum,AspectJExpressionPointcutAdvisor advisor){
        AspectJExpressionPointcut pointcut = (AspectJExpressionPointcut) advisor.getPointcut();
        for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
            Object bean = beanFactory.getBean(beanDefinitionName);
            if(!(bean instanceof Advised)){
                if(operateEventEnum == OperateEventEnum.ADD){
                    buildCandidateAdvised(beanFactory,advisor,bean,beanDefinitionName);
                }
                continue;
            }
            Advised advisedBean = (Advised) bean;
            boolean isFindMatchAdvised = findMatchAdvised(advisedBean.getClass(),pointcut);
            if(operateEventEnum == OperateEventEnum.DEL){
                if(isFindMatchAdvised){
                    advisedBean.removeAdvice(advisor.getAdvice());
                    log.info("########################################## Remove Advice -->【{}】 For Bean -->【{}】 SUCCESS !",advisor.getAdvice().getClass().getName(),bean.getClass().getName());
                }
            }else if(operateEventEnum == OperateEventEnum.ADD){
                if(isFindMatchAdvised){
                    advisedBean.addAdvice(advisor.getAdvice());
                    log.info("########################################## Add Advice -->【{}】 For Bean -->【{}】 SUCCESS !",advisor.getAdvice().getClass().getName(),bean.getClass().getName());
                }
            }


        }
    }

热插拔AOP演示示例

1、创建一个service

@Service
@Slf4j
public class HelloService implements BeanNameAware, BeanFactoryAware {
    private BeanFactory beanFactory;

    private String beanName;

    @SneakyThrows
    public String sayHello(String message) {
        Object bean = beanFactory.getBean(beanName);
        log.info("============================ {} is Advised : {}",bean, bean instanceof Advised);
        TimeUnit.SECONDS.sleep(new Random().nextInt(3));
        log.info("============================ hello:{}",message);

        return "hello:" + message;

    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
}

2、创建一个controller

@RestController
@RequestMapping("hello")
@RequiredArgsConstructor
public class HelloController {

    private final HelloService helloService;

    @GetMapping("{message}")
    public String sayHello(@PathVariable("message")String message){
        return helloService.sayHello(message);
    }
}

3、准备一个日志切面jar

image

图片

切面内容为

@Slf4j
public class LogMethodInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Object result;
        try {
            result = invocation.proceed();
        } finally {
           log.info(">>>>>>>>>>>>>>>>>>>>>>>>TargetClass:【{}】,method:【{}】,args:【{}】",invocation.getThis().getClass().getName(),invocation.getMethod().getName(), Arrays.toString(invocation.getArguments()));
        }

        return result;
    }
}

4、测试

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

image

图片

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

1、新增代理

image

观察控制台

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

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

再次观察控制台

image

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

2、删除代理

image

观察控制台

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

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

再次观察控制台
image

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

总结

本文实现热插拔AOP就在于对advice、advised、advisor、pointcut概念的理解,这是实现热插拔AOP的前提,其次就是对自定义classloader也需要有一定的了解,因为我们jar不一定从classpath底下加载,也有可能来源其他地方,比如远程链接啥的,最后就是把原先spring自动帮我们实现aop,我们利用相关的api,自己手动实现一遍,示例代码的api只是利用spring api其中一种实现方式,它还有多种实现方式,比如可以利用TargetSource,感兴趣的朋友,也可以自己实现一把。

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

标签:SpringBoot,beanFactory,热插拔,AOP,id,advisor,proxyMetaInfo,public,String
From: https://www.cnblogs.com/paylove/p/18022363

相关文章

  • java普通项目转springboot项目
    添加启动类@SpringBootApplicationpublicclassSpringBootMain{publicstaticvoidmain(String[]args){SpringApplication.run(SpringBootMain.class,args);}}添加依赖<parent><groupId>org.springframework.boot</grou......
  • springboot 单元测试
      packagecom.cx.shop.scheduler.test;importcom.cx.shop.SchedulerProviderApplication;importcom.cx.shop.scheduler.service.TaskJobService;importorg.junit.Test;importorg.junit.runner.RunWith;importorg.springframework.boot.test.context.SpringBoot......
  • springboot中,各部分的作用
    一个优秀的Controller层逻辑,它的定位,我认为是「不可或缺的配角」 说它是配角是因为Controller层的代码一般是不负责具体的逻辑业务逻辑实现,但是它负责接收和响应请求 Controller主要的工作有以下几项接收请求并解析参数调用Service执行具体的业务代码(可能包含参数......
  • Spring/SpringBoot 拦截器
    Spring/SpringBoot拦截器拦截器的作用:拦截器,可以进行请求过滤、权限管理、打印日志、数据校验等。拦截器,可以在请求前、请求后进行处理。代码示例:拦截器MyInterceptor:Spring的拦截器,需要实现HandlerInterceptor接口。publicclassMyInterceptorimplementsHandler......
  • Java21 + SpringBoot3使用Spring Security时如何在子线程中获取到认证信息
    目录前言原因分析解决方案方案1:手动设置线程中的认证信息方案2:使用DelegatingSecurityContextRunnable创建线程方案3:修改SpringSecurity安全策略通过设置JVM参数修改安全策略通过SecurityContextHolder修改安全策略总结前言近日心血来潮想做一个开源项目,目标是做一款可以适配多......
  • Skywalking-Aop Docker单机环境搭建
    本次搭建是基于MySQL进行持久化,因此需要提前准备好一个MySQL容器(MySQL容器部署略过)。如有错误还请指正。OAP服务搭建拉取skywalking-oap镜像dockerpullapache/skywalking-oap-server:8.9.0接下来可以进行一个简单的启动,目的是拷贝出config目录到宿主机后进行挂载(docke......
  • AOP简单理解
    代理框架图代理模式通俗理解:目标对象A找工作需要租房,需要查询房子位置、价格和大小并交租,很麻烦,但如果通过房屋中介代理对象B,让B查询房子位置、价格和大小,A只需要交租即可SpringAOP解决的是非业务代码抽取的问题,底层是动态代理技术,有JDK动态代理和CGLIB动态代理:JDK动态代......
  • springboot的web项目部署_前后端整合部署
    springboot的web项目部署[2]_前后端整合部署vite.config.ts  build:{   //build编译后存放静态文件的目录   outDir:"../renren-admin/src/main/resources/static",  ShiroConfig.javafilterMap.put("/**/*.html","anon");filterMap.put(&......
  • SpringBoot整合OSS中的设计模式----单例模式
    在SpringBoot项目中使用OSS存储上传的图片,需要配置OSS设置信息。避免对象的重复创建,确保一个类只有一个实例,并提供一个全局访问点来访问该实例,于是用到了单例模式,这里复习一下单例模式。单线程----懒汉式@Data@Component@ConfigurationProperties(prefix="aliyun.oss")pu......
  • Java与SpringBoot网站的重构
    Java简介Java特点: Java的运行原理: SpringBoot网站的重构有源码的情况后台重构(mysql+maven+jdk+网站源码)将网站源码放入idea软件中查看重要配置文件:pom.xml和application.propertiespom.xml有网站的jdk版本和打包软件而application.properites文件中有着数据库的配......