运用反射机制和自定义注解模拟实现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. holy shit CSDN

    holy shit CSDN 垃圾 CSDN 到处都是垃圾文章, 无人子弟 到处都是垃圾广告,看的恶心 毫无底线,窃取别人的知识成果,毫无版权意识 垃圾爬虫,垃圾小号 ...等等 Google Sea ...

  2. vuex & redux

    vuex & redux https://vuex.vuejs.org/ https://github.com/xgqfrms/VAIO/ https://scrimba.com/playli ...

  3. Dart http库

    推荐下我写的一个http库ajanuw_http 最基本的获取数据 import 'package:http/http.dart' as http; main(List<String> a ...

  4. Redis 博文索引

    博文索引 Redis 对象与编码 Redis 持久化 Redis 主从复制 Redis 哨兵 Redis 缓存淘汰 Redis 集合统计 Redis 简介

  5. 区块链项目NGK未来价值几何?

    没有人可以预知NGK未来会涨到多少钱,就像比特币只有10美分时,也无法预测它会涨到现在的价格⼀样.那时候人们把CPU超频挖矿只作为⼀种爱好和娱乐.所以,人们也没有办法预知NGK未来的价格.但可以知道的 ...

  6. [转]Linux 线程实现机制分析 Linux 线程实现机制分析 Linux 线程模型的比较:LinuxThreads 和 NPTL

    转载地址:https://www.cnblogs.com/MYSQLZOUQI/p/4233630.html 自从多线程编程的概念出现在 Linux 中以来,Linux 多线应用的发展总是与两个问题脱 ...

  7. 09、IO流—File类与IO流

    目录 一.File类 基本认识 实用方法 获取功能 重命名功能(包含剪切) 判断功能 创建.删除文件 实际小案例 二.IO流 1.认识IO流 2.IO流基类介绍 字节流基类介绍 字符流基类介绍 三.节 ...

  8. 权限管理整合springsecurity代码执行过程

    (1)输入用户名密码. (2)进入认证过滤器中,执行attemptAuthentication方法, 通过该方法获取输入的用户名和密码. (3)执行实现了UserDetailsService接口的类中 ...

  9. Dubbo之高级特性

    Dubbo 注意当启动服务时,该服务会占用本机一个端口号,故在一台电脑启动多个服务时需要在配置文件中更占用本机的端口号 <!--服务占用本机的端口-当本机启动多个服务时须保持不同--> & ...

  10. JAVA基础(三)—— 输入输出处理

    JAVA基础(三)-- 输入输出处理 1 输入解析 //Scanner获取输入 import java.util.Scanner; Scanner s = new Scanner(System.in) ...