Spring5入门-10-静态代理与动态代理

一、前言

为什么要学代理模式?因为这就是SpringAOP的底层!

代理模式的分类:

  • 静态代理
  • 动态代理


二、角色分析

  • 抽象角色:一般会使用接口或抽象类
  • 真实角色:被代理角色
  • 代理角色:代理真实的角色,代理角色后,我们一般会做一些附属操作
  • 客户:访问代理角色的人

image-20201001194736291



三、准备代码

3.1 租户->房东

在租户直接面对房东的模式中:

路径

image-20201001215210043


代码

Rent.java:

package demo01;
/**
 * @Autord: HuangDekai
 * @Date: 2020/10/1 20:29
 * @Version: 1.0
 * @since: jdk11
 */
public interface Rent {
    void getRent();
}

Landlord.java:

package demo01;
/**
 * @Autord: HuangDekai
 * @Date: 2020/10/1 20:25
 * @Version: 1.0
 * @since: jdk11
 */
public class Landlord implements Rent{
    @Override
    public void getRent(){
        System.out.println("房东获取租金。");
    }
}

Client.java:

package demo01;
/**
 * @Autord: HuangDekai
 * @Date: 2020/10/1 20:27
 * @Version: 1.0
 * @since: jdk11
 */
public class Client {
    public static void main(String[] args) {
        Landlord landlord = new Landlord();
        landlord.getRent();
    }
}

3.2 租户->中介->房东

但是在实际情况中,租户往往是找不到房东的,而房东又是只想收租,不想做找租客、维护等的工作,那么这时候就要引入一个中介:

image-20201001215255046

Proxy:

package demo01;
/**
 * @Autord: HuangDekai
 * @Date: 2020/10/1 20:47
 * @Version: 1.0
 * @since: jdk11
 */
public class Proxy implements Rent{
    private Landlord landlord;

    public Proxy() {
    }

    public Proxy(Landlord landlord) {
        this.landlord = landlord;
    }


    @Override
    public void getRent() {
        landlord.getRent();
    }
}

这时候Client.java也要修改:

package demo01;
/**
 * @Autord: HuangDekai
 * @Date: 2020/10/1 20:27
 * @Version: 1.0
 * @since: jdk11
 */
public class Client {
    public static void main(String[] args) {
        Landlord landlord = new Landlord();
        Proxy proxy = new Proxy(landlord);
        proxy.getRent();
    }
}

运行可知,这依旧是让房东收房租,即,收租这个操作依旧是让房东运行的。

那么加这个代理人有什么作用呢?现在还没体现出来。

那么比如,要加入招租、收中介费、维护房子的操作,这些不是房东想要做的,房东只想收租。

而这些是代理人要做的,这些就是附属操作。

修改Proxy.java:

package demo01;
/**
 * @Autord: HuangDekai
 * @Date: 2020/10/1 20:47
 * @Version: 1.0
 * @since: jdk11
 */
public class Proxy implements Rent{
    private Landlord landlord;

    public Proxy() {
    }

    public Proxy(Landlord landlord) {
        this.landlord = landlord;
    }


    public void forLease(){
        System.out.println("中介招租");
    }

    public void maintain(){
        System.out.println("维修房子");
    }

    public void agencyFee(){
        System.out.println("中介获取中介费");
    }

    @Override
    public void getRent() {
        this.forLease();
        this.agencyFee();
        landlord.getRent();
        this.maintain();
    }
}

Client.javaLandlord.java的代码没变,但现在运行main就变成了:

image-20201001211201095



四、静态代理小结

代码步骤:

  1. 接口
  2. 真实角色
  3. 代理角色
  4. 客户

代理模式的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务;
  • 公共业务也就交给代理角色,实现了业务的分工;
  • 公共业务发生扩展的时候,方便集中管理。

缺点:

  • 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率降低


五、加深理解

5.1 Service->ServiceImpl

路径

image-20201002102028013

代码

Service

package demo02;

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/1 22:08
 * @Version: 1.0
 * @since: jdk11
 */
public interface Service {
    void add();
    void delete();
    void update();
    void query();
}

ServiceImp:

package demo02;

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/1 22:09
 * @Version: 1.0
 * @since: jdk11
 */
public class ServiceImpl implements Service {
    @Override
    public void add() {
        System.out.println("执行add()");
    }

    @Override
    public void delete() {
        System.out.println("执行delete()");
    }

    @Override
    public void update() {
        System.out.println("执行update()");
    }

    @Override
    public void query() {
        System.out.println("执行query()");
    }
}

Controller:

package demo02;

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/2 10:17
 * @Version: 1.0
 * @since: jdk11
 */
public class Controller {
    public static void main(String[] args) {
        Service service = new ServiceImpl();
        service.add();
        service.delete();
        service.update();
        service.query();
    }
}

image-20201002102143059

这个时候假设dao---->service---->controller---->前端的代码已经完成了,由于业务量的增大,要在业务层使用日志功能。

由于巨大的代码量(假设有),直接对ServiceImpl去进行修改是很不现实的操作,那么就要加入代理。


5.2 ServiceImplProxy

路径

image-20201002103721093

代码

ServiceImplProxy

package demo02;

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/2 10:22
 * @Version: 1.0
 * @since: jdk11
 */
public class ServiceImplProxy implements Service {

    private Service service;

    public void setService(Service service) {
        this.service = service;
    }

    private void log(String log){
        System.out.println("[Debug]"+log);
    }

    @Override
    public void add() {
        log("add()");
        service.add();
    }

    @Override
    public void delete() {
        log("delete()");
        service.delete();
    }

    @Override
    public void update() {
        log("update()");
        service.update();
    }

    @Override
    public void query() {
        log("query()");
        service.query();
    }
}

由于没有用Spring做控制反转,所以还需要修改Controller这个cilent:

package demo02;

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/2 10:17
 * @Version: 1.0
 * @since: jdk11
 */
public class Controller {
    public static void main(String[] args) {
        Service service = new ServiceImpl();
        ServiceImplProxy serviceProxy = new ServiceImplProxy();
        serviceProxy.setService(service);

        serviceProxy.add();
        serviceProxy.delete();
        serviceProxy.update();
        serviceProxy.query();
    }
}

运行结果:

image-20201002103850959



六、AOP

image-20201002104203110



七、动态代理

  • 动态代理和静态代理角色一样
  • 动态代理的代理类是动态生产的,不是我们直接写好的
  • 动态代理:
    • 基于接口的动态代理------如:JDK动态代理
    • 基于类的动态代理---------如:cglib
    • java字节码实现-------------如:JAVAssist

需要了解:Proxy、InvocationHandler。

InvocationHandler是一个接口类,

public interface InvocationHandler

InvocationHandler是由代理实例的调用处理程序实现的接口

每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke方法。

它只有一个invoke方法:

Object invoke(Object proxy,
              Method method,
              Object[] args)
       throws Throwable

处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。

  • 参数

    proxy - 调用该方法的代理实例

    method -所述Method对应于调用代理实例上的接口方法的实例。 Method对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。

    args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。 原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integerjava.lang.Boolean

  • 结果

    从代理实例上的方法调用返回的值。 如果接口方法的声明返回类型是原始类型,则此方法返回的值必须是对应的基本包装类的实例; 否则,它必须是可声明返回类型的类型。 如果此方法返回的值是null和接口方法的返回类型是基本类型,那么NullPointerException将由代理实例的方法调用抛出。 如上所述,如果此方法返回的值,否则不会与接口方法的声明的返回类型兼容,一个ClassCastException将代理实例的方法调用将抛出。

  • 异常

    Throwable - 从代理实例上的方法调用抛出的异常。 异常类型必须可以分配给接口方法的throws子句中声明的任何异常类型java.lang.RuntimeException检查的异常类型java.lang.RuntimeExceptionjava.lang.Error 。 如果检查的异常是由这种方法是不分配给任何的中声明的异常类型throws接口方法的子句,则一个UndeclaredThrowableException包含有由该方法抛出的异常将通过在方法调用抛出代理实例。


而java.lang.reflect.Proxy只用到newProxyInstance这个静态方法:

newProxyInstance

public static Object newProxyInstance(ClassLoader loader,
                                      类<?>[] interfaces,
                                      InvocationHandler h)
                               throws IllegalArgumentException

返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。

Proxy.newProxyInstance因为与IllegalArgumentException相同的原因而Proxy.getProxyClass

  • 参数

    loader - 类加载器来定义代理类

    interfaces - 代理类实现的接口列表

    h - 调度方法调用的调用处理函数

  • 结果

    具有由指定的类加载器定义并实现指定接口的代理类的指定调用处理程序的代理实例

  • 异常

    IllegalArgumentException - 如果对可能传递给 getProxyClass有任何 getProxyClass被违反

    SecurityException -如果安全管理器,S存在任何下列条件得到满足:给定的loadernull ,并且调用者的类加载器不是null ,并且调用s.checkPermissionRuntimePermission("getClassLoader")权限拒绝访问;对于每个代理接口, intf ,呼叫者的类加载器是不一样的或类加载器的祖先intf和调用s.checkPackageAccess()拒绝访问intf ;任何给定的代理接口的是非公和呼叫者类是不在同一runtime package作为非公共接口和调用s.checkPermissionReflectPermission("newProxyInPackage.{package name}")权限拒绝访问。

    NullPointerException - 如果 interfaces数组参数或其任何元素是 null ,或者如果调用处理程序 hnull


路径

image-20201002130256466

代码

Rent:

package demo03;

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/1 20:29
 * @Version: 1.0
 * @since: jdk11
 */
public interface Rent {
    void getRent();
}

Landlord:

package demo03;

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/1 20:25
 * @Version: 1.0
 * @since: jdk11
 */
public class Landlord implements Rent {
    @Override
    public void getRent(){
        System.out.println("房东获取租金。");
    }
}

ProxyInvocationHandler:

package demo03;

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

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/2 11:41
 * @Version: 1.0
 * @since: jdk11
 */

// 这个类用于自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    // 被代理的接口
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    // 生成得到代理类
    public Object getProxy(){
        // 注意,这个Proxy是java.lang.reflect包下的
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
    }

    // 处理代理实例,并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object result = method.invoke(rent,args);
        return result;
    }

    public void log(String msg){
        System.out.println("执行了"+msg+"方法");
    }
}

Client:

package demo03;

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/2 11:51
 * @Version: 1.0
 * @since: jdk11
 */
public class Client {
    public static void main(String[] args) {
        Rent rent = new Landlord();
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setRent(rent);

        Rent proxy = (Rent) pih.getProxy();
        proxy.getRent();
    }
}

运行结果:

image-20201002132742348



八、加深理解

和我一样对于动态代理类怎么调用invoke()有疑问的可以看看:Java SDK动态代理类怎么调用到invoke()方法?

路径

image-20201002144238686

代码

ProxyInvocationHandler:

package demo4;

import demo02.Service;

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

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/2 13:07
 * @Version: 1.0
 * @since: jdk11
 */
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Service service;

    public void setService(Service service) {
        this.service = service;
    }

    //生成得到一个代理类的实例
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),service.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object result = method.invoke(service,args);
        return result;
    }

    public void log(String msg){
        System.out.println("[debug]"+msg);
    }
}

Client:

package demo4;

import demo02.Service;
import demo02.ServiceImpl;

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/2 13:25
 * @Version: 1.0
 * @since: jdk11
 */
public class Client {
    public static void main(String[] args) {

        //真实角色
        Service service = new ServiceImpl();

        //代理角色不存在
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setObject(service);  //设置要代理的对象
        Service proxy = (Service) pih.getProxy(); //动态生成代理类

        proxy.add();
        proxy.delete();
        proxy.update();
        proxy.query();
    }
}

结果:

image-20201002144403969



九、对于使用动态代理

其实就应用的角度来说,动态代理直接使用上面的代码修改一下就能有较高的复用性了:

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

/**
 * @Autord: HuangDekai
 * @Date: 2020/10/2 13:07
 * @Version: 1.0
 * @since: jdk11
 */
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Object target;

    public void setObject(Object taget) {
        this.target = taget;
    }

    //生成得到一个代理类的实例
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //添加某些附加方法.....
        Object result = method.invoke(target,args);
        //添加某些附加方法.....
        return result;
    }

}

参照上面的代码,把taget的类型换成要代理的类的接口,就基本可行。再在invoke里加入一些附加方法,就是了。


动态代理模式的好处:

  • 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务;
  • 公共业务也就交给代理角色,实现了业务的分工;
  • 公共业务发生扩展的时候,方便集中管理;
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务;
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口。
posted @ 2020-10-02 14:59  杜撰丶  阅读(118)  评论(0编辑  收藏  举报