静态代理、动态代理、AOP随笔记

1. 静态代理:基础但“僵化”的起点

静态代理是最直观的实现方式,它要求我们为每一个需要被代理的业务类,都手动编写一个对应的代理类。

  • 工作方式:代理类与业务类实现同一个接口,并在内部持有业务类实例的引用。这样,代理类就可以在调用实际业务方法前后,执行我们想要附加的操作。

代码示例

1
2
3
4
// 文件: UserService.java
public interface UserService {
void saveUser();
}
1
2
3
4
5
6
7
// 文件: UserServiceImpl.java
public class UserServiceImpl implements UserService {
@Override
public void saveUser() {
System.out.println("核心逻辑:保存用户。");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 文件: UserServiceStaticProxy.java
public class UserServiceStaticProxy implements UserService {
private UserService target;

public UserServiceStaticProxy(UserService target) {
this.target = target;
}

@Override
public void saveUser() {
System.out.println("日志:saveUser 方法开始执行...");
target.saveUser();
System.out.println("日志:saveUser 方法执行完毕...");
}
}

静态代理的“窘境”

初看起来,这种方式简单明了。但当系统规模扩大时,其固有的僵化和笨拙便暴露无遗。想象一下,我们的系统不仅有UserService,还有OrderServiceProductService等数十个服务,并且每一个服务的所有方法都需要添加日志

这意味着,我们必须手动创建OrderServiceStaticProxyProductServiceStaticProxy……为每一个服务都编写一个结构几乎完全相同的代理类。这不仅是巨大的、重复的体力劳动,更是一场维护的噩梦,让静态代理在规模化应用面前显得捉襟见肘

2. 动态代理:应对重复劳动的自动化方案

为了解决静态代理的僵化和重复劳动问题,Java提供了动态代理技术。它不再需要我们手动编写代理类,而是在程序运行时,根据我们的需要自动在内存中生成。

  • 核心思想:我们只需编写一个通用的调用处理器(InvocationHandler),在其中定义好普适的增强逻辑(例如日志记录)。然后,动态代理机制就可以利用这个处理器,为任何接口的实现类创建代理对象。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 文件: LogInvocationHandler.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LogInvocationHandler implements InvocationHandler {
private Object target;

public LogInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志:" + method.getName() + " 方法开始执行...");
Object result = method.invoke(target, args);
System.out.println("日志:" + method.getName() + " 方法执行完毕...");
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// 文件: ProxyFactory.java
import java.lang.reflect.Proxy;

public class ProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
}
}

现在,无论是UserService还是OrderService,我们都可以使用同一个ProxyFactory来创建它们的日志代理,彻底摆脱了手动编写大量代理类的困境。动态代理以其自动化和通用性,完美地解决了静态代理的痛点。

3. AOP:日志场景下的更优选择

动态代理虽然强大,但我们仍然需要编写InvocationHandler,并处理反射API,这依然属于编程式的范畴。对于日志这种无处不在的横切关注点,业界发展出了一种更高级、更优雅的AOP(面向切面编程)思想。

  • 核心思想:AOP让我们从“如何实现代理”的细节中解脱出来,转而以声明式的方式来描述“做什么”和“在哪里做”。我们只需定义好切面(Aspect),AOP框架(如Spring AOP)就会在底层自动利用动态代理来完成所有工作。

代码示例(Spring AOP 风格)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 文件: LoggingAspect.java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}

@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("日志:" + joinPoint.getSignature().getName() + " 方法开始执行...");
}

@After("serviceLayer()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("日志:" + joinPoint.getSignature().getName() + " 方法执行完毕...");
}
}

通过AOP,我们甚至不需要知道UserServiceImpl的存在,只需一个切面类,就能为整个service层提供日志功能。代码完全解耦,业务逻辑中没有任何代理或日志的痕迹。因此,对于日志、事务这类场景,AOP是比直接使用动态代理更优越、更专业的选择。

4. 动态代理的独有场景:RPC

既然AOP在日志等场景下表现更佳,那么动态代理是否还有其不可替代的用武之地?答案是肯定的。这个经典场景就是RPC(远程过程调用)框架的客户端

AOP的核心前提是 “增强”一个已经存在的本地对象。Spring AOP需要先在容器里找到一个目标Bean(如UserServiceImpl的实例),然后才能为它创建代理。

但在RPC客户端场景中,我们面临一个根本性的不同:我们只有接口,没有任何本地实现类OrderService的实现远在服务器上,客户端的JVM中并不存在可供AOP增强的目标。

这正是动态代理大显身手的地方。动态代理最强大的能力之一,就是可以根据接口凭空创造一个实现对象

代码示例(伪代码)

1
2
3
4
// 文件: OrderService.java (客户端仅有此接口)
public interface OrderService {
String findOrderById(String id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 文件: RpcClientFactory.java
import java.lang.reflect.Proxy;

public class RpcClientFactory {
public static <T> T getRemoteService(Class<T> serviceInterface) {
// 创建一个调用处理器,其核心逻辑是网络通信
InvocationHandler handler = (proxy, method, args) -> {
System.out.println("RPC代理:拦截到调用 " + method.getName());
// 1. 将方法调用信息打包成网络请求
// 2. 通过网络发送给服务器
// 3. 接收并解析服务器的响应
return "从远程服务器获取到的订单信息"; // 模拟返回结果
};

// 根据接口,凭空创建一个代理对象,作为远程服务的本地“代言人”
return (T) Proxy.newProxyInstance(
serviceInterface.getClassLoader(),
new Class<?>[]{serviceInterface},
handler
);
}
}

在这里,动态代理的作用不是“增强”,而是“创造”和“伪装”。它创造了一个OrderService的本地实例,并伪装成真正的实现,而其内部则是在勤勤恳恳地进行网络数据收发。这是AOP的设计模式无法覆盖的领域,也是动态代理不可或缺的独特价值所在。