6

秒懂 Java 的三种代理模式

 3 years ago
source link: https://segmentfault.com/a/1190000040407024
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

秒懂 Java 的三种代理模式

代理(Proxy)模式是一种结构型设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。

image-20210724125009890

这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需要修改,可以通过代理的方式来扩展该方法。

代理模式大致有三种角色:

  • Real Subject:真实类,也就是被代理类、委托类。用来真正完成业务服务功能;
  • Proxy:代理类,将自身的请求用 Real Subject 对应的功能来实现,代理类对象并不真正的去实现其业务功能;
  • Subject:定义 RealSubject 和 Proxy 角色都应该实现的接口。

image-20210724125955100

代理模式有三种类型,静态代理,动态代理(JDK代理,接口代理)、Cglib代理(在内存中动态的创建目标对象的子类)

静态代理需要先定义接口,被代理对象与代理对象一起实现相同的接口,然后通过调用相同的方法来调用目标对象的方法

image-20210726222112024

可以看见,代理类无非是在调用委托类方法的前后增加了一些操作。委托类的不同,也就导致代理类的不同。

某公司生产电视机,在当地销售需要找到一个代理销售商。那么客户需要购买电视机的时候,就直接通过代理商购买就可以。

代码示例:

public class TV {

    private String name;//名称

    private String address;//生产地

    public TV(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "TV{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

创建公司接口:

public interface TVCompany {

    /**
     * 生产电视机
     * @return 电视机
     */
    public TV produceTV();
}

公司的工厂生产电视机:

public class TVFactory implements TVCompany {
    @Override
    public TV produceTV() {
        System.out.println("TV factory produce TV...");
        return new TV("小米电视机","合肥");
    }
}

代理商去下单拿货(静态代理类):

public class TVProxy implements TVCompany{

    private TVCompany tvCompany;

    public TVProxy(){

    }

    @Override
    public TV produceTV() {
        System.out.println("TV proxy get order .... ");
        System.out.println("TV proxy start produce .... ");
        if(Objects.isNull(tvCompany)){
            System.out.println("machine proxy find factory .... ");
            tvCompany = new TVFactory();
        }
        return tvCompany.produceTV();
    }
}

消费者通过代理商拿货(代理类的使用):

public class TVConsumer {

    public static void main(String[] args) {
        TVProxy tvProxy = new TVProxy();
        TV tv = tvProxy.produceTV();
        System.out.println(tv);
    }
}

输出结果:

TV proxy get order .... 
TV proxy start produce .... 
machine proxy find factory .... 
TV factory produce TV...
TV{name='小米电视机', address='合肥'}

Process finished with exit code 0
  • 优点:静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。
  • 缺点:静态代理实现了目标对象的所有方法,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。

如何解决静态代理中的缺点呢?答案是可以使用动态代理方式

image-20210726224121229

动态代理具有如下特点:

  1. JDK动态代理对象不需要实现接口,只有目标对象需要实现接口。
  2. 实现基于接口的动态代理需要利用JDK中的API,在JVM内存中动态的构建Proxy对象
  3. 需要使用到 java.lang.reflect.Proxy,和其newProxyInstance方法,但是该方法需要接收三个参数。

image-20210724132028289

注意该方法是在Proxy类中是静态方法,且接收的三个参数依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
  • Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型。
  • InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。

有一天公司增加了业务,出售的商品越来越多,售后也需要更上。但是公司发现原来的代理商,还要再培训才能完成全部的业务,于是就找了另外的动态代理商B代理商B 承诺无缝对接公司所有的业务,不管新增什么业务,均不需要额外的培训即可完成。

代码示例:

公司增加了维修业务:

public interface TVCompany {

    /**
     * 生产电视机
     * @return 电视机
     */
    public TV produceTV();

    /**
     * 维修电视机
     * @param tv 电视机
     * @return 电视机
     */
    public TV repair(TV tv);
}

工厂也得把维修业务搞起来:

public class TVFactory implements TVCompany {
    @Override
    public TV produceTV() {
        System.out.println("TV factory produce TV...");
        return new TV("小米电视机","合肥");
    }

    @Override
    public TV repair(TV tv) {
        System.out.println("tv is repair finished...");
        return new TV("小米电视机","合肥");
    }
}

B代理商 全面代理公司所有的业务。使用Proxy.newProxyInstance方法生成代理对象,实现InvocationHandler中的 invoke方法,在invoke方法中通过反射调用代理类的方法,并提供增强方法。

public class TVProxyFactory {

    private Object target;

    public TVProxyFactory(Object o){
        this.target = o;
    }

    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),
                new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("TV proxy find factory for tv.... ");
                Object invoke = method.invoke(target, args);
                return invoke;
            }
        });
    }
}

购买、维修这两个业务 B代理就可以直接搞定了。后面公司再增加业务,B代理也可以一样搞定。

public class TVConsumer {

    public static void main(String[] args) {
        TVCompany target = new TVFactory();
        TVCompany tvCompany = (TVCompany) new TVProxyFactory(target).getProxy();
        TV tv = tvCompany.produceTV();
        tvCompany.repair(tv);
    }
}

输出结果:

TV proxy find factory for tv.... 
TV factory produce TV...
TV proxy find factory for tv.... 
tv is repair finished...

Process finished with exit code 0
  1. 代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。
  2. 动态代理的方式中,所有的函数调用最终都会经过 invoke 函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。

JDK 动态代理有一个最致命的问题是它只能代理实现了某个接口的实现类,并且代理类也只能代理接口中实现的方法,要是实现类中有自己私有的方法,而接口中没有的话,该方法不能进行代理调用。

怎么解决这个问题呢?我们可以用 CGLIB 动态代理机制。

Cglib代理

静态代理和JDK代理都需要某个对象实现一个接口,有时候代理对象只是一个单独对象,此时可以使用Cglib代理。

image-20210726224750356

Cglib代理可以称为子类代理,是在内存中构建一个子类对象,从而实现对目标对象功能的扩展。

C代理商不仅想代理公司,而且还想代理多个工厂的产品。

Cglib通过Enhancer 来生成代理类,通过实现MethodInterceptor接口,并实现其中的intercept方法,在此方法中可以添加增强方法,并可以利用反射Method或者MethodProxy继承类 来调用原方法。

看到 B代理商承接了公司(接口)的多种业务,那么此时C代理商又从中发现新的商机, B 只能代理某个公司的产品,而我不仅想要代理公司产品,而且对接不同的工厂,拿货渠道更广,赚钱更爽快。于是Cglib就用上了。

代码示例:

public class TVProxyCglib implements MethodInterceptor {

    //给目标对象创建一个代理对象
    public Object getProxyInstance(Class c){
        //1.工具类
        Enhancer enhancer = new Enhancer();
        //2.设置父类
        enhancer.setSuperclass(c);
        //3.设置回调函数
        enhancer.setCallback(this);
        //4.创建子类(代理对象)
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("TVProxyFactory enhancement.....");
        Object object = methodProxy.invokeSuper(o, objects);
        return object;
    }
}

新代理的B工厂

public class TVFactoryB {

    public TV produceTVB() {
        System.out.println("tv factory B producing tv.... ");
        return new TV("华为电视机", "南京");
    }

    public TV repairB(TV tv) {
        System.out.println("tv B is repair finished.... ");
        return tv;
    }
}

C代理可以直接和公司合作,也可以和工厂打交道。并且可以代理任何工厂的产品。

public class TVConsumer {

    public static void main(String[] args) {
        TVCompany tvCompany = (TVCompany) new TVProxyCglib().getProxyInstance(TVFactory.class);
        TV tv = tvCompany.produceTV();
        tvCompany.repair(tv);
        System.out.println("==============================");

        TVFactoryB tvFactoryB = (TVFactoryB) new TVProxyCglib().getProxyInstance(TVFactoryB.class);
        TV tv = tvFactoryB.produceTVB();
        tvFactoryB.repairB(tv);
    }
}

输出结果:

TVProxyFactory enhancement.....
TV factory produce TV...
TVProxyFactory enhancement.....
tv is repair finished...
==============================
TVProxyFactory enhancement.....
tv factory B producing tv.... 
TVProxyFactory enhancement.....
tv B is repair finished.... 

Process finished with exit code 0

Spring中AOP使用代理

Spring中AOP的实现有JDK和Cglib两种,如下图:

image-20210724133134109

如果目标对象需要实现接口,则使用JDK代理。

如果目标对象不需要实现接口,则使用Cglib代理。

  1. 静态代理:需要代理类和目标类都实现接口的方法,从而达到代理增强其功能。
  2. JDK动态代理:需要代理类实现某个接口,使用Proxy.newProxyInstance方法生成代理类,并实现InvocationHandler中的invoke方法,实现增强功能。
  3. Cglib动态代理:无需代理类实现接口,使用Cblib中的Enhancer来生成代理对象子类,并实现MethodInterceptor中的intercept方法,在此方法中可以实现增强功能。

我是一个正在被打击还在努力前进的码农。如果文章对你有帮助,记得点赞、关注哟,谢谢!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK