类的相同通过对是否为同一个类加载器进行判断【重点】
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
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