JAVA基础之动态代理
1.概述
代理模式是结构型设计模式的一种(23种设计模式),它是底层框架使用较多的一种设计模式(Spring、Mybatis等)。代理模式指的是在某些场景下,对象会找一个代理对象,来辅助自己完成一些工作,这些工作是对原对象行为的控制或增强。举个简单的例子:有一个明星类(Star),日常的主要工作是演戏和唱歌,而她需要一个经济人(StarManager)来帮她代理一些事情,比如演戏和唱歌前的预约、结束后的收费等等。这里就体现了代理模式的简单优势,实现隐藏委托类,同时解耦客户类与委托类,在不修改委托类的基础上,修改客户类来增强功能。本文将基于JAVA语言特点,来分析JAVA语言如何实现代理(静态和动态)。
2.概念及实现方式
2.1 JAVA代理模式
JAVA代理模式如上图所示:
StarAction(抽象主题角色):定义代理类和真实主题对外的公共方法,也是代理类代理真实主题的方法,这里是sing()和acting()方法;
Star(真实主题角色):真实实现业务逻辑的类,这里是sing()和acting()方法的主要实现逻辑;
StarManager(代理主题角色):代理和封装真实主题的类,这里是对sing()和acting()方法的增强。
JAVA中根据字节码的创建时机将代理分为静态代理和动态代理,区别如下:
- 静态代理:在程序运行之前就已经存在于代理类的字节码文件,代理类和真实主题角色类的关系在运行前就已经确认了;
- 动态代理:在程序运行期间由JVM根据反射等机制动态生成,所以在运行前不存在代理类的字节码文件。
2.2 静态代理
根据上述案例,编写如下代码:
2.2.1 StarAction
public interface StarAction {
void sing();
void acting();
}
2.2.2 Star
@Data
public class Star implements StarAction {
private String name;
private Integer age;
@Override
public void sing() {
System.out.println(name + "开始唱歌并热舞!");
System.out.println(name + "正在激情唱歌并热舞!");
System.out.println(name + "结束唱歌并热舞!");
}
@Override
public void acting() {
System.out.println(name + "开始演戏!");
System.out.println(name + "正在激情演戏!");
System.out.println(name + "结束演戏!");
}
}
2.2.3 StarManager
@Data
public class StarManager implements StarAction {
private Star star;
public StarManager(Star star) {
this.star = star;
}
@Override
public void sing() {
System.out.println("与投资方签约!");
System.out.println("收唱歌定金!");
//安排明星唱歌
star.sing();
System.out.println("收唱歌尾款!");
}
@Override
public void acting() {
System.out.println("与投资方签约!");
System.out.println("收演戏定金!");
//安排明星唱歌
star.acting();
System.out.println("收演戏尾款!");
}
}
测试类代码如下:
public class Test {
public static void main(String[] args) {
Star star = new Star();
star.setName("刘德华");
star.setAge(60);
StarManager starManager = new StarManager(star);
starManager.sing();
}
}
运行结果如下:
从上述代码可知,StarManager类代理了委托类Star类的sing()和acting()方法,在不修改委托类代码的情况下做了一些额外的处理,静态代理的局限在于运行前必须编写好代理类。如果在上述功能前提下,代理类还需要在明星唱歌和跳舞前将明星送过去,等表演结束再接回来,则需要修改代理类如下:
@Data
public class StarManager implements StarAction {
private Star star;
public StarManager(Star star) {
this.star = star;
}
@Override
public void sing() {
System.out.println("与投资方签约!");
System.out.println("收唱歌定金!");
//送明星去唱歌
before();
//安排明星唱歌
star.sing();
after();
System.out.println("收唱歌尾款!");
}
@Override
public void acting() {
System.out.println("与投资方签约!");
System.out.println("收演戏定金!");
//送明星去唱歌
before();
//安排明星演戏
star.acting();
after();
System.out.println("收演戏尾款!");
}
private void before() {
System.out.println("送明星" + star.getName() + "去唱歌!");
}
private void after() {
System.out.println("演出结束,接明星" + star.getName() + "回来!");
}
}
很明显在为代理对象添加一些共性行为时,必须显式修改代理类中对应的代理方法,这样做的缺点在于工作量大,而且不够灵活。如果需要统一控制多种对象的行为或共性行为,应该使用动态代理。
2.3 动态代理
动态代理是在程序运行期间由JVM根据反射等机制动态生成,这种情况下的代理类并不是一开始就在JAVA代码中定义的,而是运行时生成。相比于静态代理,动态代理的优势在于很方便地对代理类的方法进行统一处理,而不用修改每个代理类的函数。由2.2.3节可知,若对象中包含更多的行为(比如明星代言、跳舞等其他方法),这时候在每个方法内都要添加before()和after()方法,编写了大量冗余代码。为了避免这种情况出现,可以使用动态代理来实现。JAVA中实现动态代理的方式有两种:
- JDK动态代理:利用反射机制生成了一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理;
- CGLIB动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
两者的主要区别在于:
JDK代理只能对实现接口的类生成代理;CGLIB是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
2.3.1 JDK动态代理实现
使用JDK动态代理时,需要定义一个位于代理类与委托类之间的中介类,这个中介类需要实现InvocationHandler接口,接口定义如下:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
从方法名可知,实现这个接口的类是“调用处理器”。当调用代理类的方法时,此次调用会传送到invoke方法中,代理类对象作为proxy传入,参数method标识了调用了代理类的哪个方法,args为这个方法的调用参数。此次调用过程对代理类的调用就变成了对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。针对2.2.3节需要在表演前送过去和表演后接回来,定义before()和after()方法,在调用代理方法sing()和acting()方法后调用,具体实现如下:
public class StarManagerProxy implements InvocationHandler {
private Star star;
public StarAction getInstance(Star star) {
this.star = star;
//newProxyInstance包含三个参数:
//1.loader:代理类的ClassLoder;
//2.interfaces: 代理类实现接口列表;
//3.h: 调用处理器,这里也就是当前实现了InvocationHandler接口的类实例
return (StarAction) Proxy.newProxyInstance(star.getClass().getClassLoader(), star.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(star, args);
after();
return result;
}
private void before() {
System.out.println("送明星" + star.getName() + "去唱歌!");
}
private void after() {
System.out.println("演出结束,接明星" + star.getName() + "回来!");
}
}
测试代码如下:
public class Test {
public static void main(String[] args) {
Star star = new Star();
star.setName("刘德华");
star.setAge(60);
StarManagerProxy starManagerProxy = new StarManagerProxy();
StarAction instance = starManagerProxy.getInstance(star);
instance.sing();
}
}
运行结果如下:
上述代码的主要流程如下:首先通过newProxyInstance()方法获取代理类的实例,拿到实例后便可以调用代理类的方法,对代理类方法的调用主要通过代理类invoke方法进行,在invoke方法中调用代理类的相应方法,并添加自己的处理逻辑。
2.3.2 CGLIB动态代理
由于JDK动态代理只针对实现接口的类,而对于没有实现接口的情况,JDK无法实现动态代理。针对这种情况,CGLIB做了弥补。CGLIB采用了非常底层的字节码技术,通过字节码技术为一个类创建子类,并在子类中使用方法拦截技术拦截所有父类方法的调用,并加入横切逻辑,来实现动态代理。具体实现方式如下:代理类实现MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。同等情况下,CGLIB创建代理对象所花时间远多于JDK创建模式,而且对于final修饰的父类,无法创建其代理对象(final修饰的类无法创建子类)。本文利用CGLIB实现的代理类如下:
public class StarManagerCGLIBProxy implements MethodInterceptor {
private Star star;
public Star createProxyInstance(Star star) {
this.star = star;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(star.getClass());
enhancer.setCallback(this);
Star o = (Star) enhancer.create();
return o;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = method.invoke(star, objects);
after();
return result;
}
private void before() {
System.out.println("送明星" + star.getName() + "去唱歌!");
}
private void after() {
System.out.println("演出结束,接明星" + star.getName() + "回来!");
}
}
测试代码如下:
CGLIB也可以对接口实现代理(与类实现方式基本一致),这里不再赘述。
2.4 使用场景
动态代理技术在底层框架中使用较多,日常开发中也可根据场景使用。比如对Feign接口请求后获取到的结果进行解析,获取数据(前提下接口返回格式确定)。具体如下:
public class DataServiceHandler implements InvocationHandler {
// logger
private Logger logger = LoggerFactory.getLogger(DataServiceHandler.class);
private Object target;
private String serviceName;
public Object getInstance(Object target, String serviceName) {
this.target = target;
this.serviceName = serviceName;
Class<?> clazz = target.getClass();
Object obj = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
return obj;
}
@Override
public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
logger.debug("代理feign客户端调用服务接口");
// 第一个参数是target,也就是被代理类的对象;第二个参数是方法中的参数
String result = null;
JSONObject resultMap = null;
try {
result = (String) method.invoke(target, args);
resultMap = JSON.parseObject(result);
} catch (Exception e) {
StringBuilder detail = new StringBuilder("调用微服务").append(serviceName).append("--业务接口").append(method.getName()).append("异常");
logger.error(detail.toString(), e);
throw new VideoCloudException(ErrorContents.ERROR_NUM_600, detail.toString(), e);
}
if (resultMap == null) {
logger.error("返回结果为null");
throw new VideoCloudException("返回结果为null");
}
String dataErrorCode = resultMap.getString("errorCode");
if (dataErrorCode != null && !dataErrorCode.equals("0")) {
String dataErrorMsg = resultMap.getString("errorMsg");
StringBuilder detail = new StringBuilder("调用微服务").append(serviceName).append("--业务接口").append(method.getName()).append("异常:");
logger.error("调用feign服务{}--业务接口{}, params:{}, error result:{}", serviceName, method.toString(), JSON.toJSON(args), result);
if (dataErrorMsg != null) {
throw new InterfaceException(dataErrorCode, dataErrorMsg, detail.toString());
} else {
throw new InterfaceException(null, null, detail.toString());
}
}
return resultMap.getString("data");
}
}
返回类格式为:
@Data
public class ResultData<T> implements Serializable {
private Integer errorCode;
private String errorMsg;
private T data;
public ResultData() {
}
public ResultData(Integer errorCode, String errorMsg) {
this.errorCode= code;
this.errorMsg= errorMsg;
}
public ResultData(Integer errorCode, String errorMsg, T data) {
this.errorCode= errorCode;
this.errorMsg= errorMsg;
this.data = data;
}
public static <T> ResultData<T> success(String message, T data) {
return new ResultData<>(0, message, data);
}
public static <T> ResultData<T> success(T data) {
return new ResultData<>(0, "success", data);
}
public static <T> ResultData<T> success() {
return new ResultData<>(0, "success", null);
}
public static <T> ResultData<T> fail(T data) {
return new ResultData<>(111, "fail", data);
}
public static <T> ResultData<T> fail() {
return new ResultData<>(111, "fail", null);
}
}
3.小结
1.JAVA动态代理能帮助实现对客户类的增强,且能够帮助解耦客户类与委托类;
2.JDK动态代理只针对实现接口的类,而对于没有实现接口的情况,JDK无法实现动态代理
3.CGLIB是基于字节码创建代理类子类的方式进行代理,若该类被final修饰,则无法进行代理。
4.JDK代理方式的性能要优于CGLIB代理方式。
4.参考文献
1.https://juejin.cn/post/6844903591501627405
2.https://www.php.cn/java-article-487165.html
3.https://www.bilibili.com/video/BV1Cv411372m?p=193&vd_source=c0ee3c78abb8202795797d12feb40feb