2010年Java高新技术A(5)类加载器和代理

1、什么是类加载器

顾名思义,类加载器(classloader)用来加载Java 类到Java 虚拟机中。一般来说,Java虚拟机 使用 Java 类的方式如下:

Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。

基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。

2、类加载器处在java中的什么位置



3、三个主要加载类

Java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类负责加载特定位置的类:
 BootStrap,ExtClassLoader,AppClassLoader
·
类加载器也是Java类,因为其是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap
·Java
虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载

类加载器之间的父子关系和管辖范围图




 

4、演示类加载器的树状结构

每个 Java 类都维护着一个指向定义它的类加载器的引用,通过 getClassLoader()方法就可以获取到此引用下面通过递归调用 getParent()方法来输出全部的父类加载器。

  1. public class ClassLoaderTree {   
  2.   
  3.     public static void main(String[] args) {   
  4.         ClassLoader loader = ClassLoaderTree.class.getClassLoader();   
  5.         while (loader != null) {   
  6.             System.out.println(loader.toString());   
  7.             loader = loader.getParent();   
  8.         }   
  9.     }   
  10.  }  

输出结果

sun.misc.Launcher$AppClassLoader@9304b1 
 sun.misc.Launcher$ExtClassLoader@190d11

第一个输出的是 ClassLoaderTree类的类加载器,即系统类加载器。它是 sun.misc.Launcher$AppClassLoader类的实例;第二个输出的是扩展类加载器,是 sun.misc.Launcher$ExtClassLoader类的实例。需要注意的是这里并没有输出引导类加载器,这是由于有些 JDK 的实现对于父类加载器是引导类加载器的情况,getParent()方法返回 null

5、加载流程分析

 

Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

首先当前线程的类加载器去加载线程中的第一个类。

如果类A中引用了类BJava虚拟机将使用加载类A的类装载器来加载类B

还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

每个类加载器加载类时,又先委托给其上级类加载器。

当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?

对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。

每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。

 

有一道面试,能不能自己写个类叫java.lang.System

通常是不可以的,由于类加载器的委托机制,会先将System这个类一级级委托给最顶级的BootStrap,由于BootStrap在其指定的目录中加载的是rt.jar中的类,且其中有System这个类,那么就会直接加载自己目录中的,也就是Java已经定义好的System这个类,而不会加载自定义的这个System

但是还是有办法加载这个自定义的System类的,此时就不能交给上级加载了,需要用自定义的类加载器加载,这就需要有特殊的写法才能去加载这个自定义的System类的。

 

6、自定义类加载器

1)、自定义的类加载器必须继承抽象类ClassLoader,要覆写其中的findClass(Stringname)方法,而不用覆写loadClass()方法。

2)、覆写findClass(Stringname)方法的原因:

是要保留loadClass()方法中的流程,因为loadClass()中调用了findClass(Stringname)这个方法,此方法返回的就是去寻找父级的类加载器。

loadClass()内部是会先委托给父级,当父级找到后就会调用findClass(Stringname)方法,而找不到时就会用子级的类加载器,再找不到就报异常了,所以只需要覆写findClass方法,那么就具有了实现用自定义的类加载器加载类的目的。

流程:

父级-->loadClass-->findClass-->得到Class文件后转化成字节码-->defind()

3)、编程步骤:

编写一个对文件内容进行简单加盟的程序

编写好了一个自己的类加载器,可实现对加密过来的类进行装载和解密。

编写一个程序,调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类,程序中除了可使用ClassLoaderload方法外,还能使用放置线程的上线文类加载器加载或系统类加载器,然后在使用forName得到字节码文件。

 

  1. import java.util.Date;  
  2.   
  3. public class ClassLoaderAttachment extends Date {  
  4.     //对此类进行加密  
  5.         public String toString(){  
  6.             return "hello world";  
  7.         }  
  8.         public static void main(String [] args){  
  9.               
  10.         }  
  11. }//自定义类加载器  
  12. import java.io.*;  
  13. //继承抽象类ClassLoader  
  14. public class MyClassLoader  extends ClassLoader {  
  15.     public static void main(String[] args) throws Exception {  
  16.         //传入两个参数,源和目标  
  17.         String scrPath = args[0];  
  18.         String destDir = args[1];  
  19.         //将数据读取到输入流中,并写入到输出流中  
  20.         FileInputStream fis = new FileInputStream(scrPath);  
  21.         String destFileName =   
  22.                 scrPath.substring(scrPath.lastIndexOf('\\')+1);  
  23.         String destPath = destDir + "\\" + destFileName;  
  24.         FileOutputStream fos = new FileOutputStream(destPath);  
  25.         //加密数据  
  26.         cypher(fis,fos);  
  27.         fis.close();  
  28.         fos.close();  
  29.     }  
  30.     //定义加密数据的方法  
  31.     private static void cypher(InputStream ips,OutputStream ops)throws Exception{  
  32.         int b = 0;  
  33.         while((b=ips.read())!=-1){  
  34.             ops.write(b ^ 0xff);  
  35.         }  
  36.     }  
  37.     //定义全局变量  
  38.     private String classDir;  
  39.     @Override//覆写findClass方法,自定义类加载器  
  40.     protected Class<?> findClass(String name) throws ClassNotFoundException {  
  41.         String classFileName = classDir + "\\" + name + ".class";   
  42.         try {  
  43.             //将要加载的文件读取到流中,并写入字节流中  
  44.             FileInputStream fis = new FileInputStream(classFileName);  
  45.             ByteArrayOutputStream bos = new ByteArrayOutputStream();  
  46.             cypher(fis,bos);  
  47.             fis.close();  
  48.             byte[] bytes = bos.toByteArray();  
  49.             return defineClass(bytes, 0, bytes.length);  
  50.               
  51.         } catch (Exception e) {  
  52.             // TODO Auto-generated catch block  
  53.             e.printStackTrace();  
  54.         }  
  55.         //如果没找到类,则用父级类加载器加载  
  56.         return super.findClass(name);  
  57.     }  
  58.     //构造函数  
  59.     public MyClassLoader(){}  
  60.     public MyClassLoader(String classDir){  
  61.         this.classDir = classDir;  
  62.     }  
  63. }  

 

代理

1、代理的概念与作用

举个例子理解代理,比如你想买车比如大众的车,你可以去你附近城市的4S店去买,而不必去一汽大众的场子去买。那4S店就属于代理。再比如说买烟,在具有烟草执照的商店就能买到的不用去烟草总公司去买。那带执照的商店就是代理。代理是为了给获取目标(汽车,烟)带来方便。

程序中的代理:在一些类似的操作之前进行某种逻辑处理,或者在操作之后进行记录操作日志  用java的代理给类似的操作抽象出相同的代理部分,不同的信息用注解之类的统一模板,会给应用带来很大方便

要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?

2、代理架构图

编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。

 

  1. mport java.util.*;  
  2. public class ProxyTest {  
  3.     public static void main(String[] args) throws Exception{  
  4.         //获取代理类Proxy的Class对象,传入的是类加载器和相应的字节码对象  
  5.         Class clazzProxy1 = Proxy.getProxyClass(  
  6.                 Collection.class.getClassLoader(), Collection.class);  
  7.         System.out.println(clazzProxy1.getName());//$Proxy0  
  8.           
  9.         System.out.println("---begin constructor list------");  
  10.         //获取代理类的构造方法,可能含有多个,得到数组  
  11.         Constructor[] constructors = clazzProxy1.getConstructors();  
  12.         //遍历数组,获取每个构造方法  
  13.         for(Constructor constructor : constructors){  
  14.             //先得到构造方法的名字,并装入字符串容器中  
  15.             String name = constructor.getName();  
  16.             StringBuilder sBul = new StringBuilder(name);  
  17.             sBul.append('(');  
  18.             //获取构造方法中的参数类型,并遍历  
  19.             Class[] clazzParams = constructor.getParameterTypes();  
  20.             for(Class clazzParam : clazzParams){  
  21.                 sBul.append(clazzParam.getName()).append(',');  
  22.             }  
  23.             //将最后一个逗号去除  
  24.             if(clazzParams != null && clazzParams.length!=0)  
  25.                 sBul.deleteCharAt(sBul.length()-1);  
  26.             sBul.append(')');  
  27.             System.out.println(sBul.toString());  
  28.         }  
  29.           
  30.         System.out.println("---begin method list------");  
  31.         //获取代理类的方法,存入数组  
  32.         Method[] methods = clazzProxy1.getMethods();  
  33.         //遍历数组,获取每个方法  
  34.         for(Method method : methods){  
  35.             //先得到方法的名字,并装入字符串容器中  
  36.             String name = method.getName();  
  37.             StringBuilder sBul = new StringBuilder(name);  
  38.             sBul.append('(');  
  39.             //获取方法中的参数类型,并遍历  
  40.             Class[] clazzParams = method.getParameterTypes();  
  41.             for(Class clazzParam : clazzParams){  
  42.                 sBul.append(clazzParam.getName()).append(',');  
  43.             }  
  44.             //将最后一个逗号去除  
  45.             if(clazzParams!=null && clazzParams.length!=0)  
  46.                 sBul.deleteCharAt(sBul.length()-1);  
  47.             sBul.append(')');  
  48.             System.out.println(sBul.toString());  
  49.         }  
  50. }  

3、代理的好处

如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

 

4、AOP     

  交叉业务的编程问题即为面向方面的编程(Aspect oriented program 简称AOP)AOP

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示


 

用具体的程序代码描述交叉业务

 

交叉业务的编程问题即为面向方面的编程(Aspectoriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:

 

使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。

4、动态代理技术

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情。

JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。

JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。

CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:

1).在调用目标方法之前

2).在调用目标方法之后

3).在调用目标方法前后

4).在处理目标方法异常的catch块中

单线程用stringbuilder

多线程用stringbuffer

让java虚拟机创建动态类及实例对象需要提供的信息

生成的类有哪些方法,通过让其实现的哪些接口的方式进行告知

产生的类字节码必须有一个关联的类加载器对象

生成的类中的方法时怎么样的也得由我们提供,把我们写的代码

或者通过newProxyInstance一步到位

动态代理的工作原理图

 

下面介绍一个简单的示例代码要求打印创建的对象和调用对象的无返回值的方法和getClass方法,演示调用其他没有返回值的方法报告的异常将创建的动态类的实例对象的代理改写成为匿名内部类的形式编写。

 

    1. import java.lang.reflect.*;  
    2. import java.util.*;  
    3. public class ProxyTest {  
    4.     public static void main(String[] args) throws Exception{  
    5. //创建动态代理类的三种方式  
    6.         //方式一:通过接口的子类创建对象  
    7.         Collection proxy1 = (Collection)  
    8.                 constructor.newInstance(new MyInvocationHandler());  
    9.         System.out.println(proxy1);//null  
    10.         System.out.println(proxy1.toString());//null  
    11.         proxy1.clear();//无异常  
    12.         //proxy1.size();//异常          
    13.         //方式二:匿名内部类  
    14.         Collection proxy2 = (Collection)  
    15.                 constructor.newInstance(new InvocationHandler(){  
    16.                     public Object invoke(Object proxy, Method method,  
    17.                             Object[] args) throws Throwable {  
    18.                         // TODO Auto-generated method stub  
    19.                         return null;  
    20.                     }  
    21.                 });  
    22.           
    23.         //方式三:  
    24.         //通过代理类的newProxyInstance方法直接创建对象  
    25.         Collection proxy3 = (Collection)Proxy.newProxyInstance(  
    26.             //定义代理类的类加载器  
    27.             Collection.class.getClassLoader(),  
    28.             //代理类要实现的接口列表  
    29.             new Class[]{Collection.class},  
    30.             //指派方法调用的调用处理程序  
    31.             new InvocationHandler() {  
    32.                 //创建集合,制定一个目标  
    33.                 ArrayList target = new ArrayList();  
    34.                 public Object invoke(Object proxy, Method method, Object[] args)  
    35.                         throws Throwable {  
    36.                     //测试程序运行时间  
    37.                     long beginTime = System.currentTimeMillis();  
    38.                     //调用目标方法,将其从return抽出来,加入代理所需的代码  
    39.                     Object retVal = method.invoke(target, args);  
    40.                     long endTime = System.currentTimeMillis();  
    41.                     //测试  
    42.                     System.out.println(method.getName() +   
    43.                             " run time of " +   
    44.                             (endTime - beginTime));  
    45.                     return retVal;  
    46.                 }  
    47.             }  
    48.             );  
    49.         //通过代理类调用目标方法,每调用一个目标的方法就会执行代理类的方法  
    50.         //当调用一次add方法时,就会找一次InvocationHandler这个参数的invoke方法  
    51.         proxy3.add("sdfd");  
    52.         proxy3.add("shrt");  
    53.         proxy3.add("rtbv");  
    54.         System.out.println(proxy3.size());  
    55.         System.out.println(proxy3.getClass().getName());  
    56.     }  

posted @ 2013-04-09 19:02  谷文仁  阅读(246)  评论(0编辑  收藏  举报