动态代理

1、代理的概念与作用

     ①、生活中的代理举例:

     例如:买笔记本电脑,有两种购买方式,第一种就是直接去笔记本厂商的生产总部去购买,另一种方式则是通过该笔记本的代理商商店里去购买,而最终目的就是为了买一台笔记本电脑。

     但是这两种购买方式的区别是什么?区别就是直接去笔记本厂商总部购买的话可能需要搭乘火车或者长途汽车,交通费可能就需要额外花费一定的金钱;而去代理商商店购买的话,则可以省掉这些麻烦,购买和售后都非常方便,但是笔记本价格可能比总部直销的价格稍贵。这就是代理在现实生活中的实例理解。

 

     ②、程序中的代理:

  • 要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能。例如:异常处理、日志、计算方法的运行时间、事务管理等等。
  •  编写一个与目标类具有相同接口的代理类,代理类的每个方法都调用目标类中的相同方法,并在调用方法的时候加上系统功能代码。
  •  如果采用工厂设计模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类还是代理类,这样很以后很容易切换。例如:想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

 

2、代理(原理)架构图:

 

3、AOP(面向方面的编程)

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

                              安全       事务         日志

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

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

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

 

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

method1         method2          method3

{                      {                       {

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

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

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

}                       }                       }

 

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

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

func1    func2    func3

{             {                {

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

}             }                }

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

 

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

 

注意:

     安全、事物、日志等功能要贯穿到好多个模块中,所以它们就是交叉业务。

     重要原则:不要把供货商暴露给你的客户。(即不要把目标类直接暴露给请求客户端)

 

4、动态代理技术

       动态代理技术的好处:要为系统中的各种接口的类增加代理功能,那么将需要太多的代理类,如果全部采用静态代理的方式,那么可能将要编写成百上千的代理类,工作量太大并且许多代码会出现重复,而动态代理技术则可以避免这些问题。

  •  JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
  •  JVM生成的动态代理类必须要实现一个或者多个接口,所以JVM生成的动态类只能用作具有相同接口的目标类的代理。
  •  CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。

       代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标的返回结果外,还可以在代理方法中的如下四个位置上加上系统功能代码:

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

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

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

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

示例:

class Proxy {
   void sayHello(){
      ……
      try{
         Target.sayHello();
      }catch (Exception e){
          ……
      }
      ……
   }
}


代码示例1:创建动态类以及查看其方法列表信息

package cn.itcast.day3;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
 
public class ProxyTest {
   public static void main(String[] args) {
      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);
      }
   }
}

运行结果:

$Proxy0

---------- begin constructorslist ----------

$Proxy0(java.lang.reflect.InvocationHandler)

---------- begin methods list----------

add(java.lang.Object)

hashCode()

clear()

equals(java.lang.Object)

toString()

contains(java.lang.Object)

isEmpty()

addAll(java.util.Collection)

iterator()

size()

toArray([Ljava.lang.Object;)

toArray()

remove(java.lang.Object)

containsAll(java.util.Collection)

removeAll(java.util.Collection)

retainAll(java.util.Collection)

isProxyClass(java.lang.Class)

getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;)

getInvocationHandler(java.lang.Object)

newProxyInstance(java.lang.ClassLoader,[Ljava.lang.Class;,java.lang.reflect.InvocationHandler)

wait()

wait(long,int)

wait(long)

getClass()

notify()

notifyAll()

 

代码示例2:创建动态类的实例对象并调用其方法

实现步骤:

1)  创建动态类的实例对象。

2)  利用反射获取动态类中的构造方法。

3)  编写一个最简单的InvocationHandler接口的实现类。

4)  调用构造方法创建动态类的实例对象,并将编写的InvocationHandler实现类的实例对象做为参数传入进去。InvocationHandler实现类的实例对象可以通过匿名内部类的方式来创建并做为参数传入,可简化代码书写)

package cn.itcast.day3;
 
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 ProxyTest {
   public static void main(String[] args) throws Exception {
      Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);    //步骤 1)
     
      System.out.println("----------begin create instance object ----------");
     
      //步骤2)
      Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);
      class MyInvocationHandler1 implements InvocationHandler{
         public Object invoke(Object proxy, Method method, Object[]args) throws Throwable {    //步骤3)
            return null;
         }
      }
  
      //步骤4)
      Collection proxy1 = (Collection)constructor.newInstance(newMyInvocationHandler1());
      System.out.println(proxy1);    //结果:null
      proxy1.clear();    //执行没有返回值的方法(返回值为void),不会出现异常。
      proxy1.size();     //执行有返回值的方法,会出现异常。因为invoke方法的返回值是null,但是size方法要求的返回值类型是int,所以会出现报错。
   }
}


代码示例3:通过Proxy类中的newProxyInstance()方法一步创建代理类对象并完成InvocationHandler对象的内部功能

package cn.itcast.day3;
 
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 static void main(String[] args) throws Exception {
      Collection proxy3 = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(),
      new Class[]{Collection.class},
      newInvocationHandler(){
         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 time of "+(endTime-beginTime));
            return retVal;
         }
      });
      proxy3.add("zxx");
      proxy3.add("lhm");
      proxy3.add("bxd");
      System.out.println(proxy3.size());
   }
}

输出结果为:

add running time of 1

add running time of 0

add running time of 0

size running time of 0

3

 

       注意:如果作为target的ArrayList对象放到invoke()方法内部定义,那么每次调用代理的某个方法,都会调用到invoke方法,这样作为target的ArrayList对象每次都会被创建,这样就导致了最后调用size()方法的时候,结果都为0。

 

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

      动态生成的类可以实现Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个接收InvocationHandler实例对象参数的构造方法。

 

问题:构造方法接收一个InvocationHandler对象做什么用?并且该方法内部代码是什么样的?

答:接收的对象会通过构造函数赋值给动态类生成的类中的某个成员变量。

$Proxy0 implements Collection

{

       InvocationHandlerhandler;

       public$Proxy0(InvocationHandler handler)

      {

             this.handler = handler;

      }

}

 

实现Collection接口的动态类中的各个方法的代码又是怎样的呢?InvocationHandler接口中定义的invoke方法接收的三个参数又是代表什么?图解说明如下所示:


代码示例:

$Proxy0 implements Collection
{
   InvocationHandler handler;
   public $Proxy0(InvocationHandler handler)
   {
          this.handler = handler;
   }
   //生成的Collection接口中的方法的运行原理
   int size()
   {
          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);
   }
}


代码示例:

package cn.itcast.day3;
 
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 ProxyTest {
   public static void main(String[] args) throws Exception {
      Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(),Collection.class);
     
      System.out.println("----------begin create instance object ----------");
     
      Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);
      class MyInvocationHandler1 implements InvocationHandler{
         public Object invoke(Object proxy, Method method, Object[]args) throws Throwable {
            return null;
         }
      }
  
      Collection proxy1 = (Collection)constructor.newInstance(newMyInvocationHandler1());
      System.out.println(proxy1);     //结果:null
      proxy1.clear();     //执行没有返回值的方法,不会出现异常。
      proxy1.size();    //执行有返回值的方法,会出现异常。
   }
}

问题:

①、上面代码示例当中打印动态类的实例对象时,结果为什么是null ?

因为打印动态类的实例对象实际上就是在打印该实例对象的toString()方法,也就是执行代理对象中的如下代码:

String toString(){

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

}

由于invoke方法的返回值是null,则打印输出的结果必定是null。

 

②、调用有基本返回值类型的返回值的方法时为什么会出现NullPointException异常?

调用size()方法实际上就相当于执行以下代码:

int size(){

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

}

由于invoke方法的返回值是null,而size方法又要求返回一个int型的值,所以会出现NullPointException异常。

 

③、分析为什么动态类的实例对象的getClass()方法返回了正确的结果?

       调用代理对象的从Object类继承的hashCode()、equals()、toString()这几个方法时,代理对象将调用请求转发给InvocationHandler对象,对于其它方法,则不转发调用请求,比如getClass()方法,所以会返回正确的结果。

 

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

动态代理的工作原理图:


       注意:用于为某个对象生成和返回其代理对象,源对象必须实现接口,生成的代理对象会实现与源对象相同的接口。(源对象的类必须在自己定义时就实现接口,从该类的祖辈类上继承的接口是无效的)

 

① 将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。

② 将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供?

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

 

代码示例:

Advice.java

package cn.itcast.day3;
 
import java.lang.reflect.Method;
 
public interface Advice {
   void beforeMethod(Method method);
   void afterMethod(Method method);
}


MyAdvice.java

package cn.itcast.day3;
 
import java.lang.reflect.Method;
 
public class MyAdvice implements Advice{
   long startTime = 0;
  
   public void afterMethod(Method method) {
      long endTime = System.currentTimeMillis();
      System.out.println(method.getName()+" running time of "+(endTime-startTime));
   }
 
   public void beforeMethod(Method method) {
      startTime = System.currentTimeMillis();
   }
}


ProxyTest.java

package cn.itcast.day3;
 
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 static void main(String[] args) throws Exception { 
      final ArrayList target = new ArrayList();
      Collection proxy3 = (Collection)getProxy(target,new MyAdvice());
      proxy3.add("zxx");
      proxy3.add("lhm");
      proxy3.add("bxd");
      System.out.println(proxy3.size());
      System.out.println(proxy3.getClass().getName());
   }
 
   private static Object getProxy(final Object target,final Advice advice) {
      Object proxy3 = Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            newInvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[]args) throws Throwable {
                   advice.beforeMethod(method);
                   Object retVal = method.invoke(target, args);
                   advice.afterMethod(method);
                   return retVal;
                }
      });
      return proxy3;
   }
}


7、实现类似Spring的可配置的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充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?

  1.  目标
  2.  通知

  • 编写客户端应用:

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

       调用BeanFactory获取对象

 

代码示例:

ProxyFactoryBean.java

package cn.itcast.day3.aopframework;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
import cn.itcast.day3.Advice;
 
public class ProxyFactoryBean {
   private Object target;
   private Advice advice;
  
   public Object getTarget() {
      return target;
   }
   public void setTarget(Object target) {
      this.target = target;
   }
   public Advice getAdvice() {
      return advice;
   }
   public void setAdvice(Advice advice) {
      this.advice = advice;
   }
 
   public Object getProxy() {
      Object proxy3 = Proxy.newProxyInstance(target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
           
            new InvocationHandler() {
                public Object invoke(Object proxy, Method method, Object[]args) throws Throwable {
                advice.beforeMethod(method);
                Object retVal = method.invoke(target, args);
                advice.afterMethod(method);
                return retVal;
         }
      });
      return proxy3;
   }
}

BeanFactory.java

package cn.itcast.day3.aopframework;
 
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();
      } catch (Exception e) {
         e.printStackTrace();
      }
      if(bean instanceof ProxyFactoryBean){
         Object proxy = null;
         try {
            ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
            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) {
            // TODO Auto-generatedcatch block
            e.printStackTrace();
         }
         return proxy;
      }
      return bean;
   }
}


AopFrameworkTest.java

package cn.itcast.day3.aopframework;
 
import java.io.InputStream;
import java.util.Collection;
 
public class AopFrameworkTest {
   public static void main(String[] args) {
      InputStream ips = AopFrameworkTest.class.getResourceAsStream("config.properties");
      Object bean = new BeanFactory(ips).getBean("xxx");
      System.out.println(bean.getClass().getName());
     
      ((Collection)bean).clear();
   }
}

配置文件:config.properties

xxx=java.util.ArrayList

xxx=cn.itcast.day3.aopframework.ProxyFactoryBean

xxx.advice=cn.itcast.day3.MyAdvice

xxx.target=java.util.ArrayList






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值