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)
上一篇 2022年5月16日 上午10:00
下一篇 2022年5月16日 上午10:00


相关推荐

  • java输入输出流实例代码

    java输入输出流实例代码1.编写一个程序,读取源代码文件的内容并在控制台输出。如果源文件不存在,则显示相应的错误信息。packagesrc;importjava.io.File;importjava.io.FileNotFoundException;importjava.io.FileReader;importjava.io.IOException;publicclasstest01{

    2022年5月25日
    44
  • python第三方库的安装方法有哪些_python第三方库在哪个文件夹

    python第三方库的安装方法有哪些_python第三方库在哪个文件夹简单来说如果是系统自带的python,会使用dist-packages目录;如果你手动安装python,它会直接使用目录site-packages。这允许你让两个安装隔离开来。dist-packages:系统自带的pythonsite-packages:自己安装的pythonPython的包的安装路径如下1、系统自带python系统自带软件管理器安装,模块将被安装到dist-packages相…

    2022年10月14日
    6
  • 使用c++SFML制作月圆之夜总集篇[通俗易懂]

    使用c++SFML制作月圆之夜总集篇[通俗易懂]写在开头重新以时间线的形式整理一下去年使用c++的SFML库制作月圆之夜(游戏程序设计大作业)的开发过程,括号里面是新的补充以及对一年前自己的吐槽因为是在大二转专业后做首次接触游戏开发后才做的,当时c++学习得并不好,所以代码很乱很糟糕,许多思路也不是很清晰,完全是摸爬滚打混过来的,最后也有很多bug,不过还是一次很有收获的经历当时也尝试着学习用游戏引擎做游戏,还觉得游戏引擎太难用了,现在想想游戏引擎是真的方便,真香2020年4月6日昨天做完扫雷后,思考了一下游戏程序设计的课程设计应该做什么。虽然

    2025年7月1日
    6
  • dom 自定义事件_pix4D生成dom

    dom 自定义事件_pix4D生成dom之前做项目都是直接用jquery的bind绑定事件,不过当时都不是动态生成dom元素,而是已经页面中原本存在的dom元素进行事件绑定,最近在测试给动态生成的dom绑定事件的时候发现事件失效,于是就测试了一下:1.事件失效的原因:(1)bind事件绑定只对dom中存在的元素有效,对于我们后来动态增加的元素是监测不到,所以绑定不了(2)同样,当你使用varaa=docu

    2025年10月27日
    6
  • 一文教会你Gitlab搭建

    一文教会你Gitlab搭建安装步骤配置 yum 源 vim etc yum repos d gitlab ce repo 复制以下内容 gitlab ce name gitlab cebaseurl http mirrors tuna tsinghua edu cn gitlab ce yum el6Repo gpgcheck 0Enabled 1Gpgkey https packages gitlab com gpg key 更新本地 yum 缓存 sudoyummakec 安装 Gi

    2026年3月20日
    1
  • AMM和ASMM理解

    AMM和ASMM理解oracle11g新出参数 MEMORY_MAX_TARGET和 MEMORY_TARGET进行自动管理PGA和SGA称之为自动化内存管理(AutomaticMemory Management,AMM)              MEMORY_MAX_TARGET :MEMORY_TARGET所能设定的最大值。非动态可调

    2022年6月7日
    69

发表回复

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

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