目錄:
- 自定義注解bean
- bean工廠
- 依賴注入&循環依賴
在上一節,自定義注解實作了接口的配置調用,但是我們沒有使用到spring的依賴注入及統一bean管理;那麼本節我們将來實作這一塊的功能
1. 自定義注解@Bean
package com.mp.framework.beans;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyBean {
}
在類上表名有@MyBean注解的,代表我們要把這個bean交由spring來統一管理
2. 自定義注入: @MyAutowired
package com.mp.framework.beans;
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
}
在字段上有@MyAutowired注解的,表示我們要做依賴注入,這裡要解決的就是循環依賴的問題
3. 核心代碼,實作bean的統一管理 MyBeanFactory
package com.mp.framework.beans;
import com.mp.demo.controller.TestController;
import com.mp.framework.annotation.MyController;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Stream;
/**
* ClassName: MyBeanFactory
* Function: 核心代碼,将bean通過map的形式進行儲存
* Date: 2020-05-07 15:45
* author mp
* version V1.0
*/
public class MyBeanFactory {
// bean集合
public static Map<Class<?>,Object> beanMaps = new ConcurrentHashMap<>();
// 初始化
public static void init(List<Class<?>> list) {
list.stream().filter(cls -> cls.isAnnotationPresent(MyBean.class) || cls.isAnnotationPresent(MyController.class)) // 隻處理 @MyBean 和 @Contrller注解的類
.forEach(createBean);
System.out.println("初始化bean: "+beanMaps);
}
// 建立bean
static Consumer<Class<?>> createBean = cls -> {
// 從bean集合中檢查,bean是否已經建立
Object bean = beanMaps.get(cls);
// 擷取字段類型,用于判斷循環依賴
Field[] fields = cls.getDeclaredFields();
if (bean == null) { // 沒有建立
try {
bean = cls.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
beanMaps.put(cls,bean); // 容器管理bean
}
// 檢查循環依賴
MyBeanFactory.fieldsBeanTrans.accept(bean,fields);
};
// 檢查循環依賴
static BiConsumer<Object,Field[]> fieldsBeanTrans = (bean,fields) -> {
Stream.of(fields).filter(field -> field.isAnnotationPresent(MyAutowired.class))
.forEach(field -> {
Class<?> type = field.getType();
// 檢查依賴的bean是否已經建立
Object obj = beanMaps.get(type);
if (obj == null){
// 建立bean
try {
obj = type.newInstance();
beanMaps.put(type,obj);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
field.setAccessible(true);
try {
field.set(bean,obj); // 字段指派,即為bean設定依賴域,不然autowire 自動注入為null
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
};
// 擷取bean
static public Object getBean(Class<?> cls){
return beanMaps.get(cls);
}
}
說明:
(1)依舊利用擷取類上注解資訊來判斷是否需要初始化bean,并統一管理;這裡我們隻處理@MyBean 和 @MyController兩個注解
(2)bean的統一管理,實質是放在一個Map集合中,在初始化bean之前,需要到集合中查詢一遍,已確定bean隻初始化一次
(3)關于循環依賴,在擷取到bean後,通過反射機制,擷取到所有的字段,再擷取到字段Type,并檢查bean集合中是否已經建立該依賴bean;
(4)針對循環依賴,需要設定bean的依賴域,即 field.set(bean,obj),如果不手動設定的話,最後自動引入的話對象字段就為null
4. 接口映射類需要調整MappingHandler, 之前是手動初始化對象,現在改由統一從bean集合中取
package com.mp.framework.handler;
import com.mp.framework.beans.MyBeanFactory;
import org.apache.naming.factory.BeanFactory;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.stream.Stream;
/**
* ClassName: MappingHandler
* Function: TODO
* Date: 2020-05-07 14:12
* author mp
* version V1.0
*/
public class MappingHandler {
private String uri;
private Method method;
private Class<?> cls;
private String[] args;
public MappingHandler(String uri, Method method, Class<?> cls, String[] args) {
this.uri = uri;
this.method = method;
this.cls = cls;
this.args = args;
}
// 執行接口方法
public boolean handle(ServletRequest req, ServletResponse res) throws IllegalAccessException, InstantiationException, InvocationTargetException, IOException {
String reqUri = ((HttpServletRequest) req).getRequestURI();
if (!this.uri.equals(reqUri)) return false;
Object[] params = new Object[args.length];
// 擷取參數
for (int i=0;i< args.length;i++){
params[i] = req.getParameter(args[i]); // 擷取請求參數值
}
// Object obj = this.cls.newInstance();
Object obj = MyBeanFactory.beanMaps.get(this.cls); //統一bean管理
// 執行方法,擷取傳回值
Object response = this.method.invoke(obj, params);
res.getWriter().write(Optional.ofNullable(response).map(r -> r.toString()).orElse("")); // 針對無傳回值類型的接口,需要做null處理
return true;
}
}
5.調整MyApplication啟動類
package com.mp.framework.starter;
import com.mp.framework.beans.MyBeanFactory;
import com.mp.framework.core.ClassScanner;
import com.mp.framework.handler.HandlerManager;
import com.mp.framework.web.server.TomcatServer;
import com.mp.framework.web.server.TomcatServerNew;
import org.apache.catalina.LifecycleException;
import java.io.IOException;
import java.util.List;
/**
* ClassName: MyApplication
* Function: TODO
* Date: 2020-05-07 09:44
* author mp
* version V1.0
*/
public class MyApplication {
public static void run(Class<?> cls,String[] args) throws LifecycleException, IOException, ClassNotFoundException {
System.out.println("hello my0-spring application!!!");
// 執行個體化Tomca t服務
// TomcatServer tomcatServer = new TomcatServer(args);
TomcatServerNew tomcatServer = new TomcatServerNew(args);
tomcatServer.startServer();
// 添加擷取類清單和反射調用
List<Class<?>> list = new ClassScanner().doScanner(cls.getPackage().getName());
list.stream().forEach(c -> System.out.println(c.getName()));
// 篩選controller 接口,處理接口映射
HandlerManager.resolveMappingHandler(list);
MyBeanFactory.init(list); // 初始化bean工廠
}
}
MyBeanFactory.init(list); 啟動bean統一初始化工作.
6. 重新開機服務,通路接口 http://localhost:9999/v1/test?name=mp
傳回結果: service01: mp
至此: 我們自己寫的spring項目已經初具功能,但是還有很大的不足,很多地方考慮不周全,後面我将去讀springboot的源碼,學習大佬的經驗...