黑马程序员_Java基础加强(下)_类加载器,代理_28

来源:互联网 发布:网络广告机c语言代码 编辑:程序博客网 时间:2024/05/16 17:29

                                      ------- android培训、java培训、期待与您交流!----------

 

44、类加载器及其委托机制的深入分析

类加载器

  • 通常字节码文件放在,硬盘上的classpath路径的根目录下。而将字节码文件加载到内存中,并对于它做一些处理这些工作是类加载器在干。
  • Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
  • 类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器它不是java类,这正是BootStrap(它是嵌套在java虚拟机的内核中存在的,java的虚拟机一启动的时候它就在那里了,它是用C++语言写的一段二进制代码。它右以加载别的类,这些类中就包含了其他的类加载器)。
  • Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。(BootStrap的孩子是ExtClassLoader,ExtClassLoader的孩子是AppClassLoader)

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

  • 在类上击右键-->Export-->JARfile-->Next-->将文件输出到C:\Java\jdk1.6.0_03\jre\lib\ext\itcast.jar(lib下的文件ext是一个扩展的Jar包,我们可以把我们的Jar包放在这个目录下面,就可以被类加载器加载,即ExtClassLoader是专门用来加载类加载器中的ext文件下的jar文件的)
  • 系统中有两份ClassLoaderTest文件,但只是由某一个类加载器去加载,父亲的优先级要比孩子的优先级要高,所以由父亲的类加载器来加载。父亲找不到的话,再由儿子去加载(这就是类加载器的委托机制)。
  • 我们还可以写自己的类加载器,挂在树上。我们在写自己的类加载器的时候都要继承一个类:java.lang.ClassLoader。可以用ClassLoader中的一个方法:protectedClassLoader(ClassLoader parent),为自己写的加载器指定一个父亲。由我们的加载器加载我们特定的目录,我们加密的类放在这个目录中,当人家要用到我们的类的时候,只能由我们的加载器去加载,我们的类加载器在加载的时候就对它进行了解密。
  • import java.util.Date;

public class ClassLoaderTest {

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

              System.out.println(

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

       //getClassLoader()后得到类加载器的对象,getClass()后得到类加载器的类,getName()后得到类加载器的名字。返回结果:sun.misc.Launcher$AppClassLoader,说明ClassLoaderTest这个类是由AppClassLoader加载进来的。

                            );

              System.out.println(

                            System.class.getClassLoader()

              //System是由特殊的类加载器加载的,返回值是:null

                            );

              System.out.println("xxx");

      //查看类加载器的层次结构关系

              ClassLoader loader =ClassLoaderTest.class.getClassLoader();

              while(loader!= null){

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

                     loader= loader.getParent();

              }

              System.out.println(loader);

             

              //System.out.println(newClassLoaderAttachment().toString());

              System.out.println("xxx2");

              Class clazz = newMyClassLoader("itcastlib").loadClass("cn.itcast.day2.ClassLoaderAttachment");

              Dated1 =  (Date)clazz.newInstance();

      //ClassLoaderAttachment是乱的,不能被很好的加载,要用它的父类的引用。

              System.out.println(d1);

       }

}

 

类加载器的委托机制

  • 当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?(有三种方式)

Ø 首先当前线程的类加载器去加载线程中的第一个类。(每一个线程都有一个ClassLoader getContextClassLoader()方法,也可以为线程指定一个类加载器对象:void setContextClassLoader(ClassLoader cl))

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

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

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

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

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

  • 每个ClassLoader本身只能分别加载特定位置和目录中的类,但它们可以委托其他的类装载器去加载类,这就是类加载器的委托模式。类装载器一级级委托到BootStrap类加载器,当BootStrap无法加载当前所要加载的类时,然后才一级级回退到子孙类装载器去进行真正的加载。当回退到最初的类装载器时,如果它自己也不能完成类的装载,那就应报告ClassNotFoundException异常。
  • 有一道面试,能不能自己写个类叫java.lang.System,为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,也就是总是使用爸爸们能找到的类,这样总是使用java系统提供的System。

 

45、自定义类加载器的编写原理分析

  • 知识讲解:

Ø 自定义的类加载器的必须继承ClassLoader

Ø loadClass方法与findClass方法

Ø defineClass方法

  • 编程步骤:

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

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

Ø 编写一个程序调用类加载器加载类,在源程序中不能用该类名定义引用变量,因为编译器无法识别这个类。程序中可以除了使用ClassLoader.load方法之外,还可以使用设置线程的上下文类加载器或者系统类加载器,然后再使用Class.forName。

  • 实验步骤:

Ø 对不带包名的class文件进行加密,加密结果存放到另外一个目录,例如:java MyClassLoader MyTest.class F:\itcast

Ø 运行加载类的程序,结果能够被正常加载,但打印出来的类装载器名称为AppClassLoader:java MyClassLoader MyTest F:\itcast

Ø 用加密后的类文件替换CLASSPATH环境下的类文件,再执行上一步操作就出问题了,错误说明是AppClassLoader类装载器装载失败。

Ø 删除CLASSPATH环境下的类文件,再执行上一步操作就没问题了。

  • java.lang.ClassLoader中的类public abstract class ClassLoader extends object是一个抽象的类,它中有一个方法Class<?> loadClass(String name),把类名的字符串给它,它会返回一个Class。当我们写自己的类加载器的时候不用重写这个loadClass()方法,因为这个loadClass()内部会去找它的爸爸。当爸爸返回来之后会接着调用 protected Class<?> findClass(Stringname)方法,我们只要写完findClass()就可以了。(如果覆盖了loadClass的话,它就不去找爸爸了,直接自己干了,如果想找爸爸,还要写上自己的代码,比较难。)
  • 模板方式设计模式

父类-->loadClass (这里自己干的是findClass(),当得到了class文件中的二进制数据,将class文件转换成字节码,用loadClass中的protected Class<?> defineClass(byte[] b, intoff, int len)方法来完成)

子类1(自己干)

子类2(自己干)

子类1和子类2的找爸爸的流程是一样的这个流程放在父类方法中去编写,所有子类都继承父类的方法。区别在于自己干的地方不同,是父类在干的过程中遇到的一个小的细节不会干,那就把它定义成一个抽象的方法等待子类去干。

 

46、编写对class文件进行加密的工具类(另一代码见34)

47、编写和测试自己编写的解密类加载器(另一代码见34)

  • win+r打开运行对话框-->将文件拖到框中,可以得到完整的文件路径名称。
  • 在eclipse可在按下F5刷一下,就可以将生成的文件在package视图中显现出来。
  • import java.io.ByteArrayOutputStream;

import java.io.FileInputStream;

import java.io.FileNotFoundException;

import java.io.FileOutputStream;

import java.io.InputStream;

import java.io.OutputStream;

public class MyClassLoader extendsClassLoader{

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

              String srcPath = args[0];

              String destDir = args[1];

              FileInputStream fis = new FileInputStream(srcPath);

              String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);

              String destPath = destDir + "\\" + destFileName;

              FileOutputStream fos = new FileOutputStream(destPath);

              cypher(fis,fos);

              fis.close();

              fos.close();

       }

       private static void cypher(InputStream ips ,OutputStream ops) throws Exception{

              intb = -1;

              while((b=ips.read())!=-1){

                     ops.write(b^ 0xff);  //异或操作原来是0的给搞成1,原来是1的给搞成0

              }

       }

       private String classDir;

       @Override

       protectedClass<?> findClass(String name) throws ClassNotFoundException {

              StringclassFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class";

              try{

                     FileInputStream fis = new FileInputStream(classFileName);

                     ByteArrayOutputStream  bos = new ByteArrayOutputStream();

                     cypher(fis,bos);

                     fis.close();

                     System.out.println("aaa");

                     byte[] bytes = bos.toByteArray();

                     return defineClass(bytes, 0, bytes.length);

              }catch (Exception e) {

                     e.printStackTrace();

              }

              return null;

       }

       public MyClassLoader(){

       }

       public MyClassLoader(String classDir){

              this.classDir= classDir;

       }

}

 

import java.util.Date;

public class ClassLoaderAttachment extendsDate {

       public String toString(){

              return "hello,itcast";

       }

}

 

48、类加载器的一个高级问题的实验分析

  • Tomcat服务器也是一个大的Java程序,这个程序内部编写了好多个类加载器,并把这个类加载器,安装到了虚拟机上。也就是说Tomcat启动了之后,Java虚拟机里就有很多个类加载器,很多就是Tomcat这个项目中自己提供的。我们在Tomcat下开发Web应用程序,我们开发的主要是Servlet,Servlet也是一个Java类,它是由Tomcat提供的类加载器加载的。
  • 在Web项目中,建立的class,它是一种特殊的class,这个class是可以放在Tomcat中运行的class。它的父类是Servlet
  • 一个大的项目来分层:表现层,业务层,数据访问层。表现层(web),可以写servlet、filter、Structs Action等。
  • 没在浏览器中出现想要的结果,因为Servlet的字节码,已经被加载到虚拟机的内存中去了,再改完它后也不起作用。必须重启一下,让项目从Tomcat中卸载,然后再重新加载进来。加载要一定时候要等一下。
  • 把MyServlet.java变成jar包Export到JDK下面,如果输出的JDK和Tomcat正在运行的JDK不是同一个,那是白输出的。Tomcat是由一个Java虚拟机启动的,哪个JDK在运行,你就输出到哪个JDK下去。用记事本打开startup.bat,可以配置JAVA_JDK的目录。
  • 编写一个能打印出自己的类加载器名称和当前类加载器的父子结构关系链的MyServlet,正常发布后,看到打印结果为WebAppClassloader。
  • 把MyServlet.class文件打jar包,放到ext目录中,重启tomcat,发现找不到HttpServlet的错误。
  • 把servlet.jar也放到ext目录中,问题解决了,打印的结果是ExtclassLoader 。
  • 父级类加载器加载的类无法引用只能被子级类加载器加载的类,原理如下图:

 

49、分析代理类的作用与原理及AOP概念

代理的概念与作用

  • 一般一个无法再继承的类和方法,要用代理.

  • 代理模式是常用的Java 设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
  • 在Java开发包中包含了对动态代理的支持,但是其实现只支持对接口的的实现。其实现主要通过是java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。
  • Proxy类主要用来获取动态代理对象,InvocationHandler接口用来约束调用者实现,如下,HelloWorld接口定义的业务方法,HelloWorldImpl是HelloWorld接口的实现,HelloWorldHandler是 InvocationHandler接口实现。动态代理类只能代理接口,代理类都需要实现InvocationHandler类,实现invoke方法。该invoke方法就是调用被代理接口的所有方法时需要调用的,该invoke方法返回的值是被代理接口的一个实现类。
  • 按照代理类的创建时期,代理类可分为两种。
静态代理类:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。(静态代理类有一个很不爽的缺点:如果当接口加一个方法所有的实现类和代理类里都需要做个实现。这就增加了代码的复杂度。动态代理就可以避免这个缺点。)
动态代理类:在程序运行时,运用反射机制动态创建而成。
动态代理与普通的代理相比较,最大的好处是接口中声明的所有方法都被转移到一个集中的方法中处理(invoke),这样,在接口方法数量比较多的时候,我们可以进行灵活处理。
  • 所谓Dynamic Proxy是这样一种class:

它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。

  • 生活中的代理

Ø 武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?

  • 程序中的代理

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

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

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

代理架构图

AOP

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

                              安全       事务         日志

StudentService  ------|----------|------------|-------------

CourseService   ------|----------|------------|-------------

MiscService       ------|----------|------------|-------------

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

method1         method2          method3

{                      {                       {

------------------------------------------------------切面

....            ....              ......

------------------------------------------------------切面

}                       }                       }

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

------------------------------------------------------切面

func1         func2            func3

{             {                {

....            ....              ......

}             }                }

------------------------------------------------------切面

  • 使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
  • 安全,事务,日志等功能要贯穿到好多个模块中,所以,它们就是交叉业务
  • 重要原则:不要把供货商暴露给你的客户

 

动态代理技术

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

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

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

Ø 3.在处理目标方法异常的catch块中

Class proxy{

void sayHello(){

        ………. //干额外的事情,可以在调用目标方法之前。

        try{

               target.sayHello();

        }catch(Exceptione){

               ………..//干额外的事情,可以在目标抛出的异常之中。

        }

        ………….//干额外的事情,可以在调用目标方法之后。

}

}

 

50、创建动态类及查看其方法列表信息

分析JVM动态生成的类

  • 创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。Static Class<?>getProxyClass(ClassLoader loader, Class<?>…interfaces),当造出字节码的时候,要告诉它这个字节码实现了哪些接口。
  • 编码列出动态类中的所有构造方法和参数签名
  • 编码列出动态类中的所有方法和参数签名
  • 创建动态类的实例对象

Ø 用反射获得构造方法

Ø 编写一个最简单的InvocationHandler类

Ø 调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去

Ø 打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。

Ø 将创建动态类的实例对象的代理改成匿名内部类的形式编写,锻炼大家习惯匿名内部类。

  • 总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息?

Ø 三个方面:

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

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

n 生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。

  • 用Proxy.newInstance方法直接一步就创建出代理对象。
  • import java.lang.reflect.Constructor;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import java.util.ArrayList;

import java.util.Collection;

 

public class ProxyTest {

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

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

             //一般见到前缀是clazz,就知道这个变量代表的是字节码。为什么?不知道。一些大师们都是这样做的。

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

             

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

              /*$Proxy0()

              $Proxy0(InvocationHandler,int)*/

              Constructor[]constructors = clazzProxy1.getConstructors();

              for(Constructorconstructor : constructors){

                     String name = constructor.getName();

                     StringBuilders Builder = new StringBuilder(name);

                     sBuilder.append('(');

                     Class[] clazzParams = constructor.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());             

              }

 

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

              Method[] methods = clazzProxy1.getMethods();

              for(Methodmethod : methods){

                     String name = method.getName();

                     StringBuilders Builder = new StringBuilder(name);

                     sBuilder.append('(');

                     Class[] clazzParams = method.getParameterTypes();

                     for(ClassclazzParam : clazzParams){

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

                     }

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

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

                     sBuilder.append(')');

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

              }

             

              System.out.println("----------begincreate instance object----------");

              //Objectobj = clazzProxy1.newInstance(); //没有不带参数的构造方法。

              Constructorconstructor = clazzProxy1.getConstructor(InvocationHandler.class);//获得构造方法的时候,用的是参数类型。

              class MyInvocationHander1 implements InvocationHandler{

                     public Object invoke(Object proxy, Method method, Object[] args)

                                   throwsThrowable {

                            return null;

                     }

              }

              Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHander1());

              //调用构造方法的时候要指定一个具体的参数了。具体的参数是InvocationHandler的一个对象。InvocationHanderler是java.lang.reflect中的接口

              System.out.println(proxy1); //返回的null是toString()方法返回的null

              proxy1.clear();//调用没有返回值的方法成功。clear返回的void,如果invoke()返回的是null,就相当于void所以不报错。

              //proxy1.size();//调用有返回值的方法没有成功,抛出异常。因为size()方法要调用invoke()方法,因为invoke()方法返回的是一个null,可是size()要求的是一个整数。所以抛出NullPointException异常。

             

      //可以用匿名内部类实现,这种方法,和上面的方法都是分成两个步骤完成:第一:创建动态类,第二:创建实例对象。

              Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){

                     publicObject invoke(Object proxy, Method method, Object[] args)

                                   throwsThrowable {

                            return null;

                     }

              });

             

              final ArrayList target = new ArrayList(); //内部类要访问临时局部变量 必须加上final.         

              Collection proxy3 = (Collection)getProxy(target,new MyAdvice());

              proxy3.add("zxx");

              proxy3.add("lhm");

              proxy3.add("bxd");//每运行一次方法,就调用一次invoke()方法。

              System.out.println(proxy3.size());

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

       }

   //下面这种方式是直接一步到位,用静态的方法newProxyInstance(),即搞出了字节码,同时又搞出了实例对象。

   //将目标抽取成了一个参数。

   //给我目标,给我系统功能,我就会给你生成一个代理,那个代理就会调用你的目标,在这个目标的前后,会调用你传递过来的系统功能代码。以后要生成代理只用把目标和建议传给我就行了。

       private static Object getProxy(final Object target,final Advice advice) {

              Object proxy3 = Proxy.newProxyInstance(

                            target.getClass().getClassLoader(), //类加载器

                            /*newClass[]{Collection.class},*/ //接口可以有多个,用数组表示

                            target.getClass().getInterfaces(),

                            new InvocationHandler(){

                                   public Object invoke(Object proxy, Method method, Object[] args)

                                                 throws Throwable {

                                          /*long beginTime = System.currentTimeMillis();

                                          Objectret Val = method.invoke(target, args);

                                          long endTime = System.currentTimeMillis();

                                          System.out.println(method.getName()+ " running time of " + (endTime - beginTime));

                                          return retVal;*/

 

                                          advice.beforeMethod(method);

                                          Objectret Val = method.invoke(target, args);

                                          advice.afterMethod(method);

                                          return retVal;                                         

                                   }

                            }

                            );

              return proxy3;

       }

}

 

51、创建动态类的实例对象及调用其方法(代码见50)

52、完成InvocationHandler对象的内部功能(代码见50)

53、分析InvocationHandler对象的运行原理

猜想分析动态生成的类的内部代码

  • 动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。
  • 构造方法接受一个InvocationHandler对象,接受对象了要干什么用呢?通过构造方法接收人家传进来的参数,是要记住这个参数,以后好用它。该方法内部的代码会是怎样的呢?

$Proxy0 implements Collection

{

       InvocationHandler handler;

       public$Proxy0(InvocationHandler handler)

       {

              this.handler= handler;

       }

}

 

$Proxy0 implements Collection

{

       InvocationHandlerhandler;

       public $Proxy0(InvocationHandler handler)

       {

              this.handler= handler;

       }

       //生成的Collection接口中的方法的运行原理

       intsize()

       {

              return handler.invoke(this,this.getClass().getMethod("size"),null);

       }

       void clear(){

              handler.invoke(this,this.getClass().getMethod("clear"),null);

       }

       boolean add(Object obj){

              handler.invoke(this,this.getClass().getMethod("add"),obj);

       }

}

  • 实现Collection接口的动态类中的各个方法的代码又是怎样的呢?InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?图解说明如下:
  • 分析先前打印动态类的实例对象时,结果为什么会是null呢?调用有基本类型返回值的方法时为什么会出现NullPointerException异常?
  • 分析为什么动态类的实例对象的getClass()方法返回了正确结果呢?

Ø 调用调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于Object类中的其他方法,则不转发调用请求。

 

54、总结分析动态代理类的设计原理与结构

让动态生成的类成为目标类的代理

  • 动态代理的工作原理图
  • 将这个东西做成一个框架,圈中的代码作为一个对象参数由用户传递进来,这就是面向切面的编程,即把切面的代码,用面向对象的方式封装,然后以对象的方式传递给你,你执行了对象,就相当于执行了切面的代码。
  • javascript是一种动态的语言,可以运行的运行的时候给你塞一段代码让你走,java没有这个功能。动态语言:也就是说程序不是编程的时候编好的,而是在运行的时候给你一个字符串,你把它当作代码走。这样有一个好处,它可以在运行的时候给你塞进来,提前编写好,这样代码可以换。

var x;

eval(“x=1+1”)

?x==2

  • java中怎样传递代码呢?

传一个对象给InvocationHandler,在invoke()方法中调用这个对象的方法,只要把要执行的代码放在要执行的对象的方法中,当invoke()调用的时候,就相当于执行了我给你的代码。

  • 给InvocationHandler可以传递两个对象,第一个传入目标对象。第二个,传入系统功能对象。这样才可以把它做成框架。

 

  • 怎样将目标类传进去?

Ø 直接在InvocationHandler实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有实际意义。

Ø 为InvocationHandler实现类注入目标类的实例对象,不能采用匿名内部类的形式了。

Ø 让匿名的InvocationHandler实现类访问外面方法中的目标类实例对象的final类型的引用变量。

  • 将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。
  • 将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供?

Ø 把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外界提供的代码!

Ø 为bind方法增加一个Advice参数。

 

55、编写可生成代理和插入通告的通用方法(另一代码见50)

  • 记住一个Spring中的术语,人家希望你执行的系统功能,可以理解为人家对你的一个建议,系统功能的类名叫Advice。Spring就是在文件中配,设置代理(把目标和系统功能给我)。以后玩Spring只干一件事,那就是MyAdvice,然后在你的配置文件中配上你的目标。
  • 一般这个接口应该有四个方法:

1.       在异常中执行的方法。

2.       在方法之前执行的方法。

3.       在方法这后执行的方法。

4.       在方法前后执行的。

  • import java.lang.reflect.Method;

public interface Advice {

       void beforeMethod(Method method);

       void afterMethod(Method method);

}

 

import java.lang.reflect.Method;

public class MyAdvice implements Advice {

       long beginTime = 0;

       public void afterMethod(Method method) {

              System.out.println("从传智播客毕业上班啦!");          

              long endTime = System.currentTimeMillis();

              System.out.println(method.getName()+ " running time of " + (endTime - beginTime));

       }

       public void beforeMethod(Method method) {

              System.out.println("到传智播客来学习啦!");

              beginTime= System.currentTimeMillis();

       }

}

 

56、实现类似spring的可配置的AOP框架

  • 工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。
  • BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:

#xxx=java.util.ArrayList

xxx=cn.itcast.ProxyFactoryBean //如果BeanFactoryBean发现你是ProxyFactoryBean这个特殊的家伙,我就不返回这个特殊的家伙,而是返回它创建的代理。(如,如果你不是领导,就返回你。如果你是领导就返回领导的秘书。这个领导是一个特殊的JavaBean,这个JavaBean又充当了人家的工厂)

xxx.target=java.util.ArrayList

xxx.advice=cn.itcast.MyAdvice //领导在创建代理的时候,要指定代理的目标和代理的advice。

  • ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?

Ø 目标

Ø 通知

  • 编写客户端应用:

Ø 编写实现Advice接口的类和在配置文件中进行配置

Ø 调用BeanFactory获取对象

  • 做商业性的项目,可以有85%以上都是在处理各种异常,因为正确的道路只有一条,错误的路线有千万条。

properties文件:

#xxx=java.util.ArrayList   #把这个文件注释掉就用代理,把下面的语句注释把,就不用代理。

xxx=cn.itcast.day3.aopframework.ProxyFactoryBean

xxx.advice=cn.itcast.day3.MyAdvice

xxx.target=java.util.ArrayList

 

import java.io.IOException;

import java.io.InputStream;

import java.util.Properties;

import cn.itcast.day3.Advice;

public class BeanFactory {

       Properties props = new Properties();

       public BeanFactory(InputStream ips){

              try{

                     props.load(ips);

              }catch (IOException e) {

                     e.printStackTrace();

              }

       }

              public Object getBean(String name){

              String className = props.getProperty(name);

              Object bean = null;

              try{

                     Class clazz = Class.forName(className);

                     bean= clazz.newInstance(); //对于JavaBean来说,它必须有一个不再参数的构造方法。因为人家创建JavaBean时的实例对象的时候,用的就是这个。

              }catch (Exception e) {

                     e.printStackTrace();

              }

              if(beaninstanceof ProxyFactoryBean){

                     Object proxy = null;

                     ProxyFactoryBeanproxyFactory Bean = (ProxyFactoryBean)bean;

                     try{

                            Advice advice = (Advice)Class.forName(props.getProperty(name +".advice")).newInstance();

                            Object target = Class.forName(props.getProperty(name +".target")).newInstance();

                            proxyFactoryBean.setAdvice(advice);

                            proxyFactoryBean.setTarget(target);

                            proxy= proxyFactoryBean.getProxy();

                     }catch (Exception e) {

                            e.printStackTrace();

                     }

                     return proxy;

              }

              return bean;

       }

}

 

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

import java.lang.reflect.Proxy;

import cn.itcast.day3.Advice;

public class ProxyFactoryBean {

 //在Spring中ProxyFactoryBean不是直接写一个类,它是一个接口。要写若干个对它的实现。

       private Advice advice;

       private Object target;

       public Advice getAdvice() {

              return advice;

       }

       public void setAdvice(Advice advice) {

              this.advice= advice;

       }

       public Object getTarget() {

              return target;

       }

       public void setTarget(Object target) {

              this.target= target;

       }

       public Object getProxy() {

              Objec tproxy3 = Proxy.newProxyInstance(

                            target.getClass().getClassLoader(),

                            target.getClass().getInterfaces(),

                            newInvocationHandler(){

                                   public Object invoke(Object proxy, Method method, Object[] args)

                                                 throwsThrowable {

                                          advice.beforeMethod(method);

                                          Objectret Val = method.invoke(target, args);

                                          advice.afterMethod(method);

                                          return retVal;                                         

                                   }

                            }

                            );

              return proxy3;

       }

}

 

import java.io.InputStream;

import java.util.Collection;

public class AopFrameworkTest {

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

              InputStream ips =AopFrameworkTest.class.getResourceAsStream("config.properties");

              Object bean = new BeanFactory(ips).getBean("xxx");

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

              ((Collection)bean).clear();

       }

}


                                      ------- android培训、java培训、期待与您交流!----------  

 

0 0
原创粉丝点击