JDK动态代理理解

1. 什么是代理模式?

代理模式说是一种“结构性设计模式” ,其核心思想是通过一个代理对象(proxy)间接访问目标对象(target),从而在不修改对象的前提下,实现对目标对象的控制或者功能增强。 代理模式的核心角色包括:

  1. 抽象角色: 定义目标对象和代理对象的共同接口(如java的接口)
  2. 真实角色:实现抽象接口的具体业务逻辑类
  3. 代理对象:持有真实角色的引用,并在其基础上拓展功能(如日志,事务等)

举个例子:

假设需要租房的时候,租客并不直接联系房东,而是联系中介。此时中介在房东与租客之间进行协调、签订合同等事务,房东只需要专注出租业务,而中介负责附加业务(如资质校验,合同管理等)

2. 为什么需要代理模式?

代理模式的本质是解耦与拓展,其价值体现在

  1. 功能增强:在不修改原有的代码前提下,为方法添加日志、权限校验、性能监控等功能,使得原有的方法专注于自己的功能实现,便于维护
  2. 访问控制:能够限制对敏感对象的直接访问,如数据库连接池管理
  3. 延迟加载:代理对象可延迟初始化高性能的真实开销(如大文件加载)
  4. 接口适配:为不兼容的接口提供统一访问权限

3. 代理模式的静态实现

静态代理在编译期显示定义代理类,需要手动编写代理对象和真实对象的关系(简单来说,就是需要我们自己去编写这个代理类,这个代理类在编译时期直接体现为我们写的一个类)

  1. 定义接口(定义房东要做的事情,同时也是中介要代理的事情)
public interface HouseSubject {
    void rentHouse();
}
  1. 实现接口(真实的房东)
public class RealHouseSubject implements HouseSubject {
    @Override
    public void rentHouse() {
        System.out.println("我是房东,我出租房子");
    }
}
  1. 代理(中介,帮房东出租房子)
public class HouseProxy implements HouseSubject {
    private HouseSubject houseSubject;

    public HouseProxy (HouseSubject houseSubject) {
        this.houseSubject = houseSubject;
    }


    @Override
    public void rentHouse() {
        System.out.println("我是中介,开始代理");
        houseSubject.rentHouse();
        System.out.println("我是中介,结束代理");
    }
}

此时,中介开始代理,代理对象开始代理到结束代理的阶段就可以为原有的方法拓展功能

  1. 租客租房
public class HouseStaticMain {
    public static void main(String[] args) {

        // 真实房东
        HouseSubject subject = new RealHouseSubject();

        // 中介
        HouseProxy proxy = new HouseProxy(subject);

        // 通过中介租房子
        proxy.rentHouse();
    }
}

// 执行结果:
// 我是中介,开始代理
// 我是房东,我出租房子
// 我是中介,结束代理

上述方式就是静态代理的实现

优点明显,就是简单直观,没有其他运行时的性能损耗

缺点就是,每新增一个真实对象,都需要一个代理类(假设工厂需要招人,也是找的中介,此时要需要重新写一个工厂代理),代码冗余且难以维护

4. 动态代理实现(JDK)

动态代理的原理就是,在运行时通过反射或字节码生成技术,自动创建代理类,无需手动编码(简单来说,就是我们上面的租房代理,工厂代理或者其他什么乱七八糟的代理,不需要我们去手动写了,而是动态加载到内存里面,我们直接使用即可)

动态代理的实现实际上有两种方式:

  1. JDK动态代理
  2. CGLIB动态代理

本文讲JDK动态代理

  1. 定义jdk动态代理类
public class JSKInvocationHandler implements InvocationHandler {

    private  Object target; // 被代理的对象

    public JSKInvocationHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是动态生成的中介,开始代理");
        Object retVal = method.invoke(target,args);
        System.out.println("我是动态生成的中介,结束代理");
        return retVal;
    }
}

这里可能就会有疑问了,这个不就是代理类吗?? 为什么还说是动态代理?

实际上,这个并不是代理类,而是代理逻辑的执行者

也就是说,代理类是目标对象的替身,它对外暴露了真实对象的接口,而InvocationHandler是代理的核心逻辑

为什么要这么做呢??我们接下来继续看

  1. 创建得到代理对象,直接使用
public class DynamicMain {
    public static void main(String[] args) {
        HouseSubject target = new RealHouseSubject();

        // 动态生成一个代理类
        HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
            target.getClass().getClassLoader(), new Class[]{HouseSubject.class},new JSKInvocationHandler(target)
        );
        proxy.rentHouse();
    }
} 
// 执行结果:
// 我是动态生成的中介,开始代理
// 我是房东,我出租房子
// 我是动态生成的中介,结束代理

此时就会有很大的一点疑问,为什么得到的HouseSubject对象,调用rentHouse,执行的是JSKInvocationHandler的invoke方法??

接下来我们看一下源码

5. JDK动态代理的源码解析

我们是通过这个方法得到的代理对象,从这个方法进去

接下来我们看一下这个方法具体在做什么

  1. 校验InvocationHandler非空
  2. 安全检查,验证调用者是否有权限访问接口和类加载器(这一步不懂可以跳过)
  3. getProxyConstructor,我们知道我们的最终结果是得到代理对象,那么想得到代理对象的前提是有代理类,这个方法就是将生成代理类的字节码文件,加载到jvm里面,返回构造方法,供我们实例化代理对象
  4. 得到代理对象

接下来我们具体看一下getProxyConstructor方法的实现

判断是不是单接口,也就是代理一个接口还是多个接口,对应不同的处理方法(具体结果是提升了查询效率,节省了资源)但是最后还是通过build来生成字节码文件

我们的重心就落在build方法上面

@SuppressWarnings("removal")
Constructor<?> build() {
    Class<?> proxyClass = defineProxyClass(module, interfaces);
    assert !module.isNamed() || module.isOpen(proxyClass.getPackageName(), Proxy.class.getModule());

    final Constructor<?> cons;
    try {
        cons = proxyClass.getConstructor(constructorParams);
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            cons.setAccessible(true);
            return null;
        }
    });
    return cons;
}

这个方法一上来似乎就实现了我们想要达成的目的,就是执行了defineProxyClass方法

这个方法有点长,我们分成多部分来看看到底做了什么

  1. 遍历所有的接口,若存在非公共接口,则强制代理类与其在同一包下(我觉得是为了能够访问到这个接口) 如果多个接口来自不同的包,抛出IllegalArgumentException异常,确保代理类能够访问到所有的接口方法
  2. 模块化支持,检查接口所在模块的导出状态。若接口来自未导出包(!intf.getModule().isExported()),标记nonExported,后续生成包名时需特殊处理(如使用com.sun.proxy前缀)

这一部分主要是生成代理类列名,包名确认后,生成类名的格式为$ProxyN(如com.sun.proxy.$Proxy0,其中N是递增的唯一数字)

若模块未命名(如未模块化应用),包名默认使用PROXY_PACKAGE_PREFIX(即com.sun.proxy);若模块已命名且接口均公开,则包名与模块名一致.

(说句丢脸的话,实际上这里我也没搞懂**模块化,**但是似乎不影响我们接下来的理解…反正这一部分就是生成类名)


这一步就是生成字节码文件的关键了,也就是generateProxyClass方法

  1. 调用generateProxyClass方法生成代理类字节码文件
  2. 通过JLA.defineClass将字节码加载到JVM.

那么我们就来看generateProxyClass方法的实现(终于到这里了…)

实际上还是套了个壳,具体还是看里面的另一个方法

我们来看只主要实现:

  1. 添加Object类基础方法
addProxyMethod(hashCodeMethod);
addProxyMethod(equalsMethod);
addProxyMethod(toStringMethod);
  1. 收集接口方法
/*
 * Accumulate all of the methods from the proxy interfaces.
 */
for (Class<?> intf : interfaces) {
    for (Method m : intf.getMethods()) {
        if (!Modifier.isStatic(m.getModifiers())) {
            addProxyMethod(m, intf);
        }
    }
}

遍历所有接口,收集需要代理的非静态方法,为每个接口方法生成对应的代理方法,以便在调用时候转发给处理器的invoke方法进行代理

  1. 校验方法返回类型
/*
 * For each set of proxy methods with the same signature,
 * verify that the methods' return types are compatible.
 */
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
    checkReturnTypes(sigmethods);
}
  1. 生成构造函数
 generateConstructor();

这一步非常重要!!! 我们必须来看里面的实现细节

最主要的就是这个字段MJLR_INVOCATIONHANDLER,具体的值是private static final String MJLR_INVOCATIONHANDLER = "(Ljava/lang/reflect/InvocationHandler;)V";也就是说,构造函数接受一个InvocationHandler参数。

这也就能够理解,为什么调用代理对象的方法,能够执行我们传入的InvocationHandler实现类的invoke方法了

也就是说,经过这一步后,我们代理类的构造函数就形如

public $Proxy0(InvocationHandler h) {
    super(h);
}
  1. 代理方法字节码生成
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
    for (ProxyMethod pm : sigmethods) {
        // add static field for the Method object
        visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, pm.methodFieldName,
                   LJLR_METHOD, null, null);

        // Generate code for proxy method
        pm.generateMethod(this, className);
    }
}

生成代理方法体,硬编码调用InvocationHandler.invoke()

到此,代理类的字节码文件就生成了,同时返回了代理类的构造方法

那我们回到最开始的地方:

拿到代理对象的构造方法之后,接下来当然就是根据构造函数去实例化对象,我们前面说过,生成的构造方法是一个只有InvocationHandler参数的方法,此时我们传入的InvocationHandler h参数就是用来实例化这个代理对象的参数

从而得到一个代理对象


6. 证明上述逻辑

public static void main(String[] args) throws NoSuchMethodException {
    // 得到代理类
    Class<?> proxyClass = Proxy.getProxyClass(RealHouseSubject.class.getClassLoader(), RealHouseSubject.class.getInterfaces());
    // 代理类的名字
    System.out.println(proxyClass.getName());

    //代理对象的构造方法
    Constructor<?>[] constructors = proxyClass.getConstructors();
    for(Constructor<?> c : constructors) {
        System.out.println(c.toString());
    }
    // 代理类中的方法
    Method[] declaredMethods = proxyClass.getDeclaredMethods();
    for(Method m : declaredMethods) {
        System.out.println(m.getDeclaringClass() + " " + m.getName());
    }
    //jdk.proxy1.$Proxy0
    //public jdk.proxy1.$Proxy0(java.lang.reflect.InvocationHandler)
    //class jdk.proxy1.$Proxy0 equals
    //class jdk.proxy1.$Proxy0 toString
    //class jdk.proxy1.$Proxy0 hashCode
    //class jdk.proxy1.$Proxy0 proxyClassLookup
    //class jdk.proxy1.$Proxy0 rentHouse
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值