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

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

posted on 2020-01-15 17:19  silyvin  阅读(562)  评论(0编辑  收藏  举报