Spring AOP可以在不侵入原有代码的情况下实现行为的拓展、增强,其内部就是通过动态代理来实现。本文我们将展示两大类、三种具体的代理方法
简介
通过代理访问的方式,一方面可以屏蔽被代理对象的内部细节;另一方面,我们可以通过代理来拓展、增强被代理对象的功能、行为。在Java中,可根据代理类的生成时机划分为两大类:静态代理、动态代理。前者是在程序运行前通过开发者手动编写实现代理类;而后者则是在运行期根据需要动态地生成代理类字节码文件并创建实例。对于动态代理,有两种具体实现方式:JDK代理、CGLIB代理
静态代理
静态代理类需要开发者自行开发实现。其与被代理类(委托类)必须属于同一种类型,即要么两者实现同一个接口,要么派生自同一个类。这里我们提供了一个Sell接口及实现该接口的Apple类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public interface Sell {
void sellPhone();
void sellPc(); } ...
public class Apple implements Sell { @Override public void sellPhone() { System.out.println("Apple: Sell iPhone SE"); }
@Override public void sellPc() { System.out.println("Apple: Sell MacBook Pro"); } }
|
如果我们期望能够在Apple实例的方法调用前后执行一些额外的逻辑,但是又不想对Apple类的方法代码造成侵入。一种简单可行的途径就是静态代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public class Agent implements Sell { private Sell apple;
public Agent(Sell apple) { this.apple = apple; }
@Override public void sellPhone() { System.out.println("Static Agent: Start"); apple.sellPhone(); System.out.println("Static Agent: End\n"); }
@Override public void sellPc() { System.out.println("Static Agent: Start"); apple.sellPc(); System.out.println("Static Agent: End\n"); } }
|
从静态代理类Agent的代码我们可以看到,其与被代理类(委托类)是属于同一类型,即实现了同一个接口。通过在内部持有委托类实例的方式来实现将外部调用转发至真正的委托对象;与此同时,代理类通过实现接口方法的方式来增强委托类的行为。下面即是静态代理的测试实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public class AgentTest {
public static void testAgent() { Apple apple = new Apple(); Agent agent = new Agent(apple);
agent.sellPhone(); agent.sellPc(); } }
|
测试结果红框即为静态代理所增强的行为
静态代理虽然简单粗暴,但是缺点同样明显。当接口方法发生变动时,代理类、委托类均需同步修改;其次当委托类过多时,如果只通过一个代理类来实现,会使得该代理类中的代码越来越臃肿。而如果遵循单一功能的设计原则,一个代理类只负责代理一个接口,又会导致代理类的数量过多。那能不能让代理类在运行期动态地按需生成,答案是可以的,这就是我们下面即将介绍的动态代理
动态代理:JDK代理
JDK的动态代理方式,其只支持基于接口的动态代理,即委托类必须实现某个接口。一般地,我们只需关心JDK中的两个核心API:InvocationHandler接口、Proxy类。在实际应用中,我们必须实现InvocationHandler接口的invoke方法。一方面用于定义我们所需增强的行为,另一方面利用反射将方法调用转发给真正的被代理对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
public class JDKProxy implements InvocationHandler { private Object object;
public JDKProxy(Object o) { this.object = o; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("JDK Proxy: Start"); System.out.println("[proxy class name] : " + proxy.getClass().getName()); System.out.println("[method] : " + method.toString());
Object result = method.invoke(object, args);
System.out.println("JDK Proxy: End\n"); return result; } }
|
前面提到动态代理是在运行期动态生成代理类的字节码并加载到JVM中的,那么这个动态代理类的生成及实例化一般可通过Porxy的newProxyInstance方法实现。从该方法的签名我们可以看出其需接受三个参数:loader参数接收一个类加载器实例,用于加载动态代理类的实例;interfaces参数用于指定该动态代理类所需实现的接口数组;而最后一个参数h则用于接收一个InvocationHandler实例,用于增强委托类的行为
1 2 3 4 5
| public class Proxy implements java.io.Serializable { @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException }
|
下面我们通过一个实际的例子,来看看如何使用JDK代理。可选地,我们可通过System.getProperties().put()方法,将JDK动态生成的代理类字节码文件序列化到硬盘,便于我们后续分析查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class JDKProxyTest { public static void testJDKProxy() { Apple apple = new Apple(); JDKProxy jdkProxy = new JDKProxy(apple);
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
Sell sell = (Sell)Proxy.newProxyInstance( jdkProxy.getClass().getClassLoader(), new Class[] {Sell.class}, jdkProxy);
System.out.println("[sell class name] : " + sell.getClass().getName());
sell.sellPhone(); sell.sellPc();
} }
|
从测试结果中,我们可以看出,委托类的行为被成功增强
由于我们把动态生成的代理类字节码保存到了硬盘,所以我们可以直接打开,来看看其到底是如何实现的,可以看到动态代理内部会持有一个InvocationHandler实例,而InvocationHandler中则进一步持有委托类实例
动态代理:CGLIB代理
JDK代理看似好像已经很好的解决了静态代理的弊端,但是其也是有一定的局限性。正如我们前文所言,其是基于接口的,那如果我们期望动态地代理一个无接口的委托类,JDK代理就无能为力了。所幸的是,第三方库解决了这个问题——CGLIB (Code Generation Library),其可通过继承委托类的方式来动态生成代理类。由于是第三方库,所以我们需要首先添加其依赖
1 2 3 4 5 6
| <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.2.5</version> </dependency>
|
这里我们提供一个没有实现接口的委托类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public class Microsoft {
public void releaseOS() { System.out.println("Microsoft: Release Windows 10"); }
public void releaseOffice() { System.out.println("Microsoft: Release Office 365"); } }
|
类似地,在使用CGLIB代理的过程中,我们需要实现MethodInterceptor接口的intercept方法来增强委托类的行为。示例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
public class CglibProxy implements MethodInterceptor { private Object object;
public CglibProxy(Object o) { this.object = o; }
@Override public Object intercept(Object o, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Cglib Proxy: Start");
Object result = method.invoke(object, args);
System.out.println("Cglib Proxy: End\n"); return result; } }
|
现在我们通过代理来访问调用方法,即可实现行为增强。示例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class CglibProxyTest {
public static void testCglibProxy() { Microsoft microsoft = new Microsoft();
CglibProxy cglibProxy = new CglibProxy(microsoft); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass( microsoft.getClass() ); enhancer.setCallback(cglibProxy);
Microsoft microsoftCglibProxy = (Microsoft) enhancer.create();
microsoftCglibProxy.releaseOS(); microsoftCglibProxy.releaseOffice(); } }
|
测试结果如下,可以看到其符合我们的预期