动态代理是相对于静态代理(参见本书附录)而提出的设计模式。在Spring中,有两种方式可以实现动态代理——JDK动态代理和CGLIB动态代理。本节将介绍JDK动态代理。
对于静态代理,一个代理类只能代理一个对象,如果有多个对象需要被代理,就需要很多代理类,造成代码的冗余。JDK动态代理,从字面意思就可以看出,JDK动态代理的对象是动态生成的。
JDK动态代理的条件是被代理对象必须实现接口。
下面以一个简单的案例说明JDK动态代理的实现方式。如一个Animal接口,接口中定义一个方法eat,表示动物需要吃饭。Animal接口定义如下:
然后需要一个Dog类实现Animal接口,需要重写eat()方法:
需要创建动态代理类,动态代理类需要实现InvocationHandler接口。具体动态代理类如下:
在动态代理类中,在被代理的方法前后各加了一段输出逻辑,而不必破坏原方法。下面将用一个测试类,证明动态代理生效。测试类如下:
在这段测试代码中,首先创建原对象Dog和动态代理类AnimalInvocationHandler,然后用原对象生成代理对象animalInvocationHandler.bind(dog),最后通过调用代理对象的invoke方法实现业务逻辑。测试结果如图3-1所示。
图3-1 JDK动态代理测试效果图
这证明动态代理生效了,想要在dog对象的eat()方法前后加上额外的逻辑,可以不直接修改eat()方法,通过以上编程方式就可以实现如图3-1所示的逻辑。
以上就是Spring AOP的基本原理,只是Spring不需要开发人员自己维护代理类,其已帮开发人员生成了代理类。Spring AOP的实现是通过在程序运行时,根据具体的类对象和方法等信息动态地生成了一个代理类的class文件的字节码,再通过ClassLoader将代理类加载到内存中,最后通过生成的代理对象进行程序的方法调用。
从上一节对JDK动态代理的实现可以发现,JDK动态代理有一个缺点,即被代理类必须实现接口。这显然不能满足开发过程中的需要。有没有可能不实现接口,直接就对Java类进行代理呢?这就需要CGLIB发挥作用了。
下面将以一个简单案例说明CGLIB是如何实现动态代理的。在本例中,实现一个Cat类,其有一个cry()方法。Cat实现代码如下:
CGLIB动态代理的实现需要实现MethodInterceptor接口,重写intercept()方法。本例中接口的实现类代码如下:
如上代码注释所示,在调用被代理对象的方法前后各加入一段输出打印逻辑以观察拦截的效果。测试代码如下:
运行测试代码后的执行结果如图3-2所示。
图3-2 CGLIB动态代理测试效果图
面向切面编程(AOP)是一种编程技术,可以增强几个现有的中间件环境(例如J2EE)或开发环境(例如JBuilder,Eclipse)。
AOP联盟定义了一套用于规范AOP实现的底层API,通过这些统一的底层API,可以使得各个AOP实现工具之间实现相互兼容。现在AOP联盟已有几个项目提供了与AOP相关的技术,如通用代理,拦截器或字节码转换器。
· ASM:轻量级字节码转换器。
· AspectJ:一个面向切面的框架,扩展了Java语言。
· AspectWerkz:一个面向切面的框架,基于字节码级别的动态织入和配置。
· BCEL:字节码转换器。
· CGLIB:用于类工件操作和方法拦截的高级API。
· Javassist:具有高级API的字节码转换器。
· JBoss-AOP:拦截和基于元数据的AO框架。
除了以上列举的AOP联盟的相关项目之外,还有很多其他项目,此处不再一一列举。所有这些项目都有其各自的目标和特点。但是,一些基本组件对于构建完整的面向切面的系统是必需的。例如,一个能够在基础组件上添加元数据的组件,一个拦截框架,一个能够执行代码转换以便为类提供advice的组件,一个weaver组件,一个配置组件等。