一、背景
我们使用代理对象替代对真实对象的访问,这样就可以在不修改原目标对象的前提下,扩展目标对象的功能。比如在方法执行前后增加一些自定义操作。
不修改原目标对象有两个好处
- 降低代码耦合
- 有些对象就是无法修改,如外部类。
在Java中实现代理有两种方式,静态代理 和 动态代理
静态代理,指的是我们手动完成,为每个类手动创建一个代理类,完成增强逻辑。很不方便,一般不用。
动态代理,借助一些工具来实现,比较方便灵活。
二、动态代理常见的实现方式
JDK
Demo
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 32 33
| interface UserService { void addUser(String username); }
class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("hello, " + username); } }
class DebugInvocationHandler implements InvocationHandler { private final Object target; public DebugInvocationHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before method " + method.getName()); Object result = method.invoke(target, args); System.out.println("after method " + method.getName()); return result; } }
public class DynamicProxyExample { public static void main(String[] args) { UserService proxyInstance = (UserService) Proxy.newProxyInstance( UserServiceImpl.class.getClassLoader(), UserServiceImpl.class.getInterfaces(), new DebugInvocationHandler(new UserServiceImpl())); proxyInstance.addUser("why"); } }
|
可以看到使用newProxyInstance
来生成一个代理对象,其中需要三个类
- interface: UserService,
- interface Impl: UserServiceImpl
- InvocationHandler Impl:代理逻辑实现类
JDK只能基于接口代理,所以UserService是必备的
CGLIB
JDK只能基于接口代理,而Cglib可以在运行时对字节码进行修改和动态生成。
Demo
非Spring项目可以引入下面这个包
1 2 3 4 5
| <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Service1 { public void m1() { System.out.println("m1方法:"); } public void m2() { System.out.println("m2方法"); } }
public class CglibTest { @Test public void test1() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Service1.class); enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> { System.out.println("调用方法:" + method.getName()); return methodProxy.invokeSuper(o, objects); }); Service1 proxy = (Service1) enhancer.create(); proxy.m1(); proxy.m2(); } }
|
通过Enhancer
来创建一个组件,SuperClass
用于指定需要增强的类,Callback
用于指定代理逻辑。
JDK和CGLIB 动态代理对比
- 原理方面,JDK基于反射,运行时生成代理对象;CGLIB运行时修改字节码;
- 功能方面,JDK只能代理实现了接口的类或者直接代理接口;CGLIB可以代理未实现任何接口的类(但是不能代理final类或者方法);
- 效率方面,JDK动态代理更优秀
AspectJ
是一个AOP框架,更全面的面向切面的编程解决方案。支持编译时织入和加载时织入。
三、Spring AOP
最开始了解到AOP这个概念的时候,其实就是通过Spring。
那了解一下Spring具体使用的实现方案。
如果被代理的对象实现了某个接口,那么SpringAOP就会使用JDK动态代理;
如果对于没有实现接口的对象,就通过Cglib生成一个被代理对象的子类来作为代理。
那SpringAOP和Aspectj有关系吗?
Spring在AOP实现的时候,复用了Aspectj的语法和注解,在底层实现上也可以选择Aspectj。
四、总结
动态代理常见的三种方式分别为:
- JDK
- CGLIB
- ASPECTJ
其中JDK
和CGLIB
属于运行时增强,JDK
依赖反射,只能对实现了接口的类进行代理;CGLIB
运行时修改字节码,支持对所有非final类的非final方法进行代理;ASPECTJ
在类加载前(或编译时)织入字节码。
其中ASPECTJ
比较特别,是个全面的AOP框架。
平时使用的SpringAOP
,复用了ASPECTJ
的声明,底层实现基于JDK
和CGLIB
。