代理模式的定义:什么是代理模式呢?代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类和委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不是真正实现服务,而是通过调用委托类对象的相关的方法来提供特定的服务。
举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。
上述例子中:委托类就是我,
委托类的方法:选车+付钱
代理类:过户,质量检测,或许还有一些杂七杂八的事情,每个委托人也许杂七杂八的事情还不同,都通过代理类来处理,而委托类只需完成核心的业务,选车付钱就行了。
用图表示如下(网图):
中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
由程序员或特定工具自动生成的源代码,再对其编译,在程序运行之前,代理的类编译生成的.class文件就已经存在了。
直接上代码:
接口:
public interface A {
void buyCar();
void sallCar(String string);
}
实现类(委托类):
public class AImpl implements A {
public void buyCar() {
System.out.println("我要买车");
}
public void sallCar(String s){
System.out.println("sall car" + s);
}
}
代理类:
public class StaticStateProxy implements A {
private A a;
public StaticStateProxy(A a){
this.a = a;
}
public void buyCar(){
//do something
a.buyCar();
//do something
}
public void sallCar(String string){
a.sallCar(string);
}
}
测试:
public class StaticStateProxyTest{
public static void main(String[] args) {
A a = new AImpl();
a.buyCar();
StaticStateProxy buyHouseProxy = new StaticStateProxy(a);
buyHouseProxy.buyCar();
}
}
静态代理总结:
1、代理类和实现类(委托类),继承同一个接口类A。
2、代理类中传入接口A的实现类,通过重写接口A的方法,在重写方式时,直接调用传入A实现类的方法,并且在调用前后可以做一些其他操作,结合本章开始的例子,这里就是通过代理类,来实现委托类方法的同事,做一些其他操作。
优缺点:
优点:可以做到在符合开闭原则的情况下对目标对象进行功能扩展,调用比较简单。
缺点:我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
动态代理是在程序运行时通过反射机制动态创建的,在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK再运行时为我们动态的来创建。
上代码:
接口:
public interface A {
void buyCar();
void sallCar(String string);
}
实现类(委托类):
public class AImpl implements A {
public void buyCar() {
System.out.println("我要买车");
}
public void sallCar(String s){
System.out.println("sall car" + s);
}
}
代理类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxyHandler implements InvocationHandler {
private Object object;
public DynamicProxyHandler(final Object object) {
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("买车前凑钱");
Object result = method.invoke(object, args);
System.out.println("买车后飙车");
return result;
}
}
测试:
import java.lang.reflect.Proxy;
public class DynamicProxyTest {
public static void main(String[] args) {
A a = new AImpl();
A proxyA = (A) Proxy.newProxyInstance(A.class.getClassLoader(), new
Class[]{A.class}, new DynamicProxyHandler(a));
proxyA.buyCar();
proxyA.sallCar("BMW");
}
}
动态代理总结:
动态代理的接口和实现类和静态代理一样,主要是看代理类的实现:
DynamicProxyHandler实现了InvocationHandler接口。
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法
换句话说,我们一般调用方法都是采用a.method这种方式,代码会直接走到method方法中,而使用了动态代理类,我们需要通过InvocationHandler中的invoke来调用该方法,这样做有什么好处呢,最浅显的理解,就是在invoke中,除了通过method.invoke(object, args)来调用该方法以外,还可以做其他操作,如以上代码所示。
在写好代理类后,需要创建一个代理类的实例,来调用方法,创建方式为:
Proxy.newProxyInstance
注意Proxy.newProxyInstance()方法接受三个参数:
ClassLoader loader:指定当前目标对象使用的类加载器,也就是委托类,获取加载器的方法是固定的
Class>[] interfaces:指定目标对象实现的接口的类型,也就是委托类的接口,使用泛型方式确认类型
InvocationHandler:指定动态处理器,也就是制定代理类,执行目标对象的方法时,会触发事件处理器的方法。
动态代理和静态代理的比较:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样,重实现接口的每个方法,每一个方法都需要进行中转。而且动态代理的应用使我们的类职责更加单一,复用性更强;
但是:动态代理只能代理实现了接口的类,没有实现接口的类不能实现JDK动态代理。这时我们就需要用到Cglib代理。
JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。 Cglib不是java自带的API,我们要使用cglib代理必须引入 cglib的jar包。
上代码,借用上述例子实现类:
public class AImpl implements A {
public void buyCar() {
System.out.println("我要买车");
}
public void sallCar(String s){
System.out.println("sall car" + s);
}
public String aa(){
return "aa";
}
}
代理类:
public class CglibMethodInterceptor implements MethodInterceptor {
public Object intercept(Object object , Method method , Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Before");
Object result = methodProxy.invokeSuper(object,args);
System.out.println("After");
return result;
}
}
测试:
public class CglibProxyTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(AImpl.class);
enhancer.setCallback(new CglibMethodInterceptor());
AImpl aImpl = (AImpl) enhancer.create();
aImpl.buyCar();
aImpl.sallCar("dd");
}
}
代理类定义了一个拦截器,在调用目标方法之前,cglib回调MethodInterceptor接口方法拦截,来实现自己的业务逻辑,类似于JDK中的InvocationHandler接口。也就是通过intercept 调用methodProxy.invokeSuper来调用委托类的方法,而在intercept中可以做其他工作。
public Object intercept(Object object , Method method , Object[] args, MethodProxy methodProxy)
object:为cglib动态生成的代理实例
method:为上文中实体类所调用的被代理的方法调用
args:为method参数数值列表
methodProxy:为生成代理类对方法的代理引用
返回:从代理实例方法调用返回的值
其中,methodProxy.invokeSuper(object,args):
调用代理类实例上的proxy方法的父类方法
(这里有点拗口,简单一点就是,调用代理类实例的父类方法,为什么是父类方法,因为在创建代理类实例时,需要将代理类设置为委托类的子类,下面会有提到)
在写好代理类后,需要创建一个代理类的实例,来调用方法。
代理类对象是由Enhancer类创建的。Enhancer是CGLIB的字节码增强器,可以很方便的对类进行拓展。
创建代理对象的几个步骤:
Enhancer enhancer = new Enhancer(); 1
enhancer.setSuperclass(AImpl.class); 2
enhancer.setCallback(new CglibMethodInterceptor()); 3
AImpl aImpl = (AImpl) enhancer.create(); 4
aImpl.buyCar();
aImpl.sallCar("dd");
至此,CGlib就告一段落,等后续有时间,对源码解析一番就更好了。
网上有一段CGLIB和JDK动态带路使用场景上的对比,就借用一下啦:
CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。
代理方式 |
实现 |
优点 |
缺点 |
特点 |
JDK静态代理 |
代理类与委托类实现同一接口,并且在代理类中需要硬编码接口 |
实现简单,容易理解 |
代理类需要硬编码接口,在实际应用中可能会导致重复编码,浪费存储空间并且效率很低 |
好像没啥特点 |
JDK动态代理 |
代理类与委托类实现同一接口,主要是通过代理类实现InvocationHandler并重写invoke方法来进行动态代理的,在invoke方法中将对方法进行增强处理 |
不需要硬编码接口,代码复用率高 |
只能够代理实现了接口的委托类 |
底层使用反射机制进行方法的调用 |
CGLIB动态代理 |
代理类将委托类作为自己的父类并为其中的非final委托方法创建两个方法,一个是与委托方法签名相同的方法,它在方法中会通过super调用委托方法;另一个是代理类独有的方法。在代理方法中,它会判断是否存在实现了MethodInterceptor接口的对象,若存在则将调用intercept方法对委托方法进行代理 |
可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口 |
不能对final类以及final方法进行代理 |
底层将方法全部存入一个数组中,通过数组索引直接进行方法调用 |