Spring 代理对象详解

1. 什么是代理对象?

代理对象是指一个对象,它代表了另一个对象,并对外提供相同的接口。代理对象可以控制对目标对象(也叫做原始对象、被代理对象)的访问,通常用于:

  • 在方法调用前后插入某些额外操作(如日志、权限验证、事务管理等)
  • 延迟对象的创建(如懒加载)
  • 控制访问(如缓存)

在 Spring 中,代理对象主要通过 动态代理 实现,通常是通过 JDK 动态代理或者 CGLIB 代理技术。

2. Spring 中的代理类型

Spring 通过两种方式来实现代理:

  1. JDK 动态代理:基于接口的代理,只能代理接口实现类。它会在运行时通过反射机制动态生成一个实现了接口的代理类,并把对接口方法的调用转发到目标对象。
  2. CGLIB 代理:基于子类的代理,可以代理没有实现接口的类。它通过生成目标类的子类,并重写目标类的方法来实现代理。

2.1 JDK 动态代理

JDK 动态代理的核心是 java.lang.reflect.Proxy 类,它创建一个接口的实现类,且所有的方法调用都会被代理对象捕获,并转发给 InvocationHandler 进行处理。Spring 使用 JDK 动态代理时,目标对象必须实现接口。

示例:JDK 动态代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface MathService {
int add(int a, int b);
int subtract(int a, int b);
}

public class MathServiceImpl implements MathService {
@Override
public int add(int a, int b) {
return a + b;
}

@Override
public int subtract(int a, int b) {
return a - b;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class LoggingHandler implements InvocationHandler {
private final Object target;

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

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用方法: " + method.getName());
return method.invoke(target, args); // 调用目标方法
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
public static void main(String[] args) {
MathService target = new MathServiceImpl();

// 创建代理对象
MathService proxy = (MathService) Proxy.newProxyInstance(
MathService.class.getClassLoader(),
new Class[]{MathService.class},
new LoggingHandler(target)
);

// 调用代理对象的方法
int result = proxy.add(3, 5); // 代理对象会在调用前输出日志
System.out.println("结果: " + result);
}
}

输出:

1
2
调用方法: add
结果: 8

在这个例子中,MathServiceImpl 是目标对象,LoggingHandler 是 InvocationHandler,Proxy.newProxyInstance 用于生成代理对象。

2.2 CGLIB 代理

CGLIB(Code Generation Library)是一个开源的字节码增强库,它可以在运行时动态生成一个目标类的子类,并重写目标类的方法,从而实现代理。

CGLIB 代理不需要目标类实现接口,因此它可以用于没有接口的类。CGLIB 代理的实现是通过继承的方式创建代理类,并重写父类的方法来插入增强逻辑。

Spring 会使用 CGLIB 代理的方式来生成目标类的子类,通常这种代理方式适用于没有实现接口的类。

示例:CGLIB 代理
1
2
3
4
5
6
7
8
9
public class MathService {
public int add(int a, int b) {
return a + b;
}

public int subtract(int a, int b) {
return a - b;
}
}
1
2
3
4
5
6
7
public class LoggingInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("调用方法: " + invocation.getMethod().getName());
return invocation.proceed(); // 调用目标方法
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
MathService target = new MathService();

// 创建代理对象
ProxyFactory proxyFactory = new ProxyFactory(target);
proxyFactory.addAdvice(new LoggingInterceptor());

MathService proxy = (MathService) proxyFactory.getProxy();

// 调用代理对象的方法
int result = proxy.add(3, 5); // 代理对象会在调用前输出日志
System.out.println("结果: " + result);
}
}

在这个例子中,MathService 是目标对象,LoggingInterceptor 是增强逻辑,ProxyFactory 用于创建代理对象。

输出:

1
2
调用方法: add
结果: 8

3. Spring AOP 中的代理对象

在 Spring AOP 中,代理对象的使用是通过 切面(Aspect)来实现的。Spring AOP 默认使用 JDK 动态代理,如果目标对象实现了接口,它将使用 JDK 动态代理;如果目标对象没有实现接口,则会使用 CGLIB 代理。

3.1 JDK 动态代理 在 Spring AOP 中的应用

当你使用 @Aspect 注解来定义一个切面时,Spring 会自动创建一个代理对象,将切面逻辑应用到目标对象的相关方法上。如果目标对象实现了接口,Spring 会使用 JDK 动态代理。

示例:Spring AOP 与 JDK 动态代理
1
2
3
4
5
6
7
8
9
@Aspect
@Component
public class LoggingAspect {

@Before("execution(* com.example.MathService.add(..))")
public void logBefore() {
System.out.println("方法执行前记录日志");
}
}
1
2
3
4
5
6
7
8
9
10
11
public interface MathService {
int add(int a, int b);
}

@Service
public class MathServiceImpl implements MathService {
@Override
public int add(int a, int b) {
return a + b;
}
}
1
2
3
<!-- Spring 配置 -->
<context:component-scan base-package="com.example" />
<aop:aspectj-autoproxy />

在这个例子中,Spring 会创建 MathServiceImpl 的代理对象,当调用 add 方法时,会先执行 LoggingAspect 中的 logBefore() 方法。

3.2 CGLIB 代理 在 Spring AOP 中的应用

如果目标类没有实现接口,Spring 会使用 CGLIB 代理。CGLIB 代理会生成目标类的子类,并重写目标方法。使用 CGLIB 代理时,切面逻辑仍然会应用于目标方法。

示例:Spring AOP 与 CGLIB 代理
1
2
3
4
5
6
7
8
9
@Aspect
@Component
public class LoggingAspect {

@Before("execution(* com.example.MathService.add(..))")
public void logBefore() {
System.out.println("方法执行前记录日志");
}
}
1
2
3
4
5
6
@Service
public class MathService {
public int add(int a, int b) {
return a + b;
}
}
1
2
3
<!-- Spring 配置 -->
<context:component-scan base-package="com.example" />
<aop:aspectj-autoproxy proxy-target-class="true" />

在这个例子中,proxy-target-class="true" 表示强制使用 CGLIB 代理,即使目标类没有实现接口,Spring 也会使用 CGLIB 生成一个子类。

4. 总结

  • 代理对象:Spring 通过代理模式,使用 JDK 动态代理或 CGLIB 代理实现 AOP,允许你在目标方法前后执行额外的操作(如日志、事务等)。
  • JDK 动态代理:只能代理接口类型的目标对象,使用 java.lang.reflect.Proxy 类来创建代理。
  • CGLIB 代理:可以代理没有实现接口的类,使用 CGLIB 库通过继承目标类来实现代理。
  • Spring AOP:默认使用 JDK 动态代理,当目标对象实现接口时,使用 JDK 动态代理;否则,使用 CGLIB 代理。

代理对象是 Spring 实现 AOP 和其他功能(如事务管理、缓存、延迟加载等)的基础,使得代码更加松耦合,便于扩展和维护。