运用反射机制和自定义注解模拟实现IOC容器,使其具有自动加载、自动装配和根据全限定类名获取Bean的功能。

一. 实现原理

1-1 IOC容器的本质

IOC容器可理解为是一个map,其中的一个entry可理解为一个component(组件),entry中的key为beanId(全限定类名),entry中的value为bean(类对应的对象);

具体的体现为:

1-2 自动加载

  • 自动加载是指IOC容器会自动加载被@Component等(以下用@Component为例)注解标记的类,使其成为IOC容器中的一个组件;

  • @Component注解加在一个类前,表示此类被IOC容器管理,成为IOC容器中的一个组件;

  • 在自动加载时,容器会自动扫描给定包路径所对应的包及其子包下的所有类,判断类是否是接口,如果不是接口就再判断是否被@Component标记,如果被标记了就将其添加到IOC容器中,key为该类的全限定类名,value为该类的对象。具体流程如图:

1-3 自动装配

  • 自动装配是指IOC容器会自动装配容器中各个bean中被@Autowired注解标记属性的属性值;

  • @Autowired注解加在类中的一个属性前,表示此属性的值需要被自动装配;

  • 待自动加载完成后,容器会根据keySet中的全限定类名遍历容器中各个类的各个属性,判断属性是否被@Autowired注解标记,如果被标记了,就会根据属性的类型的全限定类名(beanId)从容器中找到对应的bean,然后将找到的bean 的引用赋值给对应属性(模拟bean的scope为singleton)。具体的流程如图:

二. 具体实现

2-1 模拟情形

IOC容器自动加载com.hutao.springioc包及其子包下的所有类,并自动完成各类对应bean的属性装配。

2-2 目录结构

  • Autowired, Component为自定义注解;

  • Cat, Dog, User为实体类;

  • IocContainer为IOC容器;

  • Demo为测试。

2-3 实现

Autowired.java

package com.hutao.springioc.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}

Component.java

package com.hutao.springioc.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}

Cat.java

package com.hutao.springioc.model;

import com.hutao.springioc.annotation.Component;

@Component
public class Cat {
public void mew(){
System.out.println("Meow Meow Meow...");
}
}

Dog.java

package com.hutao.springioc.model;

import com.hutao.springioc.annotation.Component;

@Component
public class Dog {
public void bark(){
System.out.println("Wow wow wow...");
}
}

User.java

package com.hutao.springioc.model;

import com.hutao.springioc.annotation.Autowired;
import com.hutao.springioc.annotation.Component; @Component
public class User {
@Autowired
private Dog dog; @Autowired
private Cat cat; public void chat(){
System.out.println("This is my dog and cat.");
dog.bark();
cat.mew();
}
}

IocContainer.java

package com.hutao.springioc;

import com.hutao.springioc.annotation.Autowired;
import com.hutao.springioc.annotation.Component; import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*; /**
* 模拟Ioc容器,实现自动加载组件、自动装配、根据类的全限定名获取Bean
*
* @author Hutao
* @createDate 2020/11/14
*/
public class IocContainer {
//Ioc容器 存储的键值对为 <类的完全限定名称,该类的一个对象>
private Map<String, Object> container = new HashMap<String, Object>(); //Ioc容器可扫描到该包及其子包下的所有类
private String packageName; public IocContainer(String packageName) {
this.packageName = packageName; try{
//添加组件到容器
loadComponent(); //装配组件
assemble();
}catch (Exception e){
e.printStackTrace();
} } /**
* 将制定包及其子包下的所有组件加载到容器中
*
* @author Hutao
* @createDate 2020/11/14
*/
private void loadComponent() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
//用于获取包对应的URL,而URL中的分隔符为“/”, 所以将包路径的分隔符“.” 用“/”代替
String packagePath = packageName.replace(".","/"); ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL resource = classLoader.getResource(packagePath); //通过获取此资源的协议名称,判断包名对应的资源类型
String protocol = resource.getProtocol(); if(!"file".equals(protocol)){
//只测试protocol为file的情况,其他情况还有jar等
return;
} //获取了指定包及其子包下所对应的所有以suffix(.class)结尾的文件路径
List<String> filePathList = listFilePath(resource.getPath()); //获取类的完全限定名称
List<String> fullClassNameList = listFullClassName(filePathList); //加载component到容器中
for (String fullClassName : fullClassNameList) {
addToContainer(fullClassName);
}
} /**
* 获取指定文件夹下所有以.class结尾文件的抽象路径名的规范路径
* @param directoryPath 指定文件夹的路径
* @return 如获取到:包含符合条件的文件的规范路径的List
* 如未获取到:空的List
*
* @author Hutao
* @createDate 2020/11/14
*/
private List<String> listFilePath(String directoryPath) throws IOException {
List<String> filePathList = new ArrayList<String>(); //参数校验
if(null==directoryPath){
return filePathList;
} File directoryFile = new File(directoryPath);
if(!directoryFile.isDirectory()){
return filePathList;
} String filePath = null;
File[] files = directoryFile.listFiles();
for (File file : files) {
if(!file.isDirectory()){
filePath = file.getCanonicalPath();
if(filePath.endsWith(".class")){
filePathList.add(filePath);
}
}
else{
//递归调用
filePathList.addAll(listFilePath(file.getCanonicalPath()));
}
} return filePathList;
} /**
* 根据.class文件的规范路径获取其类的全限定名
* @param filePathList .class文件的规范路径List
* @return 如获取到:包含类的全限定名的的List
* 如未获取到:空的List
*
* @author Hutao
* @createDate 2020/11/14
*/
private List<String> listFullClassName(List<String> filePathList){
List<String> fullClassNameList = new ArrayList<String>();
if(null==packageName||null==filePathList){
return fullClassNameList;
} String packagePath = packageName.replace(".","\\"); for (String filePath : filePathList) {
fullClassNameList.add(filePath.substring(filePath.indexOf(packagePath),filePath.indexOf(".class")).replace("\\","."));
}
return fullClassNameList;
} /**
* 根据类的全限定名判断该类是否被标记为容器的组件,如果是则将组件添加到容器中
* @param fullClassName 类的全限定名
*
* @author Hutao
* @createDate 2020/11/14
*/
private void addToContainer(String fullClassName) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class classObject = Class.forName(fullClassName);
if(!classObject.isInterface()
&&null!=classObject.getAnnotation(Component.class)){
//如果扫描的Class对象不是接口类型且有@Component注解,就将对应的component装配到容器中
container.put(fullClassName,classObject.newInstance());
}
} /**
* 自动装配组件的属性值
*
* @author Hutao
* @createDate 2020/11/14
*/
private void assemble() throws IllegalAccessException, ClassNotFoundException {
Set<Map.Entry<String, Object>> entrySet = container.entrySet();
for (Map.Entry<String, Object> entry : entrySet) {
Class classObj = Class.forName(entry.getKey());
Field[] declaredFieldArray = classObj.getDeclaredFields();
for (Field field : declaredFieldArray) {
if(null!=field.getAnnotation(Autowired.class)){
//如果属性被@Autowired注解标注,则根据属性的类型名进行自动装配
field.setAccessible(true);
String beanId = field.getType().getName();
field.set(entry.getValue(),container.get(beanId));
}
}
}
} /**
* 根据类的全限定名从Ioc容器中获取对应的Bean
* @param fullClassName
* @return
*
* @author Hutao
* @createDate 2020/11/14
*/
public Object getBean(String fullClassName){
if(null==fullClassName){
return null;
} return container.get(fullClassName);
}
}

Demo.class

package com.hutao.springioc;

import com.hutao.springioc.model.User;

import java.io.IOException;

public class Demo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
IocContainer iocContainer = new IocContainer("com.hutao.springioc");
User user = (User)iocContainer.getBean(User.class.getName());
user.chat();
}
}

测试结果为:

//console输出:
This is my dog and cat.
Wow wow wow...
Meow Meow Meow... Process finished with exit code 0

注意:

  1. 以上IOC容器的实现原理只是基本的原理,甚至未查看源码进行验证,仅用于初步理解;

IOC容器模拟实现的更多相关文章

  1. 自定义模拟一个Spring IOC容器

    一.模拟一个IOC容器: 介绍:现在,我们准备使用一个java project来模拟一个spring的IOC容器创建对象的方法,也就是不使用spring的jar自动帮助我们创建对象,而是通过自己手动书 ...

  2. Spring源码学习之:模拟实现BeanFactory,从而说明IOC容器的大致原理

    spring的IOC容器能够帮我们自动new对象,对象交给spring管之后我们不用自己手动去new对象了.那么它的原理是什么呢?是怎么实现的呢?下面我来简单的模拟一下spring的机制,相信看完之后 ...

  3. 模拟实现IoC容器

    Spring的IoC核心就是控制反转,将对实现对象的操作控制器交出来,由IoC容器来管理,从配置文件中获取配置信息,Java对XML文档提供了完美的支持,dom4j功能强大,而下面我就用JDOM这一开 ...

  4. (反射+内省机制的运用)简单模拟spring IoC容器的操作

    简单模拟spring IoC容器的操作[管理对象的创建.管理对象的依赖关系,例如属性设置] 实体类Hello package com.shan.hello; public class Hello { ...

  5. 通过中看不中用的代码分析Ioc容器,依赖注入....

    /** * 通过生产拥有超能力的超人实例 来理解IOC容器 */ //超能力模组接口 interface SuperModuleInterface{ public function activate( ...

  6. .net自带的IOC容器MEF使用

    IOC能做什么 IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合.更优良的程序. 控制反转: 将控制权移交给第三方容器  new 操作 依赖注入: 在程序 ...

  7. Ioc容器Autofac系列(1)-- 初窥

     一.前言 第一次接触Autofac是因为CMS系统--Orchard,后来在一个开源爬虫系统--NCrawler中也碰到过,随着深入了解,我越发觉得Ioc容器是Web开发中必不可少的利器.那么,Io ...

  8. IoC 之 2.2 IoC 容器基本原理(贰)

    2.2.1  IoC容器的概念 IoC容器就是具有依赖注入功能的容器,IoC容器负责实例化.定位.配置应用程序中的对象及建立这些对象间的依赖.应用程序无需直接在代码中new相关的对象,应用程序由IoC ...

  9. spring框架--IOC容器,依赖注入

    思考: 1. 对象创建创建能否写死? 2. 对象创建细节 对象数量 action  多个   [维护成员变量] service 一个   [不需要维护公共变量] dao     一个   [不需要维护 ...

随机推荐

  1. .dmg & .pkg & .ipa

    .dmg & .pkg & .ipa Apple, macOS, iOS .dmg mysql-8.0.21-macos10.15-x86_64.dmg https://dev.mys ...

  2. 中文域名 & 原理剖析

    中文域名 & 原理剖析 https://zh.wikipedia.org/wiki/中文域名 原理 利用浏览器的对字符编码的转换算法,实现 unicode 字符显示! 国际化域名: 就是一个普 ...

  3. js jsonParse

    mdn const rx_one = /^[\],:{}\s]*$/; const rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; // 匹配 ...

  4. 云原生系列6 基于springcloud架构风格的本地debug实现

    debug是程序员在日常开发中最常使用的操作, 那么,你是如何快速在微服务架构风格下快速debug后端服务呢? 开发现状 开发的理想状态 本地调测的使用步骤 登录智能网关 如果集成开发环境是在本地局域 ...

  5. 逆向基础 C++ Primer Plus 第二章 开始学习C++

    C++ Primer Plus 第二章 开始学习C++ 知识点梳理 本章从一个简单的C++例子出发,主要介绍了创建C++程序的步骤,以及其所包含的预处理器编译指令.函数头.编译指令.函数体.注释等组成 ...

  6. 关于 HTTP 后端人员需要了解的 20+ 图片!

    前言 当您网上冲浪时,HTTP 协议无处不在.当您浏览网页.获取一张图片.一段视频时,HTTP 协议就正在发生. 本篇将尽可能用简短的例子和必要的说明来让您了解基础的 HTTP 知识. 目录: 什么是 ...

  7. 企业安全_监控Github关键字

    目录 Hawkeye GSIL Hawkeye github: https://github.com/0xbug/Hawkeye 教程: https://my.oschina.net/adailinu ...

  8. WIFI6 基本知识(一)

    什么是WI-FI6(802.11ax) Wi-Fi 6 是下一代 802.11ax 标准的简称.随着 Wi-Fi 标准的演进,WFA 为了便于 Wi-Fi 用户和设备厂商轻松了解其设备连接或支持的 W ...

  9. Hi3559AV100外接UVC/MJPEG相机实时采图设计(二):V4L2接口的实现(以YUV422为例)

    下面将给出Hi3559AV100外接UVC/MJPEG相机实时采图设计的整体流程,主要实现是通过V4L2接口将UVC/MJPEG相机采集的数据送入至MPP平台,经过VDEC.VPSS.VO最后通过HD ...

  10. PAT-1102(Invert a Binary Tree)+二叉树的镜像+层次遍历+中序遍历+已知树的结构构树

    Invert a Binary Tree pat-1102 import java.util.Arrays; import java.util.Queue; import java.util.Scan ...