Java代理详解和应用场景

521 阅读8分钟

前言

代理是一种设计模式,我们使用代理对象来代替对真实对象(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 提供的声明式事务管理注解,用于在方法或类上声明数据库操作的事务边界,用于控制数据库操作要么都成功,要么都失败回滚。

  • 核心原理:
  1. Spring 在启动时扫描到 @Transactional
  2. Spring 生成该 Bean 的代理对象(JDK 动态代理或 CGLIB);
  3. 当你调用方法时,实际调用的是代理对象;
  4. 代理对象内部会调用 TransactionInterceptor
  5. 拦截器在方法执行前开启事务,执行后提交或回滚;
  6. 真实的数据库连接事务由底层 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 默认只在遇到 RuntimeExceptionError 时回滚;
      受检异常(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 帮我们自动完成了代理调用。

引用