• 类的相同通过对是否为同一个类加载器进行判断【重点】


    1 第一个例子是网上找的

    类加载器相同,则类相同,否则false
    
    package com.java.classLoader;
     
    import java.io.IOException;
    import java.io.InputStream;
     
    public class ClassLoaderTest {
    	
    	public static void main(String[] args) {
    		ClassLoader myLoad = new ClassLoader() {
    			
    			@Override
    			public Class<?> loadClass(String name)throws ClassNotFoundException {
    				String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
    				InputStream is = getClass().getResourceAsStream(fileName);
    				if (null == is) {
    					return super.loadClass(name);
    				}
    				try {
    					byte[] b= new byte[is.available()];
    					is.read(b);
    					return defineClass(name,b,0,b.length);
    				} catch (IOException e) {
    					throw new ClassNotFoundException();
    				}
    				
    			}
    		};
    		
    		try {
    			Object obj = myLoad.loadClass("com.java.classLoader.ClassLoaderTest").newInstance();
    			System.out.println(obj.getClass());
    			// 这个obj使用的是自定义的classLoad 与 虚拟机自带的不是一个类加载器,所以返回false
    			System.out.println(obj instanceof ClassLoaderTest);
    			
    		} catch (InstantiationException | IllegalAccessException
    				| ClassNotFoundException e) {
    			e.printStackTrace();
    		}
    			
    	}
    	
    }
    
    ————————————————
    版权声明:本文为CSDN博主「朝闻道_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u011402896/article/details/79065896
    

      

    2 第2个例子是自己开发orm时,碰上的

    oracle 的jdbc返回时,将oracle的date以java oracle.sql.TIMESTAMP的形式取了出来,做orm时,将这个timestamp塞入bean Date对象时挂了,这个地方要做一个显式的类型转换,同时还有oracle number类型到内存BigDecimal(mysql decimal类型到内存)再到bean Long及Double类型,一开始是这样的:

    if(val != null && field.getType() != val.getClass()) {
        System.out.println(field.getType() + ":" + val.getClass());
        Class fcl = field.getType();
        if(val.getClass() == BigDecimal.class) {
            BigDecimal bigDecimal = (BigDecimal)val;
            if(fcl == Double.class)
                val = bigDecimal.doubleValue();
            else if(fcl == Integer.class)
                val = bigDecimal.intValue();
            else if(fcl == Float.class)
                val = bigDecimal.floatValue();
            else if(fcl == Long.class)
                val = bigDecimal.longValue();
            else if(fcl == BigInteger.class)
                val = bigDecimal.toBigInteger();
            else if(fcl == Short.class)
                val = bigDecimal.shortValue();
            else
                throw new DBException("unknown type to cast BigDecimal");
        }
    
        if(val.getClass() == oracle.sql.TIMESTAMP.class) {
            if(fcl == Date.class) {
                val = ((oracle.sql.TIMESTAMP)val).timestampValue();
            }
        }
    }
    
    field.set(domain, val);
    

    追加field类型为BigDecimal本身的情况,否则莫名其妙抛异常了

    else if(fcl == BigDecimal.class)
    ;

    结果BigDecimal的转换可以,但Timestamp不行,打印

    class java.util.Date:class oracle.sql.TIMESTAMP,明明此时val的类型已经是class oracle.sql.TIMESTAMP,为何==居然没进去进行转换

    原来,是因为oracle的jdbc使用了 JDBC注册原理与自定义类加载器解决com.cloudera.hive.jdbc41.HS2Driver的加载【重点】 这种技术,要达到类加载器隔离朴实案例的效果

    所以类加载器结构是这样的:

    启动类
      扩展类
        系统类
          jetty-加载oracle 其它版本
          JdbcDriverClassLoader-加载oracle ojdbc6-11.2.0.3
    

     *请注意,如果oracle其它版本在系统类加载器中,则达不到类加载器隔离朴实案例的效果,因为JdbcDriverClassLoader以系统类加载器为父加载器,而JdbcDriverClassLoader目前仅改写了findClass,如果系统类加载器里面有,则跑不到findClass,如果要处理这种情况,要按类加载器顺序-另一种绕开双亲委派的方式(二)通篇改写loadClass

    所以等号的左边val.getClass()是JdbcDriverClassLoader所加载的类,右边是jetty的web类加载器加载的类;由两个相互不可见的类加载器加载,在方法区地址不同,这两个Class对象不是同一个对象,==失效;

    那为什么上面BigDecimal的就可以,因为oracle的driver使用了父加载器的BigDecimal接收number,而jetty 的类加载加载的BigDecimal类,也是这个类,可能同属于扩展类加载器,可以直接==比较

    问题找到了,改为:

    Class oracleDate = getOracleJdbcClassLoader().loadClass(oracle.sql.TIMESTAMP.class.getName());  仍然使用JdbcDriverClassLoader过一遍
    System.out.println(System.identityHashCode(oracle.sql.TIMESTAMP.class));
    System.out.println(System.identityHashCode(oracleDate));
    System.out.println(System.identityHashCode(val.getClass()));
    if(val.getClass() == oracleDate) {  如果是timeStamp则转换
    if(fcl == Date.class) {
    Method method = oracleDate.getMethod("timestampValue");
    val = method.invoke(val);
    }
    }

     identitiyHashCode(hashcode & System.identityHashCode)显示:

    class java.util.Date:class oracle.sql.TIMESTAMP
    2038711062
    393774503
    393774503,后两者同属于JdbcDriverClassLoader,前者属于JettyClassLoder,done

    3 还是上面那个项目,碰到一个错

    System.out.println(domain.getRetType());
    System.out.println(Date.class);
    if(domain.getRetType().equals(Date.class.getClass())) {

    乍看好像没什么问题,比较一个数据的所属类型是否==Date类型,结果一直返回false

    其实,等号左边返回java.util.Date,右边为java.lang.Class,如下:

            java.util.Date date = new Date();
            System.out.println(date.getClass());
            System.out.println(java.util.Date.class.getClass());
            System.out.println(date.getClass().equals(java.util.Date.class.getClass()));
            System.out.println(date.getClass());
            System.out.println(java.util.Date.class);
            System.out.println(date.getClass().equals(java.util.Date.class));
    

    class java.util.Date
    class java.lang.Class
    false
    class java.util.Date
    class java.util.Date
    true

    3.半 work log,同时也是 第4点代码的修改

    目的:只有当slf4j为我们自定义加载器加载时,才设定特定的context配置,保持该目录不会被testcase执行而多了很多主pom logback的日志

    2020.3.27

    类加载器隔离朴实案例(二)logback

    Class clLoggerFactory = loadClass("org.slf4j.LoggerFactory");
    System.out.println(clLoggerFactory.getClassLoader());
    System.out.println(clLoggerFactory.getClass().getClassLoader());  类类型的Cl为启动类加载器
    System.out.println(FakeJdbcDriverClassLoader.class);            类加载器本身类
    System.out.println(FakeJdbcDriverClassLoader.class.getClassLoader());  加载该类加载器类的类加载器
    System.out.println(FakeJdbcDriverClassLoader.class.getClass().getClassLoader()); 类类型的Cl为启动类加载器
    if(clLoggerFactory.getClassLoader() != this)  如果没有由本加载器override加载,则忽视环境重置
    return;

    com.example.demo.testcase.FakeJdbcDriverClassLoader@7c041b41
    null
    class com.example.demo.testcase.FakeJdbcDriverClassLoader
    sun.misc.Launcher$AppClassLoader@87aac27
    null

    类类型的判断tips*3:

    xxx.getClass().getClass()

    Class xxx.getClass()

    xxx.class.getClass()

    在此之后的getClassLoader均会返回null

    类对象*3:

    xxx.getClass()

    Class xxx

    xxx.class

    在此之后的getClassLoader返回这些类所在类加载器

    4

    类加载器隔离朴实案例(二)logback 中,生产testcase中

    private void configureLogback() throws Exception {
        Class clLoggerFactory = loadClass("org.slf4j.LoggerFactory");
        Method getILoggerFactory = clLoggerFactory.getMethod("getILoggerFactory");
        Object loggerContext = getILoggerFactory.invoke(null);
        Class clLoggerContext = loadClass("ch.qos.logback.classic.LoggerContext");
        Method method = clLoggerContext.getMethod("reset");
        method.invoke(loggerContext);【此句出错】
     
        Class clJoranConfigurator = loadClass("ch.qos.logback.classic.joran.JoranConfigurator");
        Object configurator = clJoranConfigurator.newInstance();
     
        URL url = this.getClass().getClassLoader().getResource("scef-logback.xml");【重点】
        method = clJoranConfigurator.getMethod("setContext", loadClass("ch.qos.logback.core.Context"));
        method.invoke(configurator, loggerContext);
        method = clJoranConfigurator.getMethod("doConfigure", URL.class);
        method.invoke(configurator, url);
    }
    

     出现:object is not an instance of declaring class

    而在非testcase是好的,原因在于,主项目有slf4j的包,没有logback的包

    两个包的关系如下:

    package ch.qos.logback.classic;
    
    import org.slf4j.ILoggerFactory;
    import org.slf4j.Marker;
    
    public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle {
    
        public static ILoggerFactory getILoggerFactory() {
           
    

    testcase中,AppClassLoader中值哦与slf4j-api,没有logback,故返回了个null 环境

    使用mvn dependency:tree,果然有slf4j,无logback 

    而非testcase中

    主pom的slf4j包由jetty类加载器加载,而JdbcDriverClassLoader与之平行,单独findClass slf4j和logback包的类

    解决方案:

    1)干掉主pom的slf4j-api,让程序走JdbcDriverClassLoader的findClass,然而有大量历史代码使用了slf4j-api 桥接 log4j 1,干掉会造成编译失败

    2)主pom增加logback,就让testcase走AppClassLoader的slf4j和logback,非testcase运行期走JdbcDriverClassLoader(与jetty、tomcat类加载器平行,互相不干扰)读取resource自行加载

    还是报同样错,居然是个log4j的factory

    mvn dependency:tree

    根据:java日志组件的关系 slf4j logback log4j从源码来理解slf4j的绑定,以及logback对配置文件的加载

    我们把 所有依赖树枝叶反复mvn dependency:tree 去除slf4j-log4j12 exclustion掉(可以看到exclusion有递归性质),该包是slf4j 到 log4j1的桥接,最后还剩一个log4j的包,不过没有桥接不要紧,而且也不能删,因为有历史代码直接调用log4j的api

    done

     2020.8.4 跑testcase时Could not initialize class org.slf4j.MDC,原因是引入一个新包,新包引用slf4j-log4j12,增加exclusion修复

    5 受第4个例子启发,我们回到第2个例子

    Class oracleDate = getOracleJdbcClassLoader().loadClass(oracle.sql.TIMESTAMP.class.getName());  仍然使用JdbcDriverClassLoader过一遍
    System.out.println(System.identityHashCode(oracle.sql.TIMESTAMP.class));
    System.out.println(System.identityHashCode(oracleDate));
    System.out.println(System.identityHashCode(val.getClass()));
    if(val.getClass() == oracleDate) {  如果是timeStamp则转换
    if(fcl == Date.class) {
    Method method = oracleDate.getMethod("timestampValue");
    val = method.invoke(val);
    }
    }

    这里如果不进行JdbcDriverClassLoader.loadClass()过一遍,也不进行val.getClass() == oracleDate,直接喂给 method.invoke

    那么将会报:object is not an instance of declaring class

    因为val是JdbcDriverClassLoader中的oracle.sql.TIMESTAMP对象,oracleDate是主pom加载到jetty/tomcat类加载器的oracle.sql.TIMESTAMP类,oracleDate.getMethod("timestampValue")取得的Method也是jetty/tomcat类加载器的,而喂进去的val确是另外一个平行的类加载器JdbcDriverClassLoader中类的对象

    6 field.getType -> 字段类型,field.getClass ->

    java.lang.reflect.Field
  • 相关阅读:
    线性表单链表的实现
    线性表顺序存储结构
    【C语言】产生随机数
    TCP/IP协议
    【bfs】奇怪的电梯(P1135)
    【DFS】取数游戏(P1123)
    【DFS】圣诞夜的极光(P1454)
    【贪心】骑士的工作(P2695)
    【贪心】纪念品分组(P1094)
    生活的那么一点反思
  • 原文地址:https://www.cnblogs.com/silyvin/p/12197809.html
Copyright © 2020-2023  润新知