类加载器隔离朴实案例【重点】【loadclass yetdone】

将子项目jar包及其依赖jar包置于tomcat类加载器不可见处(resource中)https://www.cnblogs.com/yaojf/p/9852802.html,手动以自定义类加载直接加载---避免子项目的jar包与母项目冲突,0侵入母项目;

 

问题:

主项目:tomcat,巨多jar包

  依赖xxx-1.1.jar

  同时依赖子项目:sub.jar

    中又依赖依赖xxx-1.2.jar

打包时搞在一起进war-lib目录,冲突

 

参考:

使用resource中的jar包资源作为UrlClassloader(二)

JDBC注册原理与自定义类加载器解决com.cloudera.hive.jdbc41.HS2Driver的加载【重点】 

这两篇文章侧重于maven仓库没有的包,意味着系统类加载器无法一上来就加载,没有子加载器想要覆盖父加载器的类的背景,故里面用的是改写findClass,因为这些类不在maven中,不涉及到loadClass双亲委派,findClass即可

类加载器顺序-另一种绕开双亲委派的方式(二)通篇改写loadClass

在上两篇文章的基础上侧重于实验用子加载器加载同名类覆盖掉父加载同名类的表现,从而打破双亲委派,达到本文的效果

类加载器顺序-另一种绕开双亲委派的方式

为上一篇文章绕开双亲委派的理论基础,实现了文中第2种方式

JDBC SPI 类加载机制

为上三篇文章的jdbc理论基础

类的相同通过对是否为同一个类加载器进行判断

解释了jdbc oracle碰到的类==问题 

load/find class 与 forname 在static代码块加载的不同 (二)非系统类jdbc

 解释了在使用非系统类加载器(更准确的说是DriverMagager static代码块执行时的线程类加载器)加载driver时,为什么需要显式forName或loadClass+X,以及这两者的区别

load/find class 与 forname 在static代码块加载的不同 (三)双亲委派

证明了jdbc机制大量涉及的forName也是双亲委派的

 

本文最终仍然失败,至此,类加载器顺序-另一种绕开双亲委派的方式中的三种绕开双亲委派的方式,仅有第一种(让jar不可见)是足够可靠的,幸运的是,本文的实践环境,无论是tomcat还是jetty都会用自己的加载器隔离war中pom依赖

 

本文结构

主项目:tomcat jetty war webclassloader
    依赖oracle 1
    依赖模块x JdbcDriverClassLoader
        非依赖加载oracle 2

x此前,在运行时取得的oracle driver类变成1了,因为tomcat会随机加载,导致出错
启动类
  扩展类
    系统类
      jetty-加载oracle 其它版本
      JdbcDriverClassLoader-加载oracle ojdbc6-11.2.0.3

 

使用自定义类加载器,得以从内存jvm方法区resource资源直接使用ClassLoader加载,提供一种隔离层,我所需要的特定版本的jdbc driver被隔离起来,通过这种方式,降低jdbc加载driver冲突,我的不影响它的,它的不影响我的

两种实现方式:

1)findClass

使用resource中的jar包资源作为UrlClassloader(二)

JDBC注册原理与自定义类加载器解决com.cloudera.hive.jdbc41.HS2Driver的加载【重点】

缺点是,造成冲突的oracle 干扰版本jar包不能在系统类加载器中

2)loadClass

类加载器顺序-另一种绕开双亲委派的方式(二)通篇改写loadClass

类加载器顺序-另一种绕开双亲委派的方式【yetdone】 

请注意:如果oracle其它版本在系统类加载器中,则需要用第2)种,因为JdbcDriverClassLoader以系统类加载器为父加载器,而JdbcDriverClassLoader仅改写了findClass,如果系统类加载器里面有,则跑不到findClass

 

我们选择方式2),有更广阔的应用场景,即使oracle在系统类加载器中也能用 ,设计

系统类加载器

  pom mysql-con2

  自定义加载器 load/findClass

    load resource-mysql-con5

 

 关键代码:

在pom中保持一个mysql 2的依赖,模拟冲突:

        <!--配合JdbcConfiguration中的getSystemClassLoader-->
		<!--模拟冲突-->
		<!--https://www.cnblogs.com/silyvin/p/12174761.html-->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>2.0.14</version>
		</dependency>

 DriverMagager将自动register这个类

 

 

package com.example.demo.testcase;

/**
 * Created by joyce on 2020/2/18.
 */
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * 使用这个类时,最终依据双亲委派,会调用系统类加载器的pom mysql2,Bad handshake
 */
public class FakeJdbcDriverClassLoader extends ClassLoader {

    private volatile Class aClass = null;

    JarInputStream[] list = null;
    private HashMap<String, byte[]> classes = new HashMap<>();

    private static volatile FakeJdbcDriverClassLoader jdbcDriverClassLoader;

    static {
        try {
            InputStream inputStream = FakeJdbcDriverClassLoader.class.getResourceAsStream("nativejars/mysql-connector-java-5.1.0-bin.jar");
            jdbcDriverClassLoader = new FakeJdbcDriverClassLoader(new JarInputStream[]{new JarInputStream(inputStream)});
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static FakeJdbcDriverClassLoader getJdbcDriverClassLoader() {
        return jdbcDriverClassLoader;
    }

    private FakeJdbcDriverClassLoader(JarInputStream [] jarInputStream) {
        this.list = jarInputStream;

        for(JarInputStream jar : list) {
            JarEntry entry;
            try {
                while ((entry = jar.getNextJarEntry()) != null) {
                    String name = entry.getName();
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    int len = -1;
                    byte [] tmp = new byte[1024];
                    while ((len = jar.read(tmp)) != -1) {
                        out.write(tmp, 0, len);
                    }
                    byte[] bytes = out.toByteArray();
                    classes.put(name, bytes);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("total classes - " + classes.size());

    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

        if(!name.equals("JdbcProxy")) {
            try {
                InputStream in = getResourceAsStream(name.replace('.', '/') + ".class");
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                int len = -1;
                byte[] tmp = new byte[1024];
                while ((len = in.read(tmp)) != -1) {
                    out.write(tmp, 0, len);
                }
                byte[] bytes = out.toByteArray();
                return defineClass(name, bytes, 0, bytes.length);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if(name.equals("JdbcProxy") && aClass != null)
            return aClass;

        synchronized (FakeJdbcDriverClassLoader.class) {

            if(aClass != null)
                return aClass;

            try {
                InputStream inputStream = FakeJdbcDriverClassLoader.class.getResourceAsStream("nativejars/JdbcProxy.class");
                byte[] tmp = new byte[1024];
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                int len = -1;
                int lenTotal = 0;
                while ((len = inputStream.read(tmp)) != -1) {
                    lenTotal += len;
                    byteArrayOutputStream.write(tmp, 0, len);
                }
                byte[] bytes = byteArrayOutputStream.toByteArray();
                if (bytes.length != lenTotal)
                    throw new RuntimeException("copy JdbcProxy error");

                aClass = defineClass(bytes, 0, bytes.length);
                return aClass;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        return super.findClass(name);
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        if(classes.containsKey(name)) {
            byte [] res = classes.get(name);
            classes.remove(name);
            return new ByteArrayInputStream(res);
        }
        System.out.println("getResourceAsStream - error - " + name);
        return super.getResourceAsStream(name);
    }
}

 

package com.example.demo.testcase;

/**
 * Created by joyce on 2020/2/18.
 */
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

public class JdbcDriverClassLoader extends ClassLoader {

    private volatile Class aClass = null;

    JarInputStream[] list = null;
    private HashMap<String, byte[]> classes = new HashMap<>();

    private static volatile JdbcDriverClassLoader jdbcDriverClassLoader;

    static {
        try {
            InputStream inputStream = JdbcDriverClassLoader.class.getResourceAsStream("nativejars/mysql-connector-java-5.1.0-bin.jar");
            jdbcDriverClassLoader = new JdbcDriverClassLoader(new JarInputStream[]{new JarInputStream(inputStream)});
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static JdbcDriverClassLoader getJdbcDriverClassLoader() {
        return jdbcDriverClassLoader;
    }

    private JdbcDriverClassLoader(JarInputStream [] jarInputStream) {
        this.list = jarInputStream;

        for(JarInputStream jar : list) {
            JarEntry entry;
            try {
                while ((entry = jar.getNextJarEntry()) != null) {
                    String name = entry.getName();
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    int len = -1;
                    byte [] tmp = new byte[1024];
                    while ((len = jar.read(tmp)) != -1) {
                        out.write(tmp, 0, len);
                    }
                    byte[] bytes = out.toByteArray();
                    classes.put(name, bytes);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("total classes - " + classes.size());

    }

    @Override
    /**
     * 由改写findClass改为改写loadClass,否则由于双亲委派,会调用系统类加载器的pom mysql2,Bad handshake
     */
    public Class<?> loadClass(String name) throws ClassNotFoundException {
    //protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("look for " + name);

        /**
         * 增加这句,findClass版本现成的loadClass会负责缓存已define的类
         * 在这里咱们的自己写
         * 否则attempted  duplicate class definition for name: "com/mysql/jdbc/Driver"
         */
        Class cCache = findLoadedClass(name);
        if(cCache != null)
            return cCache;

        if(!name.equals("JdbcProxy")) {
            try {
                InputStream in = getResourceAsStream(name.replace('.', '/') + ".class");
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                int len = -1;
                byte[] tmp = new byte[1024];
                while ((len = in.read(tmp)) != -1) {
                    out.write(tmp, 0, len);
                }
                byte[] bytes = out.toByteArray();
                return defineClass(name, bytes, 0, bytes.length);

            } catch (Exception e) {
                e.printStackTrace();
                /**
                 * 增加这句,否则不等于JdbcProxy又不在driver jar包中的类,无法交给父加载器
                 * 比如java/io/IOException,java/lang/Object
                 * findClass时不需要,是因为这些类本来就到不了这里
                 * 因为本加载器刷先loadClass(driver)AbstractJdbcUtil中,该driver类所使用的其它类
                 * 都会是xxx.class.getClassLoader().loadClass(...)的形式,所以都经过本函数,得往上给
                 */
                return super.loadClass(name);
            }
        }
        if(name.equals("JdbcProxy") && aClass != null)
            return aClass;

        synchronized (JdbcDriverClassLoader.class) {

            if(aClass != null)
                return aClass;

            try {
                InputStream inputStream = JdbcDriverClassLoader.class.getResourceAsStream("nativejars/JdbcProxy.class");
                byte[] tmp = new byte[1024];
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                int len = -1;
                int lenTotal = 0;
                while ((len = inputStream.read(tmp)) != -1) {
                    lenTotal += len;
                    byteArrayOutputStream.write(tmp, 0, len);
                }
                byte[] bytes = byteArrayOutputStream.toByteArray();
                if (bytes.length != lenTotal)
                    throw new RuntimeException("copy JdbcProxy error");

                aClass = defineClass(bytes, 0, bytes.length);
                return aClass;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        /**
         * 由super.findClass(name)改为super.loadClass(name)
         */
        return super.loadClass(name);
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        if(classes.containsKey(name)) {
            byte [] res = classes.get(name);
            classes.remove(name);
            return new ByteArrayInputStream(res);
        }
        System.out.println("getResourceAsStream - error - " + name);
        return super.getResourceAsStream(name);
    }
}

 

 public Connection getConnection() {
        if (conn == null) {
            synchronized (this) {
                if(conn == null) {
                    try {
                        // https://www.cnblogs.com/silyvin/p/12197809.html
                        String proxyName = "JdbcProxy";
                        if(classLoader.getClass() != JdbcDriverClassLoader.class && classLoader.getClass() != FakeJdbcDriverClassLoader.class) {
                            proxyName = JdbcProxy.class.getName();
                        }

                        // https://www.cnblogs.com/silyvin/articles/12425922.html
                        // 等价于loadClass加newIntance
                    //    Class.forName(driver, true, classLoader); https://www.cnblogs.com/silyvin/articles/12430446.html
                        Class a = classLoader.loadClass(driver);
                        /**
                         必须实例化,激活Driver的静态代码块,注册到DriverMangager中;
                         DriverManager只会自动装载DriverManager初始化时当前线程类加载器下的drivers
                         */
                        a.newInstance();

                        /**
                         * https://www.cnblogs.com/silyvin/articles/12166228.html
                         * 故意loadClass两边,测试重复defineClass问题
                         */
                        Class proxy = classLoader.loadClass(proxyName);
                        classLoader.loadClass(proxyName);

                        Method method = proxy.getMethod("getConnection", String.class, String.class, String.class);
                        conn = (Connection)method.invoke(null, url, user, pwd);
                        //    conn = DriverManager.getConnection(url, user, pwd);
                    } catch (InvocationTargetException e) {
                        throw new DBException(e.getTargetException());
                    } catch (Exception e) {
                        throw new DBException(e);
                    }
                }
            }

        }
        return conn;
    }

 

 

实验一:

FakeJdbcDriverClassLoader + pom   [Bad handshake]  冲突出现

  这种情况为什么会通过jdbc校验,事情发生的经过可能如下:

    DriverManager加载,自动读取系统类加载器下所有driver,forName加载注册

    DriverManager.getConnection-isDriverAllowed

      Class.forName(driver.getClass().getName(), true, classLoader);请注意:load/find class 与 forname 在static代码块加载的不同 (三)双亲委派

      result = ( aClass == driver.getClass() ) ? true : false;  forName也是双亲委派的,Fake~forName的类委派给父加载器系统类加载器返回,true,通过

FakeJdbcDriverClassLoader    ok  父加载器没有同名类,直接加载findClass

JdbcDriverClassLoader + pom  ok    子加载器改写loadClass,绕开双亲委派

JdbcDriverClassLoader      ok    无视你父加载器有没有,反正我就是绕开双亲

 

 

实验二:去掉c.newInstance(),相当于不进行注册

No suitable driver found for  因为pom mysql 2.0这个东西太古老,不符合jdbcDrivermanager的自动加载规则,所以没有自动加载,又没有实例化去激活Driver的static代码块

No suitable driver found for   pom都没有,怎么自动注册,你手动还漏东西

No suitable driver found for   pom都没有,怎么自动注册,你手动还漏东西

No suitable driver found for  我这有一个老的,他不支持自动注册,你自己有自己的,但是还是要来我这里注册

 

然而我们的JdbcDriverClassLoader方案在数次mysql访问后,报nested exception is java.lang.IllegalAccessError: class sun.reflect.GeneratedConstructorAccessor46 cannot access its superclass sun.reflect.ConstructorAccessorImpl] with root cause

 

=====================================================================================

此外,记录一种更common的解决方案: 

解决方案,分开打包,子项目先打,sub.jar、xxx-1.2.jar一同打进(assembly、shade)或分开打(jar)

主项目再引入

  依赖xxx-1.1.jar

  在resource引入sub.jar。。。xxx-1.2.jar,tomcat加载不到,也不会加载,会以资源文件对待

  依赖子项目代理

    某方法中自定义类加载器加载sub.jar。。。xxx-1.2.jar,形式如:使用resource中的jar包资源作为UrlClassloader(二)

    启动新线程,设置其类加载器为自定义加载器,应对spring这种会取当前线程类加载器 saturn java 热加载(二)资源文件 spring & logback 及JDBC注册机制JDBC SPI 类加载机制 

    在线程中反射执行sub.jar的主方法,启动netty server;形式如:load/find class 与 forname 在static代码块加载的不同 ;类加载器隔离朴实案例(二)logback

    

posted on 2020-01-10 10:09  silyvin  阅读(1067)  评论(0编辑  收藏  举报