AOP实现原理 代理模式
AOP(Aspect-Oriented Programming)切面式编程,之前已经说过了在Spring是如何进行使用它,今天来聊一聊它的实现原理。
GoF为面向对象编程提出了多种设计模式,其中有一种是代理模式,它是一种结构型设计模式,它允许在访问对象时引入一些额外的间接层,以控制对对象的访问。在代理模式中,代理对象充当了被访问对象的替代者,客户端通过代理对象间接地访问目标对象,从而实现了对目标对象的控制和管理,其实这种模式非常像现实生活中的中介、代理。
静态代理
现在我想要创建一个代理对象代理我的UserService
的login
方法,对login
方法进行一个增强,在登录前和登录后进行额外处理
创建UserService
接口、UserServiceImpl
实现类,里面有login
方法
1 2 3 4 5 6 7 8 9 10
| public interface UserService { void login(); }
public class UserServiceImpl implements UserService{ @Override public void login() { System.out.println("UserServiceImpl 调用 login"); } }
|
创建UserServiceProxy
代理类,代理类也实现了UserService
接口,所以它也有login
方法,在这个方法中我们调用真实的service
对象的login
方法并且,在调用前后就可以进行额外处理了!增强它!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class UserServiceProxy implements UserService { private UserService userService;
public UserServiceProxy(UserService realService) { this.userService = realService; }
@Override public void login() {
System.out.println("Proxy: Logging before login.");
userService.login();
System.out.println("Proxy: Logging after login."); } }
|
最后调用就可以看到增强效果了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static void main(String[] args) { UserServiceImpl realService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy(realService);
proxy.login(); }
Proxy: Logging before login. UserServiceImpl 调用 login Proxy: Logging after login.
|
小结
静态代理实现非常简单,但是最大的缺点就是需要为每个代理的类都创建一个代理类,并且编写相应的代码逻辑,如上面的UserServiceProxy
,以下是静态代理的各种缺点
- 编译时确定:静态代理在编译期间就已经确定了代理类和真实类的关系,因此无法在运行时动态改变代理对象的行为。
- 增加了代码量:每增加一个代理类,都需要编写相应的代码来实现代理逻辑,这会增加代码量和维护成本。
- 不够灵活:静态代理的每一个代理类只能代理一个真实主题类,如果要代理多个类的话,需要编写多个代理类,不够灵活。
- 耦合度高:代理类和真实主题类之间存在静态的耦合关系,一旦真实主题类的接口发生变化,代理类也需要相应地修改,导致耦合度较高。
- 无法实现横切关注点:静态代理需要在代理类中硬编码具体的增强逻辑,导致代理类和真实主题类之间的关注点混合,不利于代码的维护和扩展。
- 性能问题:由于每次调用都需要通过代理对象访问真实主题对象,可能会带来一定的性能损耗,尤其是在频繁调用的场景下。
动态代理
既然静态代理有那么多缺点,主流框架也都是用动态代理,我们来看看动态代理如何实现吧!
Java中一说到动态XX,基本就会用到反射的特性,这里我们使用了位于 java.lang.reflect
包中的InvocationHandler
接口,接口中定义了invoke
方法
1
| invoke(Object proxy, Method method, Object[] args)
|
具体来说,InvocationHandler
的 invoke
方法有三个参数:
proxy
:代理对象。
method
:被调用的方法。
args
:方法的参数。
invoke
方法可以根据需要对方法的调用进行拦截、修改参数、添加额外的逻辑等,然后再调用真实主题对象的方法,或者直接返回一个结果。
代码实现
UserService
和UserServiceImpl
不变,重新创建一个动态代理类DynamicProxyHandler
,它实现了InvocationHandler
中的invoke
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class DynamicProxyHandler implements InvocationHandler { private Object service;
public DynamicProxyHandler(Object realService) { this.service = realService; }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("DynamicProxyHandler: Logging before login.");
Object result = method.invoke(service, args);
System.out.println("DynamicProxyHandler: Logging after login.");
return result; } }
|
测试一下动态代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public static void main(String[] args) {
UserServiceImpl realService = new UserServiceImpl();
DynamicProxyHandler handler = new DynamicProxyHandler(realService);
UserService proxy = (UserService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), realService.getClass().getInterfaces(), handler ); proxy.login(); }
DynamicProxyHandler: Logging before login. UserServiceImpl 调用 login DynamicProxyHandler: Logging after login.
|
这时如果我们要给其他类代理,就可以在运行时,根据需要决定如何处理代理对象的方法调用,动态地添加额外的逻辑、修改参数等。
CGLib动态代理
前面说的是JDK动态代理,可以看到JDK动态代理只能代理接口,而CGLib动态代理弥补了这一缺点,可以直接代理类。
pom.xml文件引入CGLib库
1 2 3 4 5
| <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency>
|
创建CGLibDynamicProxy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class CGLibDynamicProxy implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("Before calling method: " + method.getName());
Object result = proxy.invokeSuper(obj, args);
System.out.println("After calling method: " + method.getName());
return result; } }
|
测试CGLib动态代理,可以看到这里没有出现UserService
接口,而是直接使用UserServiceImpl
类进行代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new CGLibDynamicProxy());
UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
proxy.login(); }
Before calling method: login UserServiceImpl 调用 login After calling method: login
|
总结
使用代理模式你就可以在代码中随心所欲的“切切切”,提高系统的灵活性和可扩展性,并且能够有效地解耦客户端与真实主题之间的关系。有了动态代理,在代码运行时,也可以实现对目标对象的控制和管理。