类加载器与动态代理

来源:互联网 发布:in by 是什么软件 编辑:程序博客网 时间:2024/05/29 17:55


一、类加载器

    1.类加载器就是加载类的工具,java虚拟机JVM运行类的第一件事就是将这个类的字节码加载进来,即类加载器工具类的名称定位和生产类的字节码数据,然后返回给JVM。

    2.类加载器的作用:把字节码文件从硬盘上加载进内存来,并做一些处理之后,这些硬盘的字节码文件就变成了内存字节码。

    3.java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类,分别为:BootStrap、ExtClassLoader、AppClassLoader。

      类加载器也是Java类,因为其他是Java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是Java类,这就是BootStrap。

      除了系统提供的类加载器以外,可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器。

    4.所有的类加载器都有一个父类加载器,通过getParent()方法可以得到。系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器Java类的类加载器。

      因为类加载器Java类如同其它的Java类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根节点就是引导类加载器。

     Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器时,需要为指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。


示例:获取类加载器

public class ClassLoaderTest {      public static void main(String[] args) {          //返回类的完整名称          System.out.println(ClassLoaderTest.class);                      //以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。          System.out.println(ClassLoaderTest.class.getName());                      // 返回源代码中给出的底层类的简称。          System.out.println(ClassLoaderTest.class.getSimpleName());                     //返回该类的类加载器。          System.out.println(ClassLoaderTest.class.getClassLoader());                    //返回本类的类加载器的字节码对象          System.out.println(ClassLoaderTest.class.getClassLoader().getClass());                    //返回本类的类加载器的字节码对象的名称          System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());                     //返回系统类的加载器--没有          System.out.println(System.class.getClassLoader());        }  }  

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

      (1)首先当前线程的类加载器去加载线程中的第一个类( Thread类中的方法:getContextClassLoader() 获取上下文类加载器 ,也可以设置加载器:public void setContextClassLoader(ClassLoader cl));

      (2)如果类A中引用了类B,JVM将使用加载类A的类加载器来加载类B;

      (3)还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。每个类加载器加载类时,又先委托给其上级类加载器。

           当所有父类加载器都没有加载到类,回到发起者类加载器,还加载不了,则抛出ClassNotFoundException,不会去找发起者加载器的子类加载器,因为没有调用子类加载器的方法,即使有,也可能有很多分支,不知道加载哪一个。

二、动态代理

    1.动态代理的理解

    代理这个概念不只是java的特有,在现实生活中非常常见。在经营品牌电脑的专卖店也可以看做一个代理。其实代理这个概念不是一个新名词,在我们买电脑的时候,我们可以从代理商手里去买到电脑,当然我们也可以到该电脑的生产厂家去买电脑,但是他们的最终目的都是一个,就是你买到电脑。
    那么在java中什么是代理呢?

    现在我们要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理等等,我们就可以使用动态代理了。在java中的动态代理其实实现它的方式很多,sun公司在jdk中为我们提供了Proxy类去实现动态代理,其实不只是sun公司,还有一些第三方的公司为我们提供性能更好的、使用更方便的实现动态代理的手段。比如cglib,在spring框架中spring也使用以上技术封装了自己的动态代理技术。

    2.Java的动态代理

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

    
    从图中可以看出,其实代理类和目标类是必须实现相同的接口,这也是jdk与cglib实现的区别之处,cglib实现的原理是代理类是目标类的父类,他们是不需要实现相同的接口的。

    Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类: 

    (1)InvocationHandler:该接口中仅定义了一个方法即:

                  invoke(Object obj,Method method, Object[] args)在实际应用时,第一个参数obj一般是指代理类,method是被代理的方法,args为该方法的参数数组。

    (2)Proxy:该类即为动态代理类,其中主要包含以下内容: 

     protected Proxy(InvocationHandler handler):构造函数,估计用于给内部的h赋值。

     static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。

     static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用。
示例:三种传见代理类的方法

import java.lang.reflect.*;  import java.util.*;  public class ProxyTest {      public static void main(String[] args) throws Exception{          //创建动态代理类的三种方式          //方式一:通过接口的子类创建对象          Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHandler());          System.out.println(proxy1);//null          System.out.println(proxy1.toString());//null          proxy1.clear();     //无异常          //proxy1.size();    //异常                            //方式二:匿名内部类          Collection proxy2 = (Collection)constructor.newInstance(              new InvocationHandler(){              public Object invoke(Object proxy, Method method,Object[] args)throws Throwable{                  // TODO Auto-generated method stub                  return null;              }          });                    //方式三:          //通过代理类的newProxyInstance方法直接创建对象          Collection proxy3 = (Collection)Proxy.newProxyInstance(              //定义代理类的类加载器              Collection.class.getClassLoader(),              //代理类要实现的接口列表              new Class[]{Collection.class},              //指派方法调用的调用处理程序              new InvocationHandler() {                  //创建集合,制定一个目标                  ArrayList target = new ArrayList();                  public Object invoke(Object proxy, Method method, Object[] args)throws Throwable{                      //测试程序运行时间                      long beginTime = System.currentTimeMillis();                      //调用目标方法,将其从return抽出来,加入代理所需的代码                      Object retVal = method.invoke(target, args);                      long endTime = System.currentTimeMillis();                      //测试                      System.out.println(method.getName()+" run time of "+(endTime - beginTime));                      return retVal;                  }              }              );          //通过代理类调用目标方法,每调用一个目标的方法就会执行代理类的方法          //当调用一次add方法时,就会找一次InvocationHandler这个参数的invoke方法          proxy3.add("sdfd");          proxy3.add("shrt");          proxy3.add("rtbv");          System.out.println(proxy3.size());          System.out.println(proxy3.getClass().getName());      }  }  
总结:
    (1)因为利用JDKProxy生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。

    (2)生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。

    (3)利用JDKProxy方式必须有接口的存在。

    (4)invoke方法中的三个参数可以访问目标类的被调用方法的API、被调用方法的参数、被调用方法的返回类型。


3.实现类似spring的可配置的AOP框架

     AOP:面向切面编程。主要是处理一些交叉业务。如果将切面的代码移到原始方法的周围,原始方法看做目标类的方法,那么移动以后的切面代码和原始代码就是代理类对应的方法。

    代理技术的分类:

       静态代理类: 就是手动为每一个目标类的每一个方法都增加交叉业务,也就是手动为每一个目标类增加代理类。

                    缺点:如果目标类数量非常多或者目标类中的功能非常多,直接使用静态代理的方式来为目标类增加交叉业务会非常的繁琐。

       动态代理类:通过特定的设置,在程序运行期间指示JVM动态地生成类的字节码。这种动态生成的类往往被用作代理类,即动态代理类。

                    实现方式:使用JVM生成动态代理类,不过需要实现被代理类的实现接口。

     实现类似spring的可配置的AOP框架的思路:

    (1)创建BeanFactory类:
         构造方法:接受一个配置文件,通过Properties对象加载InputStream流对象获得。
         创建getBean(String name)方法,接收Bean的名字,从上面加载后的对象获得。
         通过其字节码对象创建实例对象bean。
         判断bean是否是特殊的Bean即ProxyFactoryBean,如果是,就要创建代理类,并设置目标和通告,分别得到各自的实例对象,并返回代理类实例对象。如果不是在返回普通类的实例对象。
    (2)创建ProxyFactoryBean(接口),此处用类做测试,其中有一个getProxy方法,用于获得代理类对象。
    (3)对配置文件进行配置,如上面配置一样。
    (4)作一个测试类:AopFrameworkTest进行测试。


0 0