从JDK动态代理一步步推导到MyBatis Plugin插件实现原理

一、前言

最近项目上,要做一个日志审计的功能(通用SDK,期望可以在任何项目中使用),需要对Mybatis层所有的DDL操作做拦截,以将微服务的链路ID、执行人、Controller门面方法全部持久化到业务库。借此机会深入研究了一下MyBatis的源码,该篇文章重点在于推导从JDK动态代理如何一步步演变到MyBatis Plugin的实现。

二、JDK动态代理
概述

JDK动态代理:在程序运行时,利用反射机制获取被代理类的字节码内容 进而生成一个实现代理接口的匿名类(代理类),在调用具体方法前调用InvokeHandler来处理;

  1. JDK Proxy动态代理的API包在java.lang.reflect下,其核心api包含InvocationHandler(Interface)和Proxy(class)。

  2. Proxy是Java动态代理机制的主类,其提供一组静态方法来为一组接口动态地生成代理类及其对象。

    static InvocationHandler getInvocationHandler(Object proxy) --> 用于获取指定代理对象所关联的调用处理器。

    static Class getProxyClass(ClassLoader loader, Class[] interfaces) --> 用于获取关联于指定类装载器和一组接口的动态代理类的类对象。

    static boolean isProxyClass(Class cl) --> 用于判断指定类对象是否具有一个动态代理类

    static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) --> 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例。

    h:表示当这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。

  3. InvocationHandler是负责连接代理类和委托类的中间类必须要实现的接口。

    它定义了一个invoke()方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

  4. 优点:

    1. 可以方便的对地代理类的函数进行统一的处理,而不用修改这个代理类的函数。
    2. 简化了编程工作,提高了软件系统的扩展性。因为Java反射机制可以生成任意类型的动态代理类。
    3. 动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
    4. 接口增加一个方法,动态代理类会直接自动成成对应的代理方法。

    缺点:

    1. JDK Proxy只能对实现了接口的类才能代理,没有接口实现的类,JDK Proxy是无法代理的。

    实现方式:

    1. 委托类:JDK动态代理中接口是必须的,要求委托类必须实现某个接口。

    2. 使用动态代理时,需要定义一个位于代理类与委托类之间的中介类。

    3. 这个中介类必须实现InvocationHandler接口;作为调用处理器“拦截”对代理类方法的调动。

      InvocationHandler接口定义如下:

      当我们调用代理类对象的方法时,这个“调用”会转送到invoke()方法中,代理类对象作为proxy参数传入;

      参数method标识了我们具体调用的是代理类的哪个方法;

      args为这个方法的参数。

      package java.lang.reflect;
      
      public interface InvocationHandler {
      
          public Object invoke(Object proxy, Method method, Object[] args)
              throws Throwable;
      }
      

      中介类:

      我们对代理类中的所有方法的调动都会变为对invoke()的调用,我们可以在invoke()方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。

      中介类通过聚合方式持有委托类对象引用,把外部对invoke的调用最终都转为对委托类对象的调用。

      public class MyDynamicProxy implements InvocationHandler {
      
          /**
           * obj为委托类对象
           */
          private Object obj;
          
          public MyDynamicProxy(Object obj){
              this.obj = obj;
          }
         
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              System.out.println("before");     
              Object result = method.invoke(obj, args);
              System.out.println("after");
              return result;
          }
      }
      
      

      代理类通过Proxy.newProxyInstance()方法生成;

      总结一下:中介类与委托类构成了静态代理关系,在这个关系中,中介类是代理类,委托类就是委托类;代理类与中介类也构成一个静态代理关系。

  5. 原理

    动态代理关系由两组静态代理关系组成,这就是动态代理的原理;

    实际上,中间类(Proxy)与委托类构成了静态代理关系,在这个关系中,中间类是代理类,委托类是委托类。
    然后代理类与中间类也构成一个静态代理关系,在这个关系中,中间类是委托类,代理类是代理类。
    通过newProxyInstance()方法获取到代理类实例,然后通过这个代理类实例调用代理类的方法,对代理类的方法的调用实际上都会调用中介类(调用处理器)的invoke()方法,在invoke方法中我们调用委托类的相应方法,并且可以添加自己的处理逻辑。

动态代理

  1. 委托类(需要被代理的类)Target

  2. Target :

    public interface Target{
        String execute(String name);
    }
    
  3. 委托类(需要被代理的类)Target

    public class TargetImpl implements Target {
        @Override
        public String execute(String name) {
            System.out.println("targetImpl execute() " + name);
            return name;
        }
    }
    
    
  4. 中介类TargetProxy

    /**
     * JDK动态代理:中介类
     */
    public class TargetProxy implements InvocationHandler {
    
        /**
         * target为委托类对象
         */
        private Object target;
    
        public TargetProxy(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before intercept.......");
            Object result = method.invoke(target, args);
            System.out.println("after intercept.......");
            return result;
        }
    
        /**
         * 创建代理对象
         *
         * @param target 委托类
         * @return
         */
        public static Object wrap(Object target) {
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), new TargetProxy(target));
        }
    }
    
    

    wrap(Object target)方法用于创建代理对象。

  5. 测试类(运行结果)

    public class MainTest {
        public static void main(String[] args) {
            // 委托类
            Target target = new TargetImpl();
            // 代理类
            Target targetProxy = (Target) TargetProxy.wrap(target);
            // 实际调用的是代理对象的invoke()方法
            targetProxy.execute("Hello World!");
        }
    }
    

    运行结果:

    before intercept.......
    targetImpl execute() Hello World!
    after intercept.......
    

    结果分析:

    • 使用JDK动态代理可以解决问题,实现前置、后置拦截。不过我们要拦截处理的逻辑全部耦合在invoke()方法中,不符合面向对象的思想;想成为一个架构师,必须要抽象起来呀;
    • 尝试设计一个Interceptor接口,需要做什么拦截处理实现接口即可。
  6. 抽象出拦截逻辑(增加Interceptor接口)

    /**
     * 抽象拦截器
     *
     * @author Saint
     */
    public interface Interceptor {
    
        /**
         * 具体的拦截处理
         */
        void intercept();
    }
    

    Interceptor实现:

    日志拦截器

    public class LogInterceptor implements Interceptor {
        @Override
        public void intercept() {
            System.out.println("start record log....");
        }
    }
    

    事务拦截器

    public class TransactionInterceptor implements Interceptor {
        @Override
        public void intercept() {
            System.out.println("start transaction...");
        }
    }
    

    重写代理类TargetProxy2

    增加拦截器处理逻辑;在创建代理类TargetProxy2时添加所有的拦截器;

    public class TargetProxy2 implements InvocationHandler {
    
        /**
         * target为委托类对象
         */
        private Object target;
    
        /**
         * 拦截器集合
         */
        private List<Interceptor> interceptorList = new ArrayList<>();
    
        public TargetProxy2(Object target, List<Interceptor> interceptorList) {
            this.target = target;
            this.interceptorList = interceptorList;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            /**
             * 拦截处理
             */
            interceptorList.forEach(Interceptor::intercept);
    
            Object result = method.invoke(target, args);
            return result;
        }
    
        /**
         * 创建代理对象
         *
         * @param target 委托类
         * @return
         */
        public static Object wrap(Object target, List<Interceptor> interceptorList) {
            TargetProxy2 targetProxy = new TargetProxy2(target, interceptorList);
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), targetProxy);
        }
    }
    
    

    测试类(运行结果)

    public class MainTest2 {
       public static void main(String[] args) {
           // 委托类
           Target target = new TargetImpl();
           // 所有的拦截器
           List<Interceptor> interceptorList = new ArrayList<>();
           interceptorList.add(new LogInterceptor());
           interceptorList.add(new TransactionInterceptor());
           // 代理类
           Target targetProxy = (Target) TargetProxy2.wrap(target, interceptorList);
           // 实际调用的是代理对象的invoke()方法
           targetProxy.execute("Hello World!");
       }
    }
    

    运行结果:

    start record log....
    start transaction...
    targetImpl execute() Hello World!
    

    结果分析:

    现在可以实现动态添加拦截器了,也不需要每次都去修改中间类TargetProxy了;
    不过我们这里只有前置拦截耶,聪明的你肯定发现了,我可以定义两个在Interceptor接口里定义两个方法啊,一个beforeIntercept()、一个afterIntercept();
    但是这样拦截器无法知道拦截对象的信息;我们需要做进一步的抽象,将拦截对象的信息封装起来,作为拦截器拦截方法的入参;目标对象的真正执行交给拦截器interceptor来完成,这样我们不仅可以实现前后置拦截,还能对拦截对象的参数做一些修改。参考MyBatis源码,我们设计一个Invocation对象。

  7. 抽象逻辑器拦截方法入参

    拦截方法入参:Invocation

    /**
     * 拦截器拦截方法入参
     */
    public class Invocation {
    
        /**
         * 目标对象(委托类)
         */
        private Object target;
    
        /**
         * 执行的目标方法
         */
        private Method method;
    
        /**
         * 执行方法的参数
         */
        private Object[] args;
    
        public Invocation(Object target, Method method, Object[] args) {
            this.target = target;
            this.method = method;
            this.args = args;
        }
    
        /**
         * 执行目标对象的方法
         *
         * @return
         * @throws Exception
         */
        public Object process() throws Exception {
            return method.invoke(target, args);
        }
    }
    
    

    修改拦截接口(Interceptor2)

    public interface Interceptor2 {
    
        /**
         * 具体的拦截处理
         */
        Object intercept(Invocation invocation) throws Exception;
    }
    
    

    事务拦截器实现:

    public class TransactionInterceptor2 implements Interceptor2 {
        @Override
        public Object intercept(Invocation invocation) throws Exception {
            System.out.println("start transaction...");
            Object result = invocation.process();
            System.out.println("end transaction...");
            return result;
        }
    }
    

    中介(中间)类TargetProxy3

    public class TargetProxy3 implements InvocationHandler {
    
        /**
         * target为委托类对象
         */
        private Object target;
    
        /**
         * 拦截器集合
         */
        private Interceptor2 interceptor2;
    
        public TargetProxy3(Object target, Interceptor2 interceptor2) {
            this.target = target;
            this.interceptor2 = interceptor2;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Invocation invocation = new Invocation(target, method, args);
            return interceptor2.intercept(invocation);
        }
    
        /**
         * 创建代理对象
         *
         * @param target 委托类
         * @return
         */
        public static Object wrap(Object target, Interceptor2 interceptor2) {
            TargetProxy3 targetProxy = new TargetProxy3(target, interceptor2);
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), targetProxy);
        }
    }
    

    测试类(运行结果)

    public class MainTest3 {
        public static void main(String[] args) {
            // 委托类
            Target target = new TargetImpl();
            // 所有的拦截器
            Interceptor2 transactionInterceptor = new TransactionInterceptor2();
            // 代理类
            Target targetProxy = (Target) TargetProxy3.wrap(target, transactionInterceptor);
            // 实际调用的是代理对象的invoke()方法
            targetProxy.execute("Hello World!");
        }
    }
    

    运行结果:

    start transaction...
    targetImpl execute()Hello World!
    end transaction...
    

    结果分析:

    • 此时拦截器已经可以获取到拦截对象的信息;不过这样仿佛又回到了最初的起点,只是将原本写在代理类TargetProxy中的逻辑移到了Interceptor,并且此时拦截器也只有一个;

    • 上面这种做法看着就比较别扭,我们换种方式理解:对于目标类(委托类)而言,它只需要知道它内部被插入了哪些拦截器;

    • 换言之,我们可以把拦截器看做是委托类和代理类的中间类(TargetProxy)的代理类,即中间类执行方法的动作由拦截器负责完成;

    • 针对多个拦截器而言,也可以兼容,实际就是代理嵌套再代理。

    拦截器里插入目标对象

    ​ 重写拦截器

    public interface Interceptor3 {
    
        /**
         * 具体的拦截处理
         */
        Object intercept(Invocation invocation) throws Exception;
    
        /**
         * 插入目标类(即拦截器作用于那个委托类)
         */
        Object plugin(Object target);
    }
    

    事务拦截器:

    public class TransactionInterceptor3 implements Interceptor3 {
        @Override
        public Object intercept(Invocation invocation) throws Exception {
            System.out.println("start transaction...");
            Object result = invocation.process();
            System.out.println("end transaction...");
            return result;
        }
    
        @Override
        public Object plugin(Object target) {
            return TargetProxy4.wrap(target, this);
        }
    }
    

    日志拦截器:

    public class LogInterceptor2 implements Interceptor3 {
        @Override
        public Object intercept(Invocation invocation) throws Exception {
            System.out.println("start record log...");
            Object result = invocation.process();
            System.out.println("end record log...");
            return result;
        }
    
        @Override
        public Object plugin(Object target) {
            return TargetProxy4.wrap(target, this);
        }
    }
    

    中间类(TargetProxy4)和3>一样

    public class TargetProxy4 implements InvocationHandler {
    
        /**
         * target为委托类对象
         */
        private Object target;
    
        /**
         * 拦截器集合
         */
        private Interceptor3 interceptor3;
    
        public TargetProxy4(Object target, Interceptor3 interceptor3) {
            this.target = target;
            this.interceptor3 = interceptor3;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Invocation invocation = new Invocation(target, method, args);
            return interceptor3.intercept(invocation);
        }
    
        /**
         * 创建代理对象
         *
         * @param target 委托类
         * @return
         */
        public static Object wrap(Object target, Interceptor3 interceptor3) {
            TargetProxy4 targetProxy = new TargetProxy4(target, interceptor3);
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), targetProxy);
        }
    }
    

    测试类(执行结果)

    public class MainTest4 {
        public static void main(String[] args) {
            // 委托类
            Target target = new TargetImpl();
            // 所有的拦截器
            Interceptor3 transactionInterceptor = new TransactionInterceptor3();
            Interceptor3 logInterceptor = new LogInterceptor2();
            // 由于log拦截器在最后,所以log拦截器先执行
            target = (Target) transactionInterceptor.plugin(target);
            target = (Target) logInterceptor.plugin(target);
    
            // 实际调用的是代理对象的invoke()方法
            target.execute("Hello World!");
        }
    }
    

    执行结果:

    start record log...
    start transaction...
    targetImpl execute() Hello World!
    start transaction...
    end record log...
    

    结果分析:

    • 最后插入的拦截器先执行;
    • 本质上来讲这就是代理嵌套再代理;

    时序图如下:
    在这里插入图片描述

    不过添加拦截器的方式我们可以再优化一下,我们再利用一下OOP的思想,搞个拦截器链;

  8. 拦截器链

    再利用责任链模式,将拦截器串起来,搞成一个链;

    拦截器链(InterceptorChain)

    public class InterceptorChain {
        
        private List<Interceptor3> interceptorList = new ArrayList<>();
    
        /**
         * 插入所有拦截器
         *
         * @param target
         * @return
         */
        public Object pluginAll(Object target) {
            for (Interceptor3 interceptor : interceptorList) {
                target = interceptor.plugin(target);
            }
            return target;
        }
    
        public void addInterceptor(Interceptor3 interceptor) {
            interceptorList.add(interceptor);
        }
    
        /**
         * 返回一个不可修改集合,只能通过addInterceptor方法添加
         *
         * @return
         */
        public List<Interceptor3> getInterceptorList() {
            return Collections.unmodifiableList(interceptorList);
        }
    }
    

    这里通过pluginAll()方法包一层,将所有的拦截器插入到目标类中去;

    测试类(执行结果)

    public class MainTest5 {
        public static void main(String[] args) {
            // 委托类
            Target target = new TargetImpl();
            // 所有的拦截器
            Interceptor3 transactionInterceptor = new TransactionInterceptor3();
            Interceptor3 logInterceptor = new LogInterceptor2();
            InterceptorChain interceptorChain = new InterceptorChain();
            interceptorChain.addInterceptor(transactionInterceptor);
            interceptorChain.addInterceptor(logInterceptor);
            // 由于log拦截器在最后,所以log拦截器先执行
            target = (Target) interceptorChain.pluginAll(target);
            // 实际调用的是代理对象的invoke()方法
            target.execute("Hello World!");
        }
    }
    
    

    执行结果:

    start record log...
    start transaction...
    targetImpl execute()Hello World!
    end transaction...
    end record log...
    

    结果分析:

    • 不分析了,到这也就结束了。

    三、MyBatis Plugin插件

    在MyBatis Plugin源码中我们可以看到,基本和我们上面推导的最后一种方式一样,其中Plugin相当于我们的TargetProxy。
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/480095.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript 权威指南第七版(GPT 重译)(二)

第四章&#xff1a;表达式和运算符 本章记录了 JavaScript 表达式以及构建许多这些表达式的运算符。表达式 是 JavaScript 的短语&#xff0c;可以 评估 以产生一个值。在程序中直接嵌入的常量是一种非常简单的表达式。变量名也是一个简单表达式&#xff0c;它评估为分配给该变…

Linux安装harbor(Docker方式)

Linux安装harbor&#xff08;Docker方式&#xff09; 前置条件&#xff1a;安装docker和docker-compose先下载安装包&#xff1a;https://github.com/goharbor/harbor/releases解压到指定目录 sudo tar -zxf harbor-offline-installer-v2.1.0.tgz -C /opt/安装 cd /opt/harb…

【Canvas与艺术】简约式胡萝卜配色汽车速度表

【效果图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>胡萝卜色汽车速度仪表盘简化版</title><style type"…

AI助力生产制造质检,基于轻量级YOLOv8n模型开发构建工业生产制造场景下的瓷砖瑕疵检测识别分析系统

瓷砖生产环节一般经过原材料混合研磨、脱水、压胚、喷墨印花、淋釉、烧制、抛光&#xff0c;最后进行质量检测和包装。得益于产业自动化的发展&#xff0c;目前生产环节已基本实现无人化。而质量检测环节仍大量依赖人工完成。一般来说&#xff0c;一条产线需要配数名质检工&…

此站点正在尝试打开 ,chrome/edge 允许http网站打开url schema

正常https链接会有首次允许选项 但http没有&#xff0c;每次都会弹出&#xff0c;非常烦人。 Chrome / Edge 配置 地址栏输入 chrome://flags/搜索Insecure origins treated as secure, 配置允许网站&#xff0c;需要协议和端口再次跳转会显示始终允许选项

Sphinx + Readthedocs 避坑速通指南

博主在学习使用 Sphinx 和 Read the docs 的过程中&#xff0c; 碰到了许多奇葩的 bug, 使得很简单的任务花费了很长的时间才解决&#xff0c;现在在这里做一个分享&#xff0c;帮助大家用更少的时间高效上线文档的内容。 总的来说&#xff0c; 任务分为两个部分&#xff1a; …

Jetson AGX ORIN 初始化配置Anaconda带CUDA的OpenCV

Jetson AGX ORIN 初始化&配置CUDA&Anaconda&带CUDA的OpenCV 文章目录 Jetson AGX ORIN 初始化&配置CUDA&Anaconda&带CUDA的OpenCV1. Jetson AGX ORIN 初始化2. Jetson AGX ORIN 配置 Anaconda3. 安装带CUDA的OpenCV 1. Jetson AGX ORIN 初始化 可以参…

[音视频学习笔记]七、自制音视频播放器Part2 - VS + Qt +FFmpeg 写一个简单的视频播放器

前言 话不多说&#xff0c;重走霄骅登神路 前一篇文章 [音视频学习笔记]六、自制音视频播放器Part1 -新版本ffmpeg&#xff0c;Qt VS2022&#xff0c;都什么年代了还在写传统播放器&#xff1f; 本文相关代码仓库&#xff1a; MediaPlay-FFmpeg - Public 转载雷神的两个流程…

css3鼠标悬停图片特效,图片悬停效果源码

特效介绍 css3鼠标悬停图片特效,图片悬停效果源码&#xff0c;可以在网页上面作为自己的动态加载名片&#xff0c;放到侧边栏或者网站合适的位置即可 动态效果 代码下载 css3鼠标悬停图片特效,图片悬停效果源码

阿里云 EMR Serverless Spark 版免费邀测中

随着大数据应用的广泛推广&#xff0c;企业对于数据处理的需求日益增长。为了进一步优化大数据开发流程&#xff0c;减少企业的运维成本&#xff0c;并提升数据处理的灵活性和效率&#xff0c;阿里云开源大数据平台 E-MapReduce &#xff08;简称“EMR”&#xff09;正式推出 E…

数据挖掘与机器学习 1. 绪论

于高山之巅&#xff0c;方见大河奔涌&#xff1b;于群峰之上&#xff0c;便觉长风浩荡 —— 24.3.22 一、数据挖掘和机器学习的定义 1.数据挖掘的狭义定义 背景&#xff1a;大数据时代——知识贫乏 数据挖掘的狭义定义&#xff1a; 数据挖掘就是从大量的、不完全的、有噪声的、…

基于docker配置pycharm开发环境

开发过程中&#xff0c;为了做好环境隔离&#xff0c;经常会采用docker来进行开发&#xff0c;但是如何快速将docker中的环境和本地开发的IDE链接起来是一个常见问题&#xff0c;下面对其进行简单的总结&#xff1a; &#xff08;1&#xff09;前期准备 开发环境docker和工具p…

ENISA 2023年威胁态势报告:主要发现和建议

欧盟网络安全局(ENISA)最近发布了其年度2023年威胁态势报告。该报告确定了预计在未来几年塑造网络安全格局的主要威胁、主要趋势、威胁参与者和攻击技术。在本文中&#xff0c;我们将总结报告的主要发现&#xff0c;并提供可操作的建议来缓解这些威胁。 介绍 ENISA 威胁态势报告…

活动回顾 | 走进华为向深问路,交流数智办公新体验

3月20日下午&#xff0c;“企业数智办公之走进华为”交流活动在华为上海研究所成功举办。此次活动由上海恒驰信息系统有限公司主办&#xff0c;华为云计算技术有限公司和上海利唐信息科技有限公司协办&#xff0c;旨在通过对企业数字差旅和HR数智化解决方案的交流&#xff0c;探…

在 Linux/Ubuntu/Debian 上安装 SQL Server 2019

Microsoft 为 Linux 发行版&#xff08;包括 Ubuntu&#xff09;提供 SQL Server。 以下是有关如何执行此操作的基本指南&#xff1a; 注册 Microsoft Ubuntu 存储库并添加公共存储库 GPG 密钥&#xff1a; sudo wget -qO- https://packages.microsoft.com/keys/microsoft.as…

53、Qt/信号与槽、QSS界面设计20240322

一、使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是…

IDEA调优-四大基础配置-编码纵享丝滑

文章目录 1.JVM虚拟机选项配置2.多线程编译速度3.构建共享堆内存大小4.关闭不必要的插件 1.JVM虚拟机选项配置 -Xms128m -Xmx8192m -XX:ReservedCodeCacheSize1024m -XX:UseG1GC -XX:SoftRefLRUPolicyMSPerMB50 -XX:CICompilerCount2 -XX:HeapDumpOnOutOfMemoryError -XX:-Omi…

赋能 DevOps:平台工程的关键作用

在当今快节奏的数字环境中&#xff0c;DevOps 已成为寻求简化软件开发和交付流程的组织的关键方法。DevOps 的核心在于开发和运营团队之间协作的概念&#xff0c;通过一组旨在自动化和提高软件交付生命周期效率的实践和工具来实现。 DevOps 实践的关键推动因素之一是平台工程。…

小程序渲染层图标错误

小程序渲染图标层出现错误&#xff1a; 官方提示&#xff1a;不影响可以忽略&#xff1b; 通过阿里巴巴矢量图标库--项目设置--字体格式--选中base64格式&#xff1b; 重新更新图标库代码&#xff0c;替换项目中的图标库&#xff1b; 重新加载小程序--渲染层错误的提示消失&…

[运维] 可视化爬虫易采集-EasySpider(笔记)

一、下载 ​下载地址 下滑到Assets页面&#xff0c;选择下载 二、解压运 ​解压压缩包&#xff0c;打开文件夹 在此文件夹下打开Linux Terimal, 并输入以下命令运行软件&#xff1a; ./easy-spider.sh 注意软件运行过程中不要关闭terminal。 三、使用 1.开始 首先点击…