Spring之丐版IOC实现
文章目录
大家好,我是Leo。Spring核心中依赖注入和IOC容器是非常常见的,用起来也是非常的顺手,只能说是真香,那如何实现一个丐版的SpringIOC呢?那今天我们就来讲如何实现一个乞丐版的IOC和通过注解进行依赖注入。
IOC控制反转
在开始之前,首先得先理解什么是IOC的控制反转,Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系 。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。
说白了就是,IOC负责我们的饮食,我们要饭和菜,只需要从IOC容器获取就行。
那这样的作用也就非常明显了,解耦、方便
依赖注入
那依赖注入肯定是IOC的精髓所在,我们平常使用@Autowired注解等自动注入方式,用起来确实舒服。
那依赖注入常用方式有哪些呢?
构造函数的参数实现注入
基于set注入(静态工厂和动态工厂)
基于属性的注入
Bean的自动装配方式
可以在文件中设置自动装配(autowire)方式,支持的装配方式有
方式 | 描述 |
---|---|
no | 手动装配 |
byName | 通过id的名字自动注入对象 |
byType | 通过类型自动注入对象 |
constructor | 通过构造方法自动注入对象 |
autodectect | 完全交给Spring管理,按先Constructor后byType的顺序进行匹配 |
丐版IOC实现
BeanDefinition.java
Bean的定义类,主要是包括配置Bean定义的对应的实体,对应的构造方法和getset方法就省略了
public class BeanDefinition {
private String beanName;
private Class beanClass;
ResourceLoader.java
资源加载器,用来完成包的扫描和Bean的加载
public class ResourceLoader {
private static String rootPath;
private Map<String, BeanDefinition> beanDefinitionMap = new HashMap(16);
public Map<String, BeanDefinition> getResource(String basePackage) {
// com.zly
try {
// 1. 把.换成/
String packagePath = basePackage.replaceAll("\\.", "\\\\");
// 2.获取包的绝对路径
Enumeration<URL> urls =
Thread.currentThread()
.getContextClassLoader()
.getResources(packagePath);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
String filePath = URLDecoder.decode(url.getFile(), "utf-8");
rootPath = filePath.substring(0, filePath.length() - packagePath.length());
// 包扫描
try {
loadBean(new File(filePath));
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
return this.beanDefinitionMap;
}
private void loadBean(File file) throws Exception {
// 1. 判断是否是文件夹
if (file.isDirectory()) {
// 2. 获取文件夹的所有内容
File[] childFiles = file.listFiles();
// 3. 判断文件夹内为空,直接返回
if (childFiles == null || childFiles.length == 0) {
return;
}
// 4. 如果文件夹里面不为空,遍历文件夹所有的内容
for (File childFile : childFiles) {
// 4.1 遍历得到每个File对象,继续是文件夹,递归
if (childFile.isDirectory()) {
loadBean(childFile);
} else {
// 4.2 得到不是文件夹,是文件
// 4.3 得到包路径 和类名称
String pathWithClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);
// 4.4 判断当前文件类型是否为class
if (pathWithClass.contains(".class")) {
// 4.5 是class类型,把\替换成. 把.class去掉
String allName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
// 4.6 判断类上面是否有注解@Bean,放到map集合beanFactory
// 4.6.1 获取类的Class对象
Class<?> clazz = Class.forName(allName);
// 4.6.2 判断不是接口
if (!clazz.isInterface()) {
// 4.6.3 判断上面是否有@Bean注解
Bean annotation = clazz.getAnnotation(Bean.class);
if (annotation != null) {
// 4.6.4 实例化
String beanName = annotation.value();
if ("".equals(beanName)) {
beanName = allName;
}
if (clazz.getInterfaces().length > 0) {
System.out.println("正在加载【"+ clazz.getInterfaces()[0] +"】");
BeanDefinition beanDefinition = new BeanDefinition(beanName, clazz);
beanDefinitionMap.put(beanName, beanDefinition);
} else {
System.out.println("正在加载【"+ clazz.getInterfaces()[0]);
BeanDefinition beanDefinition = new BeanDefinition(beanName, clazz);
beanDefinitionMap.put(beanName, beanDefinition);
}
}
}
}
}
}
}
}
}
BeanRegister.java
用于向容器中注册一个Bean,在扫描后,我们的Bean主要是放在了beanDefinitionMap中,还没有进行加载和初始化,在获取中再进行加载到这个缓存Map中
public class BeanRegister {
private Map<String, Object> singletonMap = new HashMap<>(32);
public Object getSingletonBean(String beanName) {
return singletonMap.get(beanName);
}
public void registerSingletonBean(String beanName, Object bean) {
if (singletonMap.containsKey(beanName)) {
return;
}
singletonMap.put(beanName, bean);
}
}
Bean和DI的注解
这里的两个注解主要是用来标志这是一个Bean和进行依赖注入的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
String value() default "";
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Di {
}
BeanFactory.java
这里的对象工厂主要是我们最重要的一个类,在创建时,需要加载注册器和资源加载,在获取Bean中,需要进行依赖注入,并创建一个Bean
public class BeanFactory {
/**
* 创建一个map集合,放bean对象
*/
private Map<String , BeanDefinition> beanDefinitionMap = new HashMap<>();
private BeanRegister beanRegister;
public BeanFactory(String basePackage) {
beanRegister = new BeanRegister();
this.beanDefinitionMap = new ResourceLoader().getResource(basePackage);
}
public Object getBean(String beanName) {
// 从缓存中获取
Object bean = beanRegister.getSingletonBean(beanName);
if (bean != null) {
return bean;
}
// 创建bean
return createBean(beanDefinitionMap.get(beanName));
}
public Object getBean(Class<?> clazz) {
BeanDefinition beanDefinition = getBeanNameByType(clazz);
if (Objects.isNull(beanDefinition)) {
log.error("can not find {}", clazz);
return null;
} else {
return getBean(beanDefinition.getBeanName());
}
}
private Object createBean(BeanDefinition beanDefinition) {
try {
Object bean = beanDefinition.getBeanClass().getConstructor().newInstance();
beanRegister.registerSingletonBean(beanDefinition.getBeanName(), bean);
loadDi(beanDefinition.getBeanName());
return bean;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 属性注入
*/
private void loadDi(String beanName) {
// 1. 实例化对象都在beanFactory的map集合中,遍历
Object bean = this.beanRegister.getSingletonBean(beanName);
// 2. 获取map集合每个对象和属性
Class<?> objectClass = bean.getClass();
// 3. 遍历得到每个对象属性数组,得到每个属性
Field[] declaredFields = objectClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
// 4. 判断属性上面是否有@Di注解
Di annotation = declaredField.getAnnotation(Di.class);
if (annotation != null) {
declaredField.setAccessible(true);
// 如果私有属性,可以设置值
// 5. 如果有@Di注解,把对象进行注入
try {
BeanDefinition beanDefinition = getBeanNameByType(declaredField.getType());
if (Objects.isNull(beanDefinition)) {
declaredField.set(bean, null);
} else {
declaredField.set(bean, getBean(beanDefinition.getBeanName()));
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
private BeanDefinition getBeanNameByType(Class clazz) {
Set<Map.Entry<String, BeanDefinition>> entries = this.beanDefinitionMap.entrySet();
for (Map.Entry<String, BeanDefinition> entry : entries) {
if (entry.getValue().getBeanClass().equals(clazz)) {
return entry.getValue();
}
if (entry.getValue().getBeanClass().getInterfaces()[0].equals(clazz)) {
return entry.getValue();
}
}
return null;
}
}
ApplicationContext
我也模仿了Spring一样定义了一个ApplicationContext容器,获取Bean从这个容器获取,context容器再从BeanFactory中获取
public class AnnotationApplicationContext implements ApplicationContext {
/**
* 创建一个map集合,放bean对象
*/
private BeanFactory beanFactory;
@Override
public Object getBean(Class clazz) {
return beanFactory.getBean(clazz);
}
@Override
public Object getBean(String beanName) {
return beanFactory.getBean(beanName);
}
/**
* 设置包扫描规则
* 当前包及其子包,哪个类有@Bean注解,把这个类通过反射化
*/
public AnnotationApplicationContext(String basePackage) throws Exception {
this.beanFactory = getBeanFactory(basePackage);
}
private BeanFactory getBeanFactory(String baskPackage) {
return new BeanFactory(baskPackage);
}
}
测试,实现
@Bean
public class UserServiceImpl implements UserService {
@Di
private UserDao userDao;
@Override
public void add() {
System.out.println("service.........");
userDao.add();
}
}
public interface UserService {
void add();
}
@Bean
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("dao add...");
}
}
public interface UserDao {
void add();
}
public class UserTest {
public static void main(String[] args){
try {
ApplicationContext context = new AnnotationApplicationContext("com.zly");
UserService userService = (UserService)context.getBean(UserService.class);
userService.add();
} catch (Exception e) {
e.printStackTrace();
}
}
}
相信你看到这里,就可以很容易的理解了这个丐版的Spring就算完成了,希望可以对大家有帮助啦
Spring之丐版IOC实现的更多相关文章
- 简单实现Spring框架--注解版
自己写的Spring框架——简单实现IoC容器功能 前几天在网上看了篇帖子,是用xml的方式实现spring的ioc容器,觉得挺有意思的,这边自己试着用注解的形式造了一套轮子. 工程结构 codein ...
- spring.net中的IOC和DI-初使用
前面准备:下载spring.net并解压 下载地址:spring.net下载地址 Ioc:控制反转 DI:依赖注入 一.IOC(控制反转) 1.新建一个控制台程序springTest, ...
- spring 学习 AOP和IOC
自11开始接触三大框架,至今已俞5载, 当时风光无限的ssh,现在还在被广泛使用,并有扩大之势的只有spring了 spring主要特性,是广为使用的AOP(面向切面)和IOC(控制反转) 1.其中, ...
- spring源码浅析——IOC
=========================================== 原文链接: spring源码浅析--IOC 转载请注明出处! ======================= ...
- spring mvc注解版01
spring mvc是基于servlet实现的在spring mvc xml版中已经说过了,注解版相较于xml版更加简洁灵活. web项目的jar包: commons-logging-1.1.3.ja ...
- spring学习(01)之IOC
spring学习(01)之IOC IOC:控制反转——Spring通过一种称作控制反转(IOC)的技术促进了低耦合.当应用了IOC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创 ...
- spring源码分析---IOC(1)
我们都知道spring有2个最重要的概念,IOC(控制反转)和AOP(依赖注入).今天我就分享一下spring源码的IOC. IOC的定义:直观的来说,就是由spring来负责控制对象的生命周期和对象 ...
- Spring框架学习之IOC(二)
Spring框架学习之IOC(二) 接着上一篇的内容,下面开始IOC基于注解装配相关的内容 在 classpath 中扫描组件 <context:component-scan> 特定组件包 ...
- Spring框架学习之IOC(一)
Spring框架学习之IOC(一) 先前粗浅地学过Spring框架,但当时忙于考试及后期实习未将其记录,于是趁着最近还有几天的空闲时间,将其稍微整理一下,以备后期查看. Spring相关知识 spri ...
- spring之:XmlWebApplicationContext作为Spring Web应用的IoC容器,实例化和加载Bean的过程
它既是 DispatcherServlet 的 (WebApplicationContext)默认策略,又是 ContextLoaderListener 创建 root WebApplicationC ...
随机推荐
- 安装mysql8.0
安装repo源 参考mysql官方文档 参考文章 redhat7通过yum安装mysql5.7.17教程:https://www.jb51.net/article/103676.htm mysql r ...
- mymath.so共享库
共享库的使用(.so)文件 1.共享库的概念 2.创建共享库命令 # 1.将.c生成.o文件,(生成与位置无关的代码-fPIC)gcc -c add.c -o add.o -fPIC # 2.使用 ...
- 西电oj135题 拼数字并排序
类别综合 时间限制 1S 内存限制 1000Kb 问题描述 对于输入的字符串(只包含字母和数字),将其中的连续数字拼接成整数,然后将这些整数按从大到小顺序输出.例如字符串"abc123d5e ...
- 一分钟教你分清各种光纤跳线接头(SC、ST、FC、LC、MPO)
一分钟教你分清各种光纤跳线接头(SC.ST.FC.LC.MPO) 市场上常见的光纤跳线有以下几种接头:SC.ST.FC.LC.MPO,相信很多入门者和小编一样,面对各种英文缩写也是我只认识他们,却不 ...
- Unity泛型单例模式
using System.Collections; using System.Collections.Generic; using UnityEngine; public class Singleto ...
- OpenJudge2811:熄灯问题(枚举)
熄灯问题 有一个由按钮组成的矩阵,其中每行有6个按钮,共5行.每个按钮的位置上有一盏灯.当按下一个按钮后,该按钮以及周围位置(上边.下边.左边.右边)的灯都会改变一次.即,如果灯原来是点亮的,就会被熄 ...
- MySQL学习(十)索引
1.索引的种类 聚簇索引,非聚簇索引 主键索引,唯一索引,普通索引(前缀索引),全文索引 单值索引,复合索引 二级索引 覆盖索引 1.1 聚簇索引,非聚簇索引 参考文档: https://www.cn ...
- P4555 [国家集训队]最长双回文串 回文树(回文自动机)简单题
贴个题目链接:https://www.luogu.org/problem/P4555 题目:输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分X,Y,(∣X∣,∣Y∣≥1)且X和Y都是回文 ...
- AC自动机模板题 HDU - 2222
Keywords Search HDU - 2222 贴个vj的链接https://vjudge.net/problem/HDU-2222 题意:T组数据,n个单词,再给你一个串,看有几个单词在这个 ...
- 后疫情时代,RTE“沉浸式”体验还能这么玩?丨RTE 2022 编程挑战赛赛后专访
前言 9 月 17 日,由声网.环信与 RTE 开发者社区联合主办的"RTE 2022 编程挑战赛"圆满落幕.从 300+ 支参赛队伍中冲出重围的 27 支决赛队伍,在元宇宙中用精 ...