【Spring】重構--手寫Spring核心邏輯(三)實現IOC/DI(context包)
在上一篇 【Spring】重構–手寫Spring核心邏輯(二)實現IOC/DI(beans包) 我們已經實現了 beans 包中的相關類,本篇就來實現 context 中關於容器的具體邏輯。
1.MYAbstractApplicationContext
IOC容器頂層設計,是最頂層容器的規範,不管是 XmlApplication 還是 AnnotationApplication 都必須去實現。這種設計也便於我們日後擴充套件新容器。
public abstract class MYAbstractApplicationContext {
// 受保護,只提供給子類重寫(最少知道原則)
protected void refresh() throws Exception {}
}
2.MYDefaultListableBeanFactory
上面有了 MYAbstractApplicationContext 那我們就可以多種選擇,但是也必須提供 IOC 容器的預設實現。這就好比平常我們手機支付時可以用微信、支付寶、銀行卡等,但是在具體的支付頁面它都會設定好一個預設支付選項。
注意,這個類其實是在 beans 包中,因為 beans包更多放的是規範、配置、標準等。放到 context 包中說是為了更直觀的看到 AbstractApplicationContext、DefaultListableBeanFactory、ApplicationContext 的關係。
public class MYDefaultListableBeanFactory extends MYAbstractApplicationContext {
// 偽IOC容器,儲存了BeanDefinition(類資訊)
// 這裡的key是factoryBeanName,即beanName(對於一種Bean而言factoryName是唯一的)
protected final Map<String, MYBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, MYBeanDefinition>();
}
3.MYApplicationContext
// 實現 BeanFactory 介面,繼承 DefaultListableBeanFactory
public class MYApplicationContext extends MYDefaultListableBeanFactory implements MYBeanFactory {
private String[] configLocations;
private MYBeanDefinitionReader reader;
// 通用IOC容器,存的是 BeanWrapper
private Map<String, MYBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<String, MYBeanWrapper>();
// 單例的IOC容器,存的是例項物件(相當於快取,避免重複建立Bean)
private Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>();
// 建構函式,要傳入載入的配置檔案,然後呼叫refresh方法
public MYApplicationContext(String... configLocations) {
this.configLocations = configLocations;
try {
refresh();
} catch (Exception e) {
e.printStackTrace();
}
}
//...
}
我們再來看一遍繼承關係:
refresh()
在 AbstractApplicationContext 定義的模板方法,是IOC容器初始化的入口
@Override
protected void refresh() throws Exception {
// 1.定位,定位配置檔案
reader = new MYBeanDefinitionReader(configLocations);
// 2.載入,載入配置檔案到記憶體(BeanDefinition)
List<MYBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
// 3.註冊,註冊配置資訊到容器裡面(偽IOC容器)
doRegisterBeanDefinition(beanDefinitions);
// 4.把不是延時載入的類,提前初始化
// 一般不開啟懶載入,即在IOC容器初始化時就完成例項化與注入
doAutowired();
}
doRegisterBeanDefinition()
將BeanDefinition們注入容器(偽IOC容器)。該方法執行完後,IOC容器初始化就完成了。
private void doRegisterBeanDefinition(List<MYBeanDefinition> beanDefinitions) throws Exception {
for (MYBeanDefinition beanDefinition : beanDefinitions) {
if (super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
throw new Exception("The “" + beanDefinition.getFactoryBeanName() + "” is exists!!");
}
super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
}
}
doAutowired()
對於非延時載入的bean,在IOC容器初始化時就將Bean建立出來並完成依賴注入
private void doAutowired() {
// 遍歷BeanDefinition,看哪個是延時載入了
for (Map.Entry<String, MYBeanDefinition> entry : super.beanDefinitionMap.entrySet()) {
String beanName = entry.getKey();
// 這裡預設isLazyInit=false,即先提前將Bean放入IOC容器中,即這裡所有BeanDefinition都會建立一個bean
if (!entry.getValue().isLazyInit()) {
try {
getBean(beanName);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
getBean()
初始化Bean並完成依賴注入:
- 初始化:讀取BeanDefinition,通過反射建立Bean例項,包裝成BeanWrapper,並放入IOC容器
- 注入:對IOC容器管理的Bean進行依賴注入
Spring中呼叫getBean的時機:
- DispatchServlet 建立 IOC容器:refresh --> doAutowired
- DispatchServlet 建立 HandlerMapping,要拿出所有Bean
- 手動呼叫 getBean 方法去獲取Bean,比如上面doAutowired()在IOC容器初始化時呼叫
@Override
public Object getBean(String beanName) throws Exception {
MYBeanDefinition myBeanDefinition = this.beanDefinitionMap.get(beanName);
Object instance = null;
// 建立事件處理器
MYBeanPostProcessor beanPostProcessor = new MYBeanPostProcessor();
// 在建立bean之前進行一些動作
beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
// 判斷是否是Spring管理的物件
// 在建立BeanDefinition時,不是Spring管理的就沒有BeanDefinition(這裡是通過Properties配置scanPackage不是註解)
if (myBeanDefinition == null) {
throw new Exception("This Bean not exists!");
}
// 1.初始化
// 注:所有Bean例項都要封裝成BeanWrapper,然後在BeanWrapper中再取出Bean例項
MYBeanWrapper beanWrapper = instantiteBean(beanName, myBeanDefinition);
// 2.將拿到的BeanWrapper放入IOC容器
this.factoryBeanInstanceCache.put(beanName, beanWrapper);
// 在建立bean之後進行一些動作
beanPostProcessor.postProcessAfterInitialization(instance, beanName);
// 3.注入
populateBean(beanName, new MYBeanDefinition(), beanWrapper);
Object o = this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
// 注:即使是單例模式有單例IOC容器,但獲取Instance也要先封裝為Wrapper,然後再在通用容器中取
return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
}
// 通過型別獲取bean的方法跟beanName獲取大同小異,這裡就不實現了
@Override
public Object getBean(Class<?> beanClass) throws Exception {
return null;
}
迴圈注入:class A{ B b; } --> class B{ A a; }。因此要分成初始化與注入兩個方法,即先將物件建立出來,然後再將依賴注入
instantiteBean()
建立 Bean 例項,並封裝成 BeanWrapper
private MYBeanWrapper instantiteBean(String beanName, MYBeanDefinition myBeanDefinition) {
// 1.拿到要例項化的類名
String className = myBeanDefinition.getBeanClassName();
// 2.通過反射進行例項化
Object instance = null;
try {
// 預設所有物件都是單例的,即都可以通過單例IOC容器中獲取Bean
if (this.factoryBeanObjectCache.containsKey(className)) {
instance = this.factoryBeanObjectCache.get(className);
} else {
Class<?> clazz = Class.forName(className);
instance = clazz.newInstance();
this.factoryBeanObjectCache.put(myBeanDefinition.getFactoryBeanName(), instance);
// 注:這裡還要根據className(全類名)放一個
// 因為在通過型別進行getBean時,BeanDefinition只封裝了介面做factoryName
this.factoryBeanObjectCache.put(className, instance);
}
} catch (Exception e) {
e.printStackTrace();
}
// 3.封裝BeanWrapper
// 注:無論單例多例,都要先封裝成 BeanWrapper
MYBeanWrapper beanWrapper = new MYBeanWrapper(instance);
return beanWrapper;
}
populateBean()
對IOC中的Bean進行依賴注入:
- byName:指定 beanName(首字母小寫,介面,自定義) -----> @Resource時
- byType:型別注入(預設) ----> @Autowired
private void populateBean(String beanName, MYBeanDefinition myBeanDefinition, MYBeanWrapper myBeanWrapper) {
Class<?> clazz = myBeanWrapper.getWrappedClass();
// 只有容器管理的bean才會給他依賴注入
if (! clazz.isAnnotationPresent(MYController.class ) || clazz.isAnnotationPresent(MYService.class)) { return; }
Object instance = myBeanWrapper.getWrappedInstance();
// 注:這裡是getDeclaredFields,getFields只能獲取到public欄位
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (!field.isAnnotationPresent(MYAutowired.class)) { continue; }
// 1.獲取註解中指定注入的beanName(byName注入)
MYAutowired annotation = field.getAnnotation(MYAutowired.class);
String autowiredBeanName = annotation.value().trim();
// 2.沒有指定beanName的話,通過型別進行注入(byType注入)
// 注意:型別 = 自身型別 || 介面型別。在BeanDefinitionReader#loadBeanDefinitions已經對介面建立過BeanDefinition了(當一個介面有多個實現類時,後掃描的會覆蓋先掃描的)
if ("".equals(autowiredBeanName)) {
// 除了simpleName,通過class拿到的都是全類名
// 前面初始化時已經用className向容器中注入過wrapper了
autowiredBeanName = field.getType().getName();
}
field.setAccessible(true);
try {
// 因為要給當前Bean注入時,可能要注入的Bean還沒初始化,因此就暫時不給這個欄位注入
// 但是當正式使用時還會getBean一次,這時所有bean都初始化完成了,就可以注入了
if(this.factoryBeanInstanceCache.get(autowiredBeanName) == null){ continue; }
// 獲取具體Bean例項:這裡是在通用IOC容器中獲取,因為可能有多例情況
field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
getBeanDefinitionNames()
返回所有的 beanName
public String[] getBeanDefinitionNames() {
return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
}
getConfig()
獲取配置檔案的內容,實際上呼叫到 MYBeanDefinition#getConfig 方法
public Properties getConfig() {
return this.reader.getConfig();
}
4.MYApplicationContextAware
通過解耦方式獲得IOC容器的頂層設計。後面將通過一個監聽器去掃描所有的類,只要實現了此介面,將自動呼叫setApplicationContext()方法,從而將IOC容器注入到目標類中。
public interface MYApplicationContextAware {
void setApplicationContext(MYApplicationContext applicationContext);
}
IOC和DI模組到此就寫完了,下面我們寫個測試類來看看能否執行。我們首先在 MyAction 中增加一個 test 方法
public void test(String name) {
System.out.println(queryService.query(name));
}
然後再 test 包下新建一個 Test 類
public class Test {
public static void main(String[] args) {
// 初始化 IOC 容器
MYApplicationContext context = new MYApplicationContext("classpath:application.properties");
try {
// 測試 IOC
Object bean = context.getBean("myAction");
System.out.println(bean);
// 測試 DI,即 myAction 物件中是否成功注入 QueryService 物件
MyAction myAction = (MyAction)bean;
myAction.test("張三");
} catch (Exception e) {
e.printStackTrace();
}
}
}
結果如下:
可以看到 IOC 容器中已經有了 myAction 物件,並且也成功注入 QueryService!
相關文章
- 手寫 Spring 事務、IOC、DI 和 MVCSpringMVC
- Spring學習之——手寫Spring原始碼V2.0(實現IOC、DI、MVC、AOP)Spring原始碼MVC
- Spring(IOC&DI)Spring
- 【Spring】IOC&DISpring
- spring IOC/DI筆記Spring筆記
- [摘]spring-IoC與DISpring
- spring:spring再總結(ioc、aop、DI等)Spring
- 淺析Spring的IoC和DISpring
- Spring(二):IOC和DI的理解Spring
- 手寫Spring---IOC容器(1)Spring
- 30個類手寫Spring核心原理之Ioc頂層架構設計(2)Spring架構
- 手寫Spring ioc 框架,狠狠的“Spring 原始碼Spring框架原始碼
- 手寫Spring---DI依賴注入(2)Spring依賴注入
- Spring系列之DI的原理及手動實現Spring
- 對Spring IoC容器實現的結構分析Spring
- 面試:spring ioc實現原理面試Spring
- Spring系列之IOC的原理及手動實現Spring
- 對於Spring中AOP,DI,IoC概念的理解Spring
- 在Spring Batch中配置重試邏輯 - BaeldungSpringBAT
- Spring框架系列(6) - Spring IOC實現原理詳解之IOC體系結構設計Spring框架
- Spring IoC Context啟動過程解析SpringContext
- 手寫IOC實現過程
- Spring IOC容器實現機制Spring
- spring IOC容器實現探討Spring
- Spring入門學習手冊 2:怎麼用註解來DI/IOCSpring
- IOC和DI的概念,以及Spring框架的介紹Spring框架
- Spring框架之IOC/DI(控制反轉/依賴注入)Spring框架依賴注入
- Spring原始碼剖析2:初探Spring IOC核心流程Spring原始碼
- Spring原始碼剖析1:初探Spring IOC核心流程Spring原始碼
- Spring 高階原始碼核心思想:Spring IoCSpring原始碼
- Spring基礎 - Spring核心之控制反轉(IOC)Spring
- 《Spring Boot 實戰紀實》缺失的邏輯Spring Boot
- 《Spring原始碼分析》IOC的實現Spring原始碼
- 4. Spring對IoC的實現Spring
- 手寫一個最簡單的IOC容器,從而瞭解spring的核心原理Spring
- 理解Spring中依賴注入(DI)與控制反轉(IoC)Spring依賴注入
- Spring實現IOC容器的兩種實現方式Spring
- Spring框架系列(7) - Spring IOC實現原理詳解之IOC初始化流程Spring框架