01-详解静态代理,动态代理(JDK动态代理原理和CGLIB动态代理原理)

GoF之代理模式

概述

代理模式是GoF23种设计模式之一,属于结构型设计模式,本质就是通过引入代理对象间接实现对真实对象的操作

业务场景: 系统中有A、B、C三个模块,使用这些模块的前提是需要用户登录

  • 此时就可以为A、B、C三个模块提供一个代理,代理的逻辑请求来了之后先判断用户是否登录了,如果登录了则执行对应的目标,如果没有则跳转到登录页面

Java中的两种代理模式

  • 静态代理: 在编译期就生成代理对象
  • 动态代理: 在Java运行时动态生成代理对象,动态代理又有JDK代理和CGLib代理两种

代理模式的作用

  • 当一个对象需要受到保护的时候即不想让客户看到一些内容和服务,可以考虑使用代理对象去完成某个行为
  • 需要给某个对象的功能进行增强的时候,可以考虑找一个代理进行增强如添加客户需要的额外服务
  • 在程序中对象A和对象B无法直接交互时也可以使用代理模式来解决

代理模式分为三种角色

  • 目标对象(Real Subject): 实现最终核心的业务,是代理对象所代表的真实对象即最终要引用的对象
  • 代理对象(Proxy Subject): 其内部含有对目标对象的引用,它可以访问、控制或扩展目标对象的功能
  • 目标对象和代理对象的公共接口(Abstract Subject): 目标对象和代理对象需要具有相同的行为和动作,让客户端在使用代理对象时就像在使用目标对象一样

在这里插入图片描述

静态代理

准备对象和接口

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

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

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

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

OrderServiceImpl是目标类

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,在子类中重写父类的每个业务方法同时调用父类的业务方法(CGLIB动态代理实现的原理)

  • 缺点: 采用了继承(满足is a的关系)的方式会导致父类和子类的代码之间耦合度非常高,另外代码也没有得到复用
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开闭原则,同时采用的是关联关系(满足has a的关系)所以程序的耦合度较低
  • 缺点: 每个目标对象都需要一个代理对象,随着目标对象越来越多就会导致类爆炸不好维护
// 代理类和目标类需要实现相同的接口,让客户端感觉使用代理对象就像在使用目标对象一样
public class OrderServiceProxy implements OrderService{ // 代理对象
    // 关联目标对象即将目标对象作为代理对象的一个属性,使用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)+"毫秒");
    }
}

测试OrderServiceImpl业务类中每个业务方法的耗时

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动态代理

动态代理还是代理模式,只不过添加了字节码生成技术,可以在在程序运行时在内存中动态的生成字节码即代理类,有效减少代理类的数量同时解决代码复用的问题

在内存当中动态生成字节码的技术常见的包括三种

代理技术功能
JDK动态代理技术只能代理接口
CGLIB(Code Generation Library)动态代理技术既可以代理接口又可以代理类,通过继承的方式实现代理(生成目标类的子类作为),性能比JDK动态代理要好
底层通过使用一个小而快的字节码处理框架ASM
Javassist动态代理技术Javassist是一个开源的分析、编辑和创建Java字节码的类库
通过使用Javassist对字节码操作为JBoss应用服务器实现动态"AOP"框架

JDK动态代理原理

java.lang.reflect.Proxy是JDK提供的一个动态代理类,通过这个类可以在内存中生成代理类的字节码

方法功能
newProxyInstance(类加载器,公共接口类型,调用处理器接口的实现类)Proxy类提供的一个创建代理对象的静态方法
newProxyInstance方法执行结束后,首先在内存中动态生成一个代理类的字节码文件(Xxx.class)
然后通过类加载器获取代理类的字节码对象,使用Class对象实例化一个代理对象并返回
该方法的返回值是Object类型,但由于我们方法参数已经指定了代理对象实现的接口类型,所以可以放心的向下转型为对应的接口类型
invoke(代理对象,目标方法,目标方法调用时要传的参数)每次调用代理对象的任何方法最终都会先执行一次invoke方法(调用一次执行一次),调用代理对象的方法不同,invoke方法参数中的Method也对象不同,最终调用对应的目标类的方法
该方法的返回值由调用的目标对象的目标方法的执行结果决定,如果调用代理对象方法时需要接收返回值,那么就需要将目标对象的目标方法的执行结果返回

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

  • 类加载器: 要想执行JDK在内存中生成的字节码文件需要先把它通过类加载器加载到内存当中,并且JDK要求目标类的类加载器必须和代理类的类加载器是同一个
  • 公共接口类型: 告诉JDK动态代理生成的代理类要实现哪些接口,由于目标对象和代理对象实现的是同一个接口,所以可以直接通过目标对象获取接口类型
  • 调用处理器: JDK动态代理规定的java.lang.reflect.InvocationHandler是一个回调接口,调用这个接口中方法的程序已经写好了(自动调用 ),我们只要编写增强代码

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

  • Object proxy: 设计这个参数只是为了后期需要在invoke方法中使用代理对象
  • Method method: 目标对象上的目标方法,即我们最终要执行的方法
  • Object[] args: 目标方法调用时需要传递的参数
  • 当你每调用一次代理对象的代理方法的时候,InvocationHandler接口的实现类中重写的invoke()方法就会被JDK调用一次,同时会携带方法需要的三个参数
  • 因为InvocationHandler接口的实现类存储的是我们需要增强的代码,所以需要手写,这样并不会造成类爆炸,因为这种实现类只需要写一次就好,代码可以复用

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

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

JDK动态代理实现

第一步: 提供OrderService接口及其实现类(目标类)OrderServiceImpl,UserServiceProxy代理类通过从在程序中动态生成

public interface OrderService {
    /**
     * 获取姓名
     */
    Srting getName();
    /**
     * 生成订单
     */
    void generate();

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

    /**
     * 修改订单
     */
    void modify();
}
public class OrderServiceImpl implements OrderService {
    @Override
    public String getName() {
        System.out.println("执行getName方法");
        return "张三";
    }
    
    @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("订单已修改");
    }
}

第二步: 编写调用处理器接口的实现类TimerInvocationHandler同时实现接口中的invoke方法用来调用目标对象的目标方法并对其功能增强

  • 重写的方法名必须是invoke,因为invoke方法是JDK负责调用的,它已经把调用方法的程序写好了并且方法名就是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);
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");
        // 如果我们调用的目标对象的目标方法有返回值的话,invoke方法就必须将目标对象的目标方法执行结果继续返回
        return retValue;
    }
}

第三步: 测试调用代理对象的方法,底层JDK会调用invoke方法然后通过method参数去执行目标对象的目标方法

public class Client { 
    public static void main(String[] args) {
        // 第一步:创建目标对象
        OrderService target = new OrderServiceImpl();
        // 第二步:创建代理对象,由于我们方法参数已经指定了代理对象实现的接口类型,所以可以放心的向下转型为对应的接口类型
        OrderService orderServiceProxy = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimerInvocationHandler(target));
        // 第三步:调用代理对象的代理方法,调用代理对象的代理方法的时候,如果你要做增强的话目标对象的目标方法得保证执行
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();    
        // 调用有返回值的代理对象的代理方法
        String name = orderServiceProxy.getName();
        System.out.println(name);
    }
}

封装创建代理对象的工具类

在工具类ProxyUtil封装方法,实现只要传递一个目标对象就可以通过这个方法获取代理对象

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

}

// 进一步封装,使用匿名内部类的方式简化方法参数
public class ProxyUtil {
    public static Object newProxyInstance(Object target){
        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;
    				}    
                });
    }

}

使用工具类封装的newProxyInstance方法简化客户端代码

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();
    }
}

CGLIB动态代理原理

CGLIB动态代理既可以代理接口又可以代理类,底层通过继承的方式实现对目标类的代理,所以被代理的目标类不能使用fianl修饰

// CGLIB创建的代理对象的格式,可以根据这个名字推测框架底层是否使用了CGLIB动态代理
class UserService$$EnhancerByCGLIB$$82cb55e3 extends UserService{}

net.sf.cglib.proxy.MethodInterceptor是CGLIB中提供的方法拦截器接口,和JDK动态代理的调用处理器接口一样,我们也需要提供该接口实现类并重写方法

CGLIB中的Enhancer和JDK的Proxy类都是用来创建代理对象的类

  • 使用Proxy类是直接调用静态方法newProxyInstance()创建代理对象,在方法中设置目标对象相关参数和调用处理器接口实现类,直接返回代理对象
  • 使用Enhancer类需要先创建字节码增强对象,然后通过Enhancer对象的不同方法设置目标类相关参数和方法拦截器接口的实现类,最后再调用方法创建代理对象
方法名功能
intercept(目标对象,目标方法,目标方法调用时的实参,调用目标方法时需要用到的MethodProxy类)负责调用目标类的目标方法以及添加增强的代码的方法
invokeSuper(目标对象,目标方法调用时的实参)最中调用目标对象中目标方法的方法
setSuperclass(Class clazz)设置代理类的父类即目标类
setCallback()设置回调为拦截器接口的实现类,等同于JDK动态代理当中的调用处理器接口的实现类
create()创建代理对象
先在内存中生成目标类的子类即代理类的字节码,然后创建代理对象
返回值默认是Object类型,但由于我们已经设置了代理类的父类,所以可以放心强转

CGLIB动态代理实现

第一步: 引入CGLIB的依赖

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>

第二步: 准备一个目标类UserService,这个类不需要实现任何接口

public class UserService {
    public void login(String username, String password){
        System.out.printn("系统正在验证身份...");
        if ("admin".equals(username) && "123".equals(password)) {
           	return true;
        }  
         return false;
    }

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

第三步: 编写MethodInterceptor接口的实现类TimerMethodInterceptor并重写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类生成的代理类并创建代理对象,然后通过调用代理对象的方法去执行目标对象的目标方法

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

        // 告诉CGLIB生成的代理类的父类是谁即目标类是谁
        enhancer.setSuperclass(UserService.class);

        // 设置回调为方法拦截器接口MethodInterceptor的实现类,等同于JDK动态代理当中的调用处理器接口的实现类
        enhancer.setCallback(new TimerMethodInterceptor());

        // 创建代理对象,代理类的父类是UserService
        UserService userServiceProxy = (UserService) enhancer.create();

        // 查看CGLIB动态代理生成的代理对象
        System.out.println(userServiceProxy);

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

JDK高版本问题

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

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

在这里插入图片描述

动态代理与静态代理比较

优缺点

动态代理与静态代理相比较的优点是在接口方法数量比较多的时候可以进行灵活处理

  • 接口中声明的所有方法都被转移到调用处理器InvocationHandler的一个集中方法invoke中处理,而不需要像静态代理那样在每一个方法进行中转

  • 不管你有多少个Service接口,由于我们的代理对象是在程序运行中动态生成的 , 所以可以实现任何接口代码不会写死

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值