1、为什么需要自定义类加载器在《类加载器》中讲的,默认类加载器只能加载固定路径下的class,如果有特定路径下的class,需要自定义

安全性:系统自身需要一些jar,class,如果业务类代码中也有相同的class,破坏系统,类似双亲委托安全性

可以看看tomcat自定义类加载器的原因,别的就大同小异了a)、要保证部署在tomcat上的每个应用依赖的类库相互独立,不受影响。

b)、由于tomcat是采用java语言编写的,它自身也有类库依赖,为了安全考虑,tomcat使用的类库要与部署的应用的类库相互独立。

c)、有些类库tomcat与部署的应用可以共享,比如说servlet-api,使用maven编写web程序时,servlet-api的范围是provided,

表示打包时不打包这个依赖,因为我们都知道服务器已经有这个依赖了。

d)、部署的应用之间的类库可以共享。这听起来好像与第一点相互矛盾,但其实这很合理,类被类加载器加载到虚拟机后,

会生成代表该类的class对象存放在永久代区域,这时候如果有大量的应用使用spring来管理,如果spring类库不能共享,

那每个应用的spring类库都会被加载一次,将会是很大的资源浪费。

2、自定义加载器

这儿主要说下我司的自定义类加载器;更复杂点的可以看看tomcat的类加载机制

为什么需要自定义类加载器?这可以参考章节1的答案

主要在于应用与基础平台的隔离,相对应用:可以有更大技术选型自由度,不用考虑基础平台的jar包版本、相对平台:更可靠安全,不被应用class影响

类加载器结构

虽然JAVA使用了类加载的委派机制,但并没严格要求开发者必须遵守该机制,我们可以打破这种"双亲委派"机制

目录结构目录说明/servicesdir业务实现jar包

/thirddir业务依赖jar包

/platformdir平台依赖jar包

类加载器1.PlatformClassLoader平台加载器1.1.加载/platformdir下的jar包

1.2.在加载时,采用了默认的“双亲委派”

2.AppClassLoader应用加载器2.2.0.loadClass方法中,如果本加载器没有load到对应的类,则会检查该类是否处于平台类加载器白名单中:

2.2.1.如果处于白名单中,则委派PlatformClassLoader加载

2.2.2.否则,通过super.loadClass(String,boolean)走默认的双亲委派2.1.加载/servicesdir,/thirddir下的jar

2.2.该类加载器一定程度上打破了默认的“双亲委派”

此处白名单类:平台核心类,不能被同名业务类干扰

预加载

《类加载器》中说过,程序启动后,并不会加载所有类,在运行中实现到时,才会去加载。这儿就有性能损耗。

按类加载规则,一个类只加载一次

可以测试一下,加载需要的损耗/**

* 类加载时间性能测试

*

* 看一下类加载需要消耗的时间

* Created by Jack on 2018/10/8.

*/

public class ClassLoaderTest1 {

public static void main(String[] args) throws SQLException {

long s = System.nanoTime();

LoaderClass loaderClass = new LoaderClass();

long e = System.nanoTime();

//第一次时间

System.out.println(e - s);

e = System.nanoTime();

//第二次实例,但已经加载过,不再需要加载

LoaderClass loaderClass1 = new LoaderClass();

long e1 = System.nanoTime();

//第二次时间

System.out.println(e1 - e);

}

}

//输出

2409737

396

可以从输出看到性能损耗是不小的,这部分损耗可以通过预加载来消除

随着程序运行时间越久,被触发的业务越多,那加载到的业务类越多。

预加载类的逻辑

ClassWarmUp1.在classloader中loadClass时,把className加入到LinkedBlockingDeque中

2.为了性能,异步把deque中的class写入到文件中,需要起一个后台线程2.1 后台线程,从deque中取出class,写入到文件中

3.下次从文件中预先加载class

打包

对于/servicesdir 与 /thirddir 都好处理,但对于platformdir是怎么打包的呢?毕竟在开发时,只是引入一个平台基础jar就行

使用

有了自定义类加载器,在应用主函数中,就不能直接new了,不然就会使用AppClassLoader

所以需要使用反射机制Class> loadClass = platformClassLoader.loadClass("com.jack.Start");

Method startMethod = loadClass.getMethod("startUp");

startMethod.invoke(loadClass);

这样,通过Start加载的类也会通过platformClassLoader去加载

创建springcontext也一样,这儿还需使用到Thread.currentThread().getContextClassLoader()【下面有详解】ClassLoader currentThreadLoader = Thread.currentThread().getContextClassLoader();

Thread.currentThread().setContextClassLoader(appClassLoader);

Class> contextClass = appClassLoader

.loadClass("org.springframework.context.support.FileSystemXmlApplicationContext");

Class>[] parameterTypes = new Class[] { String[].class };

Constructor> constructor = contextClass.getConstructor(parameterTypes);

return constructor.newInstance(new Object[] { xmlPaths.toArray(new String[0]) });

// switch back the thread context classloader

Thread.currentThread().setContextClassLoader(currentThreadLoader);

3、反常

"双亲委派"模型有优点,也有力不从心的地方Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。

这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader)来加载的。引导类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类。 而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器。

场景:当高层提供了统一的接口让低层去实现,同时又要在高层加载(或者实例化)低层的类时,就必须要通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类

当使用本类托管类加载,然而加载本类的ClassLoader未知时,为了隔离不同的调用者,可以取调用者各自的线程上下文类加载器代为托管

解决方案:

从jdk1.2开始引入的,类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader c1),分别用来获取和设置类加载器

一般使用模式:获取-使用-还原ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

try{

Thread.currentThread().setContextClassLoader(targetTccl);

excute();

} finally {

Thread.currentThread().setContextClassLoader(classLoader);

}

jdbc

以jdbc看下场景1的情况Class.forName("com.mysql.jdbc.Driver")

String url = "jdbc:mysql://localhost:3306/testdb";

// 通过java库获取数据库连接

Connection conn = java.sql.DriverManager.getConnection(url, "name", "password");1.Class.forName("com.mysql.jdbc.Driver"); 在com.mysql.jdbc.Driver中public class Driver extends NonRegisteringDriver implements java.sql.Driver {

static {

try {

java.sql.DriverManager.registerDriver(new Driver());

} catch (SQLException E) {

throw new RuntimeException("Can't register driver!");

}

}

public Driver() throws SQLException {

// Required for Class.forName().newInstance()

}

}

通过Class.forName(),主要就是执行初始化static代码块,也就是向DriverManager注册Driver

此时:应用类、Driver是由AppClassLoader加载,但由于双亲委派java.sql.DriverManager是由BootstrapClassLoader加载2.java.sql.DriverManager.getConnection 获取连接private static Connection getConnection(

String url, java.util.Properties info, ClassLoader callerCL) throws SQLException {

java.util.Vector drivers = null;

synchronized(DriverManager.class) {

if(callerCL == null) {

callerCL = Thread.currentThread().getContextClassLoader();

}

}

if(url == null) {

throw new SQLException("The url cannot be null", "08001");

}

println("DriverManager.getConnection(\"" + url + "\")");

if (!initialized) {

initialize();

}

synchronized (DriverManager.class){

drivers = readDrivers;

}

SQLException reason = null;

for (int i = 0; i

DriverInfo di = (DriverInfo)drivers.elementAt(i);

if ( getCallerClass(callerCL, di.driverClassName ) != di.driverClass ) {

println("    skipping: " + di);

continue;

}

try {

println("    trying " + di);

Connection result = di.driver.connect(url, info);

if (result != null) {

// Success!

println("getConnection returning " + di);

return (result);

}

} catch (SQLException ex) {

if (reason == null) {

reason = ex;

}

}

}

if (reason != null)    {

println("getConnection failed: " + reason);

throw reason;

}

println("getConnection: no suitable driver found for "+ url);

throw new SQLException("No suitable driver found for "+ url, "08001");

}

private static Class getCallerClass(ClassLoader callerClassLoader,

String driverClassName) {

Class callerC = null;

try {

callerC = Class.forName(driverClassName, true, callerClassLoader);

}

catch (Exception ex) {

callerC = null;           // being very careful

}

return callerC;

}

这其中有两行代码:callerCL = Thread.currentThread().getContextClassLoader();

callerC = Class.forName(driverClassName, true, callerClassLoader);

这儿是取线程上下文中的classloader,也就是AppClassLoader;如果不取此classloader,那么Class.forName(driverClassName)就是使用DriverManager的BootstrapClassLoader加载,那必然是加载不到,这也就是父层类加载器加载不了低层类。

还有个问题,为什么在应用程序中已经加载过Driver,到了getConnection()又要再加载,还得通过Thread.currentThread().getContextClassLoader()?

其实在getConnection()中,只是对比class是否是同一个,像tomcat那样,各个应用都有自己的mysql-driver的jar包,就只能通过classloader来区分,因为class是不是相同需要classname+classloader组合鉴别

spring

对于场景2的问题如果有 10 个 Web 应用程序都用到了spring的话,可以把Spring的jar包放到 common 或 shared 目录下让这些程序共享。Spring 的作用是管理每个web应用程序的bean,getBean时自然要能访问到应用程序的类,而用户的程序显然是放在 /WebApp/WEB-INF 目录中的(由 WebAppClassLoader 加载),那么在 CommonClassLoader 或 SharedClassLoader 中的 Spring 容器如何去加载并不在其加载范围的用户程序(/WebApp/WEB-INF/)中的Class呢?

答案呼之欲出:spring根本不会去管自己被放在哪里,它统统使用线程上下文加载器来加载类,而线程上下文加载器默认设置为了WebAppClassLoader,也就是说哪个WebApp应用调用了spring,spring就去取该应用自己的WebAppClassLoader来加载bean

org.springframework.web.context.ContextLoader类public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {

if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {

throw new IllegalStateException(

"Cannot initialize context because there is already a root application context present - " +

"check whether you have multiple ContextLoader* definitions in your web.xml!");

}

Log logger = LogFactory.getLog(ContextLoader.class);

servletContext.log("Initializing Spring root WebApplicationContext");

if (logger.isInfoEnabled()) {

logger.info("Root WebApplicationContext: initialization started");

}

long startTime = System.currentTimeMillis();

try {

// Determine parent for root web application context, if any.

ApplicationContext parent = loadParentContext(servletContext);

// Store context in local instance variable, to guarantee that

// it is available on ServletContext shutdown.

this.context = createWebApplicationContext(servletContext, parent);

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

ClassLoader ccl = Thread.currentThread().getContextClassLoader();

if (ccl == ContextLoader.class.getClassLoader()) {

currentContext = this.context;

}

else if (ccl != null) {

currentContextPerThread.put(ccl, this.context);

}

if (logger.isDebugEnabled()) {

logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");

}

if (logger.isInfoEnabled()) {

long elapsedTime = System.currentTimeMillis() - startTime;

logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");

}

return this.context;

}

catch (RuntimeException ex) {

logger.error("Context initialization failed", ex);

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);

throw ex;

}

catch (Error err) {

logger.error("Context initialization failed", err);

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);

throw err;

}

}

关键代码:// 获取线程上下文类加载器,默认为WebAppClassLoader

ClassLoader ccl = Thread.currentThread().getContextClassLoader();

// 如果spring的jar包放在每个webapp自己的目录中

// 此时线程上下文类加载器会与本类的类加载器(加载spring的)相同,都是WebAppClassLoader

if (ccl == ContextLoader.class.getClassLoader()) {

currentContext = this.context;

}

else if (ccl != null) {

// 如果不同,也就是上面说的那个问题的情况,那么用一个map把刚才创建的WebApplicationContext及对应的WebAppClassLoader存下来

// 一个webapp对应一个记录,后续调用时直接根据WebAppClassLoader来取出

currentContextPerThread.put(ccl, this.context);

}

这样做的目的在于当通过ConetxtLoader的静态方法获取context的时候,能保证获取的是当前web application的context.实际上就是对于tomcat下面的任何一个线程,我们都能很方便的找出这个线程对应的webapplicationContext.于是在一些不能方便获取servletContext的场合,我们可以通过当前线程获取webapplicationContext.public static WebApplicationContext getCurrentWebApplicationContext() {

ClassLoader ccl = Thread.currentThread().getContextClassLoader();

if (ccl != null) {

WebApplicationContext ccpt = currentContextPerThread.get(ccl);

if (ccpt != null) {

return ccpt;

}

}

return currentContext;

}

总结

简而言之就是ContextClassLoader默认存放了AppClassLoader的引用,由于它是在运行时被放在了线程中,所以不管当前程序处于何处(BootstrapClassLoader或是ExtClassLoader等),在任何需要的时候都可以用Thread.currentThread().getContextClassLoader()取出应用程序类加载器来完成需要的操作

4、参考资料

以jdbc为例搞清contextClassLoader

java thread already started_自定义类加载器相关推荐

  1. java简单通讯录的实现02person类_Java自定义类加载器实现不同版本的类加载

    一 什么是类隔离技术 只要你 Java 代码写的足够多,就一定会出现这种情况:系统新引入了一个中间件的 jar 包,编译的时候一切正常,一运行就报错:java.lang.NoSuchMethodErr ...

  2. 28 Java类的加载机制、什么是类的加载、类的生命周期、加载:查找并加载类的二进制数据、连接、初始化、类加载器、双亲委派模型、自定义类加载器

    28Java类的加载机制 28.1.什么是类的加载 28.2.类的生命周期 28.2.1.加载:查找并加载类的二进制数据 28.2.2.连接 28.2.3.初始化 28.3.类加载器 28.4.类的加 ...

  3. java 自定义类加载器_Java-JVM 自定义类加载器

    一.sun.misc.Launcher (ExtClassLoader 与 AppClassLoader 的创建) publicLauncher() { Launcher.ExtClassLoader ...

  4. java自定义类加载器与实现类加载器加密与解密

    创建自定义加载器的类 package com.tuogo.classlocad;import java.io.ByteArrayOutputStream; import java.io.FileInp ...

  5. java的类加载器以及如何自定义类加载器

    ClassLoader作用 类加载流程的"加载"阶段是由类加载器完成的. 类加载器结构 结构:BootstrapClassLoader(祖父)–>ExtClassLoader ...

  6. Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论

    Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论 创建用户自定义的类加载器 要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的f ...

  7. java 扩展类加载器_java实现自定义类加载器

    各类加载器虽然以父子相称,但是没有继承关系 (视频教程推荐:java课程) 点入ClassLoader的源码查看样例:* * class NetworkClassLoader extends Clas ...

  8. java自定义类加载器

    import java.io.*;public class MyClassLoader extends ClassLoader{private String loaderName; //类加载器名称p ...

  9. java import自定义类_Java实现的自定义类加载器示例

    本文实例讲述了Java实现的自定义类加载器.分享给大家供大家参考,具体如下: 一 点睛 1 ClassLoader类有如下两个关键方法: loadClass(String name, boolean ...

  10. java加载自己写的类_java 自定义类加载器从磁盘或网络加载类

    一.编写自定义类加载器类 package com.mybatis.entity; import java.io.ByteArrayOutputStream; import java.io.File; ...

最新文章

  1. Python 格式化输出和while循环的两个小练习
  2. 协程-greenlet版(python 版)
  3. java deployment_deployment简略介绍
  4. 电泳涂装行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  5. Google在东京召开了一场AI座谈会
  6. 江苏卫视益智类节目《一战到底》不益智
  7. 西威变频器 服务器显示,西威变频器故障查询及操作方法
  8. 元旦大礼,JavaScript第四版红宝书送20本!
  9. 3d打印机自动调平Marlin固件配置
  10. 是时候重估“返利网”的市场价值了
  11. 刘德华郑秀文喜剧大片《魔幻厨房》DVD国语中字
  12. B. Shifting Sort(rotate函数旋转应用)
  13. 女工下班路上被3男子拖入黑巷 身中10多刀身亡
  14. 使用python将txt格式的数据转换为csv格式,读取csv数据前几行
  15. 教师学计算机信息,计算机教师
  16. asp.net 文件下载的五种方式
  17. 2021数维杯国际大学生数学建模挑战赛报名通知
  18. 公众号查题接口 搜题搭建
  19. Turf.js(地理空间GIS分析的js库),处理地图相关算法
  20. 自动驾驶“稳打地基”,小鹏汽车基于阿里云建自动驾驶AI智算中心算力可达600PFLOPS

热门文章

  1. AJAX 数据库实例
  2. HQL左外连接及排序
  3. 实时分析你的Windows Phone 7使用状况
  4. JVM监控及诊断工具GUI篇之Arthas(五):其他指令
  5. ElasticSearch全文搜索引擎之整合SpringBoot篇
  6. CentOS 6.5忘记root密码,怎么办?
  7. Java容器类研究8:HashMap
  8. 警惕!这5种“脸色”在暗示你这些健康问题!
  9. 解决屏蔽JS代码报错的问题
  10. IBM中低端存储解决方案