java基础加强--类加载器

来源:互联网 发布:网络博客游戏破解之法 编辑:程序博客网 时间:2024/05/29 03:17

一、用类加载器的方式管理和加载资源文件

     

    在源目录(.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

   

   编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassloader。
   而当我们把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误

 原因:

     WebAppClassLoader是ExtClassLoader的子类,开始使用WebAppClassLoader加载MyServlet,再去加载HttpServlet,就是下面的

     红线部分,而当我们把MyServlet.class文件打jar包,放到ext目录中时,这时使用的是父级加载器ExtClassLoader加载,由于委托机制,  

     只会从发起点结束,而不会去找子类加载,所以HttpServlet无法加载。

 

结论:父级类加载器加载的类无法引用只能被子级类加载器加载的类

 


原创粉丝点击