双亲委派
1、定义及本质
- 定义
一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归。如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成此加载任务时,才自己去加载。 - 本质
规定了类加载的顺序是:引导类加载器先加载,若加载不到,由扩展类加载器加载,若还加载不到,才会由系统类加载器或自定义的类加载器进行加载。
2、优势弊端
2.1、优势
- 避免类的重复加载,确保一个类的全局唯一性
- 保护程序安全,防止核心API被篡改
2.2、弊端
检查类是否加载的委托过程是单向的,这个方式从结构上说比较清晰,使各个ClassLoader的职责非常明确,但是同时会带来一个问题,即顶层的ClassLoader无法访问底层的ClassLoader加载的类。
应用类访问系统类没有问题,但是系统类访问应用类就会出现问题。
3、三次双亲委派机制的破坏
3.1、JDK1.2之前
双亲委派模型在1.2之后才被引入,但是类加载器的概念和抽象类java.lang.ClassLoader则在Java的第一个版本中就已经存在。
为了兼容这些已有代码,无法再以技术手段避免loadClass() 被子类覆盖的可能性,只能在JDK1.2之后的 java.lang.ClassLoader 中添加一个新的protected方法findClass(),并引导用户编写的类加载逻辑尽可能去重写这个方法,而不是在loadClass() 方法的逻辑。
3.2、线程上下文类加载器
双亲委派很好地解决了各个类加载器协作时基础类型的一致性问题(越基础的类由越上层的类加载器进行加载)。
如果有基础类型要调用用户的代码,怎么办?
例如:JNDI服务(对资源进行查找和集中管理),它需要调用由其他厂商实现并部署在应用程序的ClassPath 下的JNDI服务提供者接口(Service Provider Interface,SPI)的代码。问题:启动类加载器是绝不可能认识、加载这些代码的
(SPI:在Java平台中,通常把核心类rt.jar中提供外部服务、可由应用层自行实现的接口称为SPI)
解决:线程上下文类加载器(Thread Context ClassLoader)
这个类加载器可以通过java.lang.Thread类的setContextClassLoader() 方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没设置过的话,那这个类加载器默认就是应用程序类加载器。
JNDI服务使用这个线程上下文类加载器去加载所需的SPI服务代码,这是一种父类加载器去请求子类加载器完成类的加载的行为,这种行为实际上是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则。
默认上下文加载器就是应用类加载器,这样以上下文加载器为中介,使得启动类加载器中的代码也可以访问应用类加载器中的类。
3.3、代码热替换(Hot Swap)、模块热部署(Hot Deployment)等
热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为。热替换的关键需求在于服务不能中断,修改必须立即表现正在运行的系统之中。
如果一个类已经加载到系统中,通过修改类文件,并无法让系统再来加载并重新定义这个类。因此,在Java中实现这一功能的一个可行的方法是灵活运用ClassLoader。
//基础代码
public class Demo1 {
public static final Integer x=99;
public void hot(){
// System.out.println("OldDemo1");
System.out.println(x);
}
}
//循环加载需要热部署的class文件
public class LoopRun {
public static void main(String[] args) {
while(true){
try{
MyClassLoader loader=new MyClassLoader("D:\\demo\\test\\out\\production\\test");
Class clazz=loader.findClass("hotDeployment.Demo1");
Object demo=clazz.newInstance();
Method m=clazz.getMethod("hot");
m.invoke(demo);
Thread.sleep(3000);
}catch (Exception e){
System.out.println("not find");
try{
Thread.sleep(5000);
}catch (InterruptedException ex){
ex.printStackTrace();
}
}
}
}
}
//类加载器,具体的加载逻辑
public class MyClassLoader extends ClassLoader{
private String rootDir;
public MyClassLoader(String rootDir){this.rootDir=rootDir;}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
Class clazz=this.findLoadedClass(className);
FileChannel fileChannel =null;
WritableByteChannel outChannel=null;
if(null==clazz){
try{
String classFile=getClassFile(className);
FileInputStream fis=new FileInputStream(classFile);
fileChannel=fis.getChannel();
ByteArrayOutputStream baos=new ByteArrayOutputStream();
outChannel= Channels.newChannel(baos);
ByteBuffer buffer=ByteBuffer.allocateDirect(1024);
while (true) {
int i=fileChannel.read(buffer);
if(i==0 || i==-1){
break;
}
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
byte[] bytes= baos.toByteArray();
clazz =defineClass(className,bytes,0, bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try{
if (fileChannel != null) {
fileChannel.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try{
if(outChannel!=null) {
outChannel.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
return clazz;
}
private String getClassFile(String className){
return rootDir+"\\"+className.replace('.','\\')+".class";
}
}