Java总结(20) 代理

来源:互联网 发布:遗传算法c 实现 编辑:程序博客网 时间:2024/05/17 19:14


动态代理

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

生活中的代理

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

程序中的代理

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


AOP 面向方面编程

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:
                              安全       事务         日志
StudentService  ------|----------|------------|-------------
CourseService  ------|----------|------------|-------------
MiscService       ------|----------|------------|-------------

用具体的程序代码描述交叉业务:
method1         method2          method3
{                      {                       { 
------------------------------------------------------切面
....            ....              ......
------------------------------------------------------切面
}                       }                       }

交叉业务的编程问题即为面向方面的编程(Aspect oriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1         func2            func3
{             {                { 
....            ....              ......
}             }                }
------------------------------------------------------切面
使用代理技术正好可以解决这种问题,代理是实现 AOP 功能的核心和关键技术。

动态代理技术

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!需要写成百上千个代理类。又因为采用 AOP 编程方法的话,扩展的功能已经独立出来,目标的功能也已经存在,要生成代理类的过程是重复并且简单的,所以提供了可以在 JVM 运行期动态生成出类的字节码这种技术,把生成代理类的这个重复的过程独立出来,就是动态代理技术,这种动态生成的类往往被用作代理类,所以就叫动态代理类。

JVM 生成的动态类必须实现一个或多个接口,所以,JVM 生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB 库除了可以动态生成实现接口的动态类,可以动态生成一个类的子类这种动态类,一个类的子类也可以用作该类的代理(这也说明实现功能的扩展,既可以用继承也可以用组合,也就是既可以用横向扩展技术也可以用纵向扩展技术),所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用 CGLIB 库。

代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能增强的代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中

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

api 提供的动态生成代理类的功能封装在 Proxy 类中,相关的信息查看 api 文档。
/**  * 需求:使用 Proxy 创建动态代理类的 Class 对象,打印动态代理类的函数列表  */  package cn.itcast.proxy;    import java.lang.reflect.Constructor;  import java.lang.reflect.Method;  import java.lang.reflect.Proxy;  import java.util.Collection;    /**  * @class: DynamicProxyDemo  * @package: cn.itcast.proxy  * @description: TODO  * @author: vivianZhao  * @date: 2013-7-23 下午6:14:31  * @version: 1.0  */  public class DynamicProxyDemo {        public static void main(String[] args) {          // TODO Auto-generated method stub          createDynamicProxyAndMethodList();      }        public static void createDynamicProxyAndMethodList() {          // 使用 Proxy 创建动态代理类          Class<?> clazz = Proxy.getProxyClass(Collection.class.getClassLoader(),                  Collection.class);            // 使用反射技术打印类的 Constructor          System.out.println("------------------constructor------------------------");          for (Constructor<?> constructor : clazz.getConstructors()) {              StringBuilder constructorSignature = new StringBuilder(                      constructor.getName());              Class<?>[] parameterTypes = constructor.getParameterTypes();              printMethod(constructorSignature, parameterTypes);          }            // 使用反射技术打印类的 Method          System.out.println("------------------method-----------------------------");          for (Method method : clazz.getMethods()) {              StringBuilder methodSignature = new StringBuilder(                      method.getName());              Class<?>[] parameterTypes = method.getParameterTypes();              printMethod(methodSignature, parameterTypes);          }      }        private static void printMethod(StringBuilder functionSignature,              Class<?>[] parameterTypes) {          functionSignature.append('(');          for (Class<?> parameterType : parameterTypes) {              functionSignature.append(parameterType.getName() + ", ");          }          if(functionSignature.substring(functionSignature.length() - 2).equals(", ")){              functionSignature.deleteCharAt(functionSignature.length() - 1);              functionSignature.deleteCharAt(functionSignature.length() - 1);          }          functionSignature.append(')');          System.out.println(functionSignature);      }    }  

创建动态类的实例对象及调用其方法

public static void createDynamicProxyInstance() throws Exception {      // 1.获取实现指定接口的代理类的 Class 对象      Class proxyClass = Proxy.getProxyClass(              Collection.class.getClassLoader(), Collection.class);      // 2.用 Class 对象获取指定参数列表类型的 Constructor 构造器      Constructor constructor = proxyClass              .getConstructor(InvocationHandler.class);      // 4.用 Constructor 对象创建代理类的实例      Collection collection = (Collection) constructor              .newInstance(new MyInvocationHandler());      collection.clear();      // collection.size();  }    // 3.定义自己的  InvocationHandler 实现类   public static class MyInvocationHandler implements InvocationHandler {        @Override      public Object invoke(Object proxy, Method method, Object[] args)              throws Throwable {          // TODO Auto-generated method stub          return null;      }  }  
InvocationHandler 是 Proxy 增强功能和 Target 功能的封装,Proxy.getProxyClass 只是抽象了我们自定义代理类的部分,其中代理类中的函数并没有实现具体的语句。
让jvm创建动态类及其实例对象,需要给它提供哪些信息:
1.生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
2.产生的类字节码必须有个一个关联的类加载器对象;
3.生成的代理类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个 InvocationHandler 对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的 InvocationHandler 对象的 invoke 方法中加一点代码,就可以看到这些代码被调用运行了。
用 Proxy.newInstance 方法可以直接一步就创建出代理对象。

完成 InvocationHandler 对象的内部功能

public static void createDynamicProxyInstance1() throws Exception {      Collection collection = (Collection) Proxy.newProxyInstance(              Collection.class.getClassLoader(),              new Class[] { Collection.class },               new InvocationHandler() {                  // 创建 Target 的对象                  ArrayList target = new ArrayList();                  Object returnValue = null;                  @Override                  public Object invoke(Object proxy, Method method,                          Object[] args) throws Throwable {                      // TODO Auto-generated method stub                      // 1.在 target 功能之前添加代理的额外功能                      long usedTime = System.currentTimeMillis();                      // 2.调用 target 功能                                             returnValue = method.invoke(target, args);                      // 3.在 target 功能之后添加代理的额外功能                      usedTime = System.currentTimeMillis() - usedTime;                      System.out.println("功能耗时 " + usedTime + "ms");                      return returnValue;                  }              });      collection.clear();      collection.add("abc");      collection.add("def");      System.out.println(collection.size());  }  
这样就把 Targe 的功能集成到 Proxy 对象中了,而且 Proxy 的额外功能也添加上了,添加 Proxy 的额外功能还只需要写一次代码,因为用实现接口的匿名内部类时,构造函数不能带参数,不然还可以把 Target 用构造函数参数的形式传递进来。就这样把 Target、Proxy 的额外功能、实现 Proxy ,这三块完全分开。用 反射+代理模式  可以实现代码实现和代码功能的分开,也就是以后有什么功能可以让我们在写一次功能代码的情况下实现各个地方都可以使用,并且不用修改源码,让 JVM 自动创建增强后的 class。 Proxy 负责 Class 和 代理类对象的创建,InvocationHandler 负责 Target、代理额外功能 的实现。

分析 InvocationHandler 对象的运行原理

动态生成的类实现了 Collection 接口(可以实现若干接口),生成的类有 Collection 接口中的所有方法和一个接受 InvocationHandler 类型参数的构造方法。构造方法接受一个InvocationHandler对象,InvocationHandler 类型对象主要负责动态类实例方法调用的处理,也就是辅助实现各个方法的具体功能。实现Collection接口的动态类中的各个方法的代码又是怎样的呢?

动态代理类的构造函数实现的伪语言表达形式

$Proxy0 implements Collection  {      InvocationHandler handler;      public $Proxy0(InvocationHandler handler)      {          this.handler = handler;      }  }  

动态代理类的其他函数实现的伪语言表达形式

$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);      }  }  

具体的实现肯定不是这样,为了能为所有接口都能生成上述的动态代理类,一定会使用反射的方式来构造。更加具体的实现方式可以参考 Proxy 的源码。

InvocationHandler接口中定义的invoke方法接受的三个参数的意义,图解说明如下:


先前打印动态类的实例对象时,结果是null,是因为调用了代理类实例的 toString 方法,该方法已经被代理类重写,重写为和接口方法一样的实现,所以会传递调用到 InvocationHandler 对象的 invoke 方法,因为那时候 invoke 方法是自动生成的,默认就返回 null。这也说明调用有基本类型返回值的方法时会出现 NullPointerException 异常,因为我们需要把默认返回的 null 类型,强制转换为返回值对应的类型,所以会发生 NullPointException。而动态类的实例对象的 getClass() 方法返回了正确结果,是因为代理类从 Objec t类继承的 hashCode, equals,  toString 这几个方法都已经被代理类重写,代理类将调用请求转发给 InvocationHandler 对象,对于其他 Object 的方法则没有重写。

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

动态代理的工作原理图


编写可生成代理和插入通告的通用方法

1.将目标类和目标对象传进去,有三种方法

    直接在 InvocationHandler 实现类中创建目标类的实例对象,可以看运行效果和加入日志代码,但没有代理类就不能灵活改动配置目标类。
    为 InvocationHandler 实现类注入目标类的实例对象,这样写是不能用匿名内部类的形式。
    让匿名的 InvocationHandler 实现类访问外面方法中的目标类实例对象的 final 类型的引用变量。
在 InvocationHandler 实现类中创建目标类的实例对象

Collection collection = (Collection) Proxy.newProxyInstance(          Collection.class.getClassLoader(),          new Class[] { Collection.class },           new InvocationHandler() {              // 创建 Target 的对象              ArrayList target = new ArrayList();              Object returnValue = null;              @Override              public Object invoke(Object proxy, Method method,                      Object[] args) throws Throwable {                  // TODO Auto-generated method stub                  // 1.在 target 功能之前添加代理的额外功能                  long usedTime = System.currentTimeMillis();                  // 2.调用 target 功能                                         returnValue = method.invoke(target, args);                  // 3.在 target 功能之后添加代理的额外功能                  usedTime = System.currentTimeMillis() - usedTime;                  System.out.println("功能耗时 " + usedTime + "ms");                  return returnValue;              }          });  

为 InvocationHandler 实现类注入目标类的实例对象

Vector v = new Vector();          class MyInvocationHandler implements InvocationHandler          {              Collection target = null;                            public Collection bind(Collection target)              {                  this.target = target;                  Collection proxy1 = (Collection)Proxy.newProxyInstance(                          ProxyTest.class.getClassLoader(),                          new Class[]{Collection.class} ,                           this);                  return proxy1;              }              @Override              public Object invoke(Object proxy, Method method,                      Object[] args) throws Throwable {                  // TODO Auto-generated method stub                  System.out.println("begin " + method.getName());                  Object retval = method.invoke(target, args);                  System.out.println("end" + method.getName());                  return retval;              }                }          MyInvocationHandler handler = new MyInvocationHandler();          Collection proxy1 = handler.bind(v);  
让匿名的 InvocationHandler 实现类访问外面方法中的目标类实例对象的 final 类型的引用变量在下面的例子中演示

2.将系统功能代码模块化,即将切面代码也改为通过参数形式提供

createDynamicProxyInstance 函数

public static void createDynamicProxyInstance1() throws Exception {      Object target = new ArrayList();      Advice advice = new MyAdvice();      Collection collection = (Collection) createDynamicProxyInstance2(target, advice);      collection.clear();      collection.add("abc");      collection.add("def");      System.out.println(collection.size());  }    private static Object createDynamicProxyInstance2(final Object target, final Advice advice) {      Object proxyObject = Proxy.newProxyInstance(target.getClass()              .getClassLoader(), target.getClass().getInterfaces(),              new InvocationHandler() {                    @Override                  public Object invoke(Object proxy, Method method,                          Object[] args) throws Throwable {                      // TODO Auto-generated method stub                                            // 1.在 target 功能之前添加代理的额外功能                      advice.beforeMethod(method);                      // 2.调用 target 功能                      Object returnValue = method.invoke(target, args);                      // 3.在 target 功能之后添加代理的额外功能                      advice.afterMethod(method);                                            return returnValue;                  }              });      return proxyObject;  }  

Advice 接口

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

实现 Advice 接口的 MyAdvice  类 

package cn.itcast.proxy;    import java.lang.reflect.Method;    public class MyAdvice implements Advice {        private long usedTime;        @Override      public void beforeMethod(Method method) {          // TODO Auto-generated method stub          usedTime = System.currentTimeMillis();      }        @Override      public void afterMethod(Method method) {          // TODO Auto-generated method stub          usedTime = System.currentTimeMillis() - usedTime;          System.out.println(method.getName() + " 功能耗时 " + usedTime + "ms");      }    }  

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

需求:要求在不修改 Client 代码的情况下,可以修改我们创建的对象的类型,并且配置是否创建目标功能的代理类对象,那么我们可以选用   配置文件 + 工厂模式  的方法。我们的需求在配置文件中配置好以后,使用工厂类对象读取配置文件获得我们要创建的对象的类的名字的字符串,使用该字符串来在工厂中使用反射来创建我们我们需要的实例。

现在我们的配置选项可以设置为我们要创建的对象要么是一般目标类对象,要么是目标代理类对象,创建目标代理类对象的话,还需要指定 目标类 和 Advice 接口的实现类。
配置文件格式可以为:
#xxxClassName=java.util.ArrayList  
xxxClassName=cn.itcast.proxy.aopframework.ProxyFactoryBean  
xxxClassName.target=java.util.ArrayList  
xxxClassName.advice=java.util.ArrayList  
其中 xxxClassName 可以是 Collection 这种我们 Client  程序中需要的对象的类型的名字
config.properties 文件
Collection=java.util.ArrayList  
#Collection=cn.itcast.proxy.aopframework.ProxyFactoryBean  
Collection.target=java.util.ArrayList  
Collection.advice=cn.itcast.proxy.MyAdvice  

定义一个bean工厂接口

定义一个bean工厂接口,专门负责生产指定的 javabean 的对象
package cn.itcast.proxy.aopframework;    public interface BeanFactory {      Object getBean(String className) throws Exception;  }  

定义一个 ConfigurableBeanFactory 类

定义一个 ConfigurableBeanFactory 类实现 bean 工厂,它是根据配置文件产生 bean 类

package cn.itcast.proxy.aopframework;    import java.io.IOException;  import java.io.InputStream;  import java.util.Properties;  public class ConfigurableBeanFactory implements BeanFactory {        private Properties properties;        public ConfigurableBeanFactory(InputStream propertiesInputStream) {          // TODO Auto-generated constructor stub          properties = new Properties();          try {              properties.load(propertiesInputStream);          } catch (IOException e) {              // TODO Auto-generated catch block              e.printStackTrace();          }      }        @Override      public Object getBean(String className) throws Exception {          // 根据传入的类名去配置文件中拿到实际配置的类名          String configClassName = properties.getProperty(className);          Object beanObject = Class.forName(configClassName).newInstance();          // 如果配置的对象是 代理工厂类对象,那么要返回就得是下面配置的目标和Advice组合形成的代理的对象          if (beanObject instanceof ProxyFactoryBean) {              ProxyFactoryBean proxyFactoryBeanObject = (ProxyFactoryBean) beanObject;              String target = properties.getProperty(className + ".target");              String advice = properties.getProperty(className + ".advice");              proxyFactoryBeanObject.setTarget(target);              proxyFactoryBeanObject.setAdvice(advice);                beanObject = proxyFactoryBeanObject.getBean(target);          }            return beanObject;      }        public static void main(String[] args) throws Exception {          InputStream propertiesInputStream = ConfigurableBeanFactory.class                  .getResourceAsStream("config.properties");          BeanFactory beanFactory = new ConfigurableBeanFactory(                  propertiesInputStream);                    System.out.println(beanFactory.getBean("Collection").getClass().getName());      }  }  

定义一个 ProxyFactoryBean 类

定义一个 ProxyFactoryBean 类它是一个产生 代理类对象的工厂,它本身就是 javabean ,而且实现了 BeanFactory 接口。
package cn.itcast.proxy.aopframework;    import java.lang.reflect.InvocationHandler;  import java.lang.reflect.Method;  import java.lang.reflect.Proxy;    import cn.itcast.proxy.Advice;  public class ProxyFactoryBean implements BeanFactory{      private String target;      private String advice;        public ProxyFactoryBean() {          super();      }        public String getTarget() {          return target;      }        public void setTarget(String target) {          this.target = target;      }        public String getAdvice() {          return advice;      }        public void setAdvice(String advice) {          this.advice = advice;      }        @Override      public Object getBean(String className) throws Exception {          // TODO Auto-generated method stub          Object target = Class.forName(this.target).newInstance();          Advice advice = (Advice)Class.forName(this.advice).newInstance();          return createDynamicProxyInstance(target, advice);      }            private static Object createDynamicProxyInstance(final Object target, final Advice advice) {          Object proxyObject = Proxy.newProxyInstance(target.getClass()                  .getClassLoader(), target.getClass().getInterfaces(),                  new InvocationHandler() {                        @Override                      public Object invoke(Object proxy, Method method,                              Object[] args) throws Throwable {                          // TODO Auto-generated method stub                                                    // 1.在 target 功能之前添加代理的额外功能                          advice.beforeMethod(method);                          // 2.调用 target 功能                          Object returnValue = method.invoke(target, args);                          // 3.在 target 功能之后添加代理的额外功能                          advice.afterMethod(method);                                                    return returnValue;                      }                  });          return proxyObject;      }  }  




原创粉丝点击