前言

        作为 Java 开发者,代理模式绝对是绕不开的核心知识点 —— 它是 Spring AOP、事务管理、RPC 框架等底层实现的基石,也是面试高频考点。

        接下来我会从核心概念以及分类(静态代理、动态代理)讲解,带你深入了解代理是什么

一、代理模式是什么?

1.初识

        拿生活里的小例子来初步认识:

        买房子:不直接找房东,找房产中介(代理),中介帮你带看、谈价、签合同(增强功能),最终还是房东卖房(核心功能)

        买火车票:不直接去火车站,找票务代理点,代理点帮你买票,最终还是铁路系统售票

2.程序中的代理

        目标对象 / 被代理类:真正做核心业务的类

        代理对象 / 代理类:不做核心业务,只做增强操作(日志、事务、权限校验)

        接口:目标类和代理类共同实现的规范,保证两者方法一致

        代理模式就是在调用目标类中间创建一个代理类,直接调用变成间接调用,这样我们可以在代理类调用目标类之前和之后去添加一些预处理和后处理的操作,来扩展目标类没有的功能

3.核心作用

        无侵入式增强代码(不修改业务代码)

        控制对目标对象的访问

        解耦核心业务与通用功能(日志、事务)

4.分类

        分为静态代理、动态代理

二、静态代理:手写代理类

1.什么是静态代理

        就是手动编写代理类对象,编译后直接生成.class文件,并且代理类和目标类要实现同一个接口

2.优缺点

优点:简单易懂、适合固定业务

缺点冗余度极高,一个目标类就要写一个代理类

           编译前确定代理,运行时无法动态新增

           无法代理全部方法

           如果接口新增方法,所有实现类+代理类都必须重写该方法

3.静态代理代码示例

        就拿房东买房为例:

(1)定义公共接口

        目标类和代理类都要实现这个接口,保证方法统一

/**
 * 公共接口:租房接口
 */
public interface RentHouse {
    // 核心方法:租房
    void rent();
}

(2)定义目标类(房东)

        只做核心业务:出租自己的房子

/**
 * 目标类:房东(被代理对象)
 */
public class Landlord implements RentHouse {
    @Override
    public void rent() {
        System.out.println("房东:出租房子");
    }
}

(3)定义代理类(房产中介)

        包含目标对象,调用核心方法+增强方法

/**
 * 代理类:中介(代理对象)
 */
public class RentProxy implements RentHouse {
    // 持有目标对象(房东)
    private RentHouse target;

    // 构造方法传入目标对象
    public RentProxy(RentHouse target) {
        this.target = target;
    }

    @Override
    public void rent() {
        // 【增强操作:方法执行前】
        before();
        // 调用目标对象的核心方法
        target.rent();
        // 【增强操作:方法执行后】
        after();
    }

    // 前置增强:带看房屋
    private void before() {
        System.out.println("中介:带看房屋、讲解户型");
    }

    // 后置增强:签订合同
    private void after() {
        System.out.println("中介:签订租房合同、收取佣金");
    }
}

(4)测试

        测试代码:

public class StaticProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象(房东)
        RentHouse landlord = new Landlord();
        // 2. 创建代理对象,传入目标对象
        RentHouse proxy = new RentProxy(landlord);
        // 3. 调用代理对象的方法
        proxy.rent();
    }
}

        运行结果:

中介:带看房屋、讲解户型
房东:出租房子
中介:签订租房合同、收取佣金

4.致命缺点

        如果接口新增方法,所有实现类 + 代理类都必须重写该方法

        这会导致代码爆炸,维护成本极高,解决方案是使用动态代理

public interface RentHouse {
    void rent();
    // 新增方法
    void repairHouse();
}

三、动态代理:自动生成代理类

        动态代理的核心:程序运行时,自动生成代理类字节码,不需要手动编写 .java 文件

Java 中主流有两种动态代理:

  1. JDK 动态代理:基于接口实现(必须有接口)
  2. CGLIB 动态代理:基于继承实现(无接口也能用)

四、JDK动态代理:Java原生,基于接口

1.核心原理

  • 必须要求目标类实现接口
  • 通过 java.lang.reflect.Proxy 类自动生成代理对象
  • 通过 InvocationHandler 接口处理方法增强逻辑

2.核心API

  • Proxy.newProxyInstance(类加载器, 接口数组, 调用处理器):生成代理对象
  • InvocationHandler.invoke(Object proxy, Method method, Object[] args):所有代理方法都会走这里

3.JDK动态代理实例

        继续用上面的RentHouse接口和Landlord目标类

(1)编写通用调用处理器

        所有额外增加的逻辑都在这里面,一个处理器可以给多个目标类使用

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * JDK动态代理:通用调用处理器
 */
public class JdkProxyHandler implements InvocationHandler {
    // 目标对象
    private Object target;

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

    /**
     * @param proxy 代理对象
     * @param method 要执行的目标方法
     * @param args 方法参数
     * @return 方法返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 前置增强
        before();
        // 执行目标方法
        Object result = method.invoke(target, args);
        // 后置增强
        after();
        return result;
    }

    private void before() {
        System.out.println("JDK代理:前置增强(日志/权限)");
    }

    private void after() {
        System.out.println("JDK代理:后置增强(事务/统计)");
    }
}

(2)编写工具类(获取代理对象)

import java.lang.reflect.Proxy;

/**
 * JDK代理工具类
 */
public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),  // 目标类的类加载器
                target.getClass().getInterfaces(),   // 目标类实现的接口
                new JdkProxyHandler(target)         // 调用处理器
        );
    }
}

(3)测试

public class JdkProxyTest {
    public static void main(String[] args) {
        // 1. 创建目标对象
        RentHouse landlord = new Landlord();
        // 2. 获取JDK动态代理对象
        RentHouse proxy = (RentHouse) JdkProxyFactory.getProxy(landlord);
        // 3. 执行方法
        proxy.rent();
    }
}

运行结果

JDK代理:前置增强(日志/权限)
房东:出租房子
JDK代理:后置增强(事务/统计)

4.JDK动态代理优缺点

优点:

  • Java 原生,无需引入第三方依赖
  • 动态生成代理类,无代码冗余
  • 解耦通用功能与业务代码

缺点:

  • 必须实现接口,没有接口的类无法使用

五、CGLIB动态代理:基于继承、无接口也能用

1.为什么需要CGLIB?

        JDK 动态代理必须基于接口,如果一个类没有实现任何接口,就无法使用 JDK 代理,这时候就用 CGLIB

2. 核心原理

  • 基于继承实现:生成目标类的子类作为代理类
  • 重写目标类的非 final 方法,实现增强
  • 底层使用 ASM 字节码框架生成代理类

3.环境准备

CGLIB不是java原生,需要在配置文件里面引入依赖:

<!-- Maven依赖 -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

4.CGLIB动态代理实例

        我们创建一个没有接口的目标类

(1)创建目标类(无接口)

/**
 * 目标类:普通类,没有实现任何接口
 */
public class UserService {
    // 核心业务:添加用户
    public void addUser() {
        System.out.println("业务方法:添加用户成功");
    }
}

(2)编写CGLIB方法拦截器

        在该类里面添加额外的方法逻辑:

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * CGLIB代理:方法拦截器(相当于JDK的InvocationHandler)
 */
public class CglibProxyInterceptor implements MethodInterceptor {
    /**
     * 所有方法执行都会进入这里
     * @param o 代理对象
     * @param method 目标方法
     * @param objects 参数
     * @param methodProxy 方法代理
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前置增强
        before();
        // 执行目标方法(调用父类方法,因为代理类是子类)
        Object result = methodProxy.invokeSuper(o, objects);
        // 后置增强
        after();
        return result;
    }

    private void before() {
        System.out.println("CGLIB代理:前置日志");
    }

    private void after() {
        System.out.println("CGLIB代理:后置事务");
    }
}

(3)编写CGLIB代理工厂

import net.sf.cglib.proxy.Enhancer;

/**
 * CGLIB代理工厂
 */
public class CglibProxyFactory {
    public static Object getProxy(Class<?> targetClass) {
        // CGLIB核心类:增强器
        Enhancer enhancer = new Enhancer();
        // 设置父类(目标类)
        enhancer.setSuperclass(targetClass);
        // 设置方法拦截器
        enhancer.setCallback(new CglibProxyInterceptor());
        // 创建代理对象
        return enhancer.create();
    }
}

(4)测试

public class CglibProxyTest {
    public static void main(String[] args) {
        // 获取代理对象
        UserService proxy = (UserService) CglibProxyFactory.getProxy(UserService.class);
        // 执行方法
        proxy.addUser();
    }
}

运行结果:

CGLIB代理:前置日志
业务方法:添加用户成功
CGLIB代理:后置事务

5.注意事项

  • 不能代理 final 类(无法继承)
  • 不能代理 final 方法(无法重写)
  • Spring 5 之后默认使用 CGLIB 作为动态代理实现

六、静态代理 VS JDK动态代理 VS CGLIB动态代理

特性 静态代理 JDK 动态代理 CGLIB 动态代理
实现方式 手动编写代理类 基于接口反射 基于继承 + 字节码生成
代理时机 编译期生成 .class 文件 运行时生成代理类 运行时生成代理类
是否需要接口 必须实现接口 必须实现接口 不需要接口
执行效率 最高(无反射) 较高(反射调用) 较低(字节码生成)
依赖 无依赖 Java 原生,无依赖 需要引入 CGLIB 依赖
Spring 使用场景 极少使用 有接口时默认使用 无接口时使用

更多推荐