手写Spring

简单实现Spring基于注解配置

 

ComponentScan

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentScan {String value() default "";
}

相当于component-scan 

HspSpringConfig

@ComponentScan(value = "spring.write.component")
public class HspSpringConfig {
}

替代bean.xml文件,添加@ComponentScan,获得扫描的包

AppMain

public class AppMain {public static void main(String[] args) {HspApplicationContext ioc = new HspApplicationContext(HspSpringConfig.class);Object userDao = ioc.getBean("userDao");System.out.println(userDao);}
}

HspApplicationContext

public class HspApplicationContext {private Class configClass;private final ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();public HspApplicationContext(Class configClass) {//1、获得扫描包路径//得到config文件的.class类型this.configClass = configClass;//反射得到ComponentScan注解ComponentScan componentScan =(ComponentScan)configClass.getDeclaredAnnotation(ComponentScan.class);//获取注解的value就是扫描的包路径String path = componentScan.value();System.out.println(path);//spring.write.component//2、得到包下的所有.class//得到类的加载器,获取实际目录下的(out),ClassLoader classLoader = ClassLoader.getSystemClassLoader();//URL必须按照斜杠/来写//path = spring.write.componentpath = path.replace(".","/");URL resource = classLoader.getResource(path);System.out.println(resource);//file:/D:/Atest/spring/out/production/spring/spring/write/component//对路径下的文件进行遍历File file = new File(resource.getFile());if(file.isDirectory()) {File[] files = file.listFiles();for (File f : files) {//只处理.class文件(java文件)String fileAbsolutePath = f.getAbsolutePath();if(fileAbsolutePath.endsWith(".class")) {System.out.println(fileAbsolutePath);//D:\Atest\spring\out\production\spring\spring\write\component\UserDao.class//前面已经有了path,还需要具体的类名进行反射,放入容器String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));String classFullName = path.replace("/", ".") + "." + className;//判断该类是不是需要注入到容器,有无注解try {Class<?> aClass = classLoader.loadClass(classFullName);//判断该类的实例是否有注解if(aClass.isAnnotationPresent(Component.class) ||aClass.isAnnotationPresent(Controller.class) ||aClass.isAnnotationPresent(Service.class) ||aClass.isAnnotationPresent(Repository.class)) {//查看是否指定了idif(aClass.isAnnotationPresent(Component.class)) {//只演示了ComponentComponent component = aClass.getDeclaredAnnotation(Component.class);String id = component.value();if(! "".endsWith(id)) {className = id;}}//放入容器Class<?> clazz = Class.forName(classFullName);Object instance = clazz.newInstance();//类名首字母小写存入,springframework下的工具类ioc.put(StringUtils.uncapitalize(className),instance);}} catch (Exception e) {e.printStackTrace();}}}}}public Object getBean(String name) {return ioc.get(name);}
}

Spring整体架构分析

没有加@Scope(value="prototype")就是单例
ioc.getBean("name"),先到BeanDefinition Map获取,未找到异常处理,
如果是单例(single)就从单例Bean Map获取
如果是多例(prototype)就创建bean对象并返回(到BeanDefinition Map中,得到Bean的clazz对象,使用反射,创建bean返回)

1、实现扫描包,得到bean的class对象

Spring基于注解配置已经写过

HspApplicationContext

public class HspApplicationContext {private Class configClass;public HspApplicationContext(Class configClass) {this.configClass = configClass;ComponentScan componentScan =(ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);String path = componentScan.value();System.out.println(path);//com.spring.componentClassLoader classLoader =HspApplicationContext.class.getClassLoader();path = path.replace(".", "/");System.out.println(path);//com/spring/componentURL resource =classLoader.getResource(path);File file = new File(resource.getFile());if(file.isDirectory()) {File[] files = file.listFiles();for (File f : files) {String fileAbsolutePath = f.getAbsolutePath();if(fileAbsolutePath.endsWith(".class")) {String className = fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));String classFullName = path.replace("/", ".") + "." + className;try {Class<?> clazz = classLoader.loadClass(classFullName);if(clazz.isAnnotationPresent(Component.class)) {System.out.println("是Spring bean【class对象】= " + clazz + " 【类名】 = " + className);} else {System.out.println("不是Spring bean【class对象】= " + clazz + " 【类名】 = " + className);}} catch (Exception e) {e.printStackTrace();}}}}}
}

2、扫描bean信息,封装到BeanDefinition对象并放入Map

增加@Scope注解来判断单例(single)或者多例(prototype)

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {String value() default "";
}

BeanDefinition用于封装/记录Bean信息
1、单例/多例
2、多例需要动态生成,存放Bean对应的Class对象,反射生成

public class BeanDefinition {private String scope;private Class clazz;public String getScope() {return scope;}public void setScope(String scope) {this.scope = scope;}public Class getClazz() {return clazz;}public void setClazz(Class clazz) {this.clazz = clazz;}@Overridepublic String toString() {return "BeanDefinition{" +"scope='" + scope + '\'' +", clazz=" + clazz +'}';}
}

HspApplicationContext

private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =new ConcurrentHashMap<>();
//单例对象类型不确定,用Object
private ConcurrentHashMap<String, Object> singletonObejcts =new ConcurrentHashMap<>();

如果有@Component,就放入beanDefinitionMap中

可以将1、2步写成一个方法,在构造器里调用

3、初始化bean单例池并完成getBean、createBean方法

public class HspApplicationContext {private Class configClass;private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap =new ConcurrentHashMap<>();//单例对象类型不确定,用Objectprivate ConcurrentHashMap<String, Object> singletonObejcts =new ConcurrentHashMap<>();public HspApplicationContext(Class configClass) {//完成扫描指定包beanDefinitionScan(configClass);//遍历所有的beanDefinition对象Enumeration<String> keys = beanDefinitionMap.keys();while(keys.hasMoreElements()) {String beanName = keys.nextElement();BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);if("singleton".equalsIgnoreCase(beanDefinition.getScope())) {Object bean = createBean(beanDefinition);singletonObejcts.put(beanName,bean);}}System.out.println("singletonObejcts单例池=" + singletonObejcts);System.out.println("beanDefinitionMap=" + beanDefinitionMap);}public void beanDefinitionScan(Class configClass) {}public Object createBean(BeanDefinition beanDefinition) {Class clazz = beanDefinition.getClazz();try {Object instance = clazz.getDeclaredConstructor().newInstance();return instance;} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}return null;}public Object getBean(String name) {//判断beanName是否存在if(beanDefinitionMap.containsKey(name)) {BeanDefinition beanDefinition = beanDefinitionMap.get(name);if("singleton".equalsIgnoreCase(beanDefinition.getScope())) {return singletonObejcts.get(name);} else {return createBean(beanDefinition);}} else {throw new NullPointerException("没有该Bean");}}
}

4、依赖注入

增加@Autowired注解,这里只用名字来进行匹配

 

    public Object createBean(BeanDefinition beanDefinition) {Class clazz = beanDefinition.getClazz();try {Object instance = clazz.getDeclaredConstructor().newInstance();//遍历所有字段,for(Field field : clazz.getDeclaredFields()) {//是否有Autowiredif(field.isAnnotationPresent(Autowired.class)) {String name = field.getName();//通过getBean获取要组装的对象Object bean = getBean(name);//进行组装,private需要暴破field.setAccessible(true);field.set(instance,bean);//将 bean 赋值给 instance 的 field}}return instance;} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();}return null;}

5、bean后置处理器实现

原生Spring接口InitializingBean(初始化),自己实现这个接口,完成初始化方法
接口中有afterPropertiesSet()方法,在Bean的setter方法执行完毕后,被spring容器调用,就是初始化方法

public interface InitializingBean {void afterPropertiesSet() throws Exception;
}

应用:选择MonsterService来实现这个接口

@Component("monsterService")
@Scope(value = "property")
public class MonsterService implements InitializingBean {@Autowiredprivate MonsterDao monsterDao;public void m1() {monsterDao.hi();}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("monsterService初始化方法被调用");}
}

在创建好Bean实例后,判断是否需要初始化
容器中常用的一个方法是:根据该类是否实现了某个接口,来判断是否要执行某个业务(接口编程)

在HspApplicationContext的createBean返回instance之前加上判断是否是这个接口的子类型,是的话就执行初始化方法

判断如果返回为null,不变化
加上beanName

public Object createBean(String beanName, BeanDefinition beanDefinition) {Class clazz = beanDefinition.getClazz();try {Object instance = clazz.getDeclaredConstructor().newInstance();//遍历所有字段,for(Field field : clazz.getDeclaredFields()) {//是否有Autowiredif(field.isAnnotationPresent(Autowired.class)) {String name = field.getName();//通过getBean获取要组装的对象Object bean = getBean(name);//进行组装,private需要暴破field.setAccessible(true);field.set(instance,bean);//将 bean 赋值给 instance 的 field}}System.out.println("======创建好实例======" + instance);//初始化方法前,调用before方法,可以对容器的bean实例进行处理,然后返回处理后的beanfor (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {Object current = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);if(current != null)instance = current;}if(instance instanceof InitializingBean) {try {((InitializingBean) instance).afterPropertiesSet();//将instance转成InitializingBean类型,调用方法} catch (Exception e) {e.printStackTrace();}}//初始化方法后,调用after方法,可以对容器的bean实例进行处理,然后返回处理后的beanfor (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {Object current = beanPostProcessor.postProcessAfterInitialization(instance, beanName);if(current != null)instance = current;}return instance;

后置处理器接口BeanPostProcessor

对容器中的所有bean生效

public interface BeanPostProcessor {//bean的初始化前调用default Object postProcessBeforeInitialization(Object bean, String beanName) {return bean;}//bean的初始化后调用default Object postProcessAfterInitialization(Object bean, String beanName) {return bean;}
}

自己写一个类实现后置处理器,通过Component注入到容器中

实现类可以不止一个,放在Arraylist中

private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

@Component
public class HspBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println("后置处理器before()");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("后置处理器after()");return bean;}
}

在HspApplicationContext的扫描方法中扫描Component,再判断是否实现了后置处理器接口

6、AOP机制实现 

原生Spring实现方法
A接口写方法,B类实现A接口,给B类加上@Component注入到容器
切面类加上@Component和@Aspect
B类执行后置处理器的After方法后,变成代理对象

public interface SmartAnimalable {float getSum(float i, float j);float getSub(float i, float j);
}
@Component(value = "smartDog")
public class SmartDog implements SmartAnimalable {public float getSum(float i, float j) {float res = i + j;System.out.println("SmartDog-getSum-res=" + res);return res;}public float getSub(float i, float j) {float res = i - j;System.out.println("SmartDog-getSub-res=" + res);return res;}
}
public class SmartAnimalAspect {public static void showBeginLog() {System.out.println("前置通知..");}public static void showSuccessLog() {System.out.println("返回通知..");}
}

在HspBeanPostProcessor的after方法中实现AOP(写死的方法)

public Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("后置处理器after()");if("smartDog".equals(beanName)) {Object proxyInstance = Proxy.newProxyInstance(HspBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = null;if("getSum".equals(method.getName())) {SmartAnimalAspect.showBeginLog();result = method.invoke(bean,args);SmartAnimalAspect.showSuccessLog();} else {result = method.invoke(bean,args);}return result;}});return proxyInstance;}return bean;}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C#自定义曲线绘图面板

一、实现功能 1、显示面板绘制。 2、拖动面板&#xff0c;X轴、Y轴都可以拖动。 3、显示面板缩放&#xff0c;放大或者缩小。 4、鼠标在面板中对应的XY轴数值。 5、自动生成的数据数组&#xff0c;曲线显示。 6、鼠标是否在曲线上检测。 二、界面 拖动面板 鼠标在曲线上…

【随手笔记】使用J-LINK读写芯片内存数据

第一种使用JLINK.exe 1. 打开j-link.exe 2.输入【usb】 3. 连接芯片 输入【connect】输入芯片型号【STM32L071RB】输入连接方式 【S】 使用SWD连接方式输入连接速率 【4000】连接成功 4. 输入【&#xff1f;】查看指令提示 5. 读写指令 Mem Mem [<Zone>…

DataFrame生成excel后为什么多了一行数字

问题描述 python查询数据生成excel文件&#xff0c;生成的excel多了第一行数字索引&#xff0c;1,2,3,4,5...... 代码&#xff1a; df pd.DataFrame(data)df.to_excel(filename, sheet_name用户信息表, indexFalse) 解决&#xff1a; 原理也很简单&#xff0c;就是设置个参…

【python设计模式7】行为型模式2

目录 策略模式 模板方法模式 策略模式 定义一个个算法&#xff0c;把它们封装起来&#xff0c;并且使它们可以相互替换。本模式使得算法可独立于使用它的客户而变化。角色有&#xff1a;抽象策略、具体策略和上下文。 from abc import abstractmethod, ABCMeta from datetim…

前端vue-ref与document.querySelector的对比

ref只在本组件中查找&#xff0c;而document.querySelector是在整个页面查找

Golang | Leetcode Golang题解之第414题第三大的数

题目&#xff1a; 题解&#xff1a; func thirdMax(nums []int) int {var a, b, c *intfor _, num : range nums {num : numif a nil || num > *a {a, b, c &num, a, b} else if *a > num && (b nil || num > *b) {b, c &num, b} else if b ! ni…

SQL Server性能优化之读写分离

理论部分: 数据库读写分离&#xff1a; 主库&#xff1a;负责数据库操作增删改 20% 多个从库&#xff1a;负责数据库查询操作 80% 读写分离的四种模式 1.快照发布&#xff1a;发布服务器按照预定的时间间隔向订阅服务器发送已发布的数据快照 2.事务发布[比较主流常见]&#xf…

Ceph 基本架构(一)

Ceph架构图 Ceph整体组成 Ceph 是一个开源的分布式存储系统&#xff0c;设计用于提供优秀的性能、可靠性和可扩展性。Ceph 的架构主要由几个核心组件构成&#xff0c;每个组件都有特定的功能&#xff0c;共同协作以实现高可用性和数据的一致性。 以下是 Ceph 的整体架构及其…

2024 “华为杯” 中国研究生数学建模竞赛(C题)深度剖析|数据驱动下磁性元件的磁芯损耗建模|数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题&#xff01; CS团队倾注了大量时间和心血&#xff0c;深入挖掘解…

使用LangGPT提示词让大模型比较浮点数

使用LangGPT提示词让大模型比较浮点数 背景介绍环境准备创建虚拟环境安装一些必要的库安装其他依赖部署大模型启动图形交互服务设置提示词与测试 LangGPT结构化提示词 背景介绍 LLM在对比浮点数字时表现不佳&#xff0c;经验证&#xff0c;internlm2-chat-1.8b (internlm2-cha…

MySQL聚合统计和内置函数

【数据库】MySQL聚合统计 王笃笃-CSDN博客https://blog.csdn.net/wangduduniubi?typeblog显示平均工资低于2000的部门和它的平均工资 mysql> select deptno,avg(sal) deptavg from emp group by deptno; --------------------- | deptno | deptavg | --------------…

【第33章】Spring Cloud之SkyWalking服务链路追踪

文章目录 前言一、介绍1. 架构图2. SkyWalking APM 二、服务端和控制台1. 下载2. 解压3. 初始化数据库4. 增加驱动5. 修改后端配置6. 启动7. 访问控制台8. 数据库表 三、客户端1. 下载2. 设置java代理3. idea配置3.1 环境变量3.2 JVM参数3.3 启动日志 4. 启用网关插件 四、链路…

C++和OpenGL实现3D游戏编程【目录】

欢迎来到zhooyu的专栏。 个人主页&#xff1a;【zhooyu】 文章专栏&#xff1a;【OpenGL实现3D游戏编程】 贝塞尔曲面演示&#xff1a; 贝塞尔曲面演示zhooyu 本专栏内容&#xff1a; 我们从游戏的角度出发&#xff0c;用C去了解一下游戏中的功能都是怎么实现的。这一切还是要…

电脑ip会因为换了网络改变吗

在当今数字化时代&#xff0c;IP地址作为网络世界中的“门牌号”&#xff0c;扮演着至关重要的角色。它不仅是设备在网络中的唯一标识&#xff0c;也是数据交换和信息传递的基础。然而&#xff0c;对于普通用户而言&#xff0c;一个常见的问题便是&#xff1a;当电脑连接到不同…

Visual Studio 2022 - QT 环境中文字符乱码问题

Visual Studio 2022 - QT 环境中文字符乱码问题 一、Visual Studio 2022 - Qt 环境 在 QT 中使用中文字符串常会出现乱码现象&#xff0c;如下&#xff1a;以下提供了几个解决方法&#xff0c;仅供参考 QString str "百香果真是一直可爱的小猫咪"; qDebug() <…

SAP HCM 组织增量解决方案

增量&#xff1a;今天遇到一个比较麻烦的问题&#xff0c;就是客户搭建中台&#xff0c;表结构和SAP的表结构一致&#xff0c;因为中台没有SAP那么多校验的逻辑&#xff0c;导致现在两边的主数据有差异&#xff0c;现在需要做个增量方案&#xff0c;SAP修改后增量传输给中台&am…

实施项目,“流程重组”你是躲不开的

文/杨长春 作者简介&#xff1a;某IT公司项目总监&#xff0c;资深IT博主&#xff0c;专注于IT项目知识分享&#xff0c;著有《实战需求分析》、《软件需求分析实战》、《数字化管理软件实施》。 甲方跟本项目相关的领域&#xff0c;一定运行着一套管理体系&#xff0c;各个岗…

Nature|PathChat:病理学多模态生成性AI助手的创新与应用|顶刊精析·24-09-21

小罗碎碎念 今日顶刊&#xff1a;Nature 这篇文章今年6月就发表了&#xff0c;当时我分析的时候&#xff0c;还是预印本&#xff0c;没有排版。今天第一篇推文介绍的是Faisal Mahmood &#xff0c;所以又把这篇文章拉出来详细分析一下。 作者角色作者姓名单位名称单位英文名称第…

Docker 里面按照ifconfig

1. 进入Docker 容器内部 docker exec -it xxx bash2. 安装 net-tools iputils-ping apt-get update && apt-get install -y net-tools apt-get update && apt-get install -y iputils-ping 3. 执行ifconfig 执行ping

计算机毕业设计 乡村生活垃圾管理系统的设计与实现 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…