结构型模式:代理模式(Proxy)

参考资料

https://www.cnblogs.com/leeego-123/p/10995975.html

代理模式理解

1.代理模式是对象的结构型模式,代理模式用于给某个对象提供一个代理对象,并由代理来控制对原有对象的引用;
2.代理对象的根本目的还是对原有对象进行控制,注意,虽然和装饰器很像,但是从用意上来讲,代理模式是用来对原有对象进行访问控制的;
3.代理按照分类可以分为静态代理、动态代理、Cglib代理

代理模式的结构

1.抽象对象角色:定义了原始对象和代理对象的共同接口,在共同实现该接口的基础上,在任何可以使用原有对象的地方都可以使用代理对象,即可以实现代理对象对原有对象的代理和替换;
2.目标对象角色:定义了被代理的对象;
3.代理对象角色:代理对象内部含有对目标对象(或者原来对象)的引用,从而可以在任何时候操作原来对象;代理对象实现了与原来对象一样的接口,从而可以在任何时候替代原有对象。带来对象通常在客户端调用原来对象之前或者之后,执行某个操作,而不是将调用单纯地传递给原来对象;

代理模式 & 装饰者模式 & 适配器模式 的区别

代理模式是给对象提供一个代理对象,并通过代理对象来控制对原有对象的引用;
装饰模式中也有对原有对象的引用,只是在此基础上扩展原有对象的功能;
适配器模式中也有对原有对象的引用,但是是用于将原有接口切换到一个新的接口上面去;

装饰模式是用于对被装饰对象进行功能增强的,而代理模式是对被代理的对象进行控制,并不提供对对象的增强,二者的实现机制确实是一样的,可以看到他们的实例中重复代码很多,但是就语义上来说,这两者的功能是不同的,设计模式的一个重要作用是简化其他程序员对程序的理解。你说是装饰模式,大家就知道你是在增强功能,你写代理,大家知道你是在限制对对象的访问,虽然代码可能相同,但是如果都叫做装饰模式,会让人困惑,这不是设计模式想要实现的效果。

他们其实本质上原理是一样的,都是持有原有对象的引用,然后再进行修饰或者扩展,但是因为具体使用的目的不同,所以定义为了不同的设计模式。即设计模式是用来解决一些场景问题的设计方法,目的不同,用途不同,就使用不同的设计模式,即设计模式是按照使用场景这种精细粒度来划分的,可能本质上很相似,但是其实是解决不同问题的,设计语义不同,所以也划分为不同的设计模式。

代理模式 * 装饰模式在具体上的一些差别

相似之处:装饰模式中装饰类和被装饰类都实现同一个接口,对代理模式来说,代理类和被代理类也都实现同一个接口,不论使用哪一个模式,都可以很容易地实现在原有对象的方法前后加上自定义的方法;

不同之处:装饰器模式关注在一个对象上怎么动态地添加方法,然后代理模式关注于控制对于对象的访问,换句话说,用代理模式,代理类可以对客户端隐藏一个对象的具体信息,因此在使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例,当使用装饰器模式的时候,通常是将原始的对象作为一个参数传给装饰者的构造器;

总结:使用代理模式,代理和原始对象之间的关系通常在编译期间就已经确定了,而装饰者能够在运行时递归地构造出关联关系。

静态代理

静态代理要求被代理对象和代理对象都实现同一个接口或者继承自相同的父类;
缺点:因为代理对象需要与目标对象实现一样的接口,因此会有很多的代理类,并且如果原来的类增加方法,那么需要一起维护代理类;

结构型模式:代理模式(Proxy)

静态代理案例

抽象对象角色

package com.taobao.migao.pattern.proxy.staticproxy;

/**
 * @author <a href="mailto:migao.jjm@alibaba-inc.com">jiongmin.jjm</a>
 * @version 1.0
 * @description 代理模式:抽象接口:代理类和被代理类需要共同实现
 * @since 2021/3/14
 */
public interface LocWriteService {
    /**
     * 修改单据
     */
    void modifyOrder();
}

目标类:被代理类

package com.taobao.migao.pattern.proxy.staticproxy;

/**
 * @author <a href="mailto:migao.jjm@alibaba-inc.com">jiongmin.jjm</a>
 * @version 1.0
 * @description 代理模式:原始类:目标类:被代理类
 * @since 2021/3/14
 */
public class LocWriteServiceImpl implements LocWriteService{
    @Override
    public void modifyOrder() {
        System.out.println("订单已经被修改");
    }
}

代理类

package com.taobao.migao.pattern.proxy.staticproxy;

/**
 * @author <a href="mailto:migao.jjm@alibaba-inc.com">jiongmin.jjm</a>
 * @version 1.0
 * @description 代理模式:代理类:持有原始类
 * @since 2021/3/14
 */
public class LocWriteProxy implements LocWriteService {
    /**
     * 原有类:被代理类对象
     */
    private LocWriteService locWriteService;

    @Override
    public void modifyOrder() {
        System.out.println("已经记录修改前日志");
        locWriteService.modifyOrder();
        System.out.println("已经记录修改后日志");
    }

    public void setLocWriteService(LocWriteService locWriteService) {
        this.locWriteService = locWriteService;
    }
}

测试类 -客户端

package com.taobao.migao.pattern.proxy.staticproxy;
/**
 * @author <a href="mailto:migao.jjm@alibaba-inc.com">jiongmin.jjm</a>
 * @version 1.0
 * @description 静态代理测试类:客户端,直接与代理类交互
 * @since 2021/3/14
 */
public class StaticProxyTest {
    public static void main(String[] args) {
        //原始对象,被代理对象,实际中通过Spring初始化和注入
        LocWriteService locWriteService = new LocWriteServiceImpl();
        LocWriteProxy proxy = new LocWriteProxy();
        (proxy).setLocWriteService(locWriteService);
        //通过调用代理类来实现目标
        proxy.modifyOrder();
    }
}

动态代理

1.动态代理可以解决静态代理中的不足,动态代理的特点如下:

  • 动态代理基于JDK的工具包实现,又叫做JDK代理,接口代理;
  • 代理对象不需要实现接口,因为直接由JDK工具类返回代理对象,所以不需要自己创建;
  • 代理对象的生成,是利用JDK的API,动态在内存中构建代理对象,需要我们指定目标对象的接口类型;
  • JDK动态代理其实是根据被代理的接口,对接口中的方法进行代理,即是方法级别代理;

2.具体使用JDK动态代理
使用Proxy.newProxyInstance()方法来创建动态代理对象,该方法具有3个参数:

  • ClassLoader loader:指定目标对象当前使用的类加载器,固定使用target.getClass().getClassLoader()获取;
    理解:要使用动态代理,首先需要取到当前对象,即被代理对象,显然也可以据此取到被代理对象的类加载器。
  • Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方式确认类型;
    理解:要使用动态代理,目标对象必须有对应的接口,动态代理基于接口中的方法做代理,如果没有接口无法代理,即JVM不知道需要代理哪些方法。
  • InvocationHandler h:事件处理器,当后续执行目标对象的方法时,会通过生成的代理对象来访问,此时JDK的动态代理机制会调用InvocationHandler的invoke()方法,会把每次调用时的目标对象的方法和方法的参数传入到次方法传入到invoke()中。

invoke()方法:

  • Object proxy:被代理对象,从上下文可以获取;
  • Method method:当前调用的方法,从上下文可以获取;
  • Object[] args:当前调用方法的参数,从上下文可以直接获取;

理解:即在使用newProxyInstance创建动态代理对象的时候,第三个参数用来定义调用方法的环绕切面,可以在后续代理对象被调用的时候做一些通用的事情;
invoke()是JDK代理的核心方法,代理对象执行目标方法的时候会回调此对象的invoke()方法,需要在代理中做的管控逻辑和增强逻辑,就在这里添加。但是这里是通用代码,即对代理类中的每个方法调用都会经过这个handler处理,如果对不同的方法有不同的管控逻辑,需要自己在invoke()方法里面进行分别处理。

抽象对象角色:被代理类还是需要有接口,JDK基于接口方法做代理

package com.taobao.migao.pattern.proxy.dynamicproxy;
/**
 * @author <a href="mailto:migao.jjm@alibaba-inc.com">jiongmin.jjm</a>
 * @version 1.0
 * @description JDK动态代理:被代理对象需要有接口,代理对象不需要依赖接口
 * @since 2021/3/14
 */
public interface LocReadService {
    /**
     * 查询订单
     */
    void readOrder();

    /**
     * 查询订单列表
     */
    void readOrderList();
}

目标角色:被代理类

package com.taobao.migao.pattern.proxy.dynamicproxy;

/**
 * @author <a href="mailto:migao.jjm@alibaba-inc.com">jiongmin.jjm</a>
 * @version 1.0
 * @description JDK动态代理:被代理对象
 * @since 2021/3/14
 */
public class LocReadServiceImpl implements LocReadService {
    @Override
    public void readOrder() {
        System.out.println("订单已经被读取");
    }

    @Override
    public void readOrderList() {
        System.out.println("订单列表已经被读取");
    }
}

代理类角色:代理类由JDK创建,但是代理类的管控逻辑需要自定义

package com.taobao.migao.pattern.proxy.dynamicproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author <a href="mailto:migao.jjm@alibaba-inc.com">jiongmin.jjm</a>
 * @version 1.0
 * @description JDK动态代理:使用JDK来生成动态代理对象,此处使用简单工厂来封装
 * @since 2021/3/14
 */
public class ProxyFactory {
    /**
     * 被代理对象,要创建代理对象,必须先取得被代理对象
     */
    private Object target;

    public Object getInstanceProxy() {
        //1.被代理类的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();

        //2. 被代理类的上层,可以有多个,JDK基于这些接口中的方法做方法代理
        Class<?>[] interfaces = target.getClass().getInterfaces();

        /**
         * 3.JDK代理的核心方法
         * 代理对象执行目标方法的时候会回调此对象的invoke()方法,需要在代理中做的管控逻辑和增强逻辑,就在这里添加
         * 但是这里是通用代码,即对代理类中的每个方法调用都会经过这个handler处理,如果对不同的方法有不同的管控逻辑,
         * 需要自己在invoke()方法里面进行分别处理
         */
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object invokeResult;
                if ("readOrder".equals(method.getName())) {
                    System.out.println("=====读取订单监控开始=====");
                    invokeResult = method.invoke(target, args);
                    System.out.println("=====读取订单监控结束=====");
                } else if ("readOrderList".equals(method.getName())) {
                    System.out.println("=====读取订单列表监控开始=====");
                    invokeResult = method.invoke(target, args);
                    System.out.println("=====读取订单列表监控结束=====");
                } else {
                    invokeResult =  method.invoke(target, args);
                }
                return invokeResult;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }
    public void setTarget(Object target) {
        this.target = target;
    }
}

客户端:测试类

package com.taobao.migao.pattern.proxy.dynamicproxy;

/**
 * @author <a href="mailto:migao.jjm@alibaba-inc.com">jiongmin.jjm</a>
 * @version 1.0
 * @description JDK动态代理测试:客户端:先获取代理对象,然后使用代理对象实现调用
 * @since 2021/3/14
 */
public class DynamicProxyTest {
    public static void main(String[] args) {
        //1.被代理对象,原始对象
        LocReadService target = new LocReadServiceImpl();

        //2.通过代理工厂获取一个代理对象
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(target);
        LocReadService proxy = (LocReadService) factory.getInstanceProxy();

        //3.通过代理对象调用方法
        proxy.readOrder();
        proxy.readOrderList();
    }
}

结果

结构型模式:代理模式(Proxy)

cglib代理

静态代理要求:被代理类和代理类都实现一个接口;
JDK动态代理要求:被代理类必须来自一个接口,代理类可以没有接口;
cglib代理:不要求代理类和被代理类有接口;

即选择哪种代理模式根据具体的情况来决定,如果代理类和被代理类都有接口,且接口相同,那么可以使用静态代理;如果代理类无法实现接口,那么可以使用JDK动态代理;如果代理类和被代理类都没有接口,那么只能使用cglib动态代理。

cglib代理原理

1.上面的静态代理和动态代理模式都是要求目标对象实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这时候就可以以目标对象子类的方式来实现代理,这种模式叫做cglib代理;
2.cglib代理也叫做子类代理,他是在内存中构建一个子类对象从而实现对目标对象功能的扩展;

  • JDKd的动态代理有一个现实,就是使用动态代理的独享必须实现一个或者多个接口,如果想代理没有实现接口的类,就可以使用cglib来实现;
  • cglib是一个强大的高性能代码生成包,它可以在运行期间扩展jav类和实现java接口,它广泛地被使用在许多AOP框架中,例如Spring AOP和SYN SOP,为他们提供intercep拦截;
  • cblib底层是通过使用一个小而快的字节码处理框架ASM来转换字节码并生成新的类,不推荐直接使用ASM,因为它必须要求你对JVM内部结构包括class文件的格式和指令集都很熟悉;

cglib子类代理的实现方法

1.需要引入cglib的jar包,但是spring核心包中已经包括了cglib功能,所以直接引入spring包就可以;
2.引入功能包后,就可以在内存中动态构建子类;
3.被代理的类不能为final,否则报错;(因为cglib基于生成子类来代理,而final类无法创建子类)
4.目标对象的方法如果为final/static的,不会被拦截,即不会执行目标对象原有放放风之外的逻辑;(因为final方法无法被重写,static方法无法在对象中重写,所以这些方法就不支持,不会对其拦截,直接放行);

cglib代理案例:被final修饰的方法不会被代理,不拦截

被代理对象:是个类,没有对象接口:注意有final的方法和无final的方法

package com.taobao.migao.pattern.proxy.cglibproxy;

/**
 * @author <a href="mailto:migao.jjm@alibaba-inc.com">jiongmin.jjm</a>
 * @version 1.0
 * @description cglib代理:原始类,没有实现接口,只能使用cglib基于子类进行代理
 * @since 2021/3/14
 */
public class LocModifyOrderImpl {

    /**
     * 修改订单标签,无final,可以被子类重写
     */
    void modifyOrderFeature() {
        System.out.println("订单标签已经被修改");
    }

    /**
     * 修改订单创建时间,有final,不允许被子类重写
     */
    final void modifyOrderCreateTime() {
        System.out.println("订单创建时间,不允许被继承和修改");
    }
}

代理工厂,用来生成代理对象

package com.taobao.migao.pattern.proxy.cglibproxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * @author <a href="mailto:migao.jjm@alibaba-inc.com">jiongmin.jjm</a>
 * @version 1.0
 * @description cglib代理:持有被代理对象
 * @since 2021/3/14
 */
public class CglibProxyFactory implements MethodInterceptor {
    /**
     * 被代理对象,没有接口,就是个普通类
     */
    private LocModifyOrderImpl target;

    /**
     * 使用cglib模式创建一个代理对象
     */
    public Object getProxyInstance() {
        //1.使用cglib的加强器api
        Enhancer enhancer = new Enhancer();
        //2.设置代理类的父类
        enhancer.setSuperclass(target.getClass());
        //3.设置回调函数
        enhancer.setCallback(this);
        return enhancer.create();
    }

    /**
     * cglib代理的核心方法,每次调用的管控逻辑写在这里,如果对于不同方法需要不同处理,就在这里自己做区分
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object invokeResult;
        if ("modifyOrderFeature".equals(method.getName())) {
            System.out.println("====开始执行修改订单标签====");
            invokeResult = method.invoke(target, objects);
            System.out.println("====结束执行修改订单标签====");
        } else if ("modifyOrderCreateTime".equals(method.getName())) {
            System.out.println("====开始执行修改订单创建时间====");
            invokeResult = method.invoke(target, objects);
            System.out.println("====结束执行修改订单创建时间====");
        } else {
            invokeResult = method.invoke(target, objects);
        }
        return invokeResult;
    }

    public void setTarget(LocModifyOrderImpl target) {
        this.target = target;
    }
}

测试类:客户端,通过代理对象访问目标方法

package com.taobao.migao.pattern.proxy.cglibproxy;
/**
 * @author <a href="mailto:migao.jjm@alibaba-inc.com">jiongmin.jjm</a>
 * @version 1.0
 * @description 测试类:客户端
 * @since 2021/3/14
 */
public class CglibProxzTest {
    public static void main(String[] args) {
        //被代理类,没有接口,普通类
        LocModifyOrderImpl target = new LocModifyOrderImpl();

        //创建代理对象工厂
        CglibProxyFactory factory = new CglibProxyFactory();
        factory.setTarget(target);

        //通过代理工厂获取一个代理对象,将其转换为和被代理类相同的类型
        LocModifyOrderImpl proxy = (LocModifyOrderImpl) factory.getProxyInstance();
        proxy.modifyOrderFeature();
        proxy.modifyOrderCreateTime();
    }
}

结果:被代理类modifyOrderCreateTime没有final的情况下

结构型模式:代理模式(Proxy)

结果:被代理类modifyOrderCreateTime有final的情况下

结构型模式:代理模式(Proxy)

上一篇:模拟Java动态代理模式:CGLIB动态代理


下一篇:面向切面编程AOP