当前位置: 首页 > article >正文

动态创建 AOP

动态创建 AOP

场景

有些情况下,生产环境查看问题,有些公司不能用 Arthas,需要查看方法参数及请求日志,又不方便加代码重新发布,现在通过给代码动态增加 AOP 功能,实现可以在不影响现有代码情况下查看

解决方案

动态加载 jar 包,并加载 class , 动态增加 AOP 切面增强

aop-plugin 管理

AopPluginController

@RestController
public class AopPluginController {


    @Autowired
    private AopMetaInfoRepository aopMetaInfoRepository;

    /**
     * 查看 aop-plugin 列表
     * @return
     */
    @GetMapping("/aop/list")
    public List<AopMetaInfo> list() {
        return aopMetaInfoRepository.values();
    }

    /**
     * 保存
     * @param metaInfo
     * @return
     */
    @PostMapping("/aop/save")
    public Boolean save(@RequestBody AopMetaInfo metaInfo){
        aopMetaInfoRepository.add(metaInfo);
        return Boolean.TRUE;
    }

    /**
     * 删除
     * @param id
     * @return
     */
    @GetMapping("/aop/{id}")
    public Boolean delete(@PathVariable String id){
        aopMetaInfoRepository.delete(id);
        return Boolean.TRUE;
    }

}

插件定义 AopMetaInfo:

@Data
public class AopMetaInfo {

    /**
     * jar包地址,本文文件 jar ,或者 http 请求
     */
    private String proxyJar;
    /**
     * 增强类
     */
    private String proxyClassName;
    /**
     * 切点
     */
    private String pointCut;
    /**
     * id名称
     */
    private String id;


    /**
     * 加载 jar包方式
     * 0、本地
     * 1、远程
     */
    private Boolean remote = Boolean.FALSE;
}

插件存储 AopMetaInfoRepository:

@Slf4j
@Repository
public class AopMetaInfoRepository {

    /**
     * Aop 插件
     */
    private Map<String, AopMetaInfo> map = new ConcurrentHashMap<>();
    /**
     * 已加载的 class
     */
    private static Map<String, Class<?>> loadClass = new ConcurrentHashMap<>();


    @Autowired
    private ApplicationEventPublisher publisher;

    public void add(AopMetaInfo metaInfo) {
        try {
            if (map.containsKey(metaInfo.getId())) {
                log.info("aop plugin exists: {}", metaInfo.getId());
                return;
            }
            map.put(metaInfo.getId(), metaInfo);
            if (!metaInfo.getRemote()) {
                //从文件系统加载jar
                loadLocalJar(metaInfo,new File(metaInfo.getProxyJar()));
            } else {
                File tempFile = null;
                try {
                    HttpGet httpGet = new HttpGet();
                    httpGet.setURI(URI.create(metaInfo.getProxyJar()));
                    CloseableHttpResponse execute = HttpClients.createDefault().execute(httpGet);
                    byte[] byteArray = EntityUtils.toByteArray(execute.getEntity());
                    tempFile = File.createTempFile(metaInfo.getId(), ".jar");
                    FileUtil.writeBytes(byteArray, tempFile);
                    loadLocalJar(metaInfo,tempFile);
                } finally {
                    if (tempFile!=null){
                        tempFile.delete();
                    }
                }
            }
            publisher.publishEvent(new AopMetaChangeEvent(this, metaInfo.getId(), DynamicAopHelper.AopType.ADD));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 加载本地jar
     * @param metaInfo
     * @param jarFile
     * @throws Exception
     */
    private static void loadLocalJar(AopMetaInfo metaInfo, File jarFile) throws Exception {
        URLClassLoader classLoader = new URLClassLoader(new URL[]{jarFile.toURI().toURL()},
                Thread.currentThread().getContextClassLoader());
        Class<?> clz = classLoader.loadClass(metaInfo.getProxyClassName());
        loadClass.put(metaInfo.getProxyClassName(), clz);
    }


    public static Class<?> getById(String clzName) {
        return loadClass.get(clzName);
    }

    public AopMetaInfo get(String id) {
        return map.get(id);
    }


    public List<AopMetaInfo> values() {
        return new ArrayList<>(map.values());
    }


    public void delete(String id) {
        map.remove(id);
        publisher.publishEvent(new AopMetaChangeEvent(this, id, DynamicAopHelper.AopType.DEL));
    }
}

插件操作监听

public class AopMetaChangeEvent extends ApplicationEvent {

    @Getter
    private String id;

    @Getter
    private DynamicAopHelper.AopType aopType;

    public AopMetaChangeEvent(Object source, String id, DynamicAopHelper.AopType aopType) {
        super(source);
        this.id = id;
        this.aopType = aopType;
    }
}


@Component
@Slf4j
public class AopMetaInfoChangeListener {

    @Autowired
    private DefaultListableBeanFactory beanFactory;

    @EventListener(classes = AopMetaChangeEvent.class)
    public void change(AopMetaChangeEvent event) {
        log.info("recv event 【{}】 aop plugin id: {}", event.getAopType(), event.getId());
        switch (event.getAopType()) {
            case ADD:
                AopMetaInfoRepository repository = (AopMetaInfoRepository) event.getSource();
                AopMetaInfo aopMetaInfo = repository.get(event.getId());
                DynamicAopHelper.saveAdvisor(beanFactory, aopMetaInfo);
                break;
            case DEL:
                DynamicAopHelper.removeAdvisor(beanFactory, event.getId());
                break;
        }

    }
}

Aop 操作 DynamicAopHelper:

@Slf4j
public class DynamicAopHelper {

    public static enum AopType {
        ADD, DEL
    }

    /**
     * Advised: 管理 Advisor
     * Advisor: 由 point_cut 和 Advice 组成
     * Advice: 增强代码, 如实现 MethodInterceptor
     * 创建 advisor
     * @param metaInfo
     * @param beanFactory
     * @return
     */
    public static AspectJExpressionPointcutAdvisor getAdvisor(AopMetaInfo metaInfo, DefaultListableBeanFactory beanFactory) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition();
        GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionBuilder.getBeanDefinition();
        beanDefinition.setBeanClass(AspectJExpressionPointcutAdvisor.class);
        AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor();
        advisor.setExpression(metaInfo.getPointCut());
        advisor.setAdvice(createAdviceInstance(metaInfo.getProxyClassName()));
        //提供构造函数
        beanDefinition.setInstanceSupplier(() -> advisor);
        beanFactory.registerBeanDefinition(metaInfo.getId(), beanDefinition);
        return advisor;
    }

    /**
     * 根据 class 创建 Advice
     * @param className
     * @return
     */
    private static MethodInterceptor createAdviceInstance(String className) {
        try {
            // 动态加载类
            Constructor<?> constructor = AopMetaInfoRepository.getById(className).getConstructor();
            Object instance = constructor.newInstance();
            if (instance instanceof MethodInterceptor) {
                return (MethodInterceptor) instance;
            } else {
                throw new IllegalArgumentException("The provided class does not implement MethodInterceptor.");
            }
        } catch (Exception e) {
            throw new RuntimeException("Failed to create advice instance: " + className, e);
        }
    }


    /**
     * 新增 advisor
     *
     * @param metaInfo
     * @param beanFactory
     */
    public static void saveAdvisor(DefaultListableBeanFactory beanFactory, AopMetaInfo metaInfo) {
        AspectJExpressionPointcutAdvisor advisor = getAdvisor(metaInfo, beanFactory);
        //查询 Advised, 管理 advisor
        //修改 advisor
        processAdvisor(beanFactory, advisor, AopType.ADD);
    }

    /**
     * 删除 advisor
     *
     * @param beanFactory
     * @param id
     */
    public static void removeAdvisor(DefaultListableBeanFactory beanFactory, String id) {
        if (beanFactory.containsBean(id)) {
            AspectJExpressionPointcutAdvisor advisor = beanFactory.getBean(id, AspectJExpressionPointcutAdvisor.class);
            processAdvisor(beanFactory, advisor, AopType.DEL);
        }
    }

    private static void processAdvisor(DefaultListableBeanFactory beanFactory, AspectJExpressionPointcutAdvisor advisor, AopType type) {
        for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
            Object bean = beanFactory.getBean(beanDefinitionName);
            if (!(bean instanceof Advised)) {
                continue;
            }
            Advised advisedObj = (Advised) bean;

            //pointcut 是否应用于 class
            boolean b = AopUtils.canApply(advisor, bean.getClass());
            if (!b){
                continue;
            }
            if (type == AopType.ADD) {
                advisedObj.addAdvice(advisor.getAdvice());
                log.info(">>>>>>>> add advice 【{}】 for 【{}】", advisor.getAdvice().getClass().getName(), advisedObj.getClass().getName());
            } else if (type == AopType.DEL) {
                //如果存在则移除
                if (findInMatch(advisedObj, advisor)) {
                    advisedObj.removeAdvice(advisor.getAdvice());
                    log.info(">>>>>>>> remove advice 【{}】 for 【{}】", advisor.getAdvice().getClass().getName(), advisedObj.getClass().getName());
                }
            }
        }
    }

    /**
     *  advisor 是否在 Advised 中
     * @param advisedObj
     * @param advisor
     * @return
     */
    private static boolean findInMatch(Advised advisedObj, AspectJExpressionPointcutAdvisor advisor) {
        for (Advisor advisedObjAdvisor : advisedObj.getAdvisors()) {
            if (advisedObjAdvisor.getAdvice().equals(advisor.getAdvice())) {
                return true;
            }
        }
        return false;
    }


}

创建增强类

创建 一个 maven 工程,自定义增强类,这里工程名称为 aop-svc

@Slf4j
public class HelloInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object rs = invocation.proceed();
        stopWatch.stop();
        String name = invocation.getMethod().getName();
        log.info("method: 【{}】, 耗时: {} ms", name, stopWatch.getTotalTimeMillis());
        return rs;
    }
}

这里实现增强的逻辑是打印切点方法执行的耗时时间,然后打包,包名为 aop-svc.jar

测试

在目标web工程, 如 user-boot 项目,有以下接口:

@RestController
public class HealthController {

    @GetMapping("hello")
    public String index() throws InterruptedException {
        TimeUnit.SECONDS.sleep(new Random().nextInt(5));
        return "hello";
    }
}

动态增加统计接口耗时时,我们就可以添加我们的aop插件了

http://localhost:8080/aop/save,我们从文件系统加载,在生产环境可以把 jar 换成 可以http下载的文件:

{
    "proxyJar": "D:\\aop-svc.jar",
    //"proxyJar": "http://172.xx.x.:9001/api/v1/download-shared-object/aHR0cDovLzEyNy4wLjAuMTo",
    "proxyClassName": "org.example.act.interceptor.HelloInterceptor",//增强类
    "pointCut": "execution(* com.xx.controller.HealthController.*(..))",//切点
    "remote": 0,
    "id": "aop-svc"
}

日志输出:

recv event 【ADD】 aop plugin id: aop-svc
add advice 【org.HelloInterceptor】 fo【com.x.HealthController$$EnhancerBySpringCGLIB$$70c5596c】

查看已经加载的插件,http://localhost:8080/aop/list

{
    "code": "1",
    "data": [
        {
            "id": "aop-svc",
            "pointCut": "execution(* com.xx.controller.HealthController.*(..))",
            "proxyClassName": "org.example.act.interceptor.HelloInterceptor",
            "proxyJar": "D:\\aop-svc.jar",
            "remote": false
        }
    ],
    "link": [],
    "msg": "调用成功"
}

请求我们目标接口 http://localhost:8080/hello,输出:

o.e.act.interceptor.HelloInterceptor     : method: 【index】, 耗时: 4002 ms

显然,我们的增强添加成功

通过 http://localhost:8080/aop/aop-svc 删除 加载的插件, 其中 aop-svc 是插件的 id, 输出:

recv event 【DEL】 aop plugin id: aop-svc
remove advice 【org.x.HelloInterceptor】 for【com.x.HealthController$$EnhancerBySpringCGLIB$$75596c】

这样就删除了,既实现了增强,又减少了对代码的入侵

good luck!


http://www.kler.cn/news/360599.html

相关文章:

  • linux c国际化
  • 如何看待AI技术的应用前景?
  • 2024.10月18日- Vue2组件开发(3)
  • 在Aidlux融合系统中基于aildlite2版本适配yolov5目标检测模型
  • 现代数字信号处理I-P4 CRLB+LMMSE 学习笔记
  • AI赋能R-Meta分析核心技术:从热点挖掘到高级模型、助力高效科研与论文发表
  • 源代码加密技术的一大新方向!
  • 软件测试(基础+手动测试+接口测试)
  • 前端js html css 基础巩固4
  • 共筑华芯|首届“SEMiBAY湾芯展”龙华区科技创新局助力华芯邦科技携第四代半导体芯星亮相湾区半导体产业生态博览会,诚邀您莅临参观指导
  • 【Linux】僵尸进程和孤儿进程
  • 海康相 机
  • docker安装elasticsearch和ik分词器
  • 《地下蚁国》风灵月影十项修改器使用教程
  • 【Power Query】List.Select 筛选列表
  • 计算机网络安全与防火墙技术
  • 【CSS】纯CSS Loading动画组件
  • Vue.js 组件化开发:从入门到进阶
  • 华为鸿蒙开发笔记
  • 掌握网络探测的艺术:Nping工具全解析