动态代理模式

GoF之代理模式

概述

代理模式是GoF23种设计模式之一。属于结构型设计模式。

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。

  • 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式
  • 通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。

代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。

在程序中,对象A和对象B无法直接交互时。((现实生活中的婚介所)

在程序中,功能需要增强时。(现实生活中的房产中介)

  • 在程序中,目标需要被保护时

Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。

结构

代理(Proxy)模式分为三种角色:

  • 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
    • 客户端在使用代理类时就像在使用目标类,不被客户端所察觉,所以代理类和目标类要有共同的行为,也就是实现共同的接口。
  • 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  • 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

应用场景

业务场景:系统中有A、B、C三个模块,使用这些模块的前提是需要用户登录,也就是说在A模块中要编写判断登录的代码,B模块中也要编写,C模块中还要编写,这些判断登录的代码反复出现,显然代码没有得到复用,可以为A、B、C三个模块提供一个代理,在代理当中写一次登录判断即可。代理的逻辑是:请求来了之后,判断用户是否登录了,如果已经登录了,则执行对应的目标,如果没有登录则跳转到登录页面。【在程序中,目标不但受到保护,并且代码也得到了复用。】

代理模式的类图

动态代理模式_第1张图片

静态代理

概述

OrderService接口是代理类和目标类的共同接口

public interface OrderService {
    /**
     * 生成订单
     */
    void generate();

    /**
     * 查看订单详情
     */
    void detail();

    /**
     * 修改订单
     */
    void modify();
}

订单接口的实现类OrderServiceImpl即目标类

  • 其中Thread.sleep()方法的调用是为了模拟操作耗时
public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public void modify() {
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }
}

对系统的运行速度进行优化 , 需要统计每个业务方法所耗费的时长

第一种方案:直接修改Java源代码,在每个业务方法中添加统计逻辑

  • 需求可以满足,但显然是违背了OCP开闭原则。这种方案不可取
public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }
}

第二种方案:编写一个子类继承 OrderServiceImpl,在子类中重写每个方法 (在子类中调用父类的方法)

  • 第一个问题:假设系统中有100个这样的业务类,需要提供100个子类,并且之前写好的创建Service对象的代码,都要修改为创建子类对象。
  • 第二个问题:由于采用了继承的方式,导致代码之间的耦合度较高。
public class OrderServiceImplSub extends OrderServiceImpl{
    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        super.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        super.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        super.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }
}

第三种方案:使用静态代理模式 , 编写一个代理类 OrderServiceProxy 实现 OrderService 接口(在代理对象中调用目标对象的目标方法)

  • 这种方式的优点:符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的。
public class OrderServiceProxy implements OrderService{ // 代理对象

    // 目标对象
    private OrderService orderService;

    // 通过构造方法将目标对象传递给代理对象
    public OrderServiceProxy(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public void generate() {
        long begin = System.currentTimeMillis();
        // 执行目标对象的目标方法
        orderService.generate();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {
        long begin = System.currentTimeMillis();
        // 执行目标对象的目标方法
        orderService.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {
        long begin = System.currentTimeMillis();
        // 执行目标对象的目标方法
        orderService.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
    }
}

编写客户端程序

public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        OrderService proxy = new OrderServiceProxy(target);
        // 调用代理对象的代理方法
        proxy.generate();
        proxy.modify();
        proxy.detail();
    }
}

动态代理

概述

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

在内存当中动态生成类的技术常见的包括

  • JDK动态代理技术:只能代理接口
  • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM)
  • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

JDK动态代理实现步骤

第一步: 提供订单接口和订单接口的实现类(目标类)

  • 在动态代理中UserServiceProxy代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可
public interface OrderService {
    /**
     * 生成订单
     */
    void generate();

    /**
     * 查看订单详情
     */
    void detail();

    /**
     * 修改订单
     */
    void modify();
}

订单接口的实现类(目标类)

public class OrderServiceImpl implements OrderService {
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public void modify() {
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }
}

java.lang.reflect.InvocationHandler调用处理器接口的实现类,并且实现接口中的方法invoke , 在invoke方法中调用目标对象的目标方法并对其功能增强

  • 我们可以给TimerInvocationHandler提供一个构造方法,可以通过这个构造方法传过来“目标对象” , 然后在invoke方法执行过程中,使用method来调用目标对象的目标方法
// 专门负责计时的一个调用处理器对象。在这个调用处理器当中编写计时相关的增强代码。这个调用处理器只需要写一个就行了。
public class TimerInvocationHandler implements InvocationHandler {

    // 目标对象
    private Object target;

    public TimerInvocationHandler(Object target) {
        // 赋值给成员变量。
        this.target = target;
    }

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

        // 这个接口的目的就是为了让你有地方写增强代码。
        long begin = System.currentTimeMillis();

        // 调用目标对象上的目标方法
        // 方法四要素:哪个对象,哪个方法,传什么参数,返回什么值。
        Object retValue = method.invoke(target, args);

        //System.out.println("增强2");
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");

        // 注意这个invoke方法的返回值,如果我们调用的代理对象的代理方法有返回值的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
        return retValue;
    }
}

客户端程序

动态代理模式_第2张图片

public class Client {
    public static void main(String[] args) {
        // 第一步:创建目标对象
        OrderService target = new OrderServiceImpl();
        // 第二步:创建代理对象
        OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);
        // 第三步:调用代理对象的代理方法
        // 注意:调用代理对象的代理方法的时候,如果你要做增强的话,目标对象的目标方法得保证执行。
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
        
    }
}

我们可以提供一个工具类:ProxyUtil,封装一个方法 , 只要传递一个目标对象就可以通过这个方法获取代理对象

public class ProxyUtil {
    public static Object newProxyInstance(Object target){
        // 底层是调用的还是JDK的动态代理。
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimerInvocationHandler(target));
    }

}

提供一个工具类:ProxyUtil,封装一个方法 , 使用匿名内部类

public class ProxyUtil {
    
    public static Object newProxyInstance(Object target){
        // 底层是调用的还是JDK的动态代理。
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimerInvocationHandler(target){
                	 // 目标对象
    				private Object target;
    				public TimerInvocationHandler(Object target) {
        				// 赋值给成员变量。
        				this.target = target;
    				}
                    
                    @Override
    				public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        				// 这个接口的目的就是为了让你有地方写增强代码。
        				long begin = System.currentTimeMillis();

        				// 调用目标对象上的目标方法
        				// 方法四要素:哪个对象,哪个方法,传什么参数,返回什么值。
       					Object retValue = method.invoke(target, args);
       		 			long end = System.currentTimeMillis();
        				System.out.println("耗时"+(end - begin)+"毫秒");

        				//如果我们调用的代理对象的代理方法有返回值的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
        				return retValue;
    				}    
                });
    }

}

客户端代码就不需要写那么繁琐

public class Client {
    public static void main(String[] args) {
        // 创建目标对象
        OrderService target = new OrderServiceImpl();
        // 创建代理对象
        OrderService orderServiceProxy = (OrderService) ProxyUtil.newProxyInstance(target);
        // 调用代理对象的代理方法
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}

JDK动态代理程序总结

java.lang.reflect.Proxy是 JDK 提供的一个动态代理类 , 它不是代理对象的类 , 主要是通过这个类在内存中生成代理类的字节码

**newProxyInstance方法翻译为:新建代理对象 **, 是Proxy类提供了一个创建代理对象的静态方法 , Proxy.newProxyInstance()方法的执行,做了两件事

  • 第一件事:在内存中动态的生成了一个代理类的字节码class。
  • 第二件事:new对象了。通过内存中生成的代理类这个代码,实例化了代理对象。

JDK动态生成的代理对象和目标对象的唯一联系就是它们都实现了同一个接口 , 所以可以向下转型

  • 代理类($Proxy0)将我们在Proxy.newProxyInstance方法参数中的匿名内部类对象传递给了父类Proxy。

关于newProxyInstance()方法的三个重要的参数的含义

  • 第一个参数:类加载器。在内存中生成了字节码,要想执行这个字节码,也是需要先把这个字节码加载到内存当中的。所以要指定使用哪个类加载器加载。并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。
  • 第二个参数:接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口。
  • 第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler。显然这是一个回调接口,也就是说调用这个接口中方法的程序已经写好了,就差这个接口的实现类了。在调用处理器接口中编写的就是:增强代码。

自己动手写调用处理器接口的实现类,不会类爆炸 , 因为这种调用处理器只需要写一次就好。代码得到复用

  • 不管你有多少个Service接口,多少个业务类,因为我们的代理对象是在程序运行中动态生成的 , 可以实现任何接口 , 代码不会写死
  • 而且最重要的是,以后程序员只需要关注核心业务的编写了,像这种统计时间的代码根本不需要关注。因为这种统计时间的代码只需要在调用处理器中编写一次即可。

InvocationHandler接口中invoke方法上的三个参数

  • 第一个参数:Object proxy。代理对象。设计这个参数只是为了后期的方便,如果想在invoke方法中使用代理对象的话,尽管通过这个参数来使用。
  • 第二个参数:Method method。目标方法。
  • 第三个参数:Object[] args。目标方法调用时要传的参数。

InvocationHandler接口中invoke方法的调用

  • 当你每调用一次代理对象的代理方法的时候,注册在InvocationHandler接口中的invoke()方法就会被JDK调用。不用你手动调用
  • invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数。 我们可以在invoke方法的大括号中直接使用。

动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)

  • 在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样在每一个方法进行中转。
  • 每次调用代理对象的任何方法最终都会先执行一次invoke方法 , 调用代理对象的方法不同invoke方法的Method也对象不同 , 那么调用目标类的方法也不同

CGLIB动态代理实现步骤

使用CGLIB,需要引入它的依赖

<dependency>
  <groupId>cglibgroupId>
  <artifactId>cglibartifactId>
  <version>3.3.0version>
dependency>

准备一个没有实现接口的类

public class UserService {

    public void login(){
        System.out.println("用户正在登录系统....");
    }

    public void logout(){
        System.out.println("用户正在退出系统....");
    }
}

和JDK动态代理原理差不多,在CGLIB中需要提供net.sf.cglib.proxy.MethodInterceptor的接口实现类

  • 在MethodInterceptor的intercept()方法中调用目标类的目标方法以及添加增强
public class TimerMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 前面增强
        long begin = System.currentTimeMillis();

        // 调用目标对象的目标方法
        Object retValue = methodProxy.invokeSuper(target, objects);

        // 后面增强
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");

        return retValue;
    }
}

使用CGLIB在内存中为UserService类生成代理类,并创建代理对象

动态代理模式_第3张图片

public class Client {
    public static void main(String[] args) {
        // 创建字节码增强器对象
        // 这个对象是CGLIB库当中的核心对象,就是依靠它来生成代理类。
        Enhancer enhancer = new Enhancer();

        // 告诉CGLIB父类是谁。告诉CGLIB目标类是谁。
        enhancer.setSuperclass(UserService.class);

        // 设置回调(等同于JDK动态代理当中的调用处理器。InvocationHandler)
        // 在CGLIB当中不是InvocationHandler接口,是方法拦截器接口:MethodInterceptor
        enhancer.setCallback(new TimerMethodInterceptor());

        // 创建代理对象
        // 父类是UserService,子类这个代理类一定是UserService
        UserService userServiceProxy = (UserService) enhancer.create();

        // 建议大家能够把CGLIB动态代理生成的代理对象的名字格式有点印象。根据这个名字可以推测框架底层是否使用了CGLIB动态代理
        // class UserService$$EnhancerByCGLIB$$82cb55e3 extends UserService{}
        System.out.println(userServiceProxy);

        // 调用代理对象的代理方法。
        boolean success = userServiceProxy.login("admin", "123");
        System.out.println(success ? "登录成功" : "登录失败");

        userServiceProxy.logout();
    }
}

对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数

--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/sun.net.util=ALL-UNNAMED

动态代理模式_第4张图片

GLIB动态代理程序总结

CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。

Enhancer是创建代理对象的类 , 类似于JDK的Proxy类

  • Proxy类是直接调用静态方法newProxyInstance()创建代理对象 , 在方法中设置相关参数 , 直接返回代理对象
  • Enhancer类是先创建对象 , 然后通过对象的不同方法设置相关目标类和回调 , 最后调用方法创建代理对象
方法名 功能
setSuperclass(Class clazz) 设置代理类的父类即目标类
setCallback() 设置回调(等同于JDK动态代理当中的调用处理器。InvocationHandler)
create() 创建代理对象 , 先在内存中生成目标类的子类,其实就是代理类的字节码。然后创建代理对象

MethodInterceptor接口中intercept()方法的参数

  • 第一个参数:目标对象
  • 第二个参数:目标方法
  • 第三个参数:目标方法调用时的实参
  • 第四个参数:使用MethodProxy 类中的 invokeSuper方法完成目标对象中目标方法的调用

你可能感兴趣的:(Spring,代理模式)