【手写一个spring】spring源码的简单实现--BeanPostProcessor(实现AOP)以及JDK动态代理/CGLIB动态代理

ops/2024/11/26 9:46:27/

文章目录

  • `BeanPostProcessor`概念
  • `BeanPostProcessor`接口中的两个方法
  • `BeanPostProcessor`的实例化与注册
  • `BeanPostProcessor`的执行时机
  • 实现`AOP`
    • 动态代理技术
      • `JDK`动态代理:
      • `CGLIB`动态代理:
    • `JDK`动态代理和`CGLIB`动态代理的区别
      • `JDK`动态代理
      • `CGLIB`动态代理
      • 使用场景
      • 性能差异
      • 总结

BeanPostProcessor概念

BeanPostProcessorSpring框架中一个非常重要的接口,它允许开发者Bean的初始化前后对Bean实例进行自定义的修改和处理.

调用的阶段:在Bean对象初识化的前后

BeanPostProcessor接口中的两个方法

  • postProcessBeforeInitialization(Object bean, String beanName):在Bean初始化方法调用之前执行。该方法允许开发者在Bean的初始化回调之前,对Bean实例进行自定义的处理。
  • postProcessAfterInitialization(Object bean, String beanName):在Bean初始化方法调用之后执行。该方法允许开发者在Bean的初始化回调之后,对Bean实例进行进一步的自定义处理,如代理封装等。

BeanPostProcessor的实例化与注册

Spring容器的启动过程中,Spring会自动检测所有实现了BeanPostProcessor接口的Bean定义,并将它们实例化。这些BeanPostProcessor实例会被注册到Spring容器的BeanFactory中,以便在后续的Bean创建过程中使用。

BeanPostProcessor的执行时机

Spring容器创建了一个Bean实例后,会检查是否有BeanPostProcessor实例需要对该Bean进行处理。
如果有,Spring会先调用所有BeanPostProcessorpostProcessBeforeInitialization方法,允许开发者在Bean初始化之前对Bean进行修改。
接着,Spring会调用Bean的初始化方法(如实现了InitializingBean接口的afterPropertiesSet方法,或者在配置文件中指定的init-method)。
最后,Spring会调用所有BeanPostProcessorpostProcessAfterInitialization方法,允许开发者在Bean初始化之后对Bean进行进一步的修改。

自定义一个BeanPostProcessor接口:

/***** bean的后置处理器* */
public interface BeanPostProcessor {/*** 前置* */public void postProcessBeforeInitializetion(String beanName,Object bean);/*** 后置* */public Object postProcessAfterInitializetion(String beanName, Object bean);
}

自定义一个类:BailLuBeanPostProcessor 对象实现这个接口:(并且,指定了userService执行接口逻辑)

@Component
public class BailLuBeanPostProcessor implements BeanPostProcessor {@Overridepublic void postProcessBeforeInitializetion(String beanName, Object bean) {if("userService".equals(beanName)){System.out.println("bean对象初始化前--------");}}/***** 后置处理器** */public Object postProcessAfterInitializetion(String beanName, Object bean) {if("userService".equals(beanName)){System.out.println("bean对象初始化后-------");return bean;}
}

容器中调用该接口的时机:

		/***存放实现了BeanPostProcessor接口的Bean对象* */private ArrayList<BeanPostProcessor> beanPostProcessorList=new ArrayList<>();public BaiLuApplicationContext(Class configClass){Class<?> clazz = classLoader.loadClass(className);//传入全限定类名if (clazz.isAnnotationPresent(Component.class)) {if(BeanPostProcessor.class.isAssignableFrom(clazz)){//说明该类实现了BeanPostProcessor接口BeanPostProcessor instance = (BeanPostProcessor) clazz.newInstance();beanPostProcessorList.add(instance);}}                          

实现AOP

AOP(面向切面编程)的底层原理主要依赖于动态代理技术。这种技术允许开发者在不修改原有代码的情况下,为主干功能添加新的功能或行为。

动态代理技术

AOP底层主要使用两种动态代理技术:JDK动态代理和CGLIB动态代理

JDK动态代理:

JDK动态代理是基于接口的动态代理技术

Proxy.newProxyInstanceJava 中用于创建动态代理对象的方法。这个方法是 java.lang.reflect.Proxy 类的一部分,它允许你在运行时创建一个实现了指定接口的代理对象这个代理对象会将方法调用转发给一个指定的调用处理器InvocationHandler)。
当调用代理对象的方法时,实际上会调用InvocationHandler接口的invoke方法,从而可以在该方法中编写增强逻辑。
以下是 Proxy.newProxyInstance 方法的签名:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

参数:

  • ClassLoader loader:定义代理类的类加载器。通常,你可以使用目标类的类加载器来获取这个参数。
  • Class<?>[] interfaces:代理类要实现的接口列表。代理类将实现这些接口中声明的所有方法
  • InvocationHandler h调用处理器,当代理对象的方法被调用时,这个处理器的 invoke 方法会被调用

以下是JDK动态代理实现AOP:

  1. 存在一个实现了UserInterface接口的实现类:
@Component("userService")
public class UserService implements UserInterface{@Autowirdprivate OrderService orderService;public void test(){System.out.println("~~~考察AOP切面逻辑~~~");}
  1. 在后置处理器中生成代理对象proxyInstance
@Component
public class BailLuBeanPostProcessor implements BeanPostProcessor {/***** 后置处理器** */public Object postProcessAfterInitializetion(String beanName, Object bean) {if("userService".equals(beanName)){/** 这里采用的是 JDK动态代理的方式生成代理对象的* @Param: proxyInstance--->JDK生成的代理对象,动态代理对象会去执行对象实现的接口* */Object proxyInstance = Proxy.newProxyInstance(BailLuBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//这里是进行增强的逻辑System.out.println("执行代理(切面)逻辑");return method.invoke(bean,args);//这里是执行的接口中的逻辑}});return proxyInstance;}return bean;}
}    
  • 生成一个代理对象proxyInstance;
  • 使用代理对象proxyInstance调用接口中的方法时,会调用invoke方法的逻辑;
  • invoke方法中,对逻辑进行增强,并且调用invoke,实现对原本bean对象中接口的逻辑

CGLIB动态代理:

基于父类的动态代理技术,可以代理没有实现接口的类
AOP(面向切面编程)使用CGLIB动态代理,主要是通过CGLIB在运行时生成目标类的子类,并在子类中重写目标类的方法以插入额外的逻辑(即横切关注点)

使用Enhancer类来创建代理对象。

通过设置父类(目标类)和回调(MethodInterceptor),当调用代理对象的方法时,会触发回调方法中的intercept方法,从而可以在该方法中编写增强逻辑。

JDK动态代理和CGLIB动态代理的区别

JDK动态代理

  1. 基于接口JDK动态代理要求被代理的类必须实现一个或多个接口。代理对象会实现这些接口,并将方法调用委托给目标对象。
  2. 实现机制使用Java反射机制来生成代理对象代理对象会拦截接口中的方法调用,并通过InvocationHandler.invoke()方法执行额外的逻辑

CGLIB动态代理

  1. 基于类CGLIB动态代理通过生成目标类的子类来实现代理。它不要求目标类必须实现接口,因此适用于没有实现接口的类
  2. 实现机制:使用ASM字节码生成框架来生成目标类的子类,并重写目标类的方法以实现代理逻辑

使用场景

JDK动态代理:适用于接口驱动的编程模式。如果目标类实现了接口,并且希望保持简单的依赖关系,那么JDK动态代理是一个很好的选择。
Spring AOP中,如果目标对象实现了接口,Spring默认使用JDK动态代理。
CGLIB动态代理:适用于目标类没有实现接口但需要代理的场景。例如,一些现有的类或者第三方库的类没有提供接口,此时可以使用CGLIB动态代理。
Spring AOP中,如果目标对象没有实现任何接口,Spring AOP会自动使用CGLIB动态代理。

性能差异

JDK动态代理:对于实现了接口的类来说JDK动态代理在创建代理对象时开销较小,因为它仅依赖反射机制来处理接口方法的调用
对于频繁调用代理方法的场景,JDK动态代理可能比CGLIB略慢,因为每次调用都涉及反射。
CGLIB动态代理:由于CGLIB通过字节码生成来创建代理类,生成代理类的开销比JDK动态代理高一些,尤其是在代理类较多的情况下。
但是,CGLIB代理的实际方法调用性能更高,因为它通过字节码操作减少了反射调用的开销

总结

  1. spring容器会在目标类实现了接口的时候,默认选择JDK动态代理;在目标类没有实现接口的时候,默认选择CGLIB动态代理;
  2. CGLIB是基于字节码生成来创建代理类的,JDK动态代理是基于反射来创建代理类的,JDK生成代理类的开销相较于CGLIB相对较小;
  3. CGLIB代理的实际方法的调用上性能更高,因为他是通过字节码操作减少了反射调用的开销;
  4. CGLIB动态代理需要额外引入CGLIB库依赖,而JDK动态代理是JDK标准库中的一部分,无需引入额外的依赖.
  5. final类和方法:CGLIB动态代理无法代理final类或final方法,因为这些类和方法无法被继承和重写。而JDK动态代理则没有这个问题,因为它只是代理接口中的方法。

http://www.ppmy.cn/ops/136800.html

相关文章

一个全面的Vue 3组件通信演示展示

✍️ 记录 一、Vue2 中组件通信方式 props: 实现父子组件、子父组件、甚至兄弟组件通信自定义事件: 可以实现子父组件通信全局事件总线$bus: 可以实现任意组件通信pubsub: 发布订阅模式实现任意组件通信vuex: 集中状态管理容器&#xff0c;实现任意组件通信ref: 父组件获取子…

【C语言】野指针问题详解及防范方法

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C语言 文章目录 &#x1f4af;前言&#x1f4af;什么是野指针&#xff1f;&#x1f4af;未初始化的指针代码示例问题分析解决方法 &#x1f4af;指针越界访问代码示例问题分析解决方法 &#x1f4af;指向已释放内存的…

简单的爬虫脚本编写

一、数据来源分析 想爬取一个网站的数据&#xff0c;我们首先要进行数据分析。通过浏览器F12开发者工具栏进行抓包&#xff0c;可以分析我们想要的数据来源。 通过关键字搜索&#xff0c;可以找到相对应的数据包 二、爬虫实现 需要用到的模块为&#xff1a;request&#xf…

NVR小程序接入平台EasyNVR多品牌NVR管理工具:高效管理分散视频资源的解决方案

在当今数字化、智能化的时代背景下&#xff0c;视频监控已成为各行各业不可或缺的一部分&#xff0c;从公共安全到企业运维&#xff0c;再到智慧城市建设&#xff0c;视频资源的管理与应用正面临着前所未有的挑战。如何高效整合、管理这些遍布各地的分散视频资源&#xff0c;成…

单机部署kubernetes环境下Overleaf-基于MicroK8s的Overleaf应用部署指南

在本文中&#xff0c;我们将探讨如何使用MicroK8s在本地或云环境中快速部署Overleaf应用。MicroK8s是一个轻量级的Kubernetes发行版&#xff0c;它为开发者提供了一个简单的方式来部署和管理容器化应用。 需要使用Kompose转换Overleaf官方Docker Compose配置得到适用于kuberne…

数据结构——排序算法第一幕(插入排序:直接插入排序、希尔排序 选择排序:直接选择排序,堆排序)超详细!!!!

文章目录 前言一、排序1.1 概念1.2 常见的排序算法 二、插入排序2.1 直接插入排序2.2 希尔排序希尔排序的时间复杂度 三、选择排序3.1 直接选择排序3.2 堆排序 总结 前言 时间很快&#xff0c;转眼间已经到数据结构的排序算法部分啦 今天我们来学习排序算法当中的 插入排序 和 …

docker部署redis,并设置密码

获取官方镜像 redis:7.4.1 准备配置文件 文件 redis.conf requirepass xxx密码请自行设置 准备启动脚本 start_redis.sh 注意&#xff1a; 要把这个脚本和上面的redis.conf放到同一个目录下。 workdir$(cd $(dirname $0); pwd) redis_id"$(docker ps -a| grep red…

C# Winform 五子棋小游戏源码

文章目录 1.设计来源五子棋小游戏讲解1.1 主界面1.2 对弈棋盘界面1.3 对弈结束界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/…