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 ...
随机推荐
- SpringCloud之旅
现在大部分公司的项目架构都选择了微服务,我们公司也不例外,那么什么是微服务呢?今天就来开启SpringCloud之旅! SpringCloud是基于SpringBoot的一整套的微服务架构.他提供了微 ...
- K8S二进制单节点部署
一.常见的k8s部署方式 1.inikube: Minikube是一个工具,可以在本地快速运行一个单节点微型K8s,仅用于学习预览K8s的一些特性使用 部署地址: https://kubernetes ...
- linux环境变量配置错误后命令无法使用解决方案
环境变量配置时多复制了一个空格,导致执行source /etc/profile后提示错误,无法编辑和查看文件 解决方案: 查看当前系统变量:echo $PATH 临时修改:export PATH=/u ...
- C#软件增加混淆防止反编译
使用Visual Studio新建一个名为"test"的命令行项目输入如图所示的代码并生成项目,这个项目很简单,就是程序启动时,调用"GetGUID"函数返回一 ...
- 遇到 https://packetlife.net/
"I have never let my schooling interfere with my education." --Mark Twain
- ubuntu下删除U盘文件到回收站无法清空问题的解决
Ubuntu可以自动加载U盘 每当,拷贝新的文件,而空间不足的时候,就会删除原有的文件. 可是,它不是彻底删除,而是放在垃圾箱中(/home/mrc/.local/share/Trash/files) ...
- CanvasScaler的三种适配模式——缩放模式(Scale with Screen Size)
一.含义 根据屏幕尺寸进行缩放,随着屏幕尺寸进行放大缩小 二.参数介绍 第一个参数一般是美术人员根据游戏主要面向的手机市场,比如安卓市场,用市场上最常用的分辨率作为制作UI图片的标准.这里填的数就是美 ...
- js对象深拷贝方法
JSON.stringify()是目前前端开发过程中最常用的深拷贝方式, 原理是把有个对象序列化成为一个 JSON 字符串,将对象的内容转换成字符串的形式再保存到磁盘上, 再用 JSON.parse( ...
- DRF_序列化and反序列化之高级
1. source用法 序列化器内的使用 book_name = serializers.CharField(max_length=8, min_length=3,source='name')这个用来 ...
- Mathematica的Combinatorica`程序包使用笔记
目录 官方给出的程序包使用指南和一些示例 引论 步骤 0x00 导入程序包 0x01 Integer Partitions 0x02 Integer Compositions 0x03 partiti ...
