🎉🎉本文是基于尚矽谷的Spring6教程的筆記,希望對正在學習的你有一定幫助。
03spring6-ioc-demo
開發環境
- Java17
- Maven3.8.5
前言:
Spring架構的IOC是基于Java反射機制實作,是以要自己實作一個Spring-IOC容器,需要了解反射。此文篇幅有限,不再讨論反射。
目錄結構:
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─coding
│ │ │ ├─annotation
│ │ │ ├─bean
│ │ │ ├─dao
│ │ │ ├─service
│ │ │ └─test
│ │ └─resources
│ └─test
│ └─java
Task
- 自己定義@Bean注解
- 自己定義@DI注解
準備
dao
package com.coding.dao;
/**
* @time 2023-02-21-20:38
*/
public interface UserDao {
void add();
}
package com.coding.dao;
import com.coding.annotation.Bean;
/**
* @time 2023-02-21-20:39
*/
@Bean
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("自定義bean dao.......");
}
}
service
package com.coding.service;
/**
* @time 2023-02-21-20:39
*/
public interface UserService {
void add();
}
package com.coding.service;
import com.coding.annotation.Bean;
import com.coding.annotation.DI;
import com.coding.dao.UserDao;
/**
* @time 2023-02-21-20:39
*/
@Bean
public class UserServiceImpl implements UserService {
@DI
private UserDao userDao;
@Override
public void add() {
System.out.println("自定義bean service........");
userDao.add();
}
}
自定義注解
annotation
package com.coding.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @time 2023-02-22-15:20
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Bean {
}
package com.coding.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @time 2023-02-22-15:20
*/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DI {
}
定義bean容器接口
package com.coding.bean;
/**
* @time 2023-02-22-15:22
*/
public interface ApplicationContext {
Object getBean(Class clazz);
}
public class AnnotationApplicationContext implements ApplicationContext {
private Map<Class, Object> beanFactoryMap = new HashMap<>();
@Override
public Object getBean(Class clazz) {
return beanFactoryMap.get(clazz);
}
}
構造器基本邏輯
//掃描規則
public AnnotationApplicationContext(String basePackagePath) {
//包掃描
try {
//把.替換成斜杠\
String packagePath = basePackagePath.replaceAll("\\.", "\\\\");
//擷取包的絕對路徑
Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().
getResources(packagePath);
//擷取包的絕對路徑
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
//将轉義字元轉回來
String filepath = URLDecoder.decode(url.getFile(), "utf-8");
rootPath = filepath.substring(0, filepath.length() - basePackagePath.length());
//掃描包
loadBean(new File(filepath));
}
} catch (Exception e) {
e.printStackTrace();
}
//屬性注入
loadDI();
}
主要是實作loadBean方法和loadDI方法
思路:
private void loadBean(File file) {
//1判斷是否是檔案夾
//2擷取檔案夾中所有内容
//3判斷檔案夾裡面為空,直接傳回
//4如果檔案夾裡面不為空,周遊檔案夾所有内容
//4.1 周遊得到每個FiLe對象,繼續判斷,如果還是檔案夾,遞歸
//4.2周遊得到FiLe對象不是檔案夾,是檔案,
//4.3得到包路徑+類名稱部分-字元串截取
//4.4判斷目前檔案類型是否.cLass
//4.5如果是.cLass類型,把路徑\替換成. 把 .class去掉
//4.6判斷類上是否有@Bean注解,有就執行個體化
//4.6.1擷取類的Class
//4.6.2判斷是不是接口
//4.6.3判斷類上是否有@Bean注解
//4.6.4執行個體化
//4.7執行個體化後放到map集合beanFactoryMap裡面
//如果有接口把接口的class當成key,執行個體對象當成value
//如果沒有接口把自己的class當成key,執行個體對象當成value
}
//屬性注入
private void loadDI(){
//執行個體化對象在 beanFactoryMap 的map集合裡面
//1 周遊 beanFactoryMap map集合
//2擷取map集合每個對象(value) ,每個對象屬性擷取到
//3 周遊得到每個對象屬性數組,得到每個屬性
//4判斷屬性上面是否有@DI注解
//私有屬性 ,設定可以通路
//5如果有@DI注解,把對象進行設定(注入)
}
實作:
//包掃描
private void loadBean(File file) throws Exception {
//1判斷是否是檔案夾
if (file.isDirectory()) {
//2擷取檔案夾中所有内容
File[] childrenFiles = file.listFiles();
//3判斷檔案夾裡面為空,直接傳回
if (childrenFiles == null || childrenFiles.length == 0) {
return;
}
//4如果檔案夾裡面不為空,周遊檔案夾所有内容
for (File childFile : childrenFiles) {
//4.1 周遊得到每個FiLe對象,繼續判斷,如果還是檔案夾,遞歸
if (childFile.isDirectory()) {
loadBean(childFile);
} else {
//4.2周遊得到FiLe對象不是檔案夾,是檔案,
//4.3得到包路徑+類名稱部分-字元串截取
String pathWithClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);
//4.4判斷目前檔案類型是否.class
if (pathWithClass.endsWith(".class")) {
//4.5如果是.cLass類型,把路徑\替換成. 把 .class去掉
String allName = pathWithClass.substring(0, pathWithClass.length() - 6).replaceAll("\\\\", ".");
//4.6判斷類上是否有@Bean注解,有就執行個體化
//4.6.1擷取類的Class
Class<?> clazz = Class.forName(allName);
//4.6.2判斷是不是接口
if (!clazz.isInterface()) {
//4.6.3判斷類上是否有@Bean注解
Bean annotation = clazz.getAnnotation(Bean.class);
if (annotation != null) {
//4.6.4執行個體化
Object instance = clazz.getConstructor().newInstance();
//4.7執行個體化後放到map集合beanFactoryMap裡面
if (clazz.getInterfaces().length > 0) {
//如果有接口把接口的class當成key,執行個體對象當成value
beanFactoryMap.put(clazz.getInterfaces()[0], instance);
} else {
//如果沒有接口把自己的class當成key,執行個體對象當成value
beanFactoryMap.put(clazz, instance);
}
}
}
}
}
}
}
}
//屬性注入
private void loadDI() {
//執行個體化對象在 beanFactoryMap 的map集合裡面
//1 周遊 beanFactoryMap map集合
Set<Map.Entry<Class, Object>> entries = beanFactoryMap.entrySet();
for (Map.Entry<Class, Object> entry : entries) {
//2擷取map集合每個對象(value) ,每個對象屬性擷取到
Object obj = entry.getValue();
Class<?> clazz = obj.getClass();
Field[] declaredFields = clazz.getDeclaredFields();
//3 周遊得到每個對象屬性數組,得到每個屬性
for (Field field : declaredFields) {
//4判斷屬性上面是否有@DI注解
DI annotation = field.getAnnotation(DI.class);
if (annotation != null) {
//私有屬性 ,設定可以通路
field.setAccessible(true);
//5如果有@DI注解,把對象進行設定(注入)
try {
field.set(obj, beanFactoryMap.get(field.getType()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
測試:
package com.coding.test;
import com.coding.bean.AnnotationApplicationContext;
import com.coding.bean.ApplicationContext;
import com.coding.service.UserService;
/**
* @time 2023-02-22-16:23
*/
public class TestBean {
public static void main(String[] args) {
ApplicationContext context = new AnnotationApplicationContext("com.coding");
UserService userServiceClass = (UserService) context.getBean(UserService.class);
System.out.println(userServiceClass);
userServiceClass.add();
}
}
結果:
com.coding.service.User[email protected]
自定義bean service........
自定義bean dao.......