当前位置: 首页 > news >正文

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

前言

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-argif ("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);//  非引用其他beanif (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()) {//  增强beanObjbeanObj = 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

相关文章:

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

前言 XML&#xff1a;通过Dom4j对XML进行解析和验证。 IOC&#xff1a;通过获取要创建对象的Class类型、构造函数后&#xff0c;通过反射来实现。 AOP&#xff1a;通过使用JDK动态代理和Cglib动态代理实现。 一、解析XML 1.1、解析bean标签 /*** 解析bean标签* param xmlBean…...

EasyExcel的使用

这里写目录标题先导入依赖最简单的写最简单的读項目开发中的一些操作xml一定要默認放先导入依赖 <dependencies><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.7</version><…...

基础篇(-1)-java特点、JDK、JRE、JVM区别、字节码编译、跨平台、程序运行

文章目录1.Java的特点有哪些&#xff1f;2.JDK和JRE和JVM的区别3.Java为什么采用字节码编译&#xff1f;4.什么是跨平台&#xff1f;5.Java程序从源代码到运行需要三步&#xff1f;1.Java的特点有哪些&#xff1f; Java是一种编程语言&#xff0c;被特意设计用于互联网的分布式…...

【网络编程】Java快速上手InetAddress类

概念 Java具有较好的网络编程模型/库&#xff0c;其中非常重要的一个API便是InetAddress。在Java.net 网络编程中中有许多类都使用到了InetAddress 这个类代表一个互联网协议&#xff08;IP&#xff09;地址。 IP地址是一个32&#xff08;IPV4&#xff09;位或128&#xff08;…...

小小bat-day1-自动文件上传

前言&#xff1a;日常服务器备份文件或者生产设备等数据文件都统一保存至文件服务器&#xff0c;进行日志分析或者将生产文件CSV、图片等转存至数仓进行数据分析&#xff0c;尤其生产的部分数据是保存在个人电脑的PC端&#xff0c;数据杂&#xff0c;获取困难&#xff0c;手动整…...

2023年美赛D题公布

问题D:联合国可持续发展目标的优先次序背景联合国制定了17项可持续发展目标(SDGs)。实现这些目标最终会改善世界各地许多人的生活。这些目标并不是相互独立的。所以&#xff0c;在某些目标上的积极收获通常是有影响的(积极的或消极的&#xff0c;有时两者都有)。这种相互联系使…...

Gartner 再度预测2023低代码趋势,真的会赚钱吗?

2023年&#xff0c;从业者对低代码的发展充满了想象&#xff0c;人们认为&#xff0c;未来低代码的商业价值不可估量。 此话并非空穴来风。据Gartner的最新报告显示&#xff0c;到2023年&#xff0c;超过70%的企业将采用低代码作为他们发展战略的关键目标之一&#xff1b;到202…...

Zebra ZT410 ZT411 导入中文字体

1.设置--发送到打印机 2.字体--添加--下载 --windows 字体库下载到zebra 打印机 3.字体--工具--调用 ZEBRA驱动包中驱动。 4.老版打印机导入方式 Zebra ZPL条形码打印机上如何下载和使用TrueType或External字体-敏用数码(上海北京济南洛阳)|专注于条码数据处理 (chongshang.co…...

nextTick 的使用和原理(面试题)

答题思路&#xff1a; nextTick 是做什么的&#xff1f;为什么需要它&#xff1f;开发时什么时候使用&#xff1f;介绍一下如何使用nextTick原理解读&#xff0c;结合异步更新和nextTick生效方式 1. nextTick是做什么的&#xff1f; nextTick是等待下一次DOM更新刷新的工具方法…...

Hudi系列19:Hudi写入模式

一. Changelog 模式 如果希望 Hoodie 保留消息的所有变更(I/-U/U/D), 之后接上 Flink 引擎的有状态计算实现全链路近实时数仓(增量计算)&#xff0c; Hoodie 的 MOR 表通过行存 原生支持 保留消息的所有变更(format 层面的集成)&#xff0c; 通过流读MOR 表可以消费到所有的变…...

Kubernetes安全

Kubernetes RBAC授权 Kubernetes 安全框架 K8S安全控制框架主要由下面3个阶段进行控制&#xff0c;每一个阶段都支持插件方式&#xff0c;通过API Server配置来启用插件。1. Authentication&#xff08;鉴权&#xff09; K8s Apiserver提供三种客户端身份认证&#xff1a;• H…...

全国进入裁员潮,到底是大厂难混?还是我技不如人?

前言 面对裁员&#xff0c;每个人的心态不同。他们有的完全没有料想到自己会被裁&#xff0c;有的却对裁员之事早有准备。大多数人&#xff0c;我想是焦虑失落的吧。 01 “降本增效”&#xff0c;HR怒提裁员刀 小默 | 32岁 芯片行业 人力资源 1月份&#xff0c;身处芯片行业H…...

电子技术——内部电容效应以及MOS与BJT的高频响应模型

电子技术——内部电容效应以及MOS与BJT的高频响应模型 耦合和旁路电容决定了放大器的低频响应&#xff0c;同时内部电容效应决定了放大器的高频响应。本节&#xff0c;我们简单简单介绍一下内部电容效应&#xff0c;并且更重要的是如何在小信号模型中模型化内部电容效应。 MOS…...

华为OD机试题 - 出租车计费(JavaScript)

最近更新的博客 2023新华为OD机试题 - 斗地主(JavaScript)2023新华为OD机试题 - 箱子之形摆放(JavaScript)2023新华为OD机试题 - 考古学家(JavaScript)2023新华为OD机试题 - 相同数字的积木游戏 1(JavaScript)2023新华为OD机试题 - 最多等和不相交连续子序列(JavaScri…...

Django框架进阶版

一、Django介绍 1.起源 2005年发布&#xff0c;采用python语言编写的。 早期Django主要做新闻和内容管理 重量级python web框架&#xff0c;配备了大量组件 2.组件 包含组件如下 基本配置文件/路由系统 MTV设计模式 Cookies和Session 分页和发邮件 Admin管理后台 3…...

2023美赛F题全部代码+数据+结果 数学建模

2023年美赛F题全部思路 数据代码都已完成 全部内容见链接&#xff1a;https://www.jdmm.cc/file/2708700/ 1.根据文献选的GGDP的指标&#xff0c;发现GGDP与水资源等有关&#xff0c;由此可以筛选出影响GGDP的所有因子&#xff0c;并可以用所有因子利用层次分析法建立评价体…...

Java基础-logback日志使用

日志 1.1 作用&#xff1a; ​ 跟输出语句一样&#xff0c;可以把程序在运行过程中的详细信息都打印在控制台上。 ​ 利用log日志还可以把这些详细信息保存到文件和数据库中。 1.2 使用步骤&#xff1a; ​ 不是java的&#xff0c;也不是自己写的&#xff0c;是第三方提供…...

kaggle竞赛-宠物受欢迎程度(赛题讲解与数据分析)

比赛官网地址 赛题介绍 petfinder是马来西亚领先的动物福利平台宠物网站地址 该网站使用可爱指数来排名宠物照片。它分析了图片组成和其他因素&#xff0c;并与数千个宠物档案的表现进行了比较。 在这场比赛中&#xff0c;你将分析原始图像和元数据来预测宠物照片的“Pawp…...

Go语言基础知识学习笔记

环境准备 下载安装Golang&#xff1a;https://golang.google.cn/dl/ 因为国外下载速度较慢&#xff0c;我们需要配置国内代理 # 开启包管理工具 go env -w GO111MODULEon # 设置代理 go env -w GOPROXYhttps://goproxy.cn,direct # 设置不走 proxy 的私有仓库&#xff0c;多…...

Python3 错误和异常

Python3 错误和异常 作为 Python 初学者&#xff0c;在刚学习 Python 编程时&#xff0c;经常会看到一些报错信息&#xff0c;在前面我们没有提及&#xff0c;这章节我们会专门介绍。 Python 有两种错误很容易辨认&#xff1a;语法错误和异常。 Python assert&#xff08;断…...

3分钟搞定TrollStore:iOS 14-16.6.1一键安装终极指南

3分钟搞定TrollStore&#xff1a;iOS 14-16.6.1一键安装终极指南 【免费下载链接】TrollInstallerX A TrollStore installer for iOS 14.0 - 16.6.1 项目地址: https://gitcode.com/gh_mirrors/tr/TrollInstallerX 你是否曾为在iOS设备上安装TrollStore而烦恼&#xff1…...

AI工作流框架实战:从脚本到自动化流程的架构设计与应用

1. 项目概述&#xff1a;当AI遇上工作流最近在折腾自动化工具链&#xff0c;发现一个挺有意思的项目叫ai-flow。这名字听起来就挺直白&#xff0c;AI 工作流。简单来说&#xff0c;它就是一个用代码来编排和自动化AI任务&#xff08;比如调用大语言模型、处理数据、执行特定操…...

LLM训练实战:8个编程谜题带你掌握分布式训练核心技术

1. 项目概述与核心价值如果你对大型语言模型&#xff08;LLM&#xff09;的训练过程感到好奇&#xff0c;或者你听说过“千卡集群”、“万亿参数”这些词&#xff0c;但总觉得它们离自己很遥远&#xff0c;那么这个名为“LLM Training Puzzles”的项目&#xff0c;就是为你量身…...

神经科学启发的边缘AI持续学习:从突触修剪到双记忆系统的架构设计

1. 项目概述&#xff1a;为什么我们需要一个“会学习”的边缘大脑&#xff1f;想象一下&#xff0c;你家里的扫地机器人&#xff0c;第一天它学会了绕过餐桌腿&#xff0c;第二天你搬来一把新椅子&#xff0c;它却一头撞了上去&#xff0c;然后彻底忘记了怎么绕过餐桌腿。这听起…...

GTA5线上小助手:终极免费工具完整使用指南,快速提升游戏体验

GTA5线上小助手&#xff1a;终极免费工具完整使用指南&#xff0c;快速提升游戏体验 【免费下载链接】GTA5OnlineTools GTA5线上小助手 项目地址: https://gitcode.com/gh_mirrors/gt/GTA5OnlineTools 想要在《侠盗猎车手5》线上模式中摆脱繁琐操作&#xff0c;享受更流…...

用Claude Code+R零代码复现医学顶刊论文:零基础到掌握全流程医学SCI论文训练营

人工智能飞速发展&#xff0c;对于研究生、科研工作者而言&#xff0c;只需要聚焦研究问题创新&#xff0c;统计实操、图表制作、结果呈现等等SCI论文中涉及的工作都有工具可以帮你……我们团队最新开设&#xff1a;“零基础掌握SCI论文全流程&#xff1a;Claude CodeR零代码复…...

OpenClaw Deck:为Steam Deck打造开源模块化工具集

1. 项目概述&#xff1a;一个为Steam Deck量身定制的开源工具集如果你是一位Steam Deck的深度用户&#xff0c;大概率经历过这样的场景&#xff1a;想在掌机上玩一些非Steam平台的游戏&#xff0c;或者想对系统进行一些深度定制&#xff0c;却发现官方系统虽然稳定&#xff0c;…...

LynxPrompt Action:GitHub Actions 实现 AI 配置中心化与自动化管理

1. 项目概述&#xff1a;为什么我们需要一个AI配置的“中央仓库”&#xff1f; 如果你和我一样&#xff0c;日常开发中同时用着Cursor、Claude Code、GitHub Copilot&#xff0c;甚至还在尝试Windsurf和Aider&#xff0c;那你一定遇到过这个头疼的问题&#xff1a;每个工具的配…...

Gemini自动生成PPT实战手册:从零输入到专业演示文稿,3步完成95%的幻灯片工作流

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;Gemini自动生成PPT的核心原理与能力边界 Gemini 生成 PPT 的本质并非传统模板填充&#xff0c;而是基于多模态理解与结构化内容重构的端到端推理过程。其核心依赖于对用户输入&#xff08;文本、大纲、…...

从‘能用’到‘优雅’:Python函数设计的3个坏味道与5个重构技巧(附代码对比)

从‘能用’到‘优雅’&#xff1a;Python函数设计的3个坏味道与5个重构技巧&#xff08;附代码对比&#xff09; 在Python开发中&#xff0c;函数是最基本的代码组织单元。许多开发者能够快速实现功能&#xff0c;却往往忽视了函数设计的质量。本文将揭示三种典型的函数设计&qu…...