代理模式几大问题?
- 代理模式是什么?
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。来源百度百科。
那么我的理解代理模式呢,就是我们不直接和具体的对象打交道,而是让代理对象和具体的对象打交道。 - 为什么要使用代理模式?
这个问题可以理解为使用代理对象有什么好处,主要的好处之一就是方便拓展。职责清晰,代理类提供访问接口,业务类只用负责业务实现
代理类只负责提供使用接口供外部访问,而真正的实现由具体的类完成,具体的类修改了代码,对使用者来说是无感的(透明的)。 - 代理模式在什么时候适用?
当我们需要给某个类,当然不一定是类,进行功能拓展时,此时就可以使用代理模式来完成,比如为了安全,我们需要给某个具体的业务类 添加权限验证,那么使用代理模式解决是一个比较好的方案,如果将代码全写在业务类,则违背了单一职责原则,同时也不方便系统进行拓展。 - 如何实现代理模式?
代理模式的使用时比较简单的,核心思想就是使用一个类暴露接口给外部访问,然后在其基础上进行业务增强,由代理类完成具体的业务逻辑即可。
那么我们解决了这几大问题之后,我们大概就知道了代理模式是什么意思,主要应用场景是什么,那么最重要的是什么呢,就是代理模式是用来进行功能增强的,每次遇到功能增强的案例,就需要想到代理模式。
代理模式的分类
代理类在程序运行前就已经存在,代理类所实现的接口和所代理的方法都被固定,这种代理方式被称为静态代理。
静态代理会存在这样的缺点:如果Subject(目标接口)中方法发生变化,那么真实类和代理类都需要跟着变化;如果需要被代理的真实类有很多,就需要相应的创建很多个代理类。
那么,有没有一种机制能够让系统在运行时动态创建代理类,并可以对多个真实类进行代理操作?有,这就是我们将要介绍的这种动态代理(Dynamic Proxy)。
- 静态代理:静态代理的意思是,在编译代码的时候类之间的代理关系就已经确定了,如果需要添加新的被代理对象需要重新修改代码,代理关系都写死在代码里面的就叫做静态代理
- 动态代理:与之对应的就是动态代理,在代码的运行期间根据,传入的参数不同,而确定不同的被代理对象,在运行期间确定的,能够比较方便的进行拓展。
一个概念:在java中,遇到静态的,就可以理解为在编译时以及完成了的内容,就是静态的,而在运行期,根据代码执行情况完成的内容就叫做动态。
静态代理比较简单,一般使用不是很多,而动态代理在各个框架中广泛使用,那么我们来看看在java中如何使用动态代理。
动态代理的实现
基于java的动态代理实现有两种形式。
- JDK动态代理:基于jdk的动态代理是基于接口实现的,如果被代理对象没有实现接口则不能完成代理
- cglib动态代理:前面介绍了JDK动态代理的局限性,它要求被代理类必须实现一个接口。而CGLIB动态代理则没有此类强制性要求。CGLib动态代理是代理类去继承目标类,然后重写目标类的方法。既然是继承,则被代理类不能为final,并且 final 和static 修饰的方法也不会被拦截。
使用jdk中的原生api完成动态代理。
首先我们先介绍完成动态代理类的两个核心api,第一个是
java.lang.reflect.Proxy,此类的功能是帮助我们创建代理类的对象。我们给其传递相应的参数,其就会为我们创建好相应的代理对象。第二个类是:
import java.lang.reflect.InvocationHandler,此类的功能是,确定我们通过哪个接口为被代理对象创建代理对象,构成一种映射关系,在其中的invock方法中,通过反射调用被代理对象的方法和需要增强的功能。
用上面这张图来说明代理类和被代理对象之间的关系,代理类和被代理对象都实现了同样的接口,这样被代理对象就有了和被代理对象同样的方法。
分析代理对象的创建过程,从图中我们可以得到代理对象的创建传递了被代理对象实现的接口,所以其具有和被代理对象相同的方法,那么代理对象是如何调用被代理对象的方法的呢,可以看到还有一个参数我们没有使用,就是handler这个属性。那我们接着看handler这个对象。
从图中我们可以得出代理类只是调用了handler对象的invoke方法就完成了对被代理对象的代理过程。代理类中的每一个方法实现都是调用的同样的代码,也就是handler.invoke()。那么我们就需要分析handler对象的invoke方法到底做了什么。
分析invoke函数
///演示代码
public class AddServiceInvocationHandler implements InvocationHandler {
private AddInterface addInterface;//此对象是被代理对象
public AddServiceInvocationHandler(AddInterface addInterface) {
this.addInterface = addInterface;
}
/*
* @Description:
* @Author: zdonliu
* @Date: 2022/9/25 12:44 下午
* @param proxy 动态代理生成的代理类
* @param method 被代理对象调用的方法
* param args 被代理对象调用方法传递的参数
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = method.invoke(addInterface, args);
System.out.println(" >>> 发送消息通知, 执行方法名称 :: " + method.getName());
return invoke;
}
}
invoke函数的第一个参数是代理对象,因为每个代理对象都会对一个具体的类进行代理,这个类是实现了被代理接口的类,不是具体的类,主要是为了标识这个信息,method 这个参数是代理对象调用的方法,通过这个参数识别调用的方法,然后通过反射指定被代理对象的方法执行,args就是被代理对象调用函数的参数,然后在method.invoke()方法调用的前后进行代码书写就能够对方法进行功能增强了。
通过分析这个过程我们可以很清楚的得出使用jdk自带的API进行动态代理高度依赖接口,而且只能针对一个接口进行动态代理类的创建,只能代理实现了这个接口的类对象,而且是这个接口的方法,对接口高度绑定。同时我们知道代理对象并不是真正完成功能增强的对象,只是进行了转发,交给handler完成的功能增强。
Java中动态代理使用与原理详解
Java API 之 动态代理
Java设计模式及应用场景之《代理模式》
public interface AddInterface {
boolean add(String obj);
boolean sub(String obj);
}
//只能对 AddInterface接口进行功能增强
public class AddInterfaceImpl implements AddInterface, OtherInterface {
public boolean add(String obj){
System.out.println("添加元素"+obj.toString());
return true;
}
@Override
public boolean sub(String obj) {
System.out.println("删除元素"+obj.toString());
return true;
}
@Override
public boolean otherMethod(String other) {
System.out.println("其他元素"+other.toString());
return true;
}
}
public class proxyTest {
public static void main(String[] args) {
AddInterface addInterface = new AddInterfaceImpl();
AddServiceInvocationHandler handler =new AddServiceInvocationHandler(addInterface);
AddInterface addInterfaceProxy = (AddInterface) Proxy
.newProxyInstance(addInterface.getClass().getClassLoader(), addInterface.getClass().getInterfaces(), handler);
addInterfaceProxy.sub("djlfk ");
addInterfaceProxy.add("jdlfakj");
}
}
public class AddServiceInvocationHandler implements InvocationHandler {
private AddInterface addInterface;//此对象是被代理对象
public AddServiceInvocationHandler(AddInterface addInterface) {
this.addInterface = addInterface;
}
/*
* @Description:
* @Author: zdonliu
* @Date: 2022/9/25 12:44 下午
* @param proxy 动态代理生成的代理类
* @param method 被代理对象调用的方法
* param args 被代理对象调用方法传递的参数
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = method.invoke(addInterface, args);
System.out.println(" >>> 发送消息通知, 执行方法名称 :: " + method.getName());
return invoke;
}
}
基于CGLIB完成动态代理
使用jdk提供的API完成动态代理和接口高度绑定,如果类没有实现接口是不能完成动态代理的,而且只能对接口里面的方法完成代理,那么针对其他的类就需要使用另一种方式完成代理了,于是催生出cglib动态代理的技术,cglib完成动态代理的原理和基于jdk的动态代理类似,同样需要两个类,一个类完成代理对象的创建,另一个类完成功能增强,和被代理对象的业务方法调用,只不过基于cglib的动态代理是依赖于继承实现的,通过创建一个被代理对象的子类,来完成被代理对象的创建。
分析两个类,第一个类:
net.sf.cglib.proxy.Enhancer,这个类的功能同jdk的Proxy类功能类似,完成代理类的创建。
分析代码可以看出其需要的参数和proxy类创建代理类的参数类似。
public static Object getProxy(Class<?> clazz) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"./");
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
第二个重要类是实现了MethodInterceptor接口的类,这个接口是一个规范,定义了拦截器的书写规范,每个代理类都通过调用拦截器的方法,完成代理过程。
/**
* @param o 代理对象
* @param method 被拦截的方法(需要增强的方法)
* @param objects 方法入参
* @param methodProxy 原始方法函数描述类
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName()+o.getClass().getName());
Object object = methodProxy.invokeSuper(o, objects);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}
我们重写的intercept方法是一个规范,所有的代理对象都是调用这个方法,传递的参数都是固定的格式,通过这个例子我们也可以理解接口就是一个规则这句话的意思了。就是必须按照这个规范来写,才能完成相应的功能,因为在其他位置的代码是按照接口的规范进行的方法调用。
通过查看生产的代理对象,我们可以看出,cglib给我们创建好的代理对象通过重写了被代理对象的方法,然后调用通用的拦截器代码,完成的代理操作。
public final String send(String var1) {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (var10000 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? (String)var10000.intercept(this, CGLIB$send$0$Method, new Object[]{var1}, CGLIB$send$0$Proxy) : super.send(var1);
}
缺点:
CGLIB 的最大不足在于,它需要继承我们的委托类,所以如果委托类被修饰为 final,那就意味着,这个类 CGLIB 代理不了。
自然的,即便某个类不是 final 类,但是其中如果有 final 修饰的方法,那么该方法也是不能被代理的。这一点从我们反射的源码可以看出来,CGLIB 生成的代理类需要重写委托类中所有的方法,而一个修饰为 final 的方法是不允许重写的。
基于 CGLIB 库的动态代理机制
Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)