0%

AOP实现原理 代理模式

AOP实现原理 代理模式

AOP(Aspect-Oriented Programming)切面式编程之前已经说过了在Spring是如何进行使用它,今天来聊一聊它的实现原理。

GoF为面向对象编程提出了多种设计模式,其中有一种是代理模式,它是一种结构型设计模式,它允许在访问对象时引入一些额外的间接层,以控制对对象的访问。在代理模式中,代理对象充当了被访问对象的替代者,客户端通过代理对象间接地访问目标对象,从而实现了对目标对象的控制和管理,其实这种模式非常像现实生活中的中介、代理。

静态代理

现在我想要创建一个代理对象代理我的UserServicelogin方法,对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,以下是静态代理的各种缺点

  1. 编译时确定:静态代理在编译期间就已经确定了代理类和真实类的关系,因此无法在运行时动态改变代理对象的行为。
  2. 增加了代码量:每增加一个代理类,都需要编写相应的代码来实现代理逻辑,这会增加代码量和维护成本。
  3. 不够灵活:静态代理的每一个代理类只能代理一个真实主题类,如果要代理多个类的话,需要编写多个代理类,不够灵活。
  4. 耦合度高:代理类和真实主题类之间存在静态的耦合关系,一旦真实主题类的接口发生变化,代理类也需要相应地修改,导致耦合度较高。
  5. 无法实现横切关注点:静态代理需要在代理类中硬编码具体的增强逻辑,导致代理类和真实主题类之间的关注点混合,不利于代码的维护和扩展。
  6. 性能问题:由于每次调用都需要通过代理对象访问真实主题对象,可能会带来一定的性能损耗,尤其是在频繁调用的场景下。

动态代理

既然静态代理有那么多缺点,主流框架也都是用动态代理,我们来看看动态代理如何实现吧!

Java中一说到动态XX,基本就会用到反射的特性,这里我们使用了位于 java.lang.reflect 包中的InvocationHandler 接口,接口中定义了invoke方法

1
invoke(Object proxy, Method method, Object[] args) 

具体来说,InvocationHandlerinvoke 方法有三个参数:

  1. proxy:代理对象。
  2. method:被调用的方法。
  3. args:方法的参数。

invoke 方法可以根据需要对方法的调用进行拦截、修改参数、添加额外的逻辑等,然后再调用真实主题对象的方法,或者直接返回一个结果。

代码实现

UserServiceUserServiceImpl不变,重新创建一个动态代理类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 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

总结

使用代理模式你就可以在代码中随心所欲的“切切切”,提高系统的灵活性和可扩展性,并且能够有效地解耦客户端与真实主题之间的关系。有了动态代理,在代码运行时,也可以实现对目标对象的控制和管理。