本篇介绍静态代理和动态代理。
代理模式也是设计模式中的一种,主要解决的是在直接访问对象时带来的问题,其目的是为其他对象提供一个代理来控制对目标对象的访问。代理类为委托类预处理消息,过滤消息并转发消息,以及消息被委托类执行后的后续处理。
代理模式的类图如下:
为了保持行为的一致性,委托类和代理类通常会实现相同的接口,客户端调用Subject时不知道调用的是哪个,事实上它也不需要知道。
按照代理类的创建时机,可以将代理其分为两类,分别为静态代理和动态代理。
静态代理
静态代理在编译时就已经将接口、代理类、被代理类等确定下来,在程序运行之前,代理类的.class文件就已经生成。
示例代码如下:
Subject
public interface Subject {
void saySomething();
}
RealSubject
public class RealSubject implements Subject{
@Override
public void saySomething() {
System.out.println("***我是委托类***");
}
}
Proxy
public class Proxy implements Subject{
private Subject subject;
public Proxy(Subject subject) {
this.subject = subject;
}
@Override
public void saySomething() {
System.out.println("***我是代理类,我要调用委托类了哦***");
subject.saySomething();
System.out.println("***委托类调用完了,我还是代理类***");
}
}
测试代码
public class ProxyTest {
public static void main(String[] args) {
Subject subject = new RealSubject();
Proxy proxy = new Proxy(subject);
proxy.saySomething();
}
}
输出结果
***我是代理类,我要调用委托类了哦***
***我是委托类***
***委托类调用完了,我还是代理类***
由于委托类和代理类需要实现同一接口,不仅有大量代码重复,并且在接口增加方法时,所有的委托类和代理类都需要新增此方法。除此之外,代理对象只能委托同一接口的对象,当有多个接口的对象需要代理时,就要为每个接口都创建一个代理类,增加了代码复杂度。
那么能不能使用一个类就可以代理所有类呢?可以的,这就需要使用动态代理。
动态代理
动态代理的代理类是在程序运行时运用反射机制动态创建而成。动态代理又分为JDK动态代理和CGLIB动态代理,其中JDK代理要求委托类必须实现接口,CGLIB动态代理可以直接代理类。
JDK动态代理
JDK动态代理涉及一个接口和一个类,一个接口为InvocationHanlder接口,一个类为Proxy类。在介绍JDK动态代理前,我们先来看一下InvocationHanlder接口和Proxy类的API。
InvocationHanlder
InvocationHandler接口只有一个invoke()方法。
public Object invoke(Object proxy, Method method, Object[] args)
invoke()方法的第一个参数是委托类实例,第二个接口是被代理的方法实例,第三个参数是被代理的方法的参数数组。我们需要定义一个类来实现InvocationHandler接口,此类实现的invoke()方法通过反射调用委托类的指定方法。
Proxy
Proxy提供了创建动态代理类和实例的静态方法,它也是所有动态代理类的父类。在Proxy类内部维护了一个InvocationHanlder类型的成员变量,该成员变量的值在Proxy构造函数中初始化。Proxy构造函数如下:
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
常用方法如下:
//获取代理类的InvocationHandler实例
public static InvocationHandler getInvocationHandler(Object proxy);
//返回代理类的Class实例
public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces);
//判断该类是不是代理类
public static boolean isProxyClass(Class<?> cl);
//返回指定接口的代理类的实例
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
要使用动态代理为委托类RealSubject创建代理类,首先创建一个SubjectInvocationHandler类实现InvocationHandler接口,在这个类中我们需要把委托类实例传入但又不能与静态代理相同(只能代理某一接口的实现类),因此传入的类型应该是Object。
public class SubjectInvocationHandler implements InvocationHandler {
private Object object;
public SubjectInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是SubjectInvocationHandler的invoke()方法哦...");
System.out.println("即将执行委托类的指定方法...");
Object result = method.invoke(object,args);
System.out.println("执行完委托类的指定方法了哦...");
return result;
}
}
测试方法
public class DynamicProxyTest {
public static void main(String[] args) {
//创建委托类实例
RealSubject rs = new RealSubject();
//创建调用处理类实例
SubjectInvocationHandler sih = new SubjectInvocationHandler(rs);
//获取委托类的Class实例
Class subClass = rs.getClass();
//创建代理类
Subject proxySubject = (Subject) Proxy.newProxyInstance(subClass.getClassLoader(),subClass.getInterfaces(),sih);
//调用指定方法
proxySubject.saySomething();
//输出代理类实现的接口
Class[] interfaces = proxySubject.getClass().getInterfaces();
for (Class i:interfaces){
System.out.println(i.getName());
}
}
}
输入结果
我是SubjectInvocationHandler的invoke()方法哦...
即将执行委托类的指定方法...
***我是委托类***
执行完委托类的指定方法了哦...
org.practice.Subject
java.io.Serializable
由newProxyInstance()方法生成的代理类继承了Proxy类并实现了委托类实现的全部接口,在使用代理类实例调用接口方法时,通过反射调用了委托类的对应方法。
从上面可以看出,JDK动态代理要求委托类必须实现至少一个接口,这也是JDK动态代理的局限性。Spring AOP就是通过动态代理实现的,在一个类没有实现任何接口时Spring AOP还是正常工作的,原因在于在委托类没有实现任何接口时Spring AOP会使用另一个叫做CGLIB的动态代理。
CGLIB动态代理
CGLIB动态代理不要求委托类实现接口,因为它是通过使动态生成的代理类继承委托类实现的,也因此,CGLIB无法代理final修饰的类。CGLIB与JDK动态代理类似,需要一个类来生成代理类以及另一个类来设定增强逻辑,分别为Enhancer类以及MethodInterceptor接口。
MethodInterceptor接口只有一个方法。
//参数分别为:委托类对象,要拦截的方法,被拦截方法的参数,代理方法
Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
Enhancer类是一个字节码增强器,功能与JDK动态代理中的Proxy类类似,可以用来为未实现任何接口的类创建代理。以下使用这两个类实现CGLIB动态代理的例子。
委托类RealSubject
public class RealSubject {
public void saySomething() {
System.out.println("我是委托类...");
}
}
方法拦截器SubjectInterceptor
public class SubjectInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("进入拦截器...");
Object object = methodProxy.invokeSuper(o,objects);
System.out.println("被拦截方法执行完了...");
return object;
}
}
测试类
public class CGLIBProxyTest {
public static void main(String[] args) {
//生成动态代理类的Class文件
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D:\\WorkSpace\\project");
//创建Enhancer对象
Enhancer enhancer = new Enhancer();
//设置目标类的字节码文件
enhancer.setSuperclass(RealSubject.class);
//设置回调函数
enhancer.setCallback(new SubjectInterceptor());
//创建代理类
RealSubject rs = (RealSubject) enhancer.create();
//使用代理对象调用目标方法
rs.saySomething();
}
}
输出结果
CGLIB debugging enabled, writing to 'D:\WorkSpace\project'
进入拦截器...
我是委托类...
被拦截方法执行完了...
CGLIB动态代理并没有使用反射,因为反射的效率并不高,CGLIB底层是使用。CGLIB是使用Fastclass机制,即使动态生成的代理类继承委托类,在代理类里对委托类的非final方法建立索引,通过索引来调用相应的方法。
下表为静态代理,JDK动态代理和CGLIB动态代理的比较。
代理方式 | 实现方式 | 特点 |
---|---|---|
静态代理 | 委托类和代理类实现同一接口,通过代理类调用委托类的方法 | 只服务一种类型的对象,如果要服务多种类型则需要为每种类型都创建代理类,增加了代码的复杂性;由于委托类和代理类实现相同接口,势必会有大量代码重复;在编译时就确定了代理对象 |
JDK动态代理 | 委托类和代理类实现同一接口,创建InvocationHandler的实现类并重写invoke()方法来自定义增强逻辑,使用Proxy类创建代理类对象 | 通过反射机制实现;委托类必须实现至少一个接口;动态生成的代理类继承了Proxy类 |
CGLIB动态代理 | 创建MethodInterceptor接口的实现类并重写intercept()方法来自定义增强逻辑,使用Enhancer类创建代理类对象 | 使动态生成的代理类继承委托类,底层使用asm字节码生成框架来生成class文件 |