天天看點

抄寫springboot源碼--my-spring項目☞bean工廠管理

目錄:

  • 自定義注解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的源碼,學習大佬的經驗...

繼續閱讀