Java代理模式与动态代理

Java动态代理技术

一.代理模式

代理模式的实现

想要了解动态代理,我们就得从23种设计模式的代理模式开始。

我们来看看以下场景,有一个Chinese类和Englisher类,它们都实现了Speakable接口。(中国人讲中文,英国人讲英文)

public class Chinese implements Speakable{
    @Override
    public void speak() {
        System.out.println("你好 伙伴");
    }
}
public class Englisher implements Speakable{
    @Override
    public void speak() {
        System.out.println("hello guy!");
    }
}
public interface Speakable {
    void speak();
}

假设它们都分别有一个代理对象ChineseProxy 和 EnglishProxy
这里先提一点:代理模式中,代理类和被代理类需要实现同一个接口,代理对象只能够代理被代理对象实现的接口方法

ChineseProxy :

public class ChineseProxy implements Speakable{
    private final Chinese chinese = new Chinese();
    @Override
    public void speak() {
        // 代理准备工作
        System.out.println("代理开始做准备工作");
        // 通知被代理对象处理逻辑
        chinese.speak();
        // 代理收尾工作
        System.out.println("代理开始做收尾工作");
    }
}

EnglishProxy:

public class EnglisherProxy implements Speakable{
    private final Englisher englisher = new Englisher();
    @Override
    public void speak() {
        // 代理准备工作
        System.out.println("英国人代理开始做准备工作");
        // 通知被代理对象处理逻辑
        System.out.print("英国人收到代理通知,开始工作:");
        englisher.speak();
        // 代理收尾工作
        System.out.println("英国人代理开始做收尾工作");
    }
}

我们在需要使用到Chinese和Englisher的时候不直接使用它们,而是使用它们的代理

public static void main(String[] args){
    ChineseProxy chineseProxy = new ChineseProxy();
    chineseProxy.speak();
    
    System.out.println();
    
    EnglisherProxy englisherProxy = new EnglisherProxy();
    englisherProxy.speak();
}

运行结果:

Java代理模式与动态代理

小结:

代理模式的模板代码就是这样。
我们在开发过程中,出于安全等原因,有一些类是不直接开放给用户使用的。用户只能通过这些类的代理对象来间接访问这些类的方法

动态代理:

静态代理的缺点及动态代理的引入:

上述模板代码属于代理模式中的静态代理,而对于静态代理:对于每一个被代理类我们都需要手动编写一份代理类的Java类文件,比如Chinese类的代理类ChineseProxy对应一个Java类文件,Englisher类对应EnglisherProxy类对应一个Java类文件。如果被代理类很庞大的情况下,那代理类的数量自然也很庞大,那我们就需要生成很多的Java类文件,这将会影响编译速度(编译简单来说就是从 .java文件到 .class文件的过程) ,并且也不能保证每个代理类都会被使用。这种场景下,我们可以不在编译阶段就生成如此多的class文件,而是在RunTime阶段动态生成代理类的字节码并加载到内存,这就是动态代理

动态代理的代码实现:

public static void main(String[] args){
    Speakable englishProxy = (Speakable) Proxy.newProxyInstance(
            Englisher.class.getClassLoader(),
            new Class[]{Speakable.class},
            new MyInvocationHandler(new Englisher())
    );

    Speakable chineseProxy = (Speakable) Proxy.newProxyInstance(
            Englisher.class.getClassLoader(),
            new Class[]{Speakable.class},
            new MyInvocationHandler(new Englisher())
    );
    englishProxy.speak();
    System.out.println("\n\n");
    chineseProxy.speak();
}

// InvocationHandler
static class MyInvocationHandler implements InvocationHandler {
    // 被代理对象
    private Speakable people;
    public MyInvocationHandler(Speakable people){
        this.people = people;
    }

    // 这里会拦截接口对象Speakable的方法,注意接口对象Speakable继承自Object类,所以调用Object类的方法比如equal()也会被拦截
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理开始准备工作");
        System.out.print("代理对象接到通知,开始工作:");
        Object object = method.invoke(people,args);
        System.out.println("代理开始收尾工作");
        return object;
    }
}

运行结果:

Java代理模式与动态代理

小结:

JDK中提供了Proxy这个类来实现动态代理

Speakable chineseProxy = (Speakable) Proxy.newProxyInstance(
        Englisher.class.getClassLoader(),
        new Class[]{Speakable.class},
        new MyInvocationHandler(new Englisher())
);

可以看到,动态代理的生成时通过Proxy.newInstance方法,该方法主要有三个参数
1.类加载器
2.代理对象和被代理对象同时实现的接口类(可能有多个)
3.自定义的方法拦截器,InvocationHandler

而对于InvocationHandler,它是一个接口类,需要我们去实现

// InvocationHandler
static class MyInvocationHandler implements InvocationHandler {
    // 被代理对象
    private Speakable people;
    public MyInvocationHandler(Speakable people){
        this.people = people;
    }

    // 这里会拦截接口对象Speakable的方法,注意动态代理类会继承自Object类,所以调用Object类的方法比如equals()之类的,也会被拦截
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理开始准备工作");
        System.out.print("代理对象接到通知,开始工作:");
        Object object = method.invoke(people,args);
        System.out.println("代理开始收尾工作");
        return object;
    }
}

这里主要讲一下invoke回调,该回调会对动态代理对象的方法进行拦截,如果动态代理对象的方法被调用,就会触发这里的invoke回调实现对方法的拦截。

容易陷入的误区:这里的chineseProxy.speak() 方法被调用了以后就会触发invoke回调,需要注意的是不只有chineseProxy的接口实现方法被调用才会触发该回调,需要知道的是Object是所有类的父类,所以chineseProxy中父类Object方法比如equals被调用的时候,也会被拦截从而触发invoke回调。

上一篇:如何优雅的将Object转换成List


下一篇:Maven详解