1. 什么是代理模式?
代理模式说是一种“结构性设计模式” ,其核心思想是通过一个代理对象(proxy)间接访问目标对象(target),从而在不修改对象的前提下,实现对目标对象的控制或者功能增强。 代理模式的核心角色包括:
- 抽象角色: 定义目标对象和代理对象的共同接口(如java的接口)
- 真实角色:实现抽象接口的具体业务逻辑类
- 代理对象:持有真实角色的引用,并在其基础上拓展功能(如日志,事务等)
举个例子:
假设需要租房的时候,租客并不直接联系房东,而是联系中介。此时中介在房东与租客之间进行协调、签订合同等事务,房东只需要专注出租业务,而中介负责附加业务(如资质校验,合同管理等)
2. 为什么需要代理模式?
代理模式的本质是解耦与拓展,其价值体现在
- 功能增强:在不修改原有的代码前提下,为方法添加日志、权限校验、性能监控等功能,使得原有的方法专注于自己的功能实现,便于维护
- 访问控制:能够限制对敏感对象的直接访问,如数据库连接池管理
- 延迟加载:代理对象可延迟初始化高性能的真实开销(如大文件加载)
- 接口适配:为不兼容的接口提供统一访问权限
3. 代理模式的静态实现
静态代理在编译期显示定义代理类,需要手动编写代理对象和真实对象的关系(简单来说,就是需要我们自己去编写这个代理类,这个代理类在编译时期直接体现为我们写的一个类)
- 定义接口(定义房东要做的事情,同时也是中介要代理的事情)
public interface HouseSubject {
void rentHouse();
}
- 实现接口(真实的房东)
public class RealHouseSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东,我出租房子");
}
}
- 代理(中介,帮房东出租房子)
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("我是中介,结束代理");
}
}
此时,中介开始代理,代理对象开始代理到结束代理的阶段就可以为原有的方法拓展功能
- 租客租房
public class HouseStaticMain {
public static void main(String[] args) {
// 真实房东
HouseSubject subject = new RealHouseSubject();
// 中介
HouseProxy proxy = new HouseProxy(subject);
// 通过中介租房子
proxy.rentHouse();
}
}
// 执行结果:
// 我是中介,开始代理
// 我是房东,我出租房子
// 我是中介,结束代理
上述方式就是静态代理的实现
优点明显,就是简单直观,没有其他运行时的性能损耗
缺点就是,每新增一个真实对象,都需要一个代理类(假设工厂需要招人,也是找的中介,此时要需要重新写一个工厂代理),代码冗余且难以维护
4. 动态代理实现(JDK)
动态代理的原理就是,在运行时通过反射或字节码生成技术,自动创建代理类,无需手动编码(简单来说,就是我们上面的租房代理,工厂代理或者其他什么乱七八糟的代理,不需要我们去手动写了,而是动态加载到内存里面,我们直接使用即可)
动态代理的实现实际上有两种方式:
- JDK动态代理
- CGLIB动态代理
本文讲JDK动态代理
- 定义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是代理的核心逻辑
为什么要这么做呢??我们接下来继续看
- 创建得到代理对象,直接使用
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动态代理的源码解析
我们是通过这个方法得到的代理对象,从这个方法进去
接下来我们看一下这个方法具体在做什么
- 校验
InvocationHandler
非空 - 安全检查,验证调用者是否有权限访问接口和类加载器(这一步不懂可以跳过)
getProxyConstructor
,我们知道我们的最终结果是得到代理对象,那么想得到代理对象的前提是有代理类,这个方法就是将生成代理类的字节码文件,加载到jvm里面,返回构造方法,供我们实例化代理对象- 得到代理对象
接下来我们具体看一下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方法
这个方法有点长,我们分成多部分来看看到底做了什么
- 遍历所有的接口,若存在非公共接口,则强制代理类与其在同一包下(我觉得是为了能够访问到这个接口) 如果多个接口来自不同的包,抛出
IllegalArgumentException
异常,确保代理类能够访问到所有的接口方法 - 模块化支持,检查接口所在模块的导出状态。若接口来自未导出包(
!intf.getModule().isExported()
),标记nonExported
,后续生成包名时需特殊处理(如使用com.sun.proxy
前缀)
这一部分主要是生成代理类列名,包名确认后,生成类名的格式为$ProxyN
(如com.sun.proxy.$Proxy0,其中N是递增的唯一数字)
若模块未命名(如未模块化应用),包名默认使用PROXY_PACKAGE_PREFIX
(即com.sun.proxy
);若模块已命名且接口均公开,则包名与模块名一致.
(说句丢脸的话,实际上这里我也没搞懂**模块化,**但是似乎不影响我们接下来的理解…反正这一部分就是生成类名)
这一步就是生成字节码文件的关键了,也就是generateProxyClass
方法
- 调用
generateProxyClass
方法生成代理类字节码文件 - 通过
JLA.defineClass
将字节码加载到JVM.
那么我们就来看generateProxyClass
方法的实现(终于到这里了…)
实际上还是套了个壳,具体还是看里面的另一个方法
我们来看只主要实现:
- 添加Object类基础方法
addProxyMethod(hashCodeMethod);
addProxyMethod(equalsMethod);
addProxyMethod(toStringMethod);
- 收集接口方法
/*
* 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方法进行代理
- 校验方法返回类型
/*
* 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);
}
- 生成构造函数
generateConstructor();
这一步非常重要!!! 我们必须来看里面的实现细节
最主要的就是这个字段MJLR_INVOCATIONHANDLER,
具体的值是private static final String MJLR_INVOCATIONHANDLER = "(Ljava/lang/reflect/InvocationHandler;)V";
也就是说,构造函数接受一个InvocationHandler
参数。
这也就能够理解,为什么调用代理对象的方法,能够执行我们传入的InvocationHandler
实现类的invoke
方法了
也就是说,经过这一步后,我们代理类的构造函数就形如
public $Proxy0(InvocationHandler h) {
super(h);
}
- 代理方法字节码生成
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
}