java基础加强--类加载器
一、用类加载器的方式管理和加载资源文件
在源目录(.java文件所在目录)下存放自己的资源,eclipse会自动把 .java文件编译成class文件,并存放到classpath下,
而将非.java文件原封不动的按目录结构移动到classpath下。所以我们可以使用类加载器来加载资源文件,但是这种方式是只读的。
这里所有的相对都是相对于.java文件的所在目录,也是就说在使用相对路径时,开始的路径都是此录,这里就是cn.java.test
本示例的资源文件在cn.java.test中
这里使用了四种加载方式:
1、使用输入流关联一个文件的方式,相对于工程目录src
InputStream in = new FileInputStream(“src/cn/java/test/config.properties”);
另外三种都是使用类加载器加载。
1、当资源在相对路径cn.java.test下(注意cn前面没有’/’):
ClassName.class.getClassLoader().getResourceAsStream(“cn/java/test/config.properties”);
2、比较简便的方式:
ClassName.class. getResourceAsStream(“config.properties”);
3、绝对方式(相对于第2种方式,较复杂):
ClassName.class.getResourceAsStream(“/cn/java/test/config.properties”);
如果我们在新建一个子包:cn.java.test.resource,将资源文件config.properties放在里面的话
第2种方式:ClassName.class. getResourceAsStream(“resource/config.properties”);
只是为了说明:其相对路径为cn.java.test
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.HashSet; import java.util.Properties; public class ReflectTest { public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException { //创建一个输入流并关联一个文件,相对于当前工作间,即工程路径 //InputStream in = new FileInputStream("src/cn/xushuai/test/resource/config.properties"); //使用累加器加载文件,会从根目录下找,所以加上包名。没有OutputStream,所以只能读 //InputStream in = ReflectTest.class.getClassLoader().getResourceAsStream("cn/xushuai/test/config.properties"); //相对方式:会自动到classpath的相对路径下找,比较方便,其实内部也是用类加载器的方式 InputStream in = ReflectTest.class.getResourceAsStream("resource/config.properties"); //使用绝对方式,从根开始写上完整目录 //InputStream in = ReflectTest.class.getResourceAsStream("/cn/xushuai/test2/config.properties"); //创建一个Properties对象 Properties props = new Properties(); //加载文件 props.load(in); //及时关闭关联的系统资源,否则忘记关闭时,当in被垃圾回收后,资源就无法释放了 in.close(); String className = props.getProperty("className"); //通过反射的方式获取一个实例对象(由properties文件中的className的值决定实例对象的类型) Collection collection = (Collection) Class.forName(className).newInstance(); //Collection collection = new HashSet(); ReflectPoint pt1 = new ReflectPoint(3,5); ReflectPoint pt2 = new ReflectPoint(3,7); ReflectPoint pt3 = new ReflectPoint(3,5); collection.add(pt1); collection.add(pt2); collection.add(pt3); //已经复写了hashCode和equals方法,所以无法存入 collection.add(pt1); //对象重复,无法存入 //pt1.y = 3; collection.remove(pt1); //不修改时,pt1可以被删除,size为1,修改后,无法删除,size为2 System.out.println(collection.size()); } }
二、 类加载器的概念及作用
1、Java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,
BootStrap、ExtClassLoader、AppClassLoader,每个类负责加载特定位置的类
2、类加载器也是java类,因为其他java类的加载器本身也要被加载,显然必须有第一个类加载器不是java类。这正是BootStrap
3、java虚拟机中的所有类加载器采用父子关系的树形结构进行组织,在实例化每个类加载器对象时,需要为其制定一个父级类装载器
对象或者默认采用系统装载器为其父类加载
4、java虚拟机在加载一个类时的方式
1> 首先当前线程的类加载器去加载线程中的第一个类
2> 如果类A中引用了类B,java虚拟机将使用加载类A的加载器来加载类B
3> 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类
类加载器之间的父子关系和管辖范围图
三、类加载器的委托机制
每个加载器加载类时,又委托给其上级类加载器,直至最高级,如果发现已经加载过,直接拿来使用即可,这样防止了字节码重复加载。
如果父类没有这个类,则一级级推回去,当所有父级类加载器没有加载到类,回到发起者类加载器,,如果还加载不了,则会抛出
ClassNotFoundException ,不会再去找子类去加载,因为没有getChild方法。
四、自定义类加载器
原理:
继承ClassLoader,不要覆盖loadClass方法,这样可以保留原来的委托机制,只覆盖findClass方法,使用自己的方式去查找,
得到Class文件之后通过defineClass方法(把一个字节数组变成一个Class后返回),获取Class对象。
示例说明:
1、对不带包名的ClassLoaderAttachment.class文件进行加密,加密结果存放到另外一个目录classlib文件夹下。
2、用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。
3、这时将CLASSPATH下的ClassLoaderAttachment.class文件删除,并在ClassLoaderTest类中调用自定义类加载器加载classlib下的ClassLoaderAttachment.class
有包名的类不能调用无包名的类
//需要加载的文件,这里要继承一个类,为了加密后,引用父类创建对象通过编译
import java.util.Date; public class ClassLoaderAttachment extends Date { public static void main(String[] args){} public String toString(){ return "hello,world"; } }
//类加载器测试类
import java.util.Date; public class ClassLoaderTest { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException{ System.out.println( ClassLoaderTest.class.getClassLoader() ); System.out.println( System.class.getClassLoader()); ClassLoader loader = ClassLoaderTest.class.getClassLoader(); while(loader!=null){ //System.out.println(loader.getClass().getName()); loader = loader.getParent(); } //System.out.println(new ClassLoaderAttachment().toString()); Class clazz = new MyClassLoader("classlib").loadClass("ClassLoaderAttachment"); //因为已经加密,编译器无法识别,所以我们继承自一个Date类型的父类,来创建对象 //ClassLoaderAttachment cla = (ClassLoaderAttachment)clazz.newInstance(); Date d = (Date)clazz.newInstance(); System.out.println("Date----------->"+d);//Date----------->hello,world } }
自定义类加载器,内部使用了加密算法,两个参数分别为:需要加密的.class文件的绝对路径和将要存放的目的
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Date; public class MyClassLoader extends ClassLoader { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { //运行前为程序传入两个参数 String srcPath = args[0]; String desDir = args[1]; FileInputStream fis = new FileInputStream(srcPath); //获取目标文件名,这里和源文件名相同,所以使用切割的方式 String destFileName = srcPath.substring(srcPath.lastIndexOf("\\") + 1); //获取目标路径:Dir+fileName String desPath = desDir + "\\" + destFileName; FileOutputStream fos = new FileOutputStream(desPath); //对文件进行加密 cypher(fis, fos); fis.close(); fos.close(); } //为文件加密的算法 private static void cypher(InputStream ips, OutputStream ops) throws IOException { int b = -1; while ((b = ips.read()) != -1) { ops.write(b ^ 0xff); } } public MyClassLoader() { } public MyClassLoader(String classDir) { this.classDir = classDir; } private String classDir; @Override protected Class<?> findClass(String name) throws ClassNotFoundException { //获取类文件名,这里对路径进行了截取,如果传入的是父类中的路径,那么截取文件名,以便访问指定路径下的.class文件 String classFileName = classDir+File.separator+name.substring(name.lastIndexOf('.')+1)+".class"; // classDir + "\\"+ name.substring(name.lastIndexOf('.')+1)+".class"; +".class"; System.out.println(".------------>MyClassLoader"); try { FileInputStream fis = new FileInputStream(classFileName); //定义一个字节数组流 ByteArrayOutputStream bos = new ByteArrayOutputStream(); cypher(fis, bos); fis.close(); byte[] bytes = bos.toByteArray(); return defineClass(bytes, 0, bytes.length); } catch (Exception e) { e.printStackTrace(); } return null; } }
五、一个类加载器的高级问题分析
注意:当有两个类A和B, A用到了B类时,类加载器会先加载A,然后用加载A的类加载器会去加载B
WebAppClassLoader是ExtClassLoader的子类,开始使用WebAppClassLoader加载MyServlet,再去加载HttpServlet,就是下面的
红线部分,而当我们把MyServlet.class文件打jar包,放到ext目录中时,这时使用的是父级加载器ExtClassLoader加载,由于委托机制,
只会从发起点结束,而不会去找子类加载,所以HttpServlet无法加载。
结论:父级类加载器加载的类无法引用只能被子级类加载器加载的类