自定义类加载器的流程:
0:继承:java.lang.ClassLoader
1.检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回。
2.委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例。
3.调用本类加载器的findClass()方法,试图获取对应的字节码,如果获取的到,则调用defineClass()导入类型到方法区,如果获取不到对应的字节码或者其它原因失败,返回异常给loadClass() ,loadClass转抛异常,终止加载过程。
注:被两个类加载器加载的同一个类,JVM不认为是相同的类。
类加载器的层次结构:引导类加载器,扩展类加载器,应用程序类加载器,自定义类加载器(开发人员可以继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足特殊需求)
类加载器的代理模式:代理模式是指交给其它的加载器来加载指定的类,双亲委托机制是加载器代理模式的一种。就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈的,如果父类加载器可以完成加载任务,就返回。只有父类加载器无法完成类加载任务时,才自己去加载。双亲委托机制保证了java核心库的安全。类加载器除了用于加载类,也是安全的最基本保障。
1. package com.bjsxt.test;
2. import java.io.ByteArrayOutputStream;
3. import java.io.FileInputStream;
4. import java.io.IOException;
5. import java.io.InputStream;
6. /**
7. * 自定义文件系统类加载器
8. * @author 尚学堂高淇 www.sxt.cn
9. *
10. */
11. public class FileSystemClassLoader extends ClassLoader {
12. //com.bjsxt.test.User --> d:/myjava/ com/bjsxt/test/User.class
13. private String rootDir;
14. public FileSystemClassLoader(String rootDir){
15. this.rootDir = rootDir;
16. }
17. @Override
18. protected Class<?> findClass(String name) throws ClassNotFoundException {
19. Class<?> c = findLoadedClass(name);
20. //应该要先查询有没有加载过这个类。如果已经加载,则直接返回加载好的类。如果没有,则加载新的类。
21. if(c!=null){
22. return c;
23. }else{
24. ClassLoader parent = this.getParent();
25. try {
26. c = parent.loadClass(name); //委派给父类加载
27. } catch (Exception e) {
28. // e.printStackTrace();
29. }
30. if(c!=null){
31. return c;
32. }else{
33. byte[] classData = getClassData(name);
34. if(classData==null){
35. throw new ClassNotFoundException();
36. }else{
37. c = defineClass(name, classData, 0,classData.length);
38. }
39. }
40. }
41. return c;
42. }
43. private byte[] getClassData(String classname){ //com.bjsxt.test.User d:/myjava/ com/bjsxt/test/User.class
44. String path = rootDir +"/"+ classname.replace('.', '/')+".class";
45. // IOUtils,可以使用它将流中的数据转成字节数组
46. InputStream is = null;
47. ByteArrayOutputStream baos = new ByteArrayOutputStream();
48. try{
49. is = new FileInputStream(path);
50. byte[] buffer = new byte[1024];
51. int temp=0;
52. while((temp=is.read(buffer))!=-1){
53. baos.write(buffer, 0, temp);
54. }
55. return baos.toByteArray();
56. }catch(Exception e){
57. e.printStackTrace();
58. return null;
59. }finally{
60. try {
61. if(is!=null){
62. is.close();
63. }
64. } catch (IOException e) {
65. e.printStackTrace();
66. }
67. try {
68. if(baos!=null){
69. baos.close();
70. }
71. } catch (IOException e) {
72. e.printStackTrace();
73. }
74. }
75. }
76. }
定义好类加载器后,为了测试该类加载器,我们在d:/java下定义一个HelloWorld.java
编译:javac -d . HelloWorld.java 这里要指定目录主要是 HelloWorld中有包。我们没写包,就得指定路径。
1. public class Demo03 {
2. public static void main(String[] args) throws Exception{
3. FileSystemClassLoader loader = new FileSystemClassLoader("d:/myjava");
4. FileSystemClassLoader loader2 = new FileSystemClassLoader("d:/myjava");
5. Class<?> c = loader.loadClass("com.bjsxt.gaoqi.HelloWorld");
6. Class<?> c2 = loader.loadClass("com.bjsxt.gaoqi.HelloWorld");
7. Class<?> c3 = loader2.loadClass("com.bjsxt.gaoqi.HelloWorld");
8. Class<?> c4 = loader2.loadClass("java.lang.String");
9. Class<?> c5 = loader2.loadClass("com.bjsxt.test.Demo01");
10. System.out.println(c.hashCode());
11. System.out.println(c2.hashCode());//c2=c
12. System.out.println(c3.hashCode()); //同一个类,被不同的加载器加载,JVM认为也是不相同的类
13. System.out.println(c4.hashCode());
14. System.out.println(c4.getClassLoader()); //引导类加载器
15. System.out.println(c3.getClassLoader()); //自定义的类加载器
16. System.out.println(c5.getClassLoader()); //系统默认的类加载器
17. }
18. }
网络类加载器:::
1. package com.bjsxt.test;
2. import java.io.ByteArrayOutputStream;
3. import java.io.FileInputStream;
4. import java.io.IOException;
5. import java.io.InputStream;
6. import java.net.URL;
7. /**
8. * 网络类加载器
9. * @author 尚学堂高淇 www.sxt.cn
10. *
11. */
12. public class NetClassLoader extends ClassLoader {
13. //com.bjsxt.test.User --> www.sxt.cn/myjava/ com/bjsxt/test/User.class
14. private String rootUrl;
15. public NetClassLoader(String rootUrl){
16. this.rootUrl = rootUrl;
17. }
18. @Override
19. protected Class<?> findClass(String name) throws ClassNotFoundException {
20. Class<?> c = findLoadedClass(name);
21. //应该要先查询有没有加载过这个类。如果已经加载,则直接返回加载好的类。如果没有,则加载新的类。
22. if(c!=null){
23. return c;
24. }else{
25. ClassLoader parent = this.getParent();
26. try {
27. c = parent.loadClass(name); //委派给父类加载
28. } catch (Exception e) {
29. // e.printStackTrace();
30. }
31. if(c!=null){
32. return c;
33. }else{
34. byte[] classData = getClassData(name);
35. if(classData==null){
36. throw new ClassNotFoundException();
37. }else{
38. c = defineClass(name, classData, 0,classData.length);
39. }
40. }
41. }
42. return c;
43. }
44. private byte[] getClassData(String classname){ //com.bjsxt.test.User d:/myjava/ com/bjsxt/test/User.class
45. String path = rootUrl +"/"+ classname.replace('.', '/')+".class";
46. // IOUtils,可以使用它将流中的数据转成字节数组
47. InputStream is = null;
48. ByteArrayOutputStream baos = new ByteArrayOutputStream();
49. try{
50. URL url = new URL(path);
51. is = url.openStream();
52. byte[] buffer = new byte[1024];
53. int temp=0;
54. while((temp=is.read(buffer))!=-1){
55. baos.write(buffer, 0, temp);
56. }
57. return baos.toByteArray();
58. }catch(Exception e){
59. e.printStackTrace();
60. return null;
61. }finally{
62. try {
63. if(is!=null){
64. is.close();
65. }
66. } catch (IOException e) {
67. e.printStackTrace();
68. }
69. try {
70. if(baos!=null){
71. baos.close();
72. }
73. } catch (IOException e) {
74. e.printStackTrace();
75. }
76. }
77. }
78. }
自定义加密解密加载器:
1. package com.bjsxt.test;
2. import java.io.File;
3. import java.io.FileInputStream;
4. import java.io.FileNotFoundException;
5. import java.io.FileOutputStream;
6. import java.io.IOException;
7. /**
8. * 加密工具类
9. * @author 尚学堂高淇 www.sxt.cn
10. *
11. */
12. public class EncrptUtil {
13. public static void main(String[] args) {
14. encrpt("d:/myjava/HelloWorld.class", "d:/myjava/temp/HelloWorld.class");
15. }
16. public static void encrpt(String src, String dest){
17. FileInputStream fis = null;
18. FileOutputStream fos = null;
19. try {
20. fis = new FileInputStream(src);
21. fos = new FileOutputStream(dest);
22. int temp = -1;
23. while((temp=fis.read())!=-1){
24. fos.write(temp^0xff); //取反操作
25. }
26. } catch (Exception e) {
27. e.printStackTrace();
28. }finally{
29. try {
30. if(fis!=null){
31. fis.close();
32. }
33. } catch (IOException e) {
34. e.printStackTrace();
35. }
36. try {
37. if(fos!=null){
38. fos.close();
39. }
40. } catch (IOException e) {
41. e.printStackTrace();
42. }
43. }
44. }
45. }
1. package com.bjsxt.test;
2. import java.io.ByteArrayOutputStream;
3. import java.io.FileInputStream;
4. import java.io.IOException;
5. import java.io.InputStream;
6. /**
7. * 加载文件系统中加密后的class字节码的类加载器
8. * @author 尚学堂高淇 www.sxt.cn
9. *
10. */
11. public class DecrptClassLoader extends ClassLoader {
12. //com.bjsxt.test.User --> d:/myjava/ com/bjsxt/test/User.class
13. private String rootDir;
14. public DecrptClassLoader(String rootDir){
15. this.rootDir = rootDir;
16. }
17. @Override
18. protected Class<?> findClass(String name) throws ClassNotFoundException {
19. Class<?> c = findLoadedClass(name);
20. //应该要先查询有没有加载过这个类。如果已经加载,则直接返回加载好的类。如果没有,则加载新的类。
21. if(c!=null){
22. return c;
23. }else{
24. ClassLoader parent = this.getParent();
25. try {
26. c = parent.loadClass(name); //委派给父类加载
27. } catch (Exception e) {
28. // e.printStackTrace();
29. }
30. if(c!=null){
31. return c;
32. }else{
33. byte[] classData = getClassData(name);
34. if(classData==null){
35. throw new ClassNotFoundException();
36. }else{
37. c = defineClass(name, classData, 0,classData.length);
38. }
39. }
40. }
41. return c;
42. }
43. private byte[] getClassData(String classname){ //com.bjsxt.test.User d:/myjava/ com/bjsxt/test/User.class
44. String path = rootDir +"/"+ classname.replace('.', '/')+".class";
45. // IOUtils,可以使用它将流中的数据转成字节数组
46. InputStream is = null;
47. ByteArrayOutputStream baos = new ByteArrayOutputStream();
48. try{
49. is = new FileInputStream(path);
50. int temp = -1;
51. while((temp=is.read())!=-1){
52. baos.write(temp^0xff); //取反操作,相当于解密
53. }
54. return baos.toByteArray();
55. }catch(Exception e){
56. e.printStackTrace();
57. return null;
58. }finally{
59. try {
60. if(is!=null){
61. is.close();
62. }
63. } catch (IOException e) {
64. e.printStackTrace();
65. }
66. try {
67. if(baos!=null){
68. baos.close();
69. }
70. } catch (IOException e) {
71. e.printStackTrace();
72. }
73. }
74. }
75. }
1. public class Demo04 {
2. public static void main(String[] args) throws Exception {
3. //测试取反操作
4. // int a = 3; //0000011
5. // System.out.println(Integer.toBinaryString(a^0xff));
6. //加密后的class文件,正常的类加载器无法加载,报classFormatError
7. // FileSystemClassLoader loader = new FileSystemClassLoader("d:/myjava/temp");
8. // Class<?> c = loader.loadClass("HelloWorld");
9. // System.out.println(c);
10. DecrptClassLoader loader = new DecrptClassLoader("d:/myjava/temp");
11. Class<?> c = loader.loadClass("HelloWorld");
12. System.out.println(c);
13. }
14. }
双亲委托机制以及类加载器的问题:
一般情况下,保证同一个类中所关联的其它类都是由当前类的类加载器所加载的。例如ClassA本身是在Ext下找到的,那么他里面new出来的一些类也只能用Ext去加载了,不会低一个级别。所以有些明明APP可以找到的,却找不到了。例如JDBC API的接口与实现类。
线程类加载器是为了抛弃双亲委派加载链模式,每个线程都有一个关联的上下文类加载器,如果使用new Thread()方式生成新的线程,新线程将继承其父线程的上下文加载器,如果程序对线程上下文类加载器没有任何改动的话,程序中所有的线程都使用系统类加载器作为上下文类加载器。
Thread.currentThread().getContextClassLoader()
Thread.currentThread().setContextClassLoader()
1. public class Demo05 {
2. public static void main(String[] args) throws Exception {
3. ClassLoader loader = Demo05.class.getClassLoader();
4. System.out.println(loader);
5. ClassLoader loader2 = Thread.currentThread().getContextClassLoader();
6. System.out.println(loader2);
7. Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("d:/myjava/"));
8. System.out.println(Thread.currentThread().getContextClassLoader());
9. Class<Demo01> c = (Class<Demo01>) Thread.currentThread().getContextClassLoader().loadClass("com.bjsxt.test.Demo01");
10. System.out.println(c);
11. System.out.println(c.getClassLoader());
12. }
13. }
tomcat服务器的类加载机制:
一切都是为了安全,TOMCAT不能使用系统默认的类加载器,如果TOMCAT跑你的WEB项目使用系统的类加载器是相当危险的,你可以直接毫无忌惮的操作系统的各个目录了。
对于运行在J2EE容器中的WEB应用来说,类加载器的实现方式与一般的JAVA应用有所不同。每个WEB应用都有一个对应的类加载器实例,该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器,与一般类加载器的顺序是相反的,但也是为了保证安全,这样核心库就不在查询范围之内。为了安全TOMCAT需要实现自己的类加载器,。
OSGI模块介绍:OSGI是JAVA上的动态模块系统,它为开发人员提供了面向服务和基于组件的运行环境,并提供标准的方式用来管理软件的生命周期。OSGI已经被实现和部署在很多系统上, eclipse就是基于OSGI技术来构建的。OSGI中的每个模块都包含JAVA包和类,模块可以声明它所依赖的需要导入的其它模块的包和类,也可以声明导出自己的包和类。OSGI中的每个模块都有一个类加载器,它负责加载模块自己所包含的类和包,当它需要加载JAVA核心库的类时,它会代理给父类加载器来完成。当它需要加载所导入的JAVA类时,它会代理给导出此JAVA类的模块来完成加载。详细可以学习Equinox(OSGI的一个实现)