運用反射機制和自定義注解模拟實作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容器的實作原理隻是基本的原理,甚至未檢視源碼進行驗證,僅用于初步了解;