由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。简单的说就是,我们在访问实际对象时,是通过代理对象来访问的,代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK动态代理和cglib 动态代理两种。
优点与缺点
优点:
缺点:
客户可以通过房东(被代理类)直接买房子,也可以通过代理来让中间商(代理类)买房子
抽象主题类:卖房子接口
public interface RentHouse {
void rentHouse();
}
被代理类:房东
public class Landlord implements RentHouse{
@Override
public void rentHouse() {
System.out.println("房东卖出房子...");
}
}
代理类:中间商
public class Middleman implements RentHouse{
//通过组合的方式与被代理类产生联系
private Landlord landlord;
public Middleman(Landlord landlord){
this.landlord = landlord;
}
//代理房东去卖房子
@Override
public void rentHouse() {
//调用房东的rentHouse()
landlord.rentHouse();
}
}
客户端类
public class Customer {
public static void main(String[] args) {
//创建一个房东
Landlord landlord = new Landlord();
//创建一个中间商
Middleman middleman = new Middleman(landlord);
//客户可以通过房东买房子
System.out.print("直接找房东买房子=>");
landlord.rentHouse();
System.out.println("======================");
//客户也可以通过中间商买房子
System.out.print("通过中间商卖买房子=>");
middleman.rentHouse();
}
}
输出结果
日常生活中,我们是很难直接找到房东卖房子的,一般都是通过中间商去买房子,让中间商去代理房东卖房子,这就是代理模式。我们也可以在代理类中添加其他方法,这样可以在不修改被代理类的代码的同时进行扩展。
改进
public class Middleman implements RentHouse{
private Landlord landlord;
public Middleman(Landlord landlord){
this.landlord = landlord;
}
//代理房东去卖房子
@Override
public void rentHouse() {
seeHouse();
signContract();
//调用房东的rentHouse()
landlord.rentHouse();
}
public void seeHouse(){
System.out.println("中间商带顾客看房子");
}
public void signContract(){
System.out.println("达成协议=>签合同");
}
}
输出结果
在代理类中定义其他方法,在被代理类的真实方法执行前后,就可以添加其他方法。这种操作,也是使用代理模式的一个很大的优点。最直白的就是在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。
在静态代理中,代理类是定义好的,在程序运行之前就已经编译完成。而在动态代理中,代理类是在运行时根据代码中的指示动态生成的。与静态代理相比, 动态代理的优势在于可以很方便的对代理类的方法进行统一处理,而不用修改每个代理类中的方法。
在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。
InvocationHandler接口是proxy代理实例的调用处理程序实现的一个接口,每一个proxy代理实例都有一个关联的调用处理程序;在代理实例调用方法时,方法调用被编码分派到调用处理程序的invoke方法。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
Proxy类就是用来创建一个代理对象的类,它提供了很多方法,但是我们最常用的是newProxyInstance方法。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException{}
将上述案例改为动态代理
抽象主题类:卖房子接口
public interface RentHouse {
void rentHouse();
}
被代理类:房东
public class Landlord implements RentHouse{
@Override
public void rentHouse() {
System.out.println("房东卖出房子...");
}
}
代理类实现接口
public class MiddleInvocationHandler implements InvocationHandler {
//invocationHandler持有的被代理对象
Object target;
public MiddleInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("执行了=>" + method.getName());
Object result = method.invoke(target, args);
return result;
}
}
创建MiddleInvocationHandler类,实现InvocationHandler接口,这个类中组合了一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。在invoke方法中执行被代理对象target的相应方法。在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理,这也是Spring中的AOP实现的主要原理。
客户端类
public class Customer {
public static void main(String[] args) {
//创建一个被代理的对象
Landlord landlord = new Landlord();
//创建一个与代理对象相关的InvocationHandler
MiddleInvocationHandler handler = new MiddleInvocationHandler(landlord);
//创建一个代理对象MiddleProxy
RentHouse MiddleProxy = (RentHouse) Proxy.newProxyInstance(
Landlord.class.getClassLoader(),
Landlord.class.getInterfaces(),
handler);
MiddleProxy.rentHouse();
}
}
- 创建了一个需要被代理的房东类Landlord,将landlord对象传给了MiddleInvocationHandler中创建handle实例,调用Proxy类的newProxyInstance创建动态代理对象
- 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。
上述案例中,我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,关键源码如下
不难看出 ==Class> cl = getProxyClass0(loader, intfs)==这行代码产生了代理类,后面代码中的构造器也是通过这里产生的类来获得,可以看出,这个类的产生就是整个动态代理的关键。代理类是程序在运行过程中动态的在内存中生成的类
我们在命令行对MiddleInvocationHandler.java使用命令javac生成MiddleInvocationHandler.class文件,再通过jd-gui工具对.class文件进行反编译,得到代理类的结构:
package com.suqu.patterns.proxy.dynamic_proxy;
import ;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class Proxy0 extends Proxy implements RentHouse {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
* 这是生成代理类的构造方法,传入一个InvocationHandler类型的参数
*/
public Proxy0(InvocationHandler paramInvocationHandler) {
/*
* 调用父类的Proxy构造方法
* 父类持有protected InvocationHandler h
* Proxy构造方法:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
*/
super(paramInvocationHandler);
}
public final boolean equals(Object paramObject) {...}
public final String toString() {...}
//这里的rentHouse方法,直接调用了InvocationHandler中的invoke方法并把m3作为参数传递进去
public final void rentHouse() {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() {...}
//通过反射获得rentHouse方法,并命名为名m3
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.suqu.patterns.proxy.dynamic_proxy.RentHouse").getMethod("rentHouse", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
} catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
} catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
}
解析:在生成代理类的构造方法中,会传入一个InvocationHandler类型的参数,实际上传入的就是我们new的MiddleInvocationHandler,这也就是为什么代理类对象调用的是MiddleInvocationHandler中的invoke()方法,而MiddleInvocationHandler中又有一个被代理类的实例对象landlord,所有最终调用的还是被代理类Landlord中的方法。
JDK为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。
我们可以把InvocationHandler看做一个中介类,中介类持有一个被代理对象,在中介类的invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。也就是说,动态代理通过中介类实现了具体的代理功能
总结:生成的代理类:Proxy0 extends Proxy implements RentHouse,我们看到代理类继承了Proxy类,所以也就决定了JDK动态代理只能对接口进行代理,Java的继承机制注定了这些动态代理类们无法实现对class的动态代理。上面的动态代理的例子,其实就是AOP的一个简单实现了,在目标对象的方法执行之前和执行之后进行了处理,对方法耗时统计。Spring的AOP实现其实也是用了Proxy和InvocationHandler这两个东西的。
部分资料参考自该博客代理模式的使用总结
CGLIB是第三方提供的jar包,需要导入。
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>3.3.0version>
dependency>
被代理类
public class Landlord {
public void rentHouse() {
System.out.println("房东卖出房子...");
}
}
CglibProxy类,实现了MethodInterceptor
public class CglibProxy implements MethodInterceptor {
//重写拦截方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//可以在方法执行前添加操作
System.out.println("cglib动态代理start");
Object result = methodProxy.invokeSuper(o,objects);
//可以在方法执行后添加操作
System.out.println("cglib动态代理end");
return result;
}
//获取代理对象的方法
public Object getCglibProxy(Class<?> clazz){
//创建动态代理增强类
Enhancer enhancer = new Enhancer();
//设置父类,cglib是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(clazz);
//设置回调函数
enhancer.setCallback(this);
//创建代理类
return enhancer.create();
}
}
Customer测试类
public class Customer {
public static void main(String[] args) {
Landlord cglibProxy = (Landlord) new CglibProxy().getCglibProxy(Landlord.class);
cglibProxy.rentHouse();
}
}
CGLIB动态代理会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层,其实是借助了ASM这个非常强大的Java字节码生成框架。
实现步骤:
Enhancer是CGLIB中非常常用的一个类。和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。
部分参考自CGLIB动态代理
静态代理和动态代理
JDK动态代理和CGLIB动态代理
JDK动态代理只能对实现了接口的类生成代理,而不能针对类;CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
JDK动态代理的实现是通过Java的反射技术;CGLIB实现动态代理,底层采用ASM字节码生成框架,使用字节码技术生成代理类
由于底层使用反射,JDK动态代理生成类的过程比较高效;而CGLIB采用ASM字节码框架,相关执行的过程比较高效,生成类的过程可以利用缓存弥补。需要注意的是,由于CGLIB原理是动态生成被代理类的子类,故CGLib不能对声明为final的类或者方法进行代理。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
JDK动态代理是不需要第三方库支持,只需要JDK环境就可以进行代理;CGLIB必须依赖于CGLIB的类库。
远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
防火墙(Firewall)代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。