类加载器ClassLoader即用于加载其它类的类,将字节码加载进内存,创建Class对象,输入完全限定的类名,输出Class对象。
ClassLoader分三类:
Bootstrap ClassLoader→Extension ClassLoader→Application ClassLoader,后面的类有一个指针parent指向前面的类,ClassLoader在加载一个类时,基本步骤为:
该步骤所描述的模型即双亲委派模型,优先让父ClassLoader去加载。
这样做的原因是可以避免java类库被覆盖的问题,优先让父ClassLoader加载,避免自定义的类覆盖java类库,确保java安全机制,即使自定义ClassLoader可以不遵从双亲委派模型,但java安全机制保证以java开头的类不被其它类加载器加载。
ClassLoader是一个抽象类,提供将字节码(class文件)加载至内存成为Class的基本方法,每一个Class都有一个基本方法 :
默认情况下,上述三类类加载器都有默认实现,Bootstrap ClassLoader用C++实现,Extension ClassLoader与
Application ClassLoader的实现位于sun.misc.Launcher中。
ClassLoader的基本方法为:
Class的静态方法forName()同样用于加载类进入内存并返回Class对象,区别在于Class.forName加载时可执行static静态代码块,而ClassLoader的loadClass不会执行,Class.forName方法可指定参数boolean initialize决定是否执行,ClassLoader的loadClass内部调用方法loadClass(name,false),这个方法内部调用findClass(name),是真正的类加载方法,第二个参数名为resolve,为true时才链接去执行static语句块。由于双亲委派模型,即使设置为true,父类传递的仍然是false。
广泛应用于各种框架中,使用ClassLoader,可通过读取配置文件直接加载Class进入内存,这样在面向接口编程中不用使用new 声明具体的实现,通过加载配置文件加载指定的类并返回实例,不动代码动配置,可以帮助完成依赖注入这个抽象概念的一部分实现。
public class HelloWorld {
public static void say(){
System.out.println("HelloWorld");
}
}
#注意路径要正确,当前包下,也可以是其它包
word=com.lsl.kennen.common.HelloWorld
public static void main(String[] args) {
try {
//读取配置文件,注意文件名路径要正确
Properties properties=new Properties();
String fileName=new File("").getAbsolutePath()+ "/kennen/src/main/java/com/lsl/kennen/common/test.properties";
properties.load(new FileInputStream(fileName));
//从配置文件中获取源数据
String className =properties.getProperty("word");
//根据信息加载类进入内存返回Class
Class<?> cls=Class.forName(className);
//获取实例
HelloWorld helloWorld=(HelloWorld) cls.newInstance();
helloWorld.say();
}catch (Exception e){
e.printStackTrace();
}
}
//结果为HelloWorld
使用ClassLoader加载类,读取配置文件动态将Class加载进内存
自定义ClassLoader,利用defineClass方法直接返回一个Class>,可以远程调用Class的bytes读取Class文件将其加载进入本地内存并执行,这种功能就不是本地类加载器能办到的(没办法new,没办法forName,loadClass,因为读取的都是本地类的路径名加载本地bytes)。同时,面向接口编程时,可以动态加载calss,两个class,使用不同的ClassLoader加载,JVM就认为他们加载的对象是不同的,可以实现隔离与热部署。
自定义ClassLoader:继承ClassLoader,重写findClass,返回一个Class>
需要注意的是:
方法defineClass()用于实际读取bytes()返回Class>,然而指定的类名必须符合规范,如com.lsl.xxx这样的,内部类则是com.lsl.xxx$Iservice这样的
因为是通过反射创建实例,所以对象的构造器不能设置为private,否则抛出异常。
首先,先定义一个Class:
public class HelloWorld{
public void say(){
System.out.println("HelloWorld");
}
}
然后重写类加载器:
public class Main {
public static void main(String[] args) {
//注意路径
String className=new File("").getAbsolutePath()+ "/kennen/target/classes/com/lsl/kennen/common/HelloWorld.class";
ClassLoader classLoader=new MyClassLoader();
try {
Class<?> c=classLoader.loadClass(className);
((HelloWorld) c.newInstance()).say();
}catch (Exception e){
e.printStackTrace();
}
}
//重写ClassLoader
static class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
//本地测试,指定从本地读取class
FileInputStream fileInputStream;//指定源
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();//指定目的
try{
fileInputStream=new FileInputStream(name);
byte[] buf=new byte[1024];
int ite=0;
while((ite=fileInputStream.read(buf))!=-1){//从文件源中读取数据写入到buf
outputStream.write(buf,0,ite);//从buf中读取数据写入到outputStream
}
}catch (Exception e){
e.printStackTrace();
}
return defineClass("com.lsl.kennen.common.HelloWorld",outputStream.toByteArray(),0,outputStream.toByteArray().length);//加载class文件的byte数组(bytes),返回Class>
}
}
}
然而直接这样写会抛出异常,重写的ClassLoader将指定的class文件加载为Class,加载至内存,返回Class>,使用反射强制类型转换时如果直接强转为一个类会抛出异常,因为本地类没有没加载,就算加载了,使用的不是一个类加载器,加载出来的对象JVM是不认为相同的,这个时候就只能使用接口,可以强转为接口,只要加载的类实现了接口那么就可以强转为接口,调用接口方法。
定义接口:
public interface World {
public void say();
}
定义实现:
public class HelloWorld implements World{
@Override
public void say(){
System.out.println("HelloWorld");
}
}
将 ((HelloWorld) c.newInstance()).say();改为 ((World) c.newInstance()).say();
运转正常,World恢复。
热部署即在不停止程序运行的情况下动态更改Class,只要修改了Class就能立刻看出变化。
使用自定义ClassLoader,因为不同的类加载器可以加载出不同的Class,因此利用这一特性即可以实现模块隔离,也可以实现热部署。
定义服务实现:
public class HelloWorld implements World{
private static volatile World helloWorld;
public static World getHelloWorld(){
if(helloWorld==null){
synchronized (HelloWorld.class){
helloWorld=createHelloWorld();
}
}
return helloWorld;
}
public static World createHelloWorld(){
try{
MyClassLoader myClassLoader=new MyClassLoader();
String className=new File("").getAbsolutePath()+ "/kennen/target/classes/com/lsl/kennen/common/HelloWorld.class";
Class<?> c=myClassLoader.loadClass(className);
return ((World)c.newInstance());
}catch (Exception e){
e.printStackTrace();
}
return null;
}
public static void update(){
helloWorld=createHelloWorld();
}
@Override
public void say(){
System.out.println("HelloWorld");
}
}
服务内部实现内部维护一个接口World,提供服务功能,实际上是通过自定义的ClassLoader加载的Class返回的实例
类加载器为:
public class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
//本地测试,指定从本地读取class
FileInputStream fileInputStream;//指定源
ByteArrayOutputStream outputStream=new ByteArrayOutputStream();//指定目的
try{
fileInputStream=new FileInputStream(name);
byte[] buf=new byte[1024];
int ite=0;
while((ite=fileInputStream.read(buf))!=-1){//从文件源中读取数据写入到buf
outputStream.write(buf,0,ite);//从buf中读取数据写入到outputStream
}
}catch (Exception e){
e.printStackTrace();
}
return defineClass("com.lsl.kennen.common.HelloWorld",outputStream.toByteArray(),0,outputStream.toByteArray().length);//加载class文件的byte数组(bytes),返回Class>
}
}
模拟热部署,定义两个线程,一个不断访问服务,一个不断检测字节码问件是否改变以确定是否更新服务
public class Main {
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
while (true){
World world=HelloWorld.getHelloWorld();
world.say();
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
}
}
});
Thread thread1=new Thread(new Runnable() {
private long lastModilied=new File(new File("").getAbsolutePath()+ "/kennen/target/classes/com/lsl/kennen/common/HelloWorld.class").lastModified();
@Override
public void run() {
while (true){
try{
Thread.sleep(100);
//根据时间差判断文件是否被修改
long now=new File(new File("").getAbsolutePath()+ "/kennen/target/classes/com/lsl/kennen/common/HelloWorld.class").lastModified();
if(now!=lastModilied){
//由时间差更新内部服务,完成热部署,动态变更执行的Class
lastModilied=now;
HelloWorld.update();
}
}catch (Exception e){
e.printStackTrace();
}
}
}
});
thread1.setDaemon(true);
thread.start();
thread1.start();
}
}
当然直接运行时,没办法直接改Class文件,预先编译好Class,在运行时进行替换即可看到变化
对于远程加载Class,可以直接通过URLClassLoader加载,位于java.net包下,同样的,因为基于ClassLoader,所以一定要面向接口编程,具体的类强转是行不通的。参见: [locez.com/JAVA/urlcla…](
参考 :
<
www.ibm.com/developerwo…
locez.com/JAVA/urlcla…