类加载器与代理

来源:互联网 发布:mac mysql dmg 网盘 编辑:程序博客网 时间:2024/06/07 07:32
---------------------- android培训、java培训、期待与您交流! ----------------------

类加载器简而言之就是加载类的工具,当我们在java程序里要用到一个类,JVM首先要将这个类的.class文件加载到内存里去,通常这个类的.class文件是放在硬盘上classpath指定的目录里,将这个.class文件信息加载到内存后再对其进行必要的处理,处理完的结果就是字节码,而这个工作就是类加载器在做。

如下面的测试获得加载器的程序:

 

public class ClassLoaderTest {

    public static void main(String[] args)throws Exception {

              System.out.println(

              ClassLoaderTest.class.getClassLoader().getClass().getName());

       System.out.println(System.class.getClassLoader());

}

}

//打印结果:sun.misc.Launcher$AppClassLoader

           Null

为什么会出现以上两种打印结果呢?

这是因为在JVM中可以安装多个类加载器,系统默认三个主要的类加载器,每个类加载器负责加载土特定位置的类:

BootStrap ,ExtClassLoader,AppClassLoader             

类加载器本身也是一个java类,那其本身就应该也要被其他类加载器加载,所以必须有一个类加载器不适java类,这个类加载器就是BootStrap(位于JVM内核,JVM一启动它就存在了),而上面第二个打印结果null就说明了这一点。上面的三个加载器之间的关系以及各自作用范围可以通过下面的程序来显示:

public class ClassLoaderTest {

public static void main(String[] args)throws Exception {

       ClassLoader loader = ClassLoaderTest.class.getClassLoader();

       while(loader !=null){

           System.out.println(loader.getClass().getName());

           loader = loader.getParent();

       }

    System.out.println(loader);

       }

}

打印结果:

sun.misc.Launcher$AppClassLoader

sun.misc.Launcher$ExtClassLoader

null

其相应的关系及作用范围如下图:


每一个加载器都会有期父类加载器(BootStrp除外),在自定义加载器时也应指定其父类加载器,当没有显示的设置加载器时,系统会默认为我们加上。JVM要加载一个类时到底要派出哪个类加载器去加载呢?

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

2, 如果类A引用了类B,JVM将使用类A的类加载器类加载B类

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

每个类加载器加载类时,又先委托给其上级类的加载器。当所有的上级加载器(祖宗加载器)没有加载到类,就回发都发起的类加载器,还加载不了,则抛出异常ClassNotFoundException,而不是再去找发起类加载器的儿子,原因是没有getChild方法,即使有,有多个儿子时,也不知找一个。

 

自定义类加载器

1, 自定义类加载器必须继承抽象类ClassLoader

2, loadClass方法与findClass方法

loadClass方法是根据传入的字符串即类名来加载某一个类,返回一个class,但自定义加载器不用覆盖此方法,因为此loadClass内部会去找其父类加载器,当父类返回后再接着调用findClass方法,所以我们只要覆盖findClass方法就可以了,继承ClassLoader后期内部的loadClass会继而按照原来的默认方式工作,如果覆盖必将非常麻烦。

3, defineClass方法

该方法接收一个class文件对应的二进制数据,他就会返回class字节码。

 

代理的概念与作用

在日常生活中,当我们要买一件商品时,我们往往不会直接跑到生产这件商品的所在地购买,而是在距离自己较短的地方买这件商品,这个出售商品的地方就是这件商品的代理商,而对于我们来说这必然比到原产地去买药方便的多。java中的代理概念与此类似,在程序中,要为已经存在的多个具有相同接口的目标类的各个方法增加一些功能,例如,异常处理、日志、计算方法的运行时间、事物管理等等,这都要用到代理,我们就编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统的代码。其架构图为:


这种基于代理的编程在java中的运用还是很多的,比如AOP(Aspect oriented program面向方面的编程),AOP的目标就是要使交叉业务模块化。何为交叉业务,一个交叉业务就是要切入到系统中的一个方面。

 

动态代理技术:

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事,而JVN可以再运行期间动态生成出类的字节码,这种动态生成类往往被用作代理类,即动态代理类,JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理,CGLIB库可以动态的生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。 代理类的各个方法通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:

1, 在调用目标方法之前

2, 在调用目标方法之后

3, 在调用目标方法前后

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

以一个编码(利用API中的Proxy)来对JVM动态生成的类进行分析

import java.lang.reflect.Constructor;

import java.lang.reflect.Proxy;

import java.util.Collection;

public class ProxyTest {

    public static void main(String[] args)throws Exception{

       Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);

       System.out.println(clazzProxy1.getName());

       System.out.println("----beginconstructors list----");

       Constructor[] constructors =clazzProxy1.getConstructors();//构造方法,返回的是一个元素为构造方法的数组

       for(Constructor constructor : constructors){

           String name = constructor.getName();

           StringBuilder sBuilder = new StringBuilder(name);

           /*此对象定义在main中,只会被一个线程调用。就应该用StringBuilder

            * 效率就会将高些,而StringBuffer适合在

            * 多线程中使用,在这里不宜使用*/

           sBuilder.append('(');

           Class[] clazzParams =constructor.getParameterTypes();

           for(Class clazzParam : clazzParams){

               sBuilder.append(clazzParam.getName()).append(',');

/*方法链的使用,append方法返回的就是本类对象,故可以再接着调用append方法

               * 参数列表最后一个元素后也会有逗号,应再删除最后一个逗号

               */

           }

           if(clazzParams!=null && clazzParams.length != 0)

              sBuilder.deleteCharAt(sBuilder.length()-1);

           sBuilder.append(')');

           System.out.println(sBuilder.toString());        

       }

 

       System.out.println("-----beginmethods list-----");

       Method[] methods = clazzProxy1.getMethods();

       for(Method method : methods){

           String name = method.getName();

           StringBuilder sBuilder = new StringBuilder(name);

           sBuilder.append('(');

           Class[] clazzParams = method.getParameterTypes();

           for(Class clazzParam : clazzParams){

              sBuilder.append(clazzParam.getName()).append(',');

             

           }

           if(clazzParams!=null && clazzParams.length != 0)

              sBuilder.deleteCharAt(sBuilder.length()-1);

           sBuilder.append(')');

           System.out.println(sBuilder.toString());        

       }

通过以上的编码,我们就可以得到一个Collection接口的动态类,列出了其所有的构造方法和其所有的方法。在此基础上还可以创建出此动态类的实例对象,继而通过对此实例对象可以对类进行相关的操作,这就是动态代理的思想,它的工作原理图为:


客服端client调用代理($Proxy1),代理的构造方法接收一个实例对象,然后客户端就会调用代理的各种方法,如test1、test2、test3、test4、test5。代理的各种方法会把调用请求转发(invoke)给刚才代理接收的实例对象,这个实例对象在其invoke的内部有可以加入一些功能,如日志、安全等。继而这个实例对象又把各个请求分发给(method.invoke)目标的相应方法(target中的test1、test2、test3、test4、test5方法)。这就是代理的工作过程,

---------------------- android培训、java培训、期待与您交流! ----------------------
原创粉丝点击