• 深入理解JVM虚拟机_2 基于SPI破解双亲委派机制


     深入理解JVM虚拟机_2 基于SPI破解双亲委派机制

    作者:田超凡

    原创博文,仿冒必究,部分素材转载自每特教育蚂蚁课堂

    类加载器的双亲委派机制

    首先在我们类加载器分为四种 自定义类加载器、应用类加载器、扩展类加载器、启动类加载器。

    当一个类加载器收到请求之后,首先会依次向上查找到最顶层类加载器(启动类加载器),依次向下加载class文件,如果已经加载到class文件,子加载器不会再继续加载该class文件。

    双亲委派机制机制的好处

    目的就是为了防止开发者自定义的类与jdk定义的源码类产生冲突问题,保证该类在内存中的唯一性。

    ClassLoader源码解读

    Launcher类源码解读

    public Launcher() {
    
        Launcher.ExtClassLoader var1;
    
        try {
    //获取我们的扩展类加载器
    
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
    
        } catch (IOException var10) {
    
            throw new InternalError("Could not create extension class loader", var10);
    
        }
    
      
    
        try {
    // 获取我们的应用类加载器
    
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    
        } catch (IOException var9) {
    
            throw new InternalError("Could not create application class loader", var9);
    
        }
    
        // 默认设置我们的类加载器是为应用类加载器
    
        Thread.currentThread().setContextClassLoader(this.loader);
    
        String var2 = System.getProperty("java.security.manager");
    
        if (var2 != null) {
    
            SecurityManager var3 = null;
    
            if (!"".equals(var2) && !"default".equals(var2)) {
    
                try {
    
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
    
                } catch (IllegalAccessException var5) {
    
                } catch (InstantiationException var6) {
    
                } catch (ClassNotFoundException var7) {
    
                } catch (ClassCastException var8) {
    
                }
    
            } else {
    
                var3 = new SecurityManager();
    
            }
    
      
    
            if (var3 == null) {
    
                throw new InternalError("Could not create SecurityManager: " + var2);
    
            }
    
      
    
            System.setSecurityManager(var3);
    
        }
    
      
    
    }

    双亲委派机制源码分析

    1. ClassLoader.getSystemClassLoader().loadClass()
     
    
    // 查询缓存中是否有缓存 该class
    Class c = findLoadedClass(name);
    
      if (c == null) {
    
        long t0 = System.nanoTime();
    
        try {
    //获取当前类加载器的父加载器 ---扩展类加载器
    
              if (parent != null) {
    
                c = parent.loadClass(name, false);
    
            } else {
    // 如果当前没有父加载器,就是为启动类加载器
    
                  c = findBootstrapClassOrNull(name);
    
            }
    
        } catch (ClassNotFoundException e) {
    
            // ClassNotFoundException thrown if class not found
    
            // from the non-null parent class loader
    
        }
    
      
    
        if (c == null) {
    
            // If still not found, then invoke findClass in order
    
            // to find the class.
    
            long t1 = System.nanoTime();
    // 如果父加载器(扩展和启动类加载器都没有加载class,则使用当前(应用类加载器加载))
    
            c = findClass(name);
    
      
    
            // this is the defining class loader; record the stats
    
            sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
    
            sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
    
            sun.misc.PerfCounter.getFindClasses().increment();
    
        }
    
    }
    
      if (resolve) {
    
        resolveClass(c);
    
    }
    
      return c;
     
     
    

    如何自定义一个类加载器

    public class TcfClassLoader extends ClassLoader {
    
      
    
        private File fileObject;
    
      
    
        public TcfClassLoader(File fileObject) {
    
            this.fileObject = fileObject;
    
        }
    
      
    
        public void setFileObject(File fileObject) {
    
            this.fileObject = fileObject;
    
        }
    
      
    
        public File getFileObject() {
    
            return fileObject;
    
        }
    
      
    
        @Override
    
        protected Class findClass(String name) throws ClassNotFoundException {
    
            try {
    
                byte[] data = getClassFileBytes(this.fileObject);
    
                return defineClass(name, data, 0, data.length);
    
            } catch (Exception e) {
    
                e.printStackTrace();
    
                return null;
    
            }
    
      
    
        }
    
      
    
        /**
    
         * 从文件中读取去class文件
    
         *
    
         * @throws Exception
    
         */
    
        private byte[] getClassFileBytes(File file) throws Exception {
    
            //采用NIO读取
    
            FileInputStream fis = new FileInputStream(file);
    
            FileChannel fileC = fis.getChannel();
    
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
    
            WritableByteChannel outC = Channels.newChannel(baos);
    
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    
            while (true) {
    
                int i = fileC.read(buffer);
    
                if (i == 0 || i == -1) {
    
                    break;
    
                }
    
                buffer.flip();
    
                outC.write(buffer);
    
                buffer.clear();
    
            }
    
            fis.close();
    
            return baos.toByteArray();
    
        }
    
      
    
      
    
    }

    代码测试:

    Class aClass = new TcfClassLoader(new File("D:\\code\\tcf-jvm\\com\\tcf\\days01\\TcfEntity.class"))
    
            .findClass("com.tcf.days01.TcfEntity");
    
      Object o = aClass.newInstance();
    
      System.out.println(o.getClass().getClassLoader());

    根据类加载器手写热部署插件

    自定义类加载器

    ClassLoader 类加载器中  双亲委派机制 核心源码部分

    findLoadedClass()--- 首先,检查类是否已经加载
    parent.loadClass(name, false); 读取到parent.loadClass
     
    
    findBootstrapClassOrNull 使用启动类加载器读取
    findClass 扩展和应用类加载器、自定义类加载器

    public class TcfClassLoader extends ClassLoader {
    
      
    
        private File fileObject;
    
      
    
        public TcfClassLoader(File fileObject) {
    
            this.fileObject = fileObject;
    
        }
    
      
    
        public void setFileObject(File fileObject) {
    
            this.fileObject = fileObject;
    
        }
    
      
    
        public File getFileObject() {
    
            return fileObject;
    
        }
    
      
    
        @Override
    
        protected Class findClass(String name) throws ClassNotFoundException {
    
            try {
    
                byte[] data = getClassFileBytes(this.fileObject);
    
                return defineClass(name, data, 0, data.length);
    
            } catch (Exception e) {
    
                e.printStackTrace();
    
                return null;
    
            }
    
      
    
        }
    
      
    
        /**
    
         * 从文件中读取去class文件
    
         *
    
         * @throws Exception
    
         */
    
        private byte[] getClassFileBytes(File file) throws Exception {
    
            //采用NIO读取
    
            FileInputStream fis = new FileInputStream(file);
    
            FileChannel fileC = fis.getChannel();
    
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
    
            WritableByteChannel outC = Channels.newChannel(baos);
    
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
    
            while (true) {
    
                int i = fileC.read(buffer);
    
                if (i == 0 || i == -1) {
    
                    break;
    
                }
    
                buffer.flip();
    
                outC.write(buffer);
    
                buffer.clear();
    
            }
    
            fis.close();
    
            return baos.toByteArray();
    
        }
    
      
    
      
    
    }

    热部署插件代码

    热部署插件吗?我第一次使用热部署插件  底层就是基于类加载器实现。

    Javaweb框架知识

    Idea 支持插件 热部署

    热部署插件原理 (手写)

    1. 基于MD5和操作系统提供的api 获取文件最近一次的修改时间

    2、判断该class文件最近一次的修改日期和上一次部署时的修改日期是否一致,即是否发生变化。如果有发生变化,则重新使用类加载器读取最新的class文件到内存中,并更新最近一次的修改日期。

    1. 使用单独线程基于NIO做空轮询,监听class文件是否发生变化

    优点:实时、同步缺点:CPU资源消耗大

    比如 Tcf.class new Tcf()

    public class ClassFileEntity {
    
      
    
        /**
    
         * 类的名称
    
         */
    
        private String name;
    
      
    
        /**
    
         * class
    
         */
    
        private Class aClass;
    
      
    
        /**
    
         * 最后被更改的时间
    
         */
    
        private long lastModified;
    
        public ClassFileEntity(String name, long lastModified) {
    
            this.name = name;
    
            this.lastModified = lastModified;
    
        }
    
      
    
        public ClassFileEntity(String name, long lastModified, Class aClass) {
    
            this.name = name;
    
            this.lastModified = lastModified;
    
            this.aClass = aClass;
    
        }
    
      
    
        public String getName() {
    
            return name;
    
        }
    
      
    
        public Class getaClass() {
    
            return aClass;
    
        }
    
      
    
        public long getLastModified() {
    
            return lastModified;
    
        }
    
      
    
        public void setName(String name) {
    
            this.name = name;
    
        }
    
      
    
        public void setaClass(Class aClass) {
    
            this.aClass = aClass;
    
        }
    
      
    
        public void setLastModified(long lastModified) {
    
            this.lastModified = lastModified;
    
        }
    
    }

    public class HotDeploymentPlug {
    
        //存放所有的class文件
    
        private Map<String, ClassFileEntity> mapClassFiles = new HashMap<>();
    
        private String path;
    
        /**
    
         * 包的名称
    
         */
    
        private String packageName = "com.tcf.days01.";
    
      
    
        public HotDeploymentPlug(String path) {
    
            this.path = path;
    
        }
    
      
    
        public void start() {
    
            listener();
    
        }
    
      
    
        /**
    
         * 监听方法
    
         */
    
        public void listener() {
    
            new Thread(() -> {
    
                while (true) {
    
                    // 1.读取该文件下
    
                    File files = new File(path);
    
                    File[] tempList = files.listFiles();
    
                    // 2.读取class文件 存入到 mapClassFiles
    
                    for (File file :
    
                            tempList) {
    
                        String name = file.getName();
    
                        if (StringUtils.isEmpty(name)) {
    
                            continue;
    
                        }
    
                        long l = file.lastModified();
    
                        // 使用类加载器读取该 class
    
                        String className = packageName + name.replace(".class", "");
    
                        if (mapClassFiles.containsKey(className)) {
    
                            // 则比对该class文件 是否被修改
    
                            ClassFileEntity mapClassFileEntity = mapClassFiles.get(className);
    
                            if (mapClassFileEntity.getLastModified() != l) {
    
                                try {
    
                                    mapClassFileEntity.setLastModified(l);
    
                                    TcfClassLoader tcfClassLoader = new TcfClassLoader(file);
    
                                    Class aClass = tcfClassLoader.loadClass(className);
    
                                    Object o = aClass.newInstance();
    
                                    log.info(className + "class文件发生了变化");
    
      
    
                                } catch (Exception e) {
    
                                    log.error("e:{}", e);
    
      
    
                                }
    
                            }
    
      
    
                        } else {
    
                            ClassFileEntity newClassFileEntity = new ClassFileEntity(className, l);
    
                            // 如果不存在 则存入到mapClassFiles集合中
    
                            mapClassFiles.put(className, newClassFileEntity);
    
                        }
    
                        try {
    
                            Thread.sleep(300);
    
                        } catch (InterruptedException e) {
    
                            e.printStackTrace();
    
                        }
    
      
    
                    }
    
      
    
      
    
                }
    
            }).start();
    
        }
    
      
    
        public static void main(String[] args) {
    
            HotDeploymentPlug hotDeploymentPlug = new HotDeploymentPlug("D:\\code\\tcf-jvm\\com\\tcf\\days01");
    
            hotDeploymentPlug.listener();
    
        }
    
    }

    什么是SPI机制

    Java SPI全称Service Provider Interface(服务提供接口),是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制.

    实现方式:

    1. 首先需要在resources目录下:创建文件夹META-INF/services
    2. 定义接口文件的名称:

    D:\mt2020\code\tcf_jvm\src\main\resources\META-INF\services\com.tcf.service.MyService

    名称规范:包名+类名组成。

    com.tcf.service.impl.MyServiceImpl01
    
    com.tcf.service.impl.MyServiceImpl02

    ServiceLoader load = ServiceLoader.load(MyService.class);
    
    load.forEach((t) -> {
    
        System.out.println(t.get());
    
    });

    获取当前线程对应的应用程序类加载器,加载该class。

    ServiceLoader load = ServiceLoader.load(MyService.class);
    
    load.forEach((t) -> {
    
        System.out.println(t.get());
    
    });

    如何绕开双亲委派原则

    //        Thread.currentThread().setContextClassLoader(Test02.class.getClassLoader().getParent());
    
    //        Connection root =
    
    //                DriverManager
    
    //                        .getConnection
    
    //                                (
    
    //                                        "jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8",
    
    //                                        "root", "root");
    
    //        Class.forName("com.mysql.jdbc.Driver");
    
      
    
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
    
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
    
            try{
    
                while(driversIterator.hasNext()) {
    
                    Driver next = driversIterator.next();
    
                    System.out.println(next);
    
                }
    
            } catch(Throwable t) {
    
                // Do nothing
    
            }

  • 相关阅读:
    网络攻击中常见掩盖真实IP的攻击方式及虚假IP地址追踪溯源方法
    快速重拾 Tmux
    YOLO5Face:为什么要重新发明人脸检测器
    相机标定基本原理
    第七章 ObjectScript 一般系统限制
    功率放大器主要性能指标是什么(功率放大器工作状态的分类)
    Node 安装 Vuex
    从新零售到社区团购,这中间发生了多少变化?
    这些JS题面试时一定要答对!
    为什么要把Linux机器加入到Windows AD/域控制器(Linux机器为什么要入域)?
  • 原文地址:https://blog.csdn.net/qq_30056341/article/details/126392276