前言
代理是一种设计模式,我们使用代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
代理模式有静态代理和动态代理两种实现方式,而动态代理又分为JDK代理和CGLIB代理。
那么静态代理和动态代理的区别是什么呢?
区别就在于字节码生成的时机,静态代码是在编译时已经将接口,被代理类(委托类),代理类等确定下来,在程序运行前代理类的.class文件就已经存在了;而动态代理在程序运行后通过反射创建生成字节码再由 JVM 加载而成
在开始看代码之前,我们先理解这样几个概念:接口,接口实现类和代理类。
接口实现类是要被代理的类 ,如果不想接口实现类被直接访问,那么可以通过代理方式生成一个代理类,意思即代理类是通过静态代理或者动态代理生成的类。那么就会有一个问题,代理类与被代理的类会有什么关系呢?先说答案,兄弟关系或父子关系。
静态代理
静态代理其实就是指设计模式中的代理模式。从JVM角度,静态代理是在编译时就将相关类编译成class文件,然后运行时直接通过类加载器读取class文件,因为是先编译好的,所以这种方式就不如动态代理灵活,而且被代理的类需要实现接口。
静态代理代码实现:创建一个代理类实现接口,在使用时可以将其他的接口实现类的实例传给这个创建好的代理类,由代理类去访问接口实现类
public interface Subject {
public void request();
}
publicclass RealSubject implements Subject {
@Override
public void request() {
// 卖房
System.out.println("卖房");
}
}
publicclass Proxy implements Subject {
private RealSubject realSubject;
public Proxy(RealSubject subject) {
this.realSubject = subject;
}
@Override
public void request() {
// 执行代理逻辑
System.out.println("卖房前");
// 执行目标对象方法
realSubject.request();
// 执行代理逻辑
System.out.println("卖房后");
}
public static void main(String[] args) {
// 被代理对象
RealSubject subject = new RealSubject();
// 代理
Proxy proxy = new Proxy(subject);
// 代理请求
proxy.request();
}
}
动态代理
动态代理更加灵活,是在运行时动态生成类字节码,并加载到 JVM 中的。JDK代理要求被代理的类需要实现接口,CGLIB代理则没有这个限制。
JDK代理
1.OrderService接口:
public interface OrderService {
List<Order> findOrders();
}
2.OrderService接口实现类(被代理的类):
public class OrderServiceImpl implements OrderService{
@Override
public List<Order> findOrders() {
System.out.println("add 产品");
List<Order> orders = new ArrayList<>();
orders.add(new Order(111,"电子产品","2020-10-10",3000));
orders.add(new Order(222,"图书产品","2020-10-10",3000));
orders.add(new Order(111,"运动产品","2020-10-10",3000));
return orders;
}
}
3.代理生成:
public class JdkProxy<T> {
//目标对象
Object target;
public JdkProxy(Object target) {
this.target = target;
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始事务");
//调用目标方法
Object result = method.invoke(target,args);
System.out.println("关闭事务");
return result;
}
});
}
}
4.实际使用
public class TestProxy {
public static void main(String[] args) {
JdkProxy<OrderServiceImpl> jdkProxy = new JdkProxy<>(new OrderServiceImpl());
//获取代表类的对象
OrderService orderService = (OrderService)jdkProxy.getProxyInstance();
orderService.findOrders();
System.out.println("131313");
}
}
- Tips: 在上述代码中,如果将这句代码的OrderService换成用OrderServiceImpl来接收代理类,是会报错的
OrderService orderService = (OrderService)jdkProxy.getProxyInstance();
为何?因为生成的代理类与被代理的接口实现类OrderServiceImpl是兄弟关系(都实现了相同的接口),所以用OrderServiceImpl来接收一定会报错。
cglib代理
1.被代理的类不需要实现接口,此时OrderServiceImpl不实现OrderService
public class OrderServiceImpl {
@Override
public List<Order> findOrders() {
System.out.println("add 产品");
List<Order> orders = new ArrayList<>();
orders.add(new Order(111,"电子产品","2020-10-10",3000));
orders.add(new Order(222,"图书产品","2020-10-10",3000));
orders.add(new Order(111,"运动产品","2020-10-10",3000));
return orders;
}
}
2.引入pom文件,要使用CGLIB需要手动添加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
3.直接使用
public class TestProxy {
public static void main(String[] args) {
//增强类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderServiceImpl.class);
//拦截
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("前置通知");
//调用目标类的方法
Object result = methodProxy.invokeSuper(o, args);
System.out.println("后置通知");
return null;
}
});
//生成了代理类
OrderServiceImpl orderService = (OrderServiceImpl)enhancer.create();
orderService.findOrders();
System.out.println("测试。。。");
}
}
Enhancer是个增强类,使用匿名内部类实现MethodInterceptor,通过 Enhancer 类的 create()创建代理类
- Tips: 上述代码通过CGLIB生成的代理类与被代理的类OrderServiceImpl是父子关系,CGLIB内部使用了关键字extends,所以可以用OrderServiceImpl接收生成的代理类
代理总结
这篇文章代理的两种实现方式:静态代理和动态代理,并说明了这两种的区别,静态代理是在编译期事先编译好的,动态代理是在运行时编译。动态代理又有两种方式:JDK代理和CGLIB代理,JDK代理需要实现接口,CGLIB代理则没有这个限制,JDK代理中被代理类与代理类是兄弟关系,CGLIB代理中被代理类与代理类是父子关系。
说下Spring AOP使用哪种代理的情景:
1.如果目标对象实现了接口,默认情况下会采用JDK的动态代理来实现AOP
2.如果目标对象实现了接口,也可以强制使用CGLIB来实现AOP
3.如果目标对象没有实现接口,必须采用Cglib代理来实现AOP,Spring会自动在JDK和Cglib之间切换
应用场景之@Transactional注解
@Transactional 是 Spring 提供的声明式事务管理注解,用于在方法或类上声明数据库操作的事务边界,用于控制数据库操作要么都成功,要么都失败回滚。
- 核心原理:
- Spring 在启动时扫描到
@Transactional;- Spring 生成该 Bean 的代理对象(JDK 动态代理或 CGLIB);
- 当你调用方法时,实际调用的是代理对象;
- 代理对象内部会调用
TransactionInterceptor;- 拦截器在方法执行前开启事务,执行后提交或回滚;
- 真实的数据库连接事务由底层
DataSourceTransactionManager或其他事务管理器控制。
-
事务传播机制 除了使用代理之外,@Transactional的事务传播机制是通过ThreadLocal 实现的,当一个带
@Transactional的方法执行时,Spring 会在当前线程的 ThreadLocal 中绑定事务资源(如数据库连接)。后续调用其他事务方法时,会通过 ThreadLocal 判断当前线程是否已有事务:- 如果有,根据传播行为(如 REQUIRED、REQUIRES_NEW 等)决定复用、挂起或新建事务;
- 如果没有,则新建事务并绑定。
事务结束后会清理 ThreadLocal,防止资源泄漏
-
Spring中七种Propagation类的事务属性
| 值 | 说明 |
|---|---|
| NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
| NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
| NESTED | 支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务 |
| REQUIRED | 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择 默认的传播行为 |
| SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行 |
| MANDATORY | 支持当前事务,如果当前没有事务,就抛出异常 |
| REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起 |
-
@Transactional常见失效场景-
@Transactional标注在非 public 方法上
Spring 默认使用 JDK 动态代理(基于接口), 只会拦截 public 方法调用。 即使使用 CGLIB 动态代理,也只有 public 方法能被拦截。 -
异常类型不触发回滚
Spring 默认只在遇到 RuntimeException 或 Error 时回滚;
对 受检异常(Checked Exception) 不会自动回滚。可以通过显式指定异常类型解决 -
事务被异步线程破坏
Spring 的事务是基于 ThreadLocal 绑定数据库连接的。
异步线程执行时,ThreadLocal 中已经没有事务上下文。 -
方法嵌套调用
代理对象的拦截器在代理类层生效,没有经过代理 -
事务方法调用发生在非 Spring 管理的对象中 如果类不是 Spring 管理的 Bean,Spring 不会为它织入代理逻辑
-
@Transactional注解在接口上但使用 CGLIB 代理
-
应用场景之MyBatis Mapper接口
在 MyBatis 中,我们通常只写接口,不写实现类:
@Mapper
public interface UserMapper {
User selectById(Long id);
}
然后在 XML 或注解中写 SQL:
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
它就能自动执行 SQL 并返回结果
核心原理是因为 MyBatis 在运行时通过 JDK 动态代理机制 为 Mapper 接口生成了代理对象。
当我们调用接口方法时,代理对象会拦截方法调用,根据接口的全限定名 + 方法名(即 namespace.id)去查找对应的 SQL 映射(在 XML 或注解中),然后通过 SqlSession 执行 SQL 并返回结果。 整个过程不需要手写实现类,MyBatis 帮我们自动完成了代理调用。