代理模式 代理模式是一个如下的结构
其中A为真实对象,A_proxy为代理对象,A与A_proxy实现了共同的接口A_interface。
在通常情况下我们访问A时直接通过new A来访问,但是在代理模式下,我们通过A_proxy来访问A。
因为A_proxy和A都实现了同样的接口,所以我们可以像直接使用A一样使用A_proxy来间接访问A对象。
应用场景 这个时候,我们可能会产生这样一个疑问,就是这样做有什么用吗?
我们想象一下这样的场景,我们需要对于A的某个方法计算其运行时间,或者我们需要对于其所有的方法执行前后都进行日志记录。
以计算运行时间为例。若是A如下:
1 2 3 4 5 public class A implements A_interface { public void output () { System.out.println("Hello world!" ); } }
那么我们的A_proxy就可以如下
1 2 3 4 5 6 7 8 9 10 public class A_proxy implements A_interface { A a = new A(); public void output () { int startTime = System.currentTimeMillis(); a.output(); int endTime = System.currentTimeMillis(); System.out.println(endTime - startTime); } }
这其实是一种静态代理实现。因为我们需要实现知道A的内部代码,并且也需要事先写好A_proxy,所以其能力会大打折扣。
JDK动态代理 在java中,已经提供了动态代理机制。
在java动态代理机制中有一个非常重要的接口InvocationHandler。我们就是通过实现该接口在运行原始方法前后实现自己所需的功能。
我们来看看具体的代码。
1 2 3 4 public interface Hello { String hello (String msg) ; }
1 2 3 4 5 6 public class HelloImpl implements Hello { public String hello (String msg) { return "hello " + msg; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class HelloInvocationHandler implements InvocationHandler { private static Logger logger = LoggerFactory.getLogger(HelloInvocationHandler.class); private Object object; public HelloInvocationHandler (Object object) { this .object = object; } public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); Object result = method.invoke(object, args); long endTime = System.currentTimeMillis(); logger.info("{} cost {}ms" , method.getName(), endTime - startTime); return result; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class HelloFactory { public static Hello getInstance (String helloClassName) throws ClassNotFoundException, IllegalAccessException, InstantiationException { Class clazz = Class.forName(helloClassName); Hello hello = (Hello) clazz.newInstance(); InvocationHandler handler = new HelloInvocationHandler(hello); Hello proxy = (Hello) Proxy.newProxyInstance( hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), handler ); return proxy; } }
那么最后我们只需要调用HelloFactory.getInstance(“com.example.HelloImpl”)来获取代理对象。
JDK动态代理的局限性 我们可以看到JDK动态代理实现有一个非常大的前提,就是目标类必须实现接口。
那么若是对于那些没有接口的类,JDK动态代理就无法使用了。这时又有一种叫做cglib的代理方式,cglib是通过继承原来的类来实现代理。不过cglib也有缺点,由于继承的特性,对于final方法无法继承。具体此处不详解。
与spring AOP的关联 如果我们稍微敏感一点,JDK动态代理和spring AOP有异曲同工之妙。其实spring AOP框架中就是针对接口和非接口分别使用JDK动态代理以及cglib代理实现的。