jdk动态代理和cglib动态代理详解

jdk动态代理和cglib动态代理详解本文内容概括:静态代理概述 基于继承方式实现静态代理 基于聚合方式实现静态代理 jdk动态代理实现 如何实现一个HashMap的动态代理类 cglib动态代理实现 jdk和cglib代理的区别 动态代理和静态代理的区别 spring如何选择jdk和cglib代理如上图,代理模式可分为动态代理和静态代理,我们比较常用的有动态代理中的jdk动态代理和Cglib代理,像spr…

大家好,又见面了,我是你们的朋友全栈君。

本文内容概括:

  1. 静态代理概述
  2. 基于继承方式实现静态代理
  3. 基于聚合方式实现静态代理
  4. jdk动态代理实现
  5. 如何实现一个HashMap的动态代理类
  6. cglib动态代理实现
  7. jdk和cglib代理的区别
  8. 动态代理和静态代理的区别
  9.  spring如何选择jdk和cglib代理

jdk动态代理和cglib动态代理详解

如上图,代理模式可分为动态代理和静态代理,我们比较常用的有动态代理中的jdk动态代理和Cglib代理,像spring框架、hibernate框架中都采用了JDK动态代理,下面将结合代码阐述两种代理模式的使用与区别。


静态代理

静态代理的代理对象和被代理对象在代理之前就已经确定,它们都实现相同的接口或继承相同的抽象类。静态代理模式一般由业务实现类和业务代理类组成,业务实现类里面实现主要的业务逻辑,业务代理类负责在业务方法调用的前后作一些你需要的处理,如日志记录、权限拦截等功能…实现业务逻辑与业务方法外的功能解耦,减少了对业务方法的入侵。静态代理又可细分为:基于继承的方式和基于聚合的方式实现。

场景:假设一个预减库存的操作,需要在预减的前后加日志记录(我这里是springboot项目)

基于继承的方式实现静态代理

/**
 * 业务实现类接口
 */
public interface OrderService {
    //减库存操作
    void reduceStock();
}
/**
 * 业务实现类
 */
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Override
    public void reduceStock() {
        try {
            log.info("预减库存中……");
            Thread.sleep(1000);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
/**
 * 代理类
 */
@Slf4j
public class OrderServiceLogProxy extends OrderServiceImpl{
    @Override
    public void reduceStock() {
        log.info("预减库存开始……");
        super.reduceStock();
        log.info("预减库存结束……");
    }
}
    /**
     * 测试继承方式实现的静态代理
     */
    @Test
    public void testOrderServiceProxy(){
        OrderServiceLogProxy proxy = new OrderServiceLogProxy();
        proxy.reduceStock();
    }

输出结果

14:53:53.769 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy - 预减库存开始……
14:53:53.771 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 预减库存中……
14:53:54.771 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy - 预减库存结束……

可以看到,OrderServiceLogProxy已经实现了为OrderServiceImpl的代理,通过代理类的同名方法来增强了业务方法前后逻辑。

基于聚合的方式实现静态代理

聚合的意思就是把业务类引入到了代理类中,接口和业务实现类还是之前的OrderService、OrderServiceImpl,代理类改为如下:

/**
 * 聚合方式实现静态代理:代理类中引入业务类
 */
@Slf4j
public class OrderServiceLogProxy2 implements OrderService {

    private OrderServiceImpl orderService;

    public OrderServiceLogProxy2(OrderServiceImpl orderService) {
        this.orderService = orderService;
    }

    @Override
    public void reduceStock() {
        log.info("预减库存开始……");
        orderService.reduceStock();
        log.info("预减库存结束……");
    }
}
    /**
     * 测试聚合方式实现的静态代理
     */
    @Test
    public void testOrderServiceProxy2() {
        OrderServiceImpl orderService = new OrderServiceImpl();
        OrderServiceLogProxy2 proxy2 = new OrderServiceLogProxy2(orderService);
        proxy2.reduceStock();
    }

测试输出结果和上面的结果是一致的。

继承与聚合方式实现的静态代理对比

结合上面的代码来看,如果此时需要叠加代理功能,我不仅要记录预减日志,还要增加权限拦截功能,这个时候如果采用继承的方式的话,就得新建一个代理类,里面包含日志和权限逻辑;那要是再增加一个代理功能,又要新增代理类;如果要改变下代理功能的执行顺序,还是得增加代理类,结合上面分析来看,这样做肯定是不妥的。但是如果使用聚合方式的方式呢?我们稍微改造下上面使用的聚合方式实现的静态代理代码:

首先是日志代理类代码

/**
 * 聚合方式实现静态代理--日志记录功能叠加改造
 */
@Slf4j
public class OrderServiceLogProxy3 implements OrderService {

    //注意,这里换成了接口
    private OrderService orderService;

    public OrderServiceLogProxy3(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public void reduceStock() {
        log.info("预减库存开始……");
        orderService.reduceStock();
        log.info("预减库存结束……");
    }
}

然后是新增的权限验证代理类代码

/**
 * 聚合方式实现静态代理--日志记录功能叠加改造
 */
@Slf4j
public class OrderServicePermissionProxy implements OrderService {

    //注意,这里换成了接口
    private OrderService orderService;

    public OrderServicePermissionProxy(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public void reduceStock() {
        log.info("权限验证开始……");
        orderService.reduceStock();
        log.info("权限验证结束……");
    }
}

测试用例

    /**
     * 测试聚合方式实现的静态代理-功能叠加
     */
    @Test
    public void testOrderServiceProxy3() {
        OrderServiceImpl orderService = new OrderServiceImpl();
        OrderServiceLogProxy2 logProxy2 = new OrderServiceLogProxy2(orderService);
        OrderServicePermissionProxy permissionProxy = new OrderServicePermissionProxy(logProxy2);
        permissionProxy.reduceStock();
    }

测试结果

16:00:28.348 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServicePermissionProxy - 权限验证开始……
16:00:28.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy2 - 预减库存开始……
16:00:28.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 预减库存中……
16:00:29.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceLogProxy2 - 预减库存结束……
16:00:29.365 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServicePermissionProxy - 权限验证结束……

接下来,如果你需要调换一下代理类逻辑执行顺序问题,你只需要在使用(像测试一样)时调换一下实例化顺序即可实现日志功能和权限验证的先后执行顺序了,而不需要像继承方式一样去不断的新建代理类。


动态代理

看完上面的静态代理,我们发现,静态代理模式的代理类,只是实现了特定类的代理,比如上面OrderServiceLogProxy实现的OrderServiceimpl的代理,如果我还有个UserService也许要日志记录、权限校验功能,又得写双份的UserServiceLogProxy、UserServicePermissionProxy代理类,里面的逻辑很多都是相同的,也就是说你代理类对象的方法越多,你就得写越多的重复的代码,那么有了动态代理就可以比较好的解决这个问题,动态代理就可以动态的生成代理类,实现对不同类下的不同方法的代理。

JDK动态代理

jdk动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用业务方法前调用InvocationHandler处理。代理类必须实现InvocationHandler接口,并且,JDK动态代理只能代理实现了接口的类,没有实现接口的类是不能实现JDK动态代理。结合下面代码来看就比较清晰了。

import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
 * JDK动态代理实现,必须实现InvocationHandler接口
 * InvocationHandler可以理解为事务处理器,所有切面级别的逻辑都在此完成
 */
@Slf4j
public class DynamicLogProxy implements InvocationHandler {

    //需要代理的对象类
    private Object target;

    public DynamicLogProxy(Object target) {
        this.target = target;
    }

    /**
     * @param obj    代理对象
     * @param method 对象方法
     * @param args   方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        log.info("这里是日志记录切面,日志开始……");
        //使用方法的反射
        Object invoke = method.invoke(target, args);
        log.info("这里是日志记录切面,日志结束……");
        return invoke;
    }
}

使用时代码

    /**
     * 测试JDK动态代理实现的日志代理类
     */
    @Test
    public void testDynamicLogProxy() {
        OrderServiceImpl orderService = new OrderServiceImpl();
        Class<?> clazz = orderService.getClass();
        DynamicLogProxy logProxyHandler = new DynamicLogProxy(orderService);
        //通过Proxy.newProxyInstance(类加载器, 接口s, 事务处理器Handler) 加载动态代理
        OrderService os = (OrderService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), logProxyHandler);
        os.reduceStock();
    }

输出结果

16:35:54.584 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicLogProxy - 这里是日志记录切面,日志开始……
16:35:54.587 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 预减库存中……
16:35:55.587 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicLogProxy - 这里是日志记录切面,日志结束……

使用JDK动态代理类基本步骤:

1、编写需要被代理的类和接口(我这里就是OrderServiceImpl、OrderService);

2、编写代理类(例如我这里的DynamicLogProxy),需要实现InvocationHandler接口,重写invoke方法;

3、使用Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)动态创建代理类对象,通过代理类对象调用业务方法。

那么这个时候,如果我需要在代理类中叠加功能,该如何是好?比如不仅要日志,还新增权限认证,思路还是上面的聚合方式实现静态代理里的那样,贴下代码,先新增权限认证代理类

import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 基于JDK动态代理实现的权限认证代理类
 */
@Slf4j
public class DynamicPermissionProxy implements InvocationHandler{
    private Object target;

    public DynamicPermissionProxy(Object target) {
        this.target = target;
    }

    /**
     * @param obj    代理对象
     * @param method 对象方法
     * @param args   方法参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        log.info("这里是权限认证切面,开始验证……");
        Object invoke = method.invoke(target,args);
        log.info("这里是权限认证切面,结束验证……");
        return invoke;
    }
}

然后使用时候,需要稍微改动下

    /**
     * 测试JDK动态代理实现的日志、权限功能代理类
     */
    @Test
    public void testDynamicLogAndPermissProxy() {
        OrderServiceImpl orderService = new OrderServiceImpl();
        Class<?> clazz = orderService.getClass();
        DynamicLogProxy logProxyHandler = new DynamicLogProxy(orderService);
        OrderService os = (OrderService) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), logProxyHandler);
        //注:这里把日志代理类实例对象传入权限认证代理类中
        DynamicPermissionProxy dynamicPermissionProxy = new DynamicPermissionProxy(os);
        OrderService os2 = (OrderService)Proxy.newProxyInstance(os.getClass().getClassLoader(),os.getClass().getInterfaces(),dynamicPermissionProxy);
        os2.reduceStock();
    }

如上即可,后面还需要叠加功能代理类的话,按照上面的思路依次传入代理对象实例即可。

如何实现一个HashMap的动态代理类?

public class HashMapProxyTest {
    public static void main(String[] args) {
        final HashMap<String, Object> hashMap = new HashMap<>();
        Map<String, Object> mapProxy = (Map<String, Object>) Proxy.newProxyInstance(HashMap.class.getClassLoader(), HashMap.class.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return method.invoke(hashMap, args);
            }
        });
        mapProxy.put("key1", "value1");
        System.out.println(mapProxy);
    }
}

Cglib动态代理

cglib是针对类来实现代理的,它会对目标类产生一个代理子类,通过方法拦截技术对过滤父类的方法调用。代理子类需要实现MethodInterceptor接口。另外,如果你是基于Spring配置文件形式开发,那你需要显示声明:

<aop:aspectj-autoproxy proxy-target-class="true"/>

如果你是基于SpringBoot开发,则一般在启动类头部显示的添加注解 

@EnableAspectJAutoProxy(proxyTargetClass = true)
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * 基于Cglib方式实现动态代理-日志功能
 * 它是针对类实现代理的,类不用实现接口,CGlib对目标类产生一个子类,通过方法拦截技术拦截所有的方法调用
 */
@Slf4j
public class DynamicCglibLogProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    public Object getProxyObj(Class clazz) {
        //设置父类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        enhancer.setUseCache(false);
        return enhancer.create();
    }

    /**
     * 拦截所有目标类的方法调用
     *
     * @param o           目标对象
     * @param method      目标方法
     * @param args        方法参数
     * @param methodProxy 代理类实例
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        log.info("这里是日志记录切面,日志开始……");
        //代理类对象实例调用父类方法
        Object result = methodProxy.invokeSuper(o, args);
        log.info("这里是日志记录切面,日志结束……");
        return result ;
    }
}

测试用例

    /**
     * 测试Cglib实现的动态代理-日志功能
     */
    @Test
    public void testGclibDynamicLogProxy(){
        DynamicCglibLogProxy dynamicCglibLogProxy = new DynamicCglibLogProxy();
        OrderServiceImpl orderService = (OrderServiceImpl)dynamicCglibLogProxy.getProxyObj(OrderServiceImpl.class);
        orderService.reduceStock();
    }

输出结果

17:41:07.007 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicCglibLogProxy - 这里是日志记录切面,日志开始……
17:41:07.038 [main] INFO com.simons.cn.springbootdemo.proxy.OrderServiceImpl - 预减库存中……
17:41:08.038 [main] INFO com.simons.cn.springbootdemo.proxy.DynamicCglibLogProxy - 这里是日志记录切面,日志结束……

JDK与Cglib动态代理对比?

1、JDK动态代理只能代理实现了接口的类,没有实现接口的类不能实现JDK的动态代理;

2、Cglib动态代理是针对类实现代理的,运行时动态生成被代理类的子类拦截父类方法调用,因此不能代理声明为final类型的类和方法;

动态代理和静态代理的区别?

1、静态代理在代理前就知道要代理的是哪个对象,而动态代理是运行时才知道;

2、静态代理一般只能代理一个类,而动态代理能代理实现了接口的多个类;

Spring如何选择两种代理模式的?

1、如果目标对象实现了接口,则默认采用JDK动态代理;

2、如果目标对象没有实现接口,则使用Cglib代理;

3、如果目标对象实现了接口,但强制使用了Cglib,则使用Cglib进行代理

我们可以结合源码来看下,上面的选择:

jdk动态代理和cglib动态代理详解

补充

Cglib实现的MethodInterceptor接口在spring-core包下,你可能需要要引入

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-core</artifactId>
	<version>4.1.0.RELEASE</version>
</dependency>

@Slf4j是lombok里提供的,而且如果你在intellij idea开发工具中使用还需要安装lombok插件,可参考

https://blog.csdn.net/fanrenxiang/article/details/81030472

https://blog.csdn.net/fanrenxiang/article/details/81012803

@Test是Junit包下的,你可能需要引入

<dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.11</version>
     <scope>test</scope>
</dependency>

books 引申阅读: 单例模式详解

books 引申阅读: 策略模式详解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/145914.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • vim查找关键字「建议收藏」

    vim查找关键字「建议收藏」当你用vi打开一个文件后,因为文件太长,如何才能找到你所要查找的关键字呢?在vi里可没有菜单-〉查找不过没关系,你在命令模式下敲斜杆(/)这时在状态栏(也就是屏幕左下脚)就出现了“/”然后输入你要查找的关键字敲回车就可以了。如果你要继续查找此关键字,敲字符n就可以继续查找了。敲字符N(大写N)就会向前查询;…

    2022年6月16日
    43
  • 计算机启动显示安装程序正在启动服务,安装程序正在启动服务需要多久

    计算机启动显示安装程序正在启动服务,安装程序正在启动服务需要多久大家好,我是时间财富网智能客服时间君,上述问题将由我为大家进行解答。安装程序正在启动服务需要多久,这个主要看软件大小,安装完成就会自动运行的,要是系统不断的提示,这个就不一定了,时间可能会很久的,需要耐心等待。程序,香港和台湾对英文procedure的中文翻译,编程语言中的procedure在大陆翻译为“过程”,在港台和其他领域则翻译为“程序”。在大陆于计算机科学、电脑工程、电子工程、电机工程、机…

    2022年6月29日
    72
  • 后台管理系统 – 页面布局设计

    后台管理系统 – 页面布局设计前端的中后台管理系统相比于其他普通项目,从开发设计的角度来说有几点比较特殊:一个是权限设计,具体实现可参考:传送门。一个是页面布局的设计,也是本文要说的。一个好的页面布局设计,无论是对于页面布局的稳定性,还是系统功能拓展的方便性,亦或是用户体验上,都有着重要的提升作用。一、市面参考先来看看市面上的一些优秀的开源系统项目的页面布局。1、vue-element-adminvue-element-admin是vue框架的一个优秀的后台管理系统开源项目,目前star数75k,也是我入行前端的启

    2025年10月26日
    5
  • 第七章《索引》

    第七章《索引》

    2021年5月28日
    101
  • Excel均方误差计算公式_随机误差项的方差估计量推导

    Excel均方误差计算公式_随机误差项的方差估计量推导在Excel表中,有时需要计算方差,然后根据此图绘制图表,目标值指示偏差程度,然后如何计算方差?方差的概念方差是每个数据与平均值之间差异的平方和的平均值.在概率论和数理统计中,方差(英语方差)用于衡量随机变量与其数学期望值(即均值)之间的偏差程度.均方误差的概念也称为标准偏差,它是每个数据与平均值的距离的平均值.它是平方的平方根和与平均值的偏差的平均值.标准偏差可以反映数据集的分散程度.如…

    2022年9月30日
    2
  • 个人网站可以申请微信授权登录吗?

    个人网站可以申请微信授权登录吗?

    2021年10月25日
    50

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号