一、IOC容器简介
IOC(Inversion of Control,控制反转)是一种设计原则,在传统的编程中,对象之间的依赖关系是由对象自己管理和创建的,而在IOC容器中,控制权被反转,对象的创建和管理由容器来负责,简单来说,IOC容器负责各个对象之间的依赖管理,开发人员无需再去手动为对象注入依赖。
二、实现IOC容器的基本思路
1.我们需要明确哪些类需要ioc容器去管理,这里我们使用注解去标识
2.再由ioc管理的类中,哪些属性需要自动注入,这里我们同样使用注解来标识
3.需要一个扫描器来扫描包中的所有类,将需要管理的类加入ioc容器管理
4.获取需要管理的类,利用java反射机制为其创建对象以及进行依赖注入
三、IOC容器的代码实现
1.标识注解的实现
为了方便管理,创建一个名为Annotation的包用来存储标识注解,这个包下面有两个注解:InjectByName和Component,第一个注解用于标识类中属性,告诉ioc这个属性需要自动注入,第二个注解标识类,表明此类需要由ioc管理
Component
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
String value() default ""; //value用于指定该类在ioc容器中存储的名称
} //如果没有指定则由类名作为名称
InjectByName
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectByName {
String value(); //用来指定需要注入的对象的类名称,即在ioc容器中存储的对象的名称
} //(名称由Component注解指定,或默认为类的名称)
2.包扫描器的实现
设计思路如下,传入包名字符串,即为需要扫描的包,然后将拥有Component注解的类都挑选出来,由于包下面可能会还有包,所以我们需要递归的去寻找,直到把改包下以.class文件结尾且拥有Component注解的文件都挑选出来
首先创建一个名叫PackageScanner的类,用于包扫描
import java.io.File;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
public class PackageScanner {
//传入需要扫描的包名(形如"com.myPackage.*"的形式)
public static void getAllBeans(String packageName) throws Exception {
if (packageName == null) throw new IllegalArgumentException("the package name is null."); //包名为null则抛出异常
String path = packageName.replace(".", "/"); //将包名的"."替换为"/"
ClassLoader classLoader = PackageScanner.class.getClassLoader();
URL pathUrl = classLoader.getResource(path); //获取包的URL
if (pathUrl != null) {
File dir = new File(pathUrl.getFile());
File[] files = dir.listFiles(); //获取该目录下的所有文件,包括目录
assert files != null;
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".class")) { //满足要求,对该类进行处理
//找到满足要求的类后的逻辑我们待会去实现
/* ...........*/
} else if (file.isDirectory()) { //如果是目录则递归的去遍历该目录下的文件
getAllBeans(packageName + "." + file.getName());
}
}
}
}
}
3.二级缓冲以及BeanDefinition的实现
使用过spring的同学应该都听说过三级缓冲这个东西,笔者先带大家回顾一下为什么spring的IOC容器需要三重缓冲。
1.首先就是要解决对象循环依赖的问题,即A依赖B,B又依赖C,C又依赖A,如果没有缓冲池,那么就会出现问题,所以我们需要把创建对象所需的"原材料"放进一种特殊的类中,在spring中这个特殊的类叫BeanDefinition,这个类的对象可以理解为一个"还没创建完全的对象",把这些还未创建完全的对象(BeanDefinition)放进缓存池里,然后再用缓存池里的BeanDefinition里的“原材料”来创建完全的,我们需要的对象。
2.事实上,解决循环依赖只需要使用两个缓存,也就是说二级缓冲就足够了,spring还要多使用一个缓冲池的原因是因为spring还需要对对象进行AOP增强,由于本篇文章只实现IOC中的依赖注入,并没有Aop的功能,所以这里使用两级缓冲即可
了解了BeanDefinition(用于存储创建对象的原材料的类)的作用后,我们对该类做出实现
首先我们通过反射创建对象,且需要反射来为其进行依赖注入,所以原材料就包括该类的class对象,然后又因为我们是通过类名称来获取该对象的名称,所以原材料还包括类的名称
BeanDefinition
public class BeanDefinition {
private Class clazz; //目标类的class对象
private String beanName; //目标类的对象名称(用于标识ioc管理的对象)
// 有参无参构造,属性的getter,setter用idea快捷键生成一下
BeanDefinition (Class clazz, String beanName) {
this.clazz = clazz;
this.beanName = beanName;
}
BeanDefinition () {
this.clazz = null;
this.beanName = null;
}
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
@Override
public String toString() {
return "{" + this.beanName + " " + this.clazz + "}";
}
}
然后是二级缓冲中的第二级缓冲池,即用来存储类的BeanDefinition对象的缓冲池,因为是单例模式,所以这里使用Set集合,为了方便,我们把它放进PackageScanner这个类里
private static final Set<BeanDefinition> beanDefinitions = new HashSet<>();
//获取beanDefinitions的方法
public static Set<BeanDefinition> getBeanDefinitionSet() {
return beanDefinitions;
}
最后我们就要处理,在包扫描过程中,满足条件(以.class结尾,且拥有Component注解的类文件)的文件,将其封装为BeanDifinition对象并存进beanDefinitions中(二级缓存池)
//获取该类的完整限定名
String clazzName = packageName + "." + file.getName().substring(0, file.getName().length() - 6);
//使用Class.forName来获取Class对象,并获取类的名称,将其放进beanHandler方法中处理
beanHandler(Class.forName(clazzName), file.getName().substring(0, file.getName().length() - 6));
所以 getAllBeans的完整写法如下
public static void getAllBeans(String packageName) throws Exception {
if (packageName == null) throw new IllegalArgumentException("the package name is null.");
String path = packageName.replace(".", "/");
ClassLoader classLoader = PackageScanner.class.getClassLoader();
URL pathUrl = classLoader.getResource(path);
if (pathUrl != null) {
File dir = new File(pathUrl.getFile());
File[] files = dir.listFiles();
assert files != null;
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".class")) {
//处理正确文件的代码放下面
String clazzName = packageName + "." + file.getName().substring(0, file.getName().length() - 6);
//这个方法下一步实现
beanHandler(Class.forName(clazzName), file.getName().substring(0, file.getName().length() - 6));
} else if (file.isDirectory()) {
getAllBeans(packageName + "." + file.getName());
}
}
}
}
接下来就是创建BeanDefinition的方法beanHandler
传进Class对象以及类名,然后判断Component注解是否指明了对象名,如果有则用指定的对象名标识该对象,如果没有则使用类名作为默认名
private static void beanHandler(Class<?> clazz, String typeName) {
//判断是否存在Component注解
if (clazz.isAnnotationPresent(Component.class)) {
String beanName = clazz.getAnnotation(Component.class).value();
//判断Component注解是否指明了对象名,没有则使用类名作为默认对象名
beanDefinitions.add(new BeanDefinition(clazz, beanName.isEmpty() ? typeName : beanName));
}
}
最终,PackageScanner类的完整代码如下:
import java.io.File;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
public class PackageScanner {
private static final Set<BeanDefinition> beanDefinitions = new HashSet<>();
private static void beanHandler(Class<?> clazz, String typeName) {
if (clazz.isAnnotationPresent(Component.class)) {
String beanName = clazz.getAnnotation(Component.class).value();
beanDefinitions.add(new BeanDefinition(clazz, beanName.isEmpty() ? typeName : beanName));
}
}
public static Set<BeanDefinition> getBeanDefinitionSet() {
return beanDefinitions;
}
public static void getAllBeans(String packageName) throws Exception {
if (packageName == null) throw new IllegalArgumentException("the package name is null.");
String path = packageName.replace(".", "/");
ClassLoader classLoader = PackageScanner.class.getClassLoader();
URL pathUrl = classLoader.getResource(path);
if (pathUrl != null) {
File dir = new File(pathUrl.getFile());
File[] files = dir.listFiles();
assert files != null;
for (File file : files) {
if (file.isFile() && file.getName().endsWith(".class")) {
String clazzName = packageName + "." + file.getName().substring(0, file.getName().length() - 6);
beanHandler(Class.forName(clazzName), file.getName().substring(0, file.getName().length() - 6));
} else if (file.isDirectory()) {
getAllBeans(packageName + "." + file.getName());
}
}
}
}
}
至此,我们已经完成了包扫描功能以及第二级缓存池的创建
4.将BeanDefinition转换为bean对象,并存入一级缓存
创建名为BeanCreator的对象,一级缓存使用Map实现,利用BeanDefinition中的Class对象创建对象放入Map中作为值,将beanName即对象名放入Map作为键
//二级缓存
private final Set<BeanDefinition> beanDefinitionSet;
//一级缓存
private final Map<String, Object> beanMap = new HashMap<>();
//注入二级缓存
public BeanCreator(Set<BeanDefinition> beanDefinitions) {
this.beanDefinitionSet = beanDefinitions;
}
private void getBeanDefinitions() throws Exception {
//用每一个BeanDefinition对象中的Class属性来创建对象,并以beanName为值存入beanMap中
for (BeanDefinition beanDefinition : this.beanDefinitionSet) {
this.beanMap.put(beanDefinition.getBeanName(), beanDefinition.getClazz().getConstructor(null).newInstance());
}
}
然后就是最重要的依赖注入了,我们需要把对象中标记了InjectByName注解的属性利用属性进行注入,创造完整的bean对象
public void createBean() throws Exception {
//初始化以及缓存(beanMap)
this.getBeanDefinitions();
//遍历beanMap中的每一个bean
for (Map.Entry<String, Object> beanDefinition : this.beanMap.entrySet()) {
Object obj = beanDefinition.getValue();
Class<?> clazz = obj.getClass();
//反射获取bean的所有属性
Field[] fields = clazz.getDeclaredFields();
//遍历所有属性
for (Field field : fields) {
field.setAccessible(true);
//判断该属性是否存在InjectByName注解
if (field.isAnnotationPresent(InjectByName.class)) {
//获取需要注入的对象的对象名
String beanName = field.getAnnotation(InjectByName.class).value();
//判断beanMap中是否存在该类,不存在则抛出异常
if (beanMap.containsKey(beanName)) {
//存在则注入该对象
field.set(obj, beanMap.get(beanName));
} else throw new ClassNotFoundException("the bean that required is not exist.");
}
}
}
}
顺带提供一个接口,通过对象名来返回beanMap中的对象
public Object getBeanByName(String name) throws ClassNotFoundException {
if (this.beanMap.containsKey(name)) return this.beanMap.get(name);
else throw new ClassNotFoundException("the bean that required is not exist.");
}
最终,BeanCreator的完整代码如下
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class BeanCreator {
private final Set<BeanDefinition> beanDefinitionSet;
private final Map<String, Object> beanMap = new HashMap<>();
public BeanCreator(Set<BeanDefinition> beanDefinitions) {
this.beanDefinitionSet = beanDefinitions;
}
private void getBeanDefinitions() throws Exception {
for (BeanDefinition beanDefinition : this.beanDefinitionSet) {
this.beanMap.put(beanDefinition.getBeanName(), beanDefinition.getClazz().getConstructor(null).newInstance());
}
}
public void createBean() throws Exception {
this.getBeanDefinitions();
for (Map.Entry<String, Object> beanDefinition : this.beanMap.entrySet()) {
Object obj = beanDefinition.getValue();
Class<?> clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.isAnnotationPresent(InjectByName.class)) {
String beanName = field.getAnnotation(InjectByName.class).value();
if (beanMap.containsKey(beanName)) {
field.set(obj, beanMap.get(beanName));
} else throw new ClassNotFoundException("the bean that required is not exist.");
}
}
}
}
public Object getBeanByName(String name) throws ClassNotFoundException {
if (this.beanMap.containsKey(name)) return this.beanMap.get(name);
else throw new ClassNotFoundException("the bean that required is not exist.");
}
}
5.将之前的所有功能用IocApplication类进行整合
此部分比较简单,直接上代码吧
public class IocApplication {
private final BeanCreator beanCreator;
//构造方法传入需要扫描的包名
public IocApplication(String scannerPath) {
try {
PackageScanner.getAllBeans(scannerPath);
this.beanCreator = new BeanCreator(PackageScanner.getBeanDefinitionSet());
this.beanCreator.createBean();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//通过beanName来获取对象
public Object getBeanByName(String beanName) {
try {
return this.beanCreator.getBeanByName(beanName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
至此本篇文章中简易ioc容器的代码以全部完成
四、测试
我们创建三个类Test01,Test02,Test03,其中Test03依赖Test01和Test02三个类中都有print方法,具体内容如下
@Component("test01")
public class Test01 {
public void print() {
System.out.println("test01");
}
}
@Component("test02")
public class Test02 {
public void print() {
System.out.println("test02");
}
}
@Component("test03")
public class Test03 {
//表明需要注入属性需要的对象的beanName
@InjectByName("test01")
private Test01 test01;
@InjectByName("test02")
private Test02 test02;
public void print() {
test01.print();
test02.print();
}
}
main方法如下
public class Main {
public static void main(String[] args) {
IocApplication application = new IocApplication("com.example");
//通过bean name获取对象
Test03 test03 = (Test03) application.getBeanByName("test03");
test03.print();
}
}
运行结果:
打印成功,没有出现空指针异常,说明依赖注入成功
五、总结
本文做出了对ioc容器的简单实现,然而事实上spring的ioc容器由于需要扩展Aop增强,还有各种初始化处理以及各种beanFactory以及要考虑可扩展性,所以实际上要复杂的多,总而言之本文只是提供了关于spring ioc的一个基本思路,如果想要了解更多关于spring ioc的内容,建议阅读spring源码