1. 定义:代理对象和目标对象
1.1 目标对象(Target Object)
- 目标对象是指 被增强的原始对象,即需要通过 AOP 切面(Aspect)增强功能的业务对象(原始类)。
- 增强逻辑(Advice)最终是应用在目标对象的方法上的。
- 目标对象是实际的业务逻辑持有者,它的功能由我们开发时编写的类直接实现。
例子:
public class UserService {
public void createUser() {
System.out.println("Creating user...");
}
}
UserService
是目标对象,定义了业务逻辑。
1.2 代理对象(Proxy Object)
- 代理对象是 Spring AOP 框架生成的 动态代理类实例,它是目标对象的增强版本。
- 代理对象会包装目标对象,并在执行目标对象方法时:
- 增加切面中的增强逻辑(如日志、事务等)。
- 将调用转发到目标对象的方法。
- 代理对象在 Spring 应用上下文中取代了目标对象,用户通过代理对象间接访问目标对象的功能。
代理对象的特点:
- 它的外部表现与目标对象相同(方法签名一致)。
- 它在方法调用时,可以插入增强逻辑(Advice)。
例子:
如果为 UserService
添加一个切面增强功能(例如日志记录),Spring 会生成一个代理对象来拦截方法调用。
UserService proxy = (UserService) context.getBean(UserService.class);
proxy.createUser();
2. 两者的区别
特性 | 目标对象(Target Object) | 代理对象(Proxy Object) |
---|---|---|
定义 | 原始的、未增强的业务对象 | Spring AOP 框架生成的动态代理对象 |
是否真实存在 | 是真实存在的类实例(用户定义的类) | 是由 Spring 动态生成的新实例 |
增强逻辑(Advice) | 不包含增强逻辑 | 包含增强逻辑,方法调用可能被拦截 |
访问方式 | 只能通过代理对象间接访问 | 直接被容器返回,外部调用的方法实际上由代理处理 |
与 Spring 的关系 | 原始业务逻辑的实现者 | 通过 Spring AOP 动态生成的代理实现 |
实际工作 | 执行被调用的具体业务方法 | 拦截方法调用,并决定是否执行增强逻辑或目标对象 |
反射行为 | 目标对象的方法是用户定义的,直接反射可访问 | 代理对象的方法是动态生成的,可能有额外逻辑 |
3. Spring 中是如何生成代理对象的
Spring AOP 中的代理对象生成有两种方式,分别基于 JDK 动态代理 和 CGLIB 动态代理。
代理的选择由目标对象的类型决定:
3.1 JDK 动态代理
- 使用 Java 提供的动态代理机制(
java.lang.reflect.Proxy
)。 - 要求目标对象实现一个或多个接口。
- 代理对象是目标对象实现的接口的一个实现类。
优点:
- 轻量级,直接基于接口生成代理。
缺点:
- 目标对象必须实现接口,如果是纯类无法使用。
示例:
public interface UserService {
void createUser();
}
public class UserServiceImpl implements UserService {
@Override
public void createUser() {
System.out.println("Creating user...");
}
}
- Spring 会为
UserServiceImpl
生成一个动态代理对象,它实现了UserService
接口。
3.2 CGLIB 动态代理
- 使用 CGLIB(Code Generation Library)生成目标对象的子类作为代理对象。
- 不要求目标对象实现接口,可以代理普通的类。
- 代理对象是目标对象的子类,并通过方法重写(方法增强)来实现切面功能。
优点:
- 不需要目标对象实现接口,可以直接增强普通类。
缺点:
- 比 JDK 动态代理稍微重一些。
示例:
public class UserService {
public void createUser() {
System.out.println("Creating user...");
}
}
- Spring 会为
UserService
生成一个代理子类,并在方法上织入切面逻辑。
3.3 Spring 如何选择代理方式
Spring 默认通过 JDK 动态代理生成代理对象。如果目标对象没有实现接口,则自动切换为 CGLIB 动态代理。
可以通过以下配置强制使用 CGLIB 动态代理:
@EnableAspectJAutoProxy(proxyTargetClass = true)
4. 代理对象和目标对象的实际差异
在 Spring AOP 的运行时动态代理中,外部用户调用的其实是代理对象而非目标对象。以下是一些细节差异:
4.1 方法调用流程
未增强(目标对象直接调用):
UserService userService = new UserService();
userService.createUser();
方法调用的流程:
- 直接调用目标对象的
createUser()
方法。 - 输出:
Creating user...
增强(通过代理对象调用):
UserService proxy = (UserService) context.getBean(UserService.class);
proxy.createUser();
方法调用的流程:
- 调用代理对象的
createUser()
方法。 - 代理对象拦截方法调用。
- 代理对象决定是否执行切面增强逻辑(如前置通知、后置通知等)。
- 代理对象将调用转发到目标对象的
createUser()
方法。
4.2 是否可以直接访问目标对象
-
直接通过 Spring 容器获取的 Bean 是代理对象:
Spring 容器会将代理对象注册为 Bean,用户通过@Autowired
或getBean()
获取的 Bean 实际上是代理对象。 -
通过 AOP 上下文访问目标对象:
如果需要直接访问目标对象(绕过代理),可以通过 Spring 提供的AopContext
:UserService target = (UserService) AopContext.currentProxy();
4.3 代理对象与目标对象的相互关系
-
代理对象包含目标对象:
代理对象会将对业务方法的调用最终转发给目标对象。 -
目标对象不知道代理对象的存在:
目标对象的代码完全独立,不需要感知代理对象或 Spring 的存在。
5. 示例:代理对象和目标对象的工作过程
目标对象
public class UserService {
public void createUser() {
System.out.println("Creating user...");
}
}
切面
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.UserService.*(..))")
public void logBefore() {
System.out.println("Logging before method execution...");
}
}
运行时流程
- Spring 生成一个代理对象(通过 JDK 动态代理或 CGLIB)。
- 用户调用代理对象的
createUser()
方法。 - 代理对象拦截该调用,并触发切面逻辑(前置通知)。
- 代理对象将方法调用转发给目标对象。
- 目标对象执行原始业务逻辑。
6. 总结
代理对象 | 目标对象 |
---|---|
Spring 动态生成的增强版本。 | 开发者定义的原始业务逻辑类。 |
包含切面逻辑(通知)。 | 不包含切面逻辑,只有业务逻辑。 |
用户通过代理对象间接访问目标对象方法。 | 仅通过代理对象间接调用。 |
可以插入增强逻辑(如日志记录、事务)。 | 无法直接应用增强逻辑。 |