代理模式
代理(Proxy),顾名思义,就是不用自己去做,而是让别人代替你去做。它在程序开发中起到了非常重要的作用,比如传说中的AOP(面向切面编程),就是针对代理的一种应用。此外,在设计模式中,针对它也有一个代理模式。
代理模式分为“静态代理” 和“动态代理” 两种。我们先来看静态代理。
先来一个HelloWorld 吧。
package com.hd.proxy; public interface IHello { void sayHello(String name); }
再来一个实现类:
package com.hd.proxy; public class HelloImpl implements IHello { @Override public void sayHello(String name) { System.out.println("hello, "+name); } }
现在我想在sayHello 方法中加一些逻辑怎么弄,但不可以改变原来的类代码,很容易想到的就是再写一个代理类,然后在代理类中调用原来的类。想好就赶快写:
package com.hd.proxy; public class HelloProxy implements IHello { private IHello hello; public HelloProxy(IHello hello){ this.hello = hello; } @Override public void sayHello(String name) { before(); hello.sayHello(name); after(); } private void before(){ System.out.println("before..."); } private void after(){ System.out.println("after..."); } }
我创建了一个代理类HelloProxy ,让它也实现了IHello接口,并定义了一个IHello的成员变量,这样我就可以在构造函数中作为参数给它赋值了。然后在代理类中的sayHello类里调用被代理类的方法。更重要的是,我还可以在调用的前后分别加上 before() 与 after() 方法,在这两个方法里去实现那些前后逻辑。
用一个 main 方法来测试一下吧:
package com.hd.proxy; public class TestProxy { public static void main(String[] args) { IHello hello = new HelloImpl(); IHello helloProxy = new HelloProxy(hello); helloProxy.sayHello("jack"); } }
运行后结果为:
before... hello, jack after...
一个代理模式就这样被我写好了,有没有感觉 so easy。
不过细心的同学,会发现这里有点小问题。就是如果只是代理一个类的话没有问题,但是你想想,如果我现在想要给HelloImpl2,HelloImpl3 。。。等等这些类都要设置代理类怎么办,按照现在的做法,就是我们需要再定义HelloProxy2,HelloProxy3.。。。等等同样多的代理类出来,这样会造成工程里的类数量激增,就是我们熟知的类爆炸。对于程序的维护很不好。
因此下面我就介绍了动态代理,看看它是怎么实现的。
动态代理分为两种,JDK动态代理和CGLIB动态代理。
a) JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
下面我用 JDK 给我们提供的动态代理方案,写了一个DynamicProxy:
package com.hd.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class DynamicProxy implements InvocationHandler { private Object target; public DynamicProxy(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(target, args); after(); return result; } private void before(){ System.out.println("before..."); } private void after(){ System.out.println("after..."); } }
在 DynamicProxy类中,定义了一个 Object 类型的 target 变量,它就是被代理的目标对象,通过构造函数来初始化。然后在invoke方法里通过反射调用被代理类(target)的方法(method.invoke(target, args))。
下面写一个main方法,看看它是怎么实现动态代理的:
public static void main(String[] args) { IHello hello = new HelloImpl(); DynamicProxy handler = new DynamicProxy(hello); IHello h2 = (IHello)Proxy.newProxyInstance( hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler); h2.sayHello("Lee"); }
运行一下,结果和以前一样。 我写的这个DynamicProxy 就是一个通用的代理逻辑类,不管什么类,只要它实现了某个接口,都可以DynamicProxy 类来实现它定义的代理逻辑。是不是很方便。
b)如果目标类没有实现接口,我们可以选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行期间动态生成字节码,运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
说起来好高深,实际用起来一点都不难。下面再搞一个 CGLibProxy。
package com.alimama.proxy; import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class CglibProxy implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { before(); Object result = proxy.invokeSuper(obj, args); after(); return result; } private void before(){ System.out.println("before..."); } private void after(){ System.out.println("after..."); } }
我们只需要实现 CGLib 给我们提供的 MethodInterceptor 实现类,并填充 intercept() 方法就可以了。
下面写个main方法,看看怎么使用这个代理类的:
package com.hd.proxy; import net.sf.cglib.proxy.Enhancer; public class TestProxy { public static void main(String[] args) { CglibProxy cglibProxy = new CglibProxy(); HelloImpl helloProxy = (HelloImpl)<strong>Enhancer.create</strong>(HelloImpl.class, cglibProxy); helloProxy.sayHello("bruce"); } }
我们只需要调用 Enhancer 的create方法,传入被代理类的class对象,以及我们创建的CglibProxy 对象即可。
总结一下,这篇我们谈到了 静态代理,指出了静态代理模式的弊端,进而介绍了JDK动态代理和Cglib 动态代理两种实现方式。它们俩兄弟在Spring AOP 中都运用到了。大家有时间可以去研究一下。
感谢阅读,未完待续。。。