动态代理学习

要理解和说明什么是动态代理需要先解释面向对象中常见的设计模式------------代理模式

什么是代理模式(Proxy)

定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用

主要目的是为了在不改变对象具体方法的情况下实现对目标方法的增强,或解决直接访问目标对象带来的问题。

如下UML图揭示了用户,接口,委托类和代理之间的关系。

image

需要注意的是:

  1. 用户仅通过接口调用接口功能,不在乎是谁提供了具体功能;
    2.RealSubject是接口的真正实现类,但不和用户真正发生接触
    3.Proxy同样也实现了接口,所以可以和用户直接接触
    4.用户调用Proxy时,Proxy内部调用了RealSubject的功能,且可以在此基础上实现功能的增强

代理关系的直白理解:

image

如上图,原本厂家生产电脑直接销售给顾客,厂家和顾客之间直接联系(类似最基本的调用接口的实现类)。但因为需要提供后续的售后和维修服务,增加了厂家的成本(开始的实现类功能不够了)。厂家为了削减成本,减轻管理负担(避免在现有实现类上的直接修改),厂家将部分业务交给代理去完成,而厂家也不再直接接触顾客,仅将电脑提供给代理商,由代理商完成限售和保障售后服务(代理在现有类的基础上提供更多的功能)。

静态代理

如下是厂家的接口,提供了销售电脑的方法。同时一个通用的接口是实现代理的基础

package com.ed.demo;

public interface IProducer {

    public void sellComputer();
}

接下来是厂家的真正实现类,和实现接口的代理类

package com.ed.demo.impl;

import com.ed.demo.IProducer;

public class IProducerImpl implements IProducer {

    @Override
    public void sellComputer() {
        System.out.println("出售了一台电脑");
    }
}

实现接口的代理类,在售卖电脑的前提下增加了售前和售后服务

package com.ed.demo.impl;

import com.ed.demo.IProducer;

public class Seller implements IProducer {

    IProducerImpl iProducer;

    public Seller(IProducerImpl iProducer) {
        super();
        this.iProducer = iProducer;
    }

    @Override
    public void sellComputer() {
        System.out.println("进行售前引导");   //Seller额外提供的售前服务
        iProducer.sellComputer();   //厂家原来的销售电脑功能
        System.out.println("进行售后服务");   //Seller额外提供的售后服务
    }
}

那么运行通过代理方式的调用

package com.ed.demo;

import com.ed.demo.impl.IProducerImpl;
import com.ed.demo.impl.Seller;

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        Seller seller = new Seller(iProducer);
        seller.sellComputer();

    }
}

结果如下:

  进行售前引导
  出售了一台电脑
  进行售后服务

现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类
但静态代理仍然存在一些缺陷,如:

  • 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
  • 代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

动态代理

在上述静态代理中,一个静态代理只能服务于一个接口,实际开发中必然导致代理类过多。

动态代理利用反射机制,在程序运行时才根据需求实现一个被代理对象的接口,而不需要定义具体的代理类(上述中的Seller)。

下面通过动态代理来实现经销商的功能,首先创建一个InvocationHandler的实现类

package com.ed.demo.impl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    private Object producer;

    public MyInvocationHandler(Object producer) {
        this.producer = producer;
    }

    /**
     * 
     * @param proxy: 被代理的对象
     * @param method: 要调用的方法
     * @param args: 方法调用时所需要参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        System.out.println("进行售前服务");
        method.invoke(producer, args);
        System.out.println("进行售后服务");
        return null;
    }
}

InvocationHandler 内部只包含一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。

  • proxy 代理对象
  • method 代理对象调用的方法
  • args 调用的方法中的参数

因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。

再按如下方式进行调用

import java.lang.reflect.Proxy;

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        InvocationHandler handler = new MyInvocationHandler(iProducer);

        IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

        producer.sellComputer();


    }
}

结果如下:

  进行售前引导
  出售了一台电脑
  进行售后服务

其中最重要的方法是

IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

第一个参数传入了委托类的类加载器,运行时负责将字节码加载到JVM中并为其定义类对象。
第二个参数返回了委托类对象所引用的类实现的所有接口。
第三个参数是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用。在此处中实现了原方法的增强。

对于动态代理而言,上述代码中 前两个参数几乎是固定的。(先传入类加载器,后传入全部接口)

如果此时厂家同时售卖显示器

package com.ed.demo;

public interface IProducer {

    public void sellComputer();

    public void sellMonitor();
}

委托类中进行实现

import com.ed.demo.IProducer;

public class IProducerImpl implements IProducer {

    @Override
    public void sellComputer() {
        System.out.println("出售了一台电脑");
    }

    @Override
    public void sellMonitor() {
        System.out.println("出售了一台显示器");
    }
}

这次调用sellMonitor()

package com.ed.demo;

import com.ed.demo.impl.IProducerImpl;
import com.ed.demo.impl.MyInvocationHandler;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        InvocationHandler handler = new MyInvocationHandler(iProducer);

        IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

        producer.sellMonitor();


    }
}

可以看到代理仍然提供了售前售后服务

  进行售前服务
  出售了一台显示器
  进行售后服务

而且如果有另外的Iproducer实现类作为新的委托类,也可以以同样的方法享受代理服务,这里不再演示。

有选择性的中转

在上述代码基础上,对MyInvocationHandler做如下修改

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if(method.getName().equals("sellMonitor")){
            //如果是出售显示器则不进行售后服务
            method.invoke(producer, args);
            return null;
        }
        System.out.println("进行售前服务");
        method.invoke(producer, args);
        System.out.println("进行售后服务");
        return null;
    }
}

不对销售的显示器进行售后服务。那么再次调用

public class ProxyTestDemo {

    public static void main(String[] args) {
        IProducerImpl iProducer = new IProducerImpl();

        InvocationHandler handler = new MyInvocationHandler(iProducer);

        IProducer producer =(IProducer) Proxy.newProxyInstance(IProducerImpl.class.getClassLoader(),
                IProducerImpl.class.getInterfaces(),
                handler);

        producer.sellMonitor();


    }
}

则结果中就没有售前售后服务

 出售了一台显示器

可以看到利用动态代理某个接口下的多个引用可以集中到一处进行方法增强或者有选择性的进行代理工作。

动态代理的优点

与静态代理相比较,最大的好处是接口中的所有方法都转移到调用处理器的一个集中的方法(InvocationHandler.invoke)进行处理。同时接口方法数量较多时也可以灵活处理。

部分源码解析

未完待续

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容