簡單實現Spring依賴注入以及控制反轉
執行流程
從上一章分析可得,spring建立bean的流程如下所示
獲取class資訊 -> 根據class資訊呼叫構造方法建立物件 -> 判斷成員變數中是否有依賴注入註解並進行注入操作 -> 初始化前(@PostConstruct) -> 初始化(實現InitializingBean介面) -> 初始化後(AOP) -> 生成代理物件 -> Bean
掃描bean
掃描bean主要根據包名獲取類資訊以及判斷使用者建立的類是否繼承BeanProcessor
介面以及將該介面放入到spring容器中,並將該資訊儲存到map集合中。
類資訊包含bean名稱、單例還是原型、是否懶載入。
BeanProcessor介面用於處理初始化前和初始化後的方法。
首先根據傳入的類class判斷該類是否有ComponentScan
註解,如果有就根據該註解獲取要掃描的包。
掃描類時載入的並不是.java檔案而是編譯後的二進位制位元組碼class檔案,想要載入該檔案需要找到編譯後的檔案目錄,可以根據應用程式類載入器獲取該目錄,呼叫resouces方法獲取包目錄下的url,獲取前得先將包名.
替換成'/',這樣才能表示目錄,可以根據該url來建立file物件,之後判斷file物件是否為目錄,如果是目錄就掃描該目錄下的所有檔案,如下程式碼所示
if (aClass.isAnnotationPresent(ComponentScan.class)) { ComponentScan componentScan = (ComponentScan) aClass.getAnnotation(ComponentScan.class); String packagePath = componentScan.value(); packagePath = packagePath.replace(".", "/"); URL resource = LyraApplicationContext.class.getClassLoader().getResource(packagePath); assert resource != null; File file = new File(URLDecoder.decode(resource.getFile(), StandardCharsets.UTF_8)); if (file.isDirectory()) { } }
之後遍歷該目錄下的所有檔案,然後判斷檔案中是否有Component
註解,如果有該註解那麼就表示這個類是一個bean,在然後判斷包中的類是否實現BeanProcessor
介面,如果有類實現了則將該類例項化並存儲到map中。
if (aClass1.isAnnotationPresent(Component.class)) { if (BeanProcessor.class.isAssignableFrom(aClass1)) { BeanProcessor instance = (BeanProcessor) aClass1.getDeclaredConstructor().newInstance(); beanProcessorList.add(instance); } }
之後就該獲取類資訊了,類資訊所在類具體由下所示,類中儲存的資料由bean的class、是否為單例和bean名稱,單獨抽出BeanDefinition的原因是在掃描階段和獲取bean階段都需要掃描類從而獲取類是否為單例,這樣使程式碼重複且效率較低,不如建立個類資訊類,在掃描類時獲取類資訊儲存,之後獲取bean時直接從BeanDefinition中獲取即可。
public class BeanDefinition {
private Class<?> clazz;
private BeanScopeConstant scope;
private String beanName;
@Override
public String toString() {
return "BeanDefinition{" +
"clazz=" + clazz +
", scope=" + scope +
", beanName='" + beanName + '\'' +
'}';
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public Class<?> getClazz() {
return clazz;
}
public void setClazz(Class<?> clazz) {
this.clazz = clazz;
}
public BeanScopeConstant getScope() {
return scope;
}
public void setScope(BeanScopeConstant scope) {
this.scope = scope;
}
}
獲取類資訊時判斷是否有Scope註解,如果有該註解則將該註解的資訊儲存到BeanDefinition中,如果沒有則預設為單例,之後將bean名稱和class資訊儲存到BeanDefinition中即可,再然後將BeanDefinition儲存到map中,map的key為beanName。bean掃描的整體程式碼如下所示
private void scanPackage() {
// 判斷類是否有componentScan註解
if (aClass.isAnnotationPresent(ComponentScan.class)) {
ComponentScan componentScan = (ComponentScan) aClass.getAnnotation(ComponentScan.class);
String packagePath = componentScan.value();
packagePath = packagePath.replace(".", "/");
URL resource = LyraApplicationContext.class.getClassLoader().getResource(packagePath);
assert resource != null;
File file = new File(URLDecoder.decode(resource.getFile(), StandardCharsets.UTF_8));
// 判斷類檔案是否為目錄
if (file.isDirectory()) {
for (File file1 : Objects.requireNonNull(file.listFiles())) {
try {
// 將包名.替換為/
String componentPackagePath = file1.getAbsolutePath().substring(file1.getAbsolutePath().indexOf("com"), file1.getAbsolutePath().indexOf(".class")).replace("\\", ".");
BeanDefinition beanDefinition = new BeanDefinition();
// 獲取類編譯目錄
Class<?> aClass1 = LyraApplicationContext.class.getClassLoader().loadClass(componentPackagePath);
// 載入類 判斷類是否有component註解
if (aClass1.isAnnotationPresent(Component.class)) {
// 判斷當前類是否實現了BeanProcessor介面
if (BeanProcessor.class.isAssignableFrom(aClass1)) {
BeanProcessor instance = (BeanProcessor) aClass1.getDeclaredConstructor().newInstance();
beanProcessorList.add(instance);
}
// 在類資訊中儲存類scope 單例/原型
if (aClass1.isAnnotationPresent(Scope.class)) {
Scope annotation = aClass1.getAnnotation(Scope.class);
beanDefinition.setScope(annotation.value());
} else {
beanDefinition.setScope(BeanScopeConstant.SINGLETON);
}
// 設定類資訊
beanDefinition.setBeanName(aClass1.getAnnotation(Component.class).value());
beanDefinition.setClazz(aClass1);
beanDefinitionMap.put(aClass1.getAnnotation(Component.class).value(), beanDefinition);
}
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |
InstantiationException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
}
bean建立
單例
掃描類完畢之後會根據掃描類的scope來建立bean,如果類的scope為單例則立即建立則儲存到儲存到單例池中。
public LyraApplicationContext(Class<ApplicationConfig> applicationConfigClass) {
this.aClass = applicationConfigClass;
scanPackage();
for (Map.Entry<String, BeanDefinition> stringBeanDefinitionEntry : beanDefinitionMap.entrySet()) {
String beanName = stringBeanDefinitionEntry.getKey();
BeanScopeConstant scope = stringBeanDefinitionEntry.getValue().getScope();
if (scope == BeanScopeConstant.SINGLETON) {
Object singletonBean = createBean(beanName, stringBeanDefinitionEntry.getValue());
singletonMap.put(beanName, singletonBean);
}
}
}
原型
如果類的scope為原型則每次呼叫 getBean方法都會建立不同的bean返回
獲取bean中首先從bean資訊map中根據bean名稱獲取bean資訊,如果不存在則表示該類沒有被掃描到容器中,獲取單例bean時,如果單例bean還未建立則呼叫getbean建立bean並掃描到單例池中
public Object getBean(String beanName) {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (beanDefinition == null) {
throw new NullPointerException();
}
if (beanDefinition.getScope() == BeanScopeConstant.PROTOTYPE) {
return createBean(beanName, beanDefinition);
} else {
Object bean = singletonMap.get(beanName);
if (bean == null) {
bean = createBean(beanName, beanDefinition);
singletonMap.put(beanName, bean);
}
return bean;
}
}
建立bean
建立bean的流程為
獲取class資訊 -> 根據class資訊呼叫構造方法建立物件 -> 判斷成員變數中是否有依賴注入註解並進行注入操作 -> 初始化前(@PostConstruct) -> 初始化(實現InitializingBean介面) -> 初始化後(AOP) -> 生成代理物件 -> Bean
- 獲取類資訊
掃描時已經將類資訊儲存到beanDefinition中,直接從map中獲取beanDefinition即可
Class<?> clazz = beanDefinition.getClazz();
- 利用反射呼叫構造方法建立bean
這裡呼叫的是無參構造方法,沒有構造方法推理,還略微有些問題
Object instance = clazz.getConstructor().newInstance();
- 檢視bean欄位中有沒有被
Autowired
註解標識,如果有則呼叫geatBean方法為欄位注入值,這裡有個坑,如果掃描的bean為單例bean,雖然標識了Component註解,建立bean在注入之前,容器還沒來得及建立就已經執行注入操作,肯定會注入失敗的,這就在getBean方法獲取單例bean時獲取,如果單例bean還未建立則呼叫getbean建立bean並掃描到單例池中
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
field.set(instance, getBean(field.getName()));
}
}
- 呼叫初始化前方法
BeanProcessor
中封裝了兩個方法,初始化前和初始化後的方法,這個介面很重要,因為AOP代理物件就是從這個介面的實現類建立的,通過對bean的處理,然後返回代理物件
public interface BeanProcessor {
default Object postProcessBeforeInitialization(Object bean, String beanName) {
System.out.println("before");
return bean;
}
default Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("after");
return bean;
}
}
執行初始化前方法,這個list填充是由掃描bean時完成的,所以不為空
beanProcessorList.forEach((beanProcessor -> {
beanProcessor.postProcessBeforeInitialization(instance, beanName);
}));
- 呼叫初始化方法
初始化方法是由實現InitializingBean介面的bean完成的,直接判斷當前bean是否實現了InitializingBean
介面即可,如果實現了之江將bean強轉為InitializingBean然後呼叫初始化方法即可。
if (instance instanceof InitializingBean) {
((InitializingBean) instance).afterPropertiesSet();
}
- 呼叫初始化後方法
beanProcessorList.forEach((beanProcessor -> {
beanProcessor.postProcessAfterInitialization(instance, beanName);
}));
建立bean完整程式碼
private Object createBean(String beanName, BeanDefinition beanDefinition) {
Class<?> clazz = beanDefinition.getClazz();
try {
Object instance = clazz.getConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
field.set(instance, getBean(field.getName()));
}
}
beanProcessorList.forEach((beanProcessor -> {
beanProcessor.postProcessBeforeInitialization(instance, beanName);
}));
if (instance instanceof InitializingBean) {
((InitializingBean) instance).afterPropertiesSet();
}
beanProcessorList.forEach((beanProcessor -> {
beanProcessor.postProcessAfterInitialization(instance, beanName);
}));
return instance;
} catch (Exception e) {
throw new RuntimeException(e);
}
}