今日整理记录内容:
1、类加载器(classLoader)
2、代理类(proxy)
一、类加载器(classLoader)
1、类加载器的作用就是将硬盘中的.class文件中的二进制数据加载进内存并将二进制数据转换为字节码,判断某个.class文件是否被加载过是基于类加载器的,也就是说不同的类加载器可以加载同一个.class文件,这时候内存中就有同一个类的两份字节码。
2、BootStrap—>(Jar/lib/rt.jar) 注意: 这不是一个类,而是C语言编写的底层代码
ExtClassLoader—>(Jar/lib/ext/*.jar)
AppClassLoader—>(ClassPath指定的所有jar或目录)
3、类加载器的向上委托机制:当我们用AppClassLoader加载器加载某个类时,这时AppClassLoader会先委托父类ExtClassLoader加载器去加载,而ExtClassLoader又会委托其父BootStrap去加载,如果BootStrap能加载这个类,就让BootStrap这个加载器去加载,如果不能就让其子类ExtClassLoader去加载,同理是否让AppClassLoader加载。
4、如果A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B。
还可以直接调用ClassLoadeer.loadClass()方法来指定某个类加载器去加载某个类。
5、自定义类加载器的用处:
当class文件不在ClassPath路径下,默认系统类加载器无法找到该class文件,在这种情况下我们需要实现一个自定义的ClassLoader来加载特定路径下的class文件生成class对象。
当一个class文件是通过网络传输并且可能会进行相应的加密操作时,需要先对class文件进行相应的解密后再加载到JVM内存中,这种情况下也需要编写自定义的ClassLoader并实现相应的逻辑。
当需要实现热部署功能时(一个class文件通过不同的类加载器产生不同class对象从而实现热部署功能),需要实现自定义ClassLoader的逻辑。
6、类加载器间的关系(并非指继承关系):
启动类加载器(BootStrap),由C++实现,没有父类。
拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null
系统类加载器(AppClassLoader),**由Java语言实现,父类加载器为**ExtClassLoader
7、注意这里所指的父类并不是Java继承关系中的那种父子关系
8、自定义类加载器,父类加载器肯定为AppClassLoader。
解释:
自定义加载器类:
public class MyClassLoaderTest extends ClassLoader{
private String rootDir;
public MyClassLoaderTest(String rootDir) {
this.rootDir = rootDir;
}
/**
* 编写findClass方法的逻辑
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 获取类的class文件字节数组
System.out.println("调用了findClass()");
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
//直接生成class对象
return defineClass(name, classData, 0, classData.length);
}
}
/**
* 编写获取class文件并转换为字节码流的逻辑
* @param className
* @return
*/
private byte[] getClassData(String className) {
// 读取类文件的字节
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
// 读取类文件的字节码
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 类文件的完全路径
* @param className
* @return
*/
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
public static void main(String[] args) throws IllegalAccessException, InstantiationException{
MyClassLoaderTest myClazzLoader1 = new MyClassLoaderTest("F:/");
MyClassLoaderTest myClazzLoader2 = new MyClassLoaderTest("F:/");
try {
//直接调用findClass方法可以避免向上委托机制的发生和读取内存的过程;
Class clazz1 = myClazzLoader1.findClass("com.hbbfxy.ClassTest");
Class clazz2 = myClazzLoader2.findClass("com.hbbfxy.ClassTest");
//Class clazz3 = myClazzLoader1.findClass("com.hbbfxy.ClassTest"); //这里会报错,因为内存中已经有了myClazzLoader1对象的类对象,报告重复定义错误。
System.out.println("clazz1:"+clazz1.hashCode());
System.out.println("clazz2:"+clazz2.hashCode());
//调用loadClass方法会执行向上委托机制和读取内存过程
Class clazz4 = myClazzLoader1.loadClass("com.hbbfxy.ClassTest");
Class clazz5 = myClazzLoader2.loadClass("com.hbbfxy.ClassTest");
//Class clazz6 = myClazzLoader2.loadClass("com.hbbfxy.ClassTest");//这里不会报错,因为这是从内存中直接取出,没有向内存中存储。
System.out.println("clazz4:"+clazz4.hashCode());
System.out.println("clazz5:"+clazz5.hashCode());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出结果:
调用了findClass()
调用了findClass()
clazz1:118352462
clazz2:1550089733
clazz4:118352462 //因为之前myClazzLoader1调用findClass()已经将myClazzLoader1类的类对象存储到内存,所以这里直接获取。
clazz5:1550089733 //因为之前myClazzLoader2调用findClass()已经将myClazzLoader2类的类对象存储到内存,所以这里直接获取。
上述自定义加载器类(摘自深入理解Java类加载器(ClassLoader))
测试类加载器之间的关系:
public static void main(String[] args) {
// TODO Auto-generated method stub
/*获得Bootstrap类加载器*/
ClassLoader clazzLoader = System.class.getClassLoader();
System.out.println("返回null就说明使用的是BootStrap类加载器:"+clazzLoader);
/*查看自定义加载器的相关加载器之间的关系*/
MyClassLoaderTest myClazzLoader = new MyClassLoaderTest(" ");
System.out.println("加载自定义加载器类的加载器:"+MyClassLoaderTest.class.getClassLoader());
System.out.println("自定义类加载器的父类:"+myClazzLoader.getParent());
System.out.println("自定义类加载器的父类的父类:"+myClazzLoader.getParent().getParent());
System.out.println("自定义类加载器的父类的父类的父类:"+myClazzLoader.getParent().getParent().getParent());
}
输出结果:
返回null就说明使用的是BootStrap类加载器:null
加载自定义加载器类的加载器:sun.misc.Launcher$AppClassLoader@73d16e93
自定义类加载器的父类:sun.misc.Launcher$AppClassLoader@73d16e93
自定义类加载器的父类的父类:sun.misc.Launcher$ExtClassLoader@6d06d69c
自定义类加载器的父类的父类的父类:null
二、代理Proxy
JVM动态生成的类所用的接口和生成的类所代理的类用的接口一致,这样保证方法存在。主要用于想在调用一个方法的前后自动完成某件事情。
解释:
动态生成代理类:
1、创建功能扩展方法的接口类
publicinterface Advice{
void before();
void after();
}
2、创建代理框架
/*生成代理框架*/
private static Object getProxy(Object target, Advice advice){
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
advice.before();
Object retVal = method.invoke(target, args);
advice.after();
return retVal;
}
});
return proxy;
}
3、使用代理类
Collection<String> list = (Collection)getProxy(new ArrayList(), new Advice(){
@Override
public void before() {
// TODO Auto-generated method stub
System.out.println("执行方法前调用的代码。");
}
@Override
public void after() {
// TODO Auto-generated method stub
System.out.println("执行方法后调用的代码。");
}
});
list.add("1");
输出结果:
执行方法前调用的代码。
执行方法后调用的代码。
这里我们需要明白动态生成代理类需要类加载器和要实现的接口,类加载器和要实现的接口和被代理的类相同。
Object invoke(Object proxy, Method method, Object[] args)三个参数:
proxy: 动态生成的代理类;
method:调用的方法;
args:方法中的参数。
注意:被代理的类一定要有接口。
学习心得:观看资料–》思考问题–》实践证明–》整理记录 = 有思想的技术大牛!