【Spring】手动实现简易AOP和IOC

news2024/11/17 21:40:44

前言

XML:通过Dom4j对XML进行解析和验证。
IOC:通过获取要创建对象的Class类型、构造函数后,通过反射来实现。
AOP:通过使用JDK动态代理Cglib动态代理实现。

一、解析XML

1.1、解析bean标签

/**
  * 解析bean标签
  * @param xmlBean bean标签
  */
 private void parseBeanDefinitionXML(Element xmlBean){
     String beanId = xmlBean.attributeValue("id");
     if (beanDefinitionMap.containsKey(beanId)) {
         return;
     }
     String beanClass = xmlBean.attributeValue("class");
     BeanDefinition beanDefinition = new BeanDefinition();
     beanDefinition.setId(beanId);
     beanDefinition.setClassName(beanClass);
     List<Element> xmlConstructor = xmlBean.elements();
     //  无参bean,则解析完成
     if (UsualUtil.isNull(xmlConstructor)) {
         beanDefinitionMap.put(beanId, beanDefinition);
         return;
     }
     List<BeanDefinition.ConstructArg> constructArgs = new ArrayList<>();
     //  解析参数
     for (Element xmlConstructArg : xmlConstructor) {
         //  目前仅解析constructor-arg
         if ("constructor-arg".equals(xmlConstructArg.getName())) {
             String typeClass = xmlConstructArg.attributeValue("type");
             String value = xmlConstructArg.attributeValue("value");
             String refName = xmlConstructArg.attributeValue("ref");
             BeanDefinition.ConstructArg constructArg = new BeanDefinition.ConstructArg();
             constructArg.setType(typeClass);
             constructArg.setValue(value);
             constructArg.setRef(refName);
             //  非引用其他bean
             if (UsualUtil.isNull(refName)) {
                 if (typeClass.endsWith("List")) {
                     Element xmlCollection = xmlConstructArg.elements().get(0);
                     String valueTypeClass = xmlCollection.attributeValue("value-type");
                     constructArg.setItemType(valueTypeClass);
                     List<String> itemValue = new ArrayList<>();
                     for (Element xmlItem : xmlCollection.elements()) {
                         itemValue.add(xmlItem.getStringValue());
                     }
                     constructArg.setItemValue(itemValue);
                 }
             }
             constructArgs.add(constructArg);
         }
     }
     beanDefinition.setConstructArgs(constructArgs);
     beanDefinitionMap.put(beanId, beanDefinition);
 }

1.2、解析aspect标签

/**
  * 解析aspect标签
  * @param xmlAspect aspect标签
  * @param useCglib 是否使用cglib
  */
 private void parseAspectXML(Element xmlAspect, boolean useCglib) {
//    <aop:config>
//        <aop:aspect ref="aspect">
//            <aop:before method="run" pointcut="execution(public void org.tree.aspect.AspectProcessor.before())"/>
//            <aop:after method="run" pointcut="execution(public void org.tree.aspect.AspectProcessor.after())"/>
//        </aop:aspect>
//    </aop:config>
     String refName = xmlAspect.attributeValue("ref");
     if (aspectDefinitionMap.containsKey(refName)) {
         return;
     }
     AspectDefinition aspectDefinition = new AspectDefinition();
     aspectDefinition.setUseCglib(useCglib);
     aspectDefinition.setRef(refName);
     List<AspectDefinition.Advice> advices = new ArrayList<>();
     for (Element xmlAdvice : xmlAspect.elements()) {
         AspectDefinition.Advice advice = new AspectDefinition.Advice();
         advice.setType(xmlAdvice.getName());
         //  execution(public void org.tree.aspect.AspectProcessor.before())
         String pointcutStr = xmlAdvice.attributeValue("pointcut");
         pointcutStr = pointcutStr.replace("execution", "");
         pointcutStr = pointcutStr.substring(0, pointcutStr.length() - 1);
         //  public void org.tree.aspect.AspectProcessor.before()
         String[] strings = pointcutStr.split(" ");
         advice.setMethod(xmlAdvice.attributeValue("method"));
         advice.setProxyMethod(strings[2].substring(strings[2].lastIndexOf('.') + 1, strings[2].length() - 2));
         advice.setProxyClass(strings[2].substring(0, strings[2].lastIndexOf('.')));
         advices.add(advice);
     }
     aspectDefinition.setAdvices(advices);
     aspectDefinitionMap.put(refName, aspectDefinition);
 }

二、加载Bean

加载Bean时,本文实现的是通过构造函数注入参数,所以无法解决循环依赖的问题。
浅说下发现构造函数循环依赖的原理:当要加载一个bean时,记录下当前bean的加载状态为“正在创建中”,准备好构造函数所需要的参数,如果参数是未加载的,则取加载其参数,当加载的参数发现自己正处于“正在创建中”的状态时,则此刻这两个bean之间发生循环依赖了。

2.1、getBean

/**
  * 通过bean实例名字获取实例
  * @param id bean实例名字
  * @return bean实例
  */
 public Object getBean(String id) throws Exception {
     if (beanFactory.containsKey(id)) {
         return beanFactory.get(id);
     }
     if (!beanDefinitionMap.containsKey(id)) {
         return null;
     }
     BeanDefinition beanDefinition = beanDefinitionMap.get(id);
     List<BeanDefinition.ConstructArg> constructArgs = beanDefinition.getConstructArgs();
     if (UsualUtil.isNull(constructArgs)) {
         //  创建bean前操作
         if (beanPostProcessor != null) {
             beanPostProcessor.preInitBean(objectFactory.get(id), id);
         }
         Object beanObj = Class.forName(beanDefinition.getClassName()).getConstructor().newInstance();
         //  如果该bean被aop引用
         beanObj = doEnhanceBean(beanDefinition, beanObj);
         //  创建bean后操作
         if (beanPostProcessor != null) {
             beanPostProcessor.postInitBean(beanObj, id);
         }
         beanFactory.put(id, beanObj);
         return beanObj;
     }
     return doCreateBean(beanDefinition);
 }

2.2、doCreateBean

/**
  * 创建bean
  * @param beanDefinition bean标签
  * @return bean实例
  * @throws Exception 空
  */
 private Object doCreateBean(BeanDefinition beanDefinition) throws Exception{
     //  标记该bean正在创建实例中
     objectFactory.put(beanDefinition.getId(), Class.forName(beanDefinition.getClassName()).getConstructor().newInstance());
     List<BeanDefinition.ConstructArg> constructArgs = beanDefinition.getConstructArgs();
     Class<?>[] paramTypes = new Class[constructArgs.size()];
     Object[] paramValues= new Object[constructArgs.size()];
     int paramIndex = 0;
     for (BeanDefinition.ConstructArg constructArg : constructArgs) {
         //  构造函数参数为引用
         if (!UsualUtil.isNull(constructArg.getRef())) {
             //  该引用bean已经创建实例
             if (beanFactory.containsKey(constructArg.getRef())) {
                 Object refBeanObj = beanFactory.get(constructArg.getRef());
                 paramTypes[paramIndex] = refBeanObj.getClass();
                 paramValues[paramIndex] = refBeanObj;
                 paramIndex++;
             //  该引用bean正在创建实例中,循环引用
             } else if (objectFactory.containsKey(constructArg.getRef())) {
                 throw new Exception("Both id '" + beanDefinition.getId() + "' and '" + constructArg.getRef() + "' are circular reference");
             //  该引用bean未创建实例
             } else {
                 Object refBeanObj = doCreateBean(beanDefinitionMap.get(constructArg.getRef()));
                 paramTypes[paramIndex] = refBeanObj.getClass();
                 paramValues[paramIndex] = refBeanObj;
                 paramIndex++;
             }
             continue;
         }
         paramTypes[paramIndex] = Class.forName(constructArg.getType());
         //  构造函数参数为非引用类型, 且是非集合类型
         if (!(constructArg.getType().endsWith("List"))) {
             paramValues[paramIndex++] = Class.forName(constructArg.getType()).getConstructor(String.class).newInstance(constructArg.getValue());
             continue;
         }
         List<Object> objParamList = new ArrayList<>();
         for (String itemValue : constructArg.getItemValue()) {
             objParamList.add(Class.forName(constructArg.getItemType()).getConstructor(String.class).newInstance(itemValue));
         }
         paramValues[paramIndex++] = objParamList;
     }
     //  创建bean前操作
     if (beanPostProcessor != null) {
         beanPostProcessor.preInitBean(objectFactory.get(beanDefinition.getId()), beanDefinition.getId());
     }
     Object beanObj = Class.forName(beanDefinition.getClassName()).getConstructor(paramTypes).newInstance(paramValues);
     //  如果该bean被aop引用
     beanObj = doEnhanceBean(beanDefinition, beanObj);
     //  创建bean后操作
     if (beanPostProcessor != null) {
         beanPostProcessor.postInitBean(beanObj, beanDefinition.getId());
     }
     beanFactory.put(beanDefinition.getId(), beanObj);
     //  标记该bean没有正在创建
     objectFactory.remove(beanDefinition.getId());
     return beanObj;
 }

三、增强Bean

3.1、doEnhanceBean

/**
  * 创建增强bean
  * @param beanDefinition bean标签
  * @param beanObj bean对象
  * @return 增强bean
  * @throws Exception 空
  */
 private Object doEnhanceBean(BeanDefinition beanDefinition, Object beanObj) throws Exception {
     if (aspectDefinitionMap.containsKey(beanDefinition.getId())) {
         AspectDefinition aspectDefinition = aspectDefinitionMap.get(beanDefinition.getId());
         for (AspectDefinition.Advice advice : aspectDefinition.getAdvices()) {
             //  增强beanObj
             beanObj = doCreateProxy(beanObj, advice.getMethod(), advice.getProxyClass(), advice.getProxyMethod()
                     , "before".equals(advice.getType()), aspectDefinition.isUseCglib());
         }
     }
     return beanObj;
 }

3.2、doCreateProxy

/**
  * 创建代理对象
  * @param beanObj bean实例
  * @param joinPointMethod 要代理的bean方法
  * @param strProxyClass 代理类
  * @param strProxyMethod 代理类的方法
  * @param isBefore 通知类型
  * @param useCglib 是否使用cglib
  * @return 代理对象
  * @throws Exception 空
  */
 private Object doCreateProxy(Object beanObj, String joinPointMethod, String strProxyClass, String strProxyMethod
         , boolean isBefore, boolean useCglib) throws Exception {
     Class<?> clazz = Class.forName(strProxyClass);
     Object proxyObject = clazz.getConstructor().newInstance();
     Method proxyMethod = clazz.getMethod(strProxyMethod);
     //  如果使用cglib动态代理
     if (useCglib) {
         Enhancer enhancer = new Enhancer();
         enhancer.setSuperclass(beanObj.getClass());
         /*
          * 设置被代理类执行方法时的逻辑
          * o 被代理类生成的对象实例(子类)
          * method 被代理类的方法
          * args 方法的参数
          * methodProxy 被代理类的方法的代理方法
          */
         enhancer.setCallback((MethodInterceptor) (o, method, args, methodProxy) -> {
             //  当方法是我们要代理的方法时
             if (method.getName().equals(joinPointMethod)) {
                 if (isBefore) {
                     proxyMethod.invoke(proxyObject);
                 }
                 //  调用父类的方法
                 methodProxy.invokeSuper(o, args); // 使用beanObj会报错,因为参数需要的是生成的子类
                 if (!isBefore) {
                     proxyMethod.invoke(proxyObject);
                 }
             } else { // 如果不写,则是全放空(由于生成的是子类,其他方法全空,不调用父类的【被代理类】)
                 methodProxy.invokeSuper(o, args); // 调用父类的方法
             }
             return o;
         });
         return enhancer.create();
     }
     /*
      * clazz 加载被代理类的加载器
      * interfaces 被代理类实现的所有接口
      * 增强方法
      *  o 被代理类的对象实例
      *  method 被代理类实现的所有接口的方法,且要执行
      *  args 方法需要的参数
      */
     // 代理一个类,该类通过loader加载,且实现interfaces接口
     return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), beanObj.getClass().getInterfaces(), (o, method, args) -> {
         //  当方法为我们要代理的方法时,执行特定增强
         if (method.getName().equals(joinPointMethod)) {
             if (isBefore) {
                 proxyMethod.invoke(proxyObject);
             }
             //  调用被代理类实际的方法
             method.invoke(beanObj, args); // 这里写beanObj不报错是因为它是AspectInterface的子类, 但不能写o,会死循环(调用了代理的自己,又再次调用)
             if (!isBefore) {
                 proxyMethod.invoke(proxyObject);
             }
         } else {
             method.invoke(beanObj, args);
         }
         //  返回值就是代理类执行代理方法的返回值
         return o;
     });

四、类图在这里插入图片描述

gitee地址:https://gitee.com/sir-tree/my-spring.git

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/353422.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端面试当中CDN会问啥------CDN详细教程来啦

⼀、CDN 1. CDN的概念 CDN&#xff08;Content Delivery Network&#xff0c;内容分发⽹络&#xff09;是指⼀种通过互联⽹互相连接的电脑⽹络系统&#xff0c;利 ⽤最靠近每位⽤户的服务器&#xff0c;更快、更可靠地将⾳乐、图⽚、视频、应⽤程序及其他⽂件发送给⽤户&…

代谢组学资讯,全球爆火的ChatGPT,是如何看待三阴性乳腺癌的?

领导说 今天下午6点前必须发出一篇推文 我表面毫无波澜实则内心风起云涌 那么问题来了 我如何才能在下班前发送推文准时下班呢 我要怎么写才能获得趣粉们的认可呢 全球爆火的ChatGPT&#xff0c;让我的格局一下打开~&#xff0c;它能不能成为我的“得力助手”&#xff1f;…

跳空缺口指标公式,主图显示向上向下跳空缺口

跳空缺口包含两种类型&#xff0c;向上跳空缺口和向下跳空缺口。向上跳空缺口是指当天最低价高于昨天的最高价&#xff0c;K线图出现缺口。向下跳空缺口是指当天最高价低于昨天的最低价&#xff0c;K线图出现缺口。 注意一下&#xff0c;上面的缺口定义与百科上有区别&#xf…

授权验证方式有很多、但AOP最为优雅。

前言 有时候项目中需要对接口进行校验&#xff0c;增加鉴权&#xff0c;确保 API 不被恶意调用。 项目中都是这样 这样&#xff0c;部分需要查询一些信息&#xff0c;下文需要使用 这样的代码很多&#xff0c;重复率太高。看着我蛋疼&#xff0c;对此优化一下。 方案 1 …

剑指offer 7 数组中和为0的三个数

此问题属于nsum问题&#xff0c;题目链接&#xff1a;力扣 要求在数组中找到不重复的三元组&#xff0c;三个数加起来为0&#xff0c;且每个下标只能用一次。而且需要返回所有这样的不重复数组。 1. 排序 双指针 1. 「不重复」的本质是什么&#xff1f;我们保持三重循环的大…

SpringBoot——日志文件

基本概念 日志文件记录了程序的报错信息&#xff0c;执行时间&#xff0c;用户的登录状态&#xff0c;操作时间等等 通过日志&#xff0c;我们可以轻松的找到程序的问题&#xff0c;得到程序的相关信息 springBoot启动时控制台打印的这些&#xff0c;就是程序的日志 创建日志…

Kafka报错:Controller 219 epoch 110 failed to change state for partition

集群里面kafka报错&#xff1a;Controller 219 epoch 110 failed to change state for partition maxwell_atlas-0 from OfflinePartition to OnlinePartitionkafka.common.stateChangeFailedException: Failed to elect leader for partition maxwell_atlas-0 under strategy …

SpringWeb

SpringWeb 概述 springWeb 是 spring 框架的一个模块&#xff0c;springWeb 和 spring 无需通过中间整 合层进行整合。 springWeb 是一个基于 mvc 的 web 框架,方便前后端数据的传输. SpringWeb 拥有控制器&#xff0c;接收外部请求&#xff0c;解析参数传给服务层. SpringM…

盘点界面组件DevExtreme 2023年值得期待的一些新功能!

DevExtreme拥有高性能的HTML5 / JavaScript小部件集合&#xff0c;使您可以利用现代Web开发堆栈&#xff08;包括React&#xff0c;Angular&#xff0c;ASP.NET Core&#xff0c;jQuery&#xff0c;Knockout等&#xff09;构建交互式的Web应用程序&#xff0c;该套件附带功能齐…

一文学会进程控制

目录进程的诞生fork函数fork的本质fork的常规用法fork调用失败的原因进程的死亡进程退出的场景常见的进程退出方法正常终止&#xff08;代码跑完&#xff09;echo $?main函数返回调用exit调用_exitexit和_exit的区别进程等待进程等待的重要性进程等待的函数waitwaitpid进程退出…

uniapp中条件编译

官方&#xff1a;https://uniapp.dcloud.net.cn/tutorial/platform.html#%E8%B7%A8%E7%AB%AF%E5%85%BC%E5%AE%B9 #ifndef H5 代码段… #endif 表示除了H5其他都可以编译 #ifdef H5 代码段… #endef 表示只能编译H5&#xff0c;其他的都不能编译 其他编译平台请查看官方文档。 …

连接器产业深度分析报告,国产化替代如何突出重围?(附厂商名录)

前言 2022年3-4月&#xff0c;上海疫情的封城举措&#xff0c;使得其它地区连接器类产品难以进入上海产业链&#xff0c;车载连接器的终端供应受阻&#xff0c;最终影响到全国多家车企生产&#xff1b; 同年12月&#xff0c;欧洲理事会批准—2024年12月28日之前&#xff0c;各类…

MySQL数据库调优————索引调优技巧

长字段的索引调优 当某张表需要给一个长字段创建索引时&#xff0c;因为索引长度越长&#xff0c;效率越差&#xff0c;所以我们需要对其进行优化。 创建额外的长字段的Hash值列 当长字段需要创建索引时&#xff0c;我们可以为其创建额外的一列&#xff0c;用其Hash值作为值…

如何利用Power Virtual Agents机器人实现成绩查询服务

今天我们继续介绍如何利用Power Virtual Agents来实现成绩查询服务。设计思路是在PVA聊天机器人的对话框中输入学生的姓名和学号来进行成绩的查询。首先&#xff0c;在Microsoft 365的OneDrive中制作一个Excel格式的成绩单。 可以将学生的学号、姓名、各学科成绩进行添加。 在P…

【初探人工智能】2、雏形开始长成

【初探人工智能】2、雏形开始长成【初探人工智能】2、雏形开始长成安装Flask封装Web接口雏形设置接收参数功能验证聊天写代码代码补全生成图片写在后面笔者初次接触人工智能领域&#xff0c;文章中错误的地方还望各位大佬指正&#xff01; 【初探人工智能】2、雏形开始长成 在…

限时活动|凭徽章领披萨大奖,玩转Moonbeam治理论坛

动动手指&#xff0c;无需每天打卡&#xff0c;用刷手机的零碎时间领一份Web3惊喜&#xff01; 本次挑战的目标是鼓励大家参与社区治理、熟悉论坛操作。有关参与方式和原因的信息在Twitter上共享&#xff1a;有兴趣可以和ThinkWildCrypto一起探索论坛以解锁其功能、了解最近和正…

【虹科干货】如何有效运用虹科任意波形发生器工作模式?

图 1&#xff1a;显示从存储器到输出的数据路径的 AWG 概念框图 01引言 任意波形发生器 (AWG) 的强大功能之一是它们可以生成几乎无限数量的波形。 AWG 的工作模式控制这些波形输出方式的时序。 在本应用说明中&#xff0c;我们将研究虹科Spectrum M4i.66xx 系列 AWG 工作模式…

JVM的GC机制和常见GC算法

文章目录[toc]1. 堆内存的分代2. GC分类3. 什么是GC3.1 需要GC的内存区域3.2 GC回收的对象3.3 判断对象存活的两种算法3.3.1 引用计数3.3.2 可达性分析3.4 什么时候触发GC4. 常见的GC算法4.1 标记-清除算法4.2 复制算法4.3 标记-压缩算法1. 堆内存的分代 堆中内存分为新生代和老…

String类 [上]

一、编码的基础介绍 编码&#xff1a;是信息从一种形式或格式转换为另一种形式的过程。 ASCLL 编码表:主要表示的是英文的编码表 Unicode&#xff1a;是为了解决传统的字符编码方案的局限而产生的&#xff0c;它为每种语言中的每个字符设定了统一并且唯一的二进制编码二进制编码…

小白式linux系统怎么安装宝塔面板

有很多小白同学问我linux系统服务器怎么远程连接。那么今天我们重点来教教大家如何用电脑远程服务器配上图文教程&#xff0c;让不懂的新手小白一看就会&#xff0c;分分钟上手教程怎么安装宝塔面板&#xff1f;这个其实很简单接下来跟着我操作。以linux centos7.6 举例Centos安…