一、前言
代理,相信大多数人对这个名词并不陌生,从本质上说,代理并不是 Java 语言层面特有的特性,它更多的是一种思想,或者说一种行为模式,但笔者认为这也是 Java 核心基础 。因为代理的思想在 Java 的世界里非常重要,充分理解代理的思想,对于我们理解 Java 整个技术体系非常重要。
二、日常生活中的代理
在生活当中,代理的场景可以说是随处可见,各种中介公司、代理加盟品牌、便利店等等,从本质上来说,都算是一种代理。
以便利店为例,它并不直接生产各种商品,而是将各个工厂生产的商品聚集在一起,以方便消费者选择自己需要的商品。在这个过程中,工厂、便利店、消费者这三者之间的关系简化如下所示:
在这三者关系中,便利店就充当了工厂、消费者之间的代理这个角色,而也正是有了中间这个代理,工厂不需要将生产的大量商品逐个寻找需要的消费者销售,而当消费者需要购买多种物品时,也不再需要自己去寻找多家工厂一一购买,通过便利店这一中间代理角色的引入,极大地解放了工厂、消费者之间的供需关系。
通过便利店的例子可以发现,代理在某些场景下是可以给我们带来极大地便利的。虽然说消费者也可以直接找工厂购买所需商品,工厂也可以直接将生产的商品卖给消费者,但是这样的话,不管是对消费者还是工厂来说,都是一件效率低下且麻烦的事情,而有了便利店这一层代理,就充分解决了这些问题。日常生活中如此,在代码的世界里同样如此,毕竟,从某种意义上来说,程序代码是对生活的高度抽象。
Tips:
为了解释代理这种模式,笔者简化了示例,实际生活当中的工厂、便利店、消费者之间的关系远比笔者描述的要更加复杂。比方说,各工厂其实可能同时代理生产多个品牌的多种商品,而商品品牌方同时可能找多家工厂代理生产,便利店也有总店、分店、加盟店等相关层级代理。类似这样的,多个代理方,多层代理的场景,在代码的世界里,同样也是允许存在的,当我们面对这种情况的时候,不必过于慌张,只要逐步理清其间的关系 ,问题也就迎刃而解了。
三、静态代理
我们从一个例子说起。假如说,我们有一个照相机的对象,其有拍照片、美化照片、删除照片共 3 个方法,那么,我们设计接口和实现类时,通常如下所示:
照相机接口 ICamera.java
package com.xiaojiang.demo.proxy;
/**
* @Description : 照相机接口
* @Version : 0.0.1
* @Author : xiaojiang
* @Date : Created in 2020-11-13 22:07
*/
public interface ICamera {
/**
* 拍照片
*/
void takePhoto();
/**
* 美化照片
*/
void modifyPhoto();
/**
* 删除照片
*/
void deletePhoto();
}
复制代码
照相机接口实现类 CameraImpl.java
package com.xiaojiang.demo.proxy;
/**
* @Description : 照相机接口实现类
* @Version : 0.0.1
* @Author : xiaojiang
* @Date : Created in 2020-11-13 22:08
*/
public class CameraImpl implements ICamera {
/**
* 拍照片
*/
@Override
public void takePhoto() {
System.out.println("--拍照片--");
}
/**
* 美化照片
*/
@Override
public void modifyPhoto() {
System.out.println("--美化照片--");
}
/**
* 删除照片
*/
@Override
public void deletePhoto() {
System.out.println("--删除照片--");
}
}
复制代码
而当我们需要调用照相机ICamera
这个接口相关方法时,示例如下:
package com.xiaojiang.demo.proxy;
/**
* @Description : 主函数
* @Version : 0.0.1
* @Author : xiaojiang
* @Date : Created in 2020-11-13 21:56
*/
public class App {
public static void main(String[] args) {
// 直接创建实现类 调用
ICamera iCamera = new CameraImpl();
iCamera.takePhoto();
iCamera.modifyPhoto();
iCamera.deletePhoto();
}
}
复制代码
这样看起来挺好,面向接口设计,业务逻辑也清晰,没毛病。但是呢,实际需求通常是不断变化的,如果说有一天,我们需要在这几个业务方法中添加额外的业务逻辑,比如说,添加操作日志记录一下相关操作,那我们该怎么办呢?
简单的做法,当然是直接修改原有的实现方法,在原有代码中添加新的业务逻辑,修改后,示例如下:
/**
* 拍照片
*/
@Override
public void takePhoto() {
System.out.println("某年某月末日,拍照片,记录一下");
System.out.println("--拍照片--");
}
/**
* 美化照片
*/
@Override
public void modifyPhoto() {
System.out.println("某年某月末日,美化照片,记录一下");
System.out.println("--美化照片--");
}
/**
* 删除照片
*/
@Override
public void deletePhoto() {
System.out.println("某年某月末日,删除照片,记录一下");
System.out.println("--删除照片--");
}
复制代码
这样做的话,一般情况下当然是可以的,但是,思考一下,这样做有没有什么不足呢?
笔者此处简单列举几点:
需要修改原有业务逻辑代码;(修改原有业务逻辑易出错、风险高,且不一定允许直接修改,如项目底层 framework 包、第三方服务包等);
如果是通用操作的,通常修改量非常大,如日志、接口参数校验、权限校验等;
不便于后续修改扩展,如后续相关通用逻辑更改了,就又需要大量修改代码。
那么,有没有一种更好的方式,或者说更优雅的方式来实现类似场景的需求呢?
当然是有的,那就是代理。通过引入一个中间的代理角色,让代理角色去代理实现最终的业务逻辑,而我们就可以在代理角色中添加我们所需的业务逻辑。
还是以照相机这个场景为例,当我们需要添加操作日志时,我们不直接在原有代码逻辑中修改,而是先创建一个照相机实现类的代理对象,然后在代理对象中添加操作日志的逻辑,代理类示例如下:
照相机代理实现类ProxyCameraImpl.java
package com.xiaojiang.demo.proxy;
/**
* @Description : 照相机接口 代理实现类
* @Version : 0.0.1
* @Author : xiaojiang
* @Date : Created in 2020-11-13 22:18
*/
public class ProxyCameraImpl implements ICamera {
/**
* 在代理类内部定义 被代理实现的接口
*/
private ICamera iCamera;
/**
* 通过构造函数,在代理类内中传入实际被代理的对象
* @param iCamera
*/
public ProxyCameraImpl(ICamera iCamera) {
this.iCamera = iCamera;
}
/**
* 拍照片
*/
@Override
public void takePhoto() {
System.out.println("某年某月末日,拍照片,记录一下");
iCamera.takePhoto();
}
/**
* 美化照片
*/
@Override
public void modifyPhoto() {
System.out.println("某年某月末日,美化照片,记录一下");
iCamera.modifyPhoto();
}
/**
* 删除照片
*/
@Override
public void deletePhoto() {
System.out.println("某年某月末日,删除照片,记录一下");
iCamera.deletePhoto();
}
}
复制代码
创建代理对象后,我们在使用照相机接口时,示例如下:
package com.xiaojiang.demo.proxy;
/**
* @Description : 主函数
* @Version : 0.0.1
* @Author : xiaojiang
* @Date : Created in 2020-11-13 21:56
*/
public class App {
public static void main(String[] args) {
// 静态代理调用
ICamera iCamera = new ProxyCameraImpl(new CameraImpl());
iCamera.takePhoto();
iCamera.modifyPhoto();
iCamera.deletePhoto();
}
}
复制代码
通过示例代码可以看出,通过引入代理类ProxyCameraImpl
,我们在并没有修改原有实现类CameraImpl
的情况下,成功地添加了我们所需的操作日志业务代码。在使用照相机接口ICamera
的时候,我们不再直接实例化原CameraImpl
对象,而是实例化代理对象ProxyCameraImpl
,然后将原CameraImpl
对象实例传入代理对象ProxyCameraImpl
中,最终通过接口调用方法时,和原来并无任何差别。(有点绕,多看几遍代码)
像这种不修改原有代码逻辑,而是提供一个新的对象实现扩展原有对象的接口,从而实现对原有对象的间接控制与扩展,就是代理模式。需要注意的是,代理对象与被代理对象需要实现同一个接口,或继承同一个类。而之所以叫静态代理,是因为我们的代理对象ProxyCameraImpl
是我们自己手动编写的代码,而不是系统自动创建的。(注:该描述非官方严格定义,仅供参考理解)
四、动态代理
我们继续思考一下,在以上的例子中,我们手动为原实现类创建了一个对应代理类,从而实现对原实现类的代理。假如说原实现类不止三个方法,而是有十几个方法,那我们是否就要修改代理类中的十几个方法?再假如说,我们有多个类需要添加通用业务逻辑,比如说需要对多个类的多个方法进行耗时统计,那我们是不是就需要手动创建多个代理类,然后依次修改各个代理类的方法呢?
如果真的是这样的话,那实在是一件麻烦的事情。好在前辈大佬们已经想到了一种更好的方式,那就是动态代理,也就是在程序运行过程中,动态地创建代理对象。
JDK 为实现动态代理的机制,提供了java.lang.reflect.InvocationHandler
接口和java.lang.reflect.Proxy
类这两个核心基础,通过InvocationHandler
接口、Proxy
类的组合使用,我们就可以构建一个动态代理类。
我们还是以照相机场景为例,假如我们需要统计拍照片、美化照片、删除照片这 3 个接口调用的耗时,那么通过动态代理的方式,示例如下:
照相机动态代理实现类ProxyCameraImplHandler.java
package com.xiaojiang.demo.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @Description : 照相机接口 动态代理实现类
* @Version : 0.0.1
* @Author : xiaojiang
* @Date : Created in 2020-11-13 12:12
*/
public class ProxyCameraImplHandler implements InvocationHandler {
/**
* 被代理的对象
*/
private Object targetObject;
/**
* 构造函数 ( 通过构造函数将被代理的对象传入动态代理类中 )
* @param targetObject 被代理的对象
*/
public ProxyCameraImplHandler(Object targetObject) {
this.targetObject = targetObject;
}
/**
* 执行调用被代理对象的方法
* @param proxy 被代理对象的真实代理对象,即动态生成的代理对象;
* 执行invoke方法后,我们通常返回 '调用被代理对象方法的返回结果'【笔者示例就是】;
* 但是,我们也可直接返回真实代理对象,也就是proxy,
* 通过这个proxy对象,我们就可以直接调用真实对象的各个方法,从而灵活地满足具体需求。
* @param method 被代理的方法
* @param args 被代理的方法参数
* @return Object
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//TODO 这里就是我们可以添加自己所需业务逻辑的地方,如添加操作日志、调用方法执行耗时等
long start = System.currentTimeMillis();
Object result = method.invoke(targetObject,args);
long end = System.currentTimeMillis();
System.out.println("执行方法" + method.getName() + "调用耗时:" + (end - start) + "ms");
return result;
}
}
复制代码
创建动态代理对象后,我们在使用照相机接口时,示例如下:
package com.xiaojiang.demo.proxy;
/**
* @Description : 主函数
* @Version : 0.0.1
* @Author : xiaojiang
* @Date : Created in 2020-11-13 21:56
*/
public class App {
public static void main(String[] args) {
// 动态代理调用
InvocationHandler invocationHandler = new ProxyCameraImplHandler(new CameraImpl());
ICamera iCamera = (ICamera) Proxy.newProxyInstance(CameraImpl.class.getClassLoader(),CameraImpl.class.getInterfaces(),invocationHandler);
iCamera.takePhoto();
iCamera.modifyPhoto();
iCamera.deletePhoto();
}
}
复制代码
通过示例我们可以发现,笔者并没有手动地直接创建原CameraImpl
类的代理对象,而是借助于 InvocationHandler 接口、Proxy
类创建了动态代理对象ProxyCameraImplHandler
,通过动态代理创建代理对象实现对 CameraImpl 类的代理。
为帮助理解,笔者大致说一下 JDK 提供的 InvocationHandler 接口、Proxy
类。(此处描述仅笔者为帮助理解的描述,非官方定义,如有兴趣,读者可自行查看 JDK 源码中的javadoc定义描述)
1、java.lang.reflect.InvocationHandler
接口:
InvocationHandler
接口内只有一个invoke
方法,我们创建的动态代理类需要实现该接口,然后在invoke
方法中添加我们需要的业务逻辑,当用Proxy
类动态创建代理对象时,传入我们创建的动态代理类对象,从而当最终调用被代理的类的方法时,就会调用该invoke
方法。
2、java.lang.reflect.Proxy
类:
Proxy
类就是用来动态创建代理对象的类,其提供多个方法,比较常用的是newProxyInstance
方法,其接口参数说明如下:
/**
* @param loader 加载被代理类的classloader对象
* @param interfaces 被代理类的接口对象数组
* @param h InvocationHandler对象,即实现该接口的动态代理对象,最终将调用该对象中的invoke方法
* @return Object
* @throws IllegalArgumentException
*/
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,
InvocationHandler h) throws IllegalArgumentException
复制代码
以上就是一个基本的 JDK 动态代理示例,通过示例我们可以发现:通过动态代理的方式,代理对象的代码就相对固定了,在同一业务场景下,不需要随着相关业务类、方法的增加而创建相应的代理类;同时,我们也不再需要事先实例化代理对象,而是程序运行时动态判断被代理的类,从而动态创建代理对象,大大增加了灵活性,降低了代码耦合度。另外,通过动态代理,也就可以相对轻松地实现 AOP 编程。
当然,以上的动态代理示例,我们可以进一步优化代码结构,让接口调用时更加方便,优化后代码如下:
package com.xiaojiang.demo.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @Description : 照相机接口 动态代理实现类(优化版)
* @Version : 0.0.1
* @Author : xiaojiang
* @Date : Created in 2020-11-21 22:27
*/
public class ProxyCameraImplHandler implements InvocationHandler {
/**
* 被代理的对象
*/
private Object targetObject;
/**
* 执行调用被代理对象的方法
* @param proxy 被代理对象的真实代理对象,即动态生成的代理对象;
* 执行invoke方法后,我们通常返回 '调用被代理对象方法的返回结果'【笔者示例就是】;
* 但是,我们也可直接返回真实代理对象,也就是proxy,
* 通过这个proxy对象,我们就可以直接调用真实对象的各个方法,从而灵活地满足具体需求。
* @param method 被代理的方法
* @param args 被代理的方法参数
* @return Object
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//TODO 这里就是我们可以添加自己所需业务逻辑的地方,如添加操作日志、调用方法执行耗时等
long start = System.currentTimeMillis();
Object result = method.invoke(targetObject,args);
long end = System.currentTimeMillis();
System.out.println("执行方法" + method.getName() + "调用耗时:" + (end - start) + "ms");
return result;
}
/**
* 将被代理的对象传入,从而获得它的类加载器和实现接口,然后作为Proxy.newProxyInstance方法的参数。
* this参数即表示当前ProxyCameraImplHandler对象的实例
*/
public Object newProxyInstance(Object targetObject){
this.targetObject = targetObject;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(),this);
}
}
复制代码
优化后,调用示例如下:
package com.xiaojiang.demo.proxy;
/**
* @Description : 主函数
* @Version : 0.0.1
* @Author : xiaojiang
* @Date : Created in 2020-11-13 21:56
*/
public class App {
public static void main(String[] args) {
// 动态代理调用 ,进一步封装改进
ICamera iCamera = (ICamera) new ProxyCameraImplHandler().newProxyInstance(new CameraImpl());
iCamera.takePhoto();
iCamera.modifyPhoto();
iCamera.deletePhoto();
}
}
复制代码
通过优化后的代码可以发现,使用 JDK 的动态代理还是较为简单的,且功能强大。那么,思考一下,JDK 是如何实现动态代理的呢,它是如何通过动态判断实现类,从而动态创建对应代理对象的呢?
通过示例代码可知,动态代理的操作核心是Proxy.newProxyInstance(...)
方法,此处笔者贴一下其源代码(JDK1.8):
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
复制代码
通过源代码初步分析可以发现,newProxyInstance
方法内是通过 Java 的反射机制来动态创建代理对象的。当然,newProxyInstance
方法内还有许多分支方法调用,笔者此处就不再贴过多源代码了,有兴趣的同学可以去看一看。
不过,此处笔者需要特别指出的是,通过反射获取代理对象实例时,有一个优先从缓存中获取的操作,这也就意味着,JDK1.8 后的动态代理性能没有那么差。该操作源代码片段如下:
/**
* Generate a proxy class. Must call the checkProxyAccess method
* to perform permission checks before calling this.
*/
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
复制代码
至此,动态代理也就说完了。不过,为帮助理解 JDK 动态代理基本原理,尤其是调用InvocationHandler
接口的invoke
方法这个地方,笔者建议去了解一下反射相关的知识。
五、小结
1、Java 中的代理分为静态代理、动态代理;不管是静态代理还是动态代理,代理对象和被代理对象都需要实现同一个接口;
2、理解代理这种思维方式很重要,日常工作也许直接写动态代理代码的场景不多,但是,很多基础框架都是基于动态代理来实现相关功能的,如 Spring、Mybatis 等,理解代理有利于我们深入理解框架的实现原理;
4、通过动态代理,可以方便地扩展原有代码功能,也可以实现 AOP 编程;(Spring 的 IOC 与 AOP 两大特性的核心基础都是动态代理,不过 Spring 的动态代理和 JDK 的动态代理有所不同,在有些场景下它是利用 Cglib 来实现的)
评论 (2 条评论)