Java基础之静态代理和动态代理

本篇介绍静态代理和动态代理。

代理模式也是设计模式中的一种,主要解决的是在直接访问对象时带来的问题,其目的是为其他对象提供一个代理来控制对目标对象的访问。代理类为委托类预处理消息,过滤消息并转发消息,以及消息被委托类执行后的后续处理。

代理模式的类图如下:

为了保持行为的一致性,委托类和代理类通常会实现相同的接口,客户端调用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文件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章