java--代理

来源:互联网 发布:零基础学javascript 编辑:程序博客网 时间:2024/06/12 06:30

1、代理

生活中的代理:
      武汉人从武汉的代理商手中买联想电脑和直接跑到北京传智播客旁边来找联想总部买电脑,你觉得最终的主体业务目标有什么区别吗?
      基本上一样吧,都解决了核心问题,但是,一点区别都没有吗?从代理商那里买真的一点好处都没有吗?
 
程序中的代理:
      要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?

编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。
如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,
譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
 
代理类和目标类:
目标类:已经开发好且不能被修改的类称为目标类。
代理类:为目标类增加一些系统功能之后的类称为代理类。
 
代理类和目标类的关系:
1,代理类的功能与目标类的功能一样。
2,只是在执行目标类相关功能的前或者后增加了新的辅助功能。
3,代理类的每个方法名与目标类的每个方法名都一样。
4,代理类的每个方法都要调用目标类的每个方法。
5,代理类的每个方法在调用目标类的每个方法的前或者后都会加上系统的额外功能。
6,客户端不直接使用目标类,而是直接使用代理类,代理类实际上是在调用目标类的功能。
 
代理出现前后的变化:
1,代理类出现之前,客户端是直接调用目标类。
2,代理类出现之后,客户端不再直接调用目标类的功能,而是调用对应的代理类中的功能。代理类的方法中调用了目标类的同名方法。
3,为了保证代理类和目标类具有相同的功能,代理类和目标类必须实现相同的接口。
 

代理架构图:


2、面向方面编程(AOP)
系统中存在交叉业务,
比如,处理学生、课程和管理的三种业务,有各自的CRUD。
但是无论这些类的各自的业务如何不同,都会涉及到安全、事务管理和日志三个方面的内容。
安全、事物、日志贯穿在这三个模块中,所以他们就是交叉业务。
 
交叉业务:就是不同模块都具有的功能。如图


安全、食物、日志这三个功能贯穿在不同的模块中,所以它们就是交叉业务。
 
用代码描述交叉业务:


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


使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
如果将切面的代码移到原始方法的外面,原始方法看做目标类的方法,
那么,移动以后的切面代码+原始代码,就是代理类对应的方法。
所以,代理是实现AOP的核心和关键技术。只要是AOP,就一定会涉及代理技术。

3、代理的分类
按照是否是在程序运行期间产生代理类,可以将代理分为静态代理和动态代理
<1>静态代理:就是手动为每一个目标类的每一个方法都增加交叉业务,也就是手动为每一个目标类增加代理类。
       缺点:如果目标类数量非常多或者目标类中的功能非常多,直接使用静态代理的方式来为目标类增加交叉业务会非常的繁琐。
<2>动态代理:通过特定的设置,在程序运行期间指示JVM动态地生成类的字节码。
       这种动态生成的类往往被用作代理类,即动态代理类。
       也就是运行时做编译的事情并且把生成的字节码加载成这个类的Class对象.。

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

class proxy{  //代理类      void sayHello(){          ……….  //前          try{              target.sayHello(); //调用目标类的方法          }          catch(Exception e){              ……….. //处理异常的catch块中          }          …………. //后      }  } //代理类添加的系统功能,可以在调用目标方法之前、后,或是catch块中  
5、创建动态类及查看其方法列表信息
创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。
编码列出动态类中的所有构造方法和参数签名。

编码列出动态类中的所有方法和参数签名。


代码示例:
package mypkg;  import java.lang.reflect.Constructor;  import java.lang.reflect.Method;  import java.lang.reflect.Proxy;//动态代理类  import java.util.Collection;    public class ProxyDemo {      public static void main(String[] args) {          //getProxyClass方法运行时期动态生成代理类的Class对象,并指定类加载器和接口          Class clazzProxy =               Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class );          System. out.println(clazzProxy.getName());                    System. out.println("--------begin constructors list-------");          Constructor[] constructors = clazzProxy.getConstructors();          for(Constructor constructor : constructors){              String name = constructor.getName();              StringBuilder sBuilder = 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);          }                    System. out.println("--------begin methods list-------" );          Method[] methods = clazzProxy.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);          }      }  }  

6、创建动态代理类的实例对象及调用其方法
创建动态代理类的实例对象:
用反射获得构造方法。
编写一个最简单的InvocationHandler类。
调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去。
在创建动态代理类的实例对象中,参数用匿名内部类的形式编写,锻炼大家习惯匿名内部类。
 
总结思考:
让JVM创建动态类及其实例对象,需要给它提供哪些信息?
三个方面:
1,生成的类中有哪些方法,通过让其实现哪个接口的方式进行告知;
2,产生的类字节码必须有一个关联的类加载器对象;
3,生成的类中的方法的代码是怎样的,也得由我们提供。
把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码(即相当于在代理类的方法中插入了目标类的代码)。
提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的(Proxy类的构造函数)。
在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
 
代码示例:

package mypkg;  import java.lang.reflect.Constructor;  import java.lang.reflect.InvocationHandler;  import java.lang.reflect.Method;  import java.lang.reflect.Proxy;  import java.util.Collection;    public class ProxyDemo2{      public static void main(String[] args) throws Exception {          //动态生成代理类,getProxyClass获得代理类的Class对象,指定加载器和接口          Class clazzProxy =               Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class );                    System. out.println("--------begin create instance object-------");          //获取动态代理类的构造方法          Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class) ;                    //创建动态代理类的实例对象          Collection proxy = (Collection)constructor.newInstance(new InvocationHandler(){              public Object invoke(Object proxy, Method method, Object[] args)                  throws Throwable {                  return null ;              }          });                    System. out.println(proxy);//结果:null          proxy.clear();//执行没有返回值的方法,不会报告异常          proxy.size();//执行有返回值的方法,会报告异常      }  }  

7、完成InvocationHandler对象的内部功能
用Proxy.newProxyInstance方法可以直接一步就创建出代理对象。
 
newProxyInstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):
返回一个指定接口的代理类实例,该接口可以将方法调用,指派到指定的调用处理程序h中。
就相当于,目标类中的方法代码在InvocationHandler中,代理类需调用它。
 
代码示例:

package mypkg;  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 ProxyDemo3{      public static void main(String[] args) throws Exception {          //newProxyInstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)                    Collection proxy = (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();                      Object retVal = method.invoke(target,args);                      long endTime = System.currentTimeMillis();                      System. out.println(method.getName()+"running out of "+(endTime - beginTime));                      return retVal;                  }              }          );                    proxy.add("zxx");          proxy.add("lhm");          proxy.add("bxd");          System. out.println(proxy.size());          //结果:3      }  }  
8、分析InvocationHandler对象的运行原理
在程序ProxyDemo2.java 中,
动态生成的代理类实现了Collection接口(可以实现多个接口),
生成的代理类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。
 
构造方法接受一个InvocationHandler对象,接受对象了要干什么用呢?该方法内部的代码会是怎样的呢?
答:接受的对象会通过构造函数赋值给某个成员变量。
//$Proxy0 代表代理类对象,这里只是概念展示  $Proxy0 implements Collection {       InvocationHandler handler;       public $Proxy0(InvocationHandler handler)//构造函数接收InvocationHandler对象       {            this.handler = handler;       }  }  
实现Collection接口的动态代理类中的各个方法的代码又是怎样的呢?
InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?
图解说明如下:


代理类中的方法,就是通过InvocationHandler对象来调用目标类的相应方法。

//$Proxy0 代表代理类对象,这里只是概念展示  $Proxy0 implements Collection {      InvocationHandler handler;            public $Proxy0(InvocationHandler handler){          this.handler = handler;      }            //生成的Collection接口中的方法的运行原理      int size() {          //handler对象的invoke方法中,封装了要具体执行的代码(目标类代码)。          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);      }  }  
9、通过实例深入研究InvocationHandler
先看如下代码:
import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Collection;public class ProxyDemo4 {public static void main(String[] args) throws Exception {//动态生成代理类的Class实例,此代理类实现Collection接口并调用目标类的方法Class clazzProxy = Proxy.getProxyClass(Collection. class.getClassLoader(), Collection.class );System.out.println("--------begin create instance object-------");//获得此代理类的构造方法Constructor constructor = clazzProxy.getConstructor(InvocationHandler. class) ;//生成一个代理类对象,InvocationHandler用匿名内部类。invoke方法封装了要执行的目标代码Collection proxy = ( Collection)constructor.newInstance( new InvocationHandler(){public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {return null ;}});System.out.println(proxy); //结果:nullproxy.clear();  //执行没有返回值的方法,不会报告异常proxy.size();  //执行有返回值的方法,会报告空指针异常        System.out.println(proxy.getClass()); //class $Proxy0}}
(1)分析上面打印动态类的实例对象时,结果为什么会是null呢?
         答:打印动态类的示例对象实际上就是打印proxy的toString方法,也就是执行代理对象中的如下代码:
         String toString(){
              return handler.invoke(this, this.getClass().getMethod("toString"), null);
         }
         由于invoke方法返回的是null,打印出来的结果肯定是null。
 
(2)调用有基本类型返回值的方法时为什么会出现NullPointerException异常?
         答:执行proxy.size()方法,就是执行下面的代码:
         int size(){
              return handler.invoke(this, this.getClass().getMethod("size"), null);
         }
         由于invoke方法返回的是null,要将null转换为int类型,肯定会报告空指针异常。
 
(3)分析为什么动态类的实例对象的getClass()方法返回了正确结果呢?
         答:调用代理对象的从Object类继承的hashCode, equals, 或toString这几个方法时,代理对象将调用请求转发给InvocationHandler对象,
     对于其他方法,则不转发调用请求,比如getClass方法,所以它会返回正确的结果。


10、总结分析动态代理类的设计原理与结构
简单总结:
      代理类和目标类都实现了同一个接口,目标类中有具体实现,
      而代理类中是通过InvocationHandler对像调用目标类中的具体实现:
      即,代理类的方法中,通过调用InvocationHandler对象的invoke方法,指向目标类的相应方法(具体实现)。
 
      当然,前面的例子中,是直接在InvocationHandler对象的invoke方法内,完成了具体实现,这样就没有目标类。
 
      注意:目标类必须自己定义时就实现接口,从该类的祖辈类上继承的接口是无效的。
 
将创建代理的过程改为一种更优雅的方式,Eclipse重构出一个getProxy方法绑定接收目标,
同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。
将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供?
把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,
接收者只要调用这个对象的方法,即等于执行了外界提供的代码!为方法增加一个Advice参数。
代码示例:
package mypkg;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.ArrayList;import java.util.Collection;interface Advice {    void beforeAdvice(Method method);    void afterAdvice(Method method);}//封装了切面代码:class MyAdvice implements Advice {    private long beginTime = 0;        public void afterAdvice(Method method) {        long endTime = System.currentTimeMillis();        System.out.println(method.getName()+"running out of"+(endTime - beginTime));    }    public void beforeAdvice(Method method) {        beginTime = System.currentTimeMillis();    }}public class ProxyTest {    public static void main(String[] args) throws Exception {       final ArrayList target = new ArrayList(); //目标类      //通过自定义的getProxy方法,生成代理类对象,并添加切面代码。       Collection collection = (Collection) getProxy(target,new MyAdvice());       collection.add("zxx" );       //结果:add running out of 0    }//实现框架功能,生成代理只需要传递target目标类,和封装了系统功能的对象MyAdvice    public static Object getProxy(final ArrayList target,final Advice advice) {Object proxy = Proxy.newProxyInstance(   //生成代理类对象    target.getClass().getClassLoader(),    target.getClass().getInterfaces(),    new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {    advice.beforeAdvice(method); //切面代码Object retVal = method.invoke(target ,args);//调用目标类的方法advice.afterAdvice(method);  //切面代码return retVal;    }    });return proxy;    }}
11、实现AOP功能的封装与配置
  ①工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。
  ②BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
     #xxx=java.util.ArrayList
     xxx=cn.itcast.ProxyFactoryBean
     xxx.target=java.util.ArrayList
     xxx.advice=cn.itcast.MyAdvice
  ③ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
    目标
    通知
  ④编写客户端应用:
    编写实现Advice接口的类和在配置文件中进行配置
    调用BeanFactory获取对象


0 0