Java动态代理
Java动态代理
一、什么是代理
编程中有个思想,不要随意的去修改别人已经写好的代码或者方法。但是如果我想在原有实现的基础上增加额外的功能呢,即对目标对象的功能进行拓展。比如在执行某个方法的时候记录下日志,这个时候就可以使用代理。
代理是一种常见的设计模式,其目的就是为目标对象提供一个代理对象以控制对目标对象的访问,即通过代理对象访问目标对象。其中代理对象是目标对象的拓展,并且会使用到目标对象。
代理模式又分为静态代理和动态代理。静态代理简单来说就是代理类和目标类(委托类)实现同一个接口,然后代理类中引入对目标类对象的引用。目的:这样可以实现一些其他功能,但是不会让目标类变得膨胀。缺点:这样必须为目标对象创建一个实现了相同接口的代理对象,并且代理对象中的方法也要和目标对象保持一致。一旦目标对象改动了,代理对象也要变更相应的代码。这样就出现了大量的重复代码,增加了代码的维护复杂度。而且这种方法代理对象只能服务于一种类型的目标对象,如果要服务多个类型的对象,则需要为每一种对象都进行代理。举例:比如我想在调用具体实现类前后打印日志等信息,在不修改已有代码的情况下,我们只需要增加一个代理类,在代理类中增加打印日志的功能,然后去调用目标类,这样就可以避免修改目标类。在创建代理对象时,通过构造器塞入一个目标对象,然后在代理对象的方法内部调用目标对象同名方法,并在调用前后打印日志。也就是说,代理对象=目标对象+增强代码,有了代理对象之后,就不用原对象了。但是如果想让多个目标类都添加打印日志功能,那么就需要增加多个代理类,代理类中各个方法都要增加打印日志功能,这就不堪负重了。
动态代理:上面我们知道静态代理每个代理类只能为一个接口服务,这样程序中就会出现很多代理类,动态代理可以通过一个代理类完成全部的代理功能。这个代理类是在运行时候动态生成的,是通过反射机制动态创建(反射机制是指在运行状态中,对于任意一个类,都能知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意属性和方法。)这样不需要为每一个接口写一个代理类,避免一个类对应一个代理的问题,大大提高了系统的灵活性,减少重复,降低了代码维护的复杂性和成本。Java动态代理机制以巧妙的方式实践了代理模式的设计理念。
Java动态代理:Java的动态代理是基于接口的,代理类和目标类实现相同的接口,这样他们行为保持了一致性,在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效的对委托类的对象的直接访问,可以很好的隐藏和保护委托对象,同时为实施不同的策略预留空间。
二、Java的动态代理类
Java动态代理机制中有两个重要的接口和类:接口InvocationHandler()和类Proxy(),位于java.lang.reflect包中,这是实现动态代理的核心。
(1)接口InvocationHandler:动态代理类的调用处理程序
InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,该接口中仅定义了一个方法:public Object invoke(Object proxy, Method method, Object[] args)
其中:第一个参数proxy表示执行这个方法的代理对象,method表示目标对象实际需要执行的方法,args表示目标对象实际要执行的方法所需要的参数。
每一个proxy代理实例都要有一个关联的调用处理程序,该调用处理程序都必须实现InvocationHandler接口。所以在实际编程中,需要先定义一个实现了InvocationHandler接口的调用处理器对象,然后将它作为创建代理类实例的参数(见Proxy类的newProxyInstance()方法)。
(2)Proxy:动态代理类
Proxy类就是用来创建一个代理对象的类,最常用的方法是static Object newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)。这个方法的作用就是创建一个代理对象,其中三个参数为:
1、ClassLoader loader:指定当前目标对象使用类加载器,对于不同来源(系统库或网络等)的类需要不同的类加载器来加载,这是Java安全模型的一部分。
2、Class[] interfaces:一个interface对象数组,目标对象实现的接口的类型。表示我们要给代理对象提供什么样的接口。如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
3、InvocationHandler h:一个InvocationHandler调用处理程序对象,它必须是实现了InvocationHandler接口的对象,作用就是定义代理对象中需要执行的具体操作。当执行目标对象的方法时,会关联到一个InvocationHandler对象上,从而触发调用处理程序的方法,会把当前执行目标对象的方法作为参数传入,并最终调用h中的invoke()方法。
其实动态代理类可以看做是这样一种类:它是在运行时生成的类,在生成它时你必须提供一组接口给它,然后该类就宣称它实现了这些接口。你当然可以把该类的实例当做这里接口中的任何一个来用。当然,这个动态代理类其实就是一个代理,它不会替你做实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。在实际使用代理类的时候,我们必须实现InvocationHandler接口。这样目标对象、需要控制的接口和控制方式都可以动态改变,从而实现灵活的动态代理关系。
三、动态代理的步骤
1、创建目标类(委托类)的接口
这里定义了两个接口,interface IBusiness1 和interface IBusiness2,各包含一个方法。
interface IBusiness1:
1 package com.dynamicproxy; 2 3 public interface IBusiness1 { 4 public void doSomeThing1() ; 5 }
View Code
interface IBusiness2:
1 package com.dynamicproxy; 2 3 public interface IBusiness2 { 4 public void doSomeThing2() ; 5 }
View Code
2、创建目标类(委托类)
创建一个目标类Business,实现这两个接口。
1 package com.dynamicproxy; 2 3 public class Business implements IBusiness1, IBusiness2 { 4 @Override 5 public void doSomeThing1() { System.out.println("执行业务逻辑1"); } 6 7 @Override 8 public void doSomeThing2() { 9 System.out.println("执行业务逻辑2"); 10 } 11 }
View Code
3、定义一个代理类的调用处理程序。该程序必须实现接口InvocationHandler,且必须实现接口的invoke方法。
InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被转发到调用处理程序的invoke方法。
1 package com.dynamicproxy; 2 3 import java.lang.reflect.Method; 4 import java.lang.reflect.InvocationHandler; 5 6 public class LogInvocationHandler implements InvocationHandler{ 7 //目标对象 8 private Object target; 9 10 //构造函数,给目标对象赋值 11 LogInvocationHandler(Object target) { 12 this.target = target; 13 } 14 15 @Override 16 /** 17 * proxy:代理类 18 * method:被代理的方法 19 * args:该方法的参数数组 20 */ 21 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 22 //在真实的对象执行之前添加自己的操作 23 System.out.println("日志: 方法" + method.getName() + "即将执行"); 24 //执行原有逻辑 25 Object rev = method.invoke(target, args); 26 //在真实的对象执行之后添加自己的操作 27 System.out.println("日志: 方法" + method.getName() + "执行完毕"); 28 29 return rev; 30 } 31 }
View Code
4、通过Proxy的静态方法newProxyInstance()创建一个代理对象。
Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。这个方法的作用就是创建一个代理类对象。该方法有三个参数,三个参数具体解释见上述。
1 package com.dynamicproxy; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Proxy; 5 6 public class Test { 7 public static void main(String[] args) { 8 //要代理的目标对象 9 Business myBusiness = new Business(); 10 //代理类要实现的接口列表 11 Class[] proxyInterface = myBusiness.getClass().getInterfaces(); 12 13 //代理对象的调用处理程序。我们将要代理的目标对象传入代理对象的调用处理的构造函数中,代理对象的调用处理程序最终会调用目标对象的方法 14 LogInvocationHandler handler = new LogInvocationHandler(myBusiness); 15 16 /** 17 * 通过Proxy类的newProxyInstance方法创建代理对象 18 * 第一个参数:handler.getClass().getClassLoader(),使用handler对象的classloader对象来加载我们的代理对象 19 * 第二个参数:proxyInterface,这里为代理类提供的接口是目标对象实现的接口,这样代理对象就能像目标对象一样调用接口中的所有方法 20 * 第三个参数:handler,我们将代理对象关联到上面的InvocationHandler对象上 21 */ 22 IBusiness1 proxyBusiness = (IBusiness1) Proxy.newProxyInstance(handler.getClass().getClassLoader(), proxyInterface, handler); 23 24 //使用代理类的实例来调用方法。 25 proxyBusiness.doSomeThing1(); 26 ((IBusiness2) proxyBusiness).doSomeThing2(); 27 } 28 }
View Code
5、通过代理对象调用委托类对象的方法。
其实Proxy类只是一个连接桥,把代理(InvocationHandler)与被代理类关联起来,真正处理事情的是InvocaHandler。InvocationHandler接口中的invoke方法在代理类中是动态实现的,当我们通过动态代理调用一个方法的时候,这个方法的调用会被转发到到调用处理程序的invoke方法中。
1 //使用代理类的实例来调用方法。 2 proxyBusiness.doSomeThing1(); 3 ((IBusiness2) proxyBusiness).doSomeThing2();
View Code
运行结果:
动态代理的优势就是可以很方便的对目标类的函数进行统一的处理,而不用修改每个目标类的方法。所有被代理执行的方法在调用执行的时候,其方法只是作为参数传入InvocationHandler中的invoke方法,实际是在invoke方法中处理的,这样通过在invoke方法中我们可以对所有被代理的方法进行相同的增强操作。
博众家之所长,集群英之荟萃。遴选各IT领域精品雄文!
欢迎关注“IT架构精选”