手寫spring框架,幫你徹底了解spring的工作原理機(jī)制

在我們的日常工作中宙拉,經(jīng)常會(huì)用到Spring宾尚、Spring Boot、Spring Cloud谢澈、Struts煌贴、Mybatis、Hibernate等開源框架锥忿,有了這些框架的誕生牛郑,平時(shí)的開發(fā)工作量也是變得越來(lái)越輕松,我們用 Spring Boot 分分鐘可以新建一個(gè)Web項(xiàng)目敬鬓。
今天通過(guò)手寫Spring框架淹朋,幫大家深入了解一下Spring的工作機(jī)制,文中涉及的代碼只用來(lái)幫助大家理解Spring钉答,不會(huì)在線上使用础芍,有不嚴(yán)謹(jǐn)?shù)牡胤竭€請(qǐng)大家掠過(guò)。

項(xiàng)目結(jié)構(gòu)

file

框架部分實(shí)現(xiàn)

  1. 為了區(qū)分框架部分代碼和業(yè)務(wù)部分代碼数尿,我們將這兩部分分別劃分在不同的包內(nèi) com.mars.demo 和 com.mars.framework仑性,以便隨后只掃描業(yè)務(wù)代碼。
  2. 這里是自己手寫Spring框架右蹦,所以不會(huì)引入任何Spring項(xiàng)目相關(guān)的包诊杆。
  3. 由于是一個(gè)Web項(xiàng)目,所有我們需要引入 servlet-api 包何陆,僅供編譯器使用晨汹,所有配置 scope 為 provided。

新建一個(gè)Servlet

首先新建一個(gè) HttpServlet 的實(shí)現(xiàn)類 MarsDispatcherServlet甲献,用來(lái)接收請(qǐng)求宰缤。

public class MarsDispatcherServlet extends HttpServlet {


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6. 處理請(qǐng)求
    }

    @Override
    public void init(ServletConfig config) throws ServletException {

    }
    

配置web.xml

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <display-name>Spring Mvc Education</display-name>

    <servlet>
        <servlet-name>marsmvc</servlet-name>
        <servlet-class>com.mars.framework.servlet.MarsDispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>application.properties</param-value>
        </init-param>

        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>marsmvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>
  1. 首先配置了一個(gè) servlet, 名字是 marsmvc, 類全路徑是 com.mars.framework.servlet.MarsDispatcherServlet慨灭。
  2. 設(shè)置了初始化參數(shù)名和值(這里的值是整個(gè)項(xiàng)目的配置文件)朦乏。
  3. 配置 load-on-startup, 標(biāo)記容器是否在啟動(dòng)的時(shí)候就加載這個(gè)servlet(實(shí)例化并調(diào)用其init()方法)。
  4. 配置 servlet-mapping, 將所有請(qǐng)求轉(zhuǎn)發(fā)到這個(gè)servlet處理氧骤。

配置application.properties

scanPackage=com.mars.demo

這個(gè)比較好理解呻疹,僅配置了一項(xiàng)內(nèi)容,意思是要掃描的包筹陵,隨后我們會(huì)獲取這個(gè)值去加載容器刽锤。

定義我們常用的注解

  1. MarsAutowired
  2. MarsController
  3. MarsRequestMapping
  4. MarsRequestParam
  5. MarsService
    這里僅列舉兩個(gè),其他都大同小異朦佩,需要源碼的可以去我的代碼倉(cāng)庫(kù)fork并思。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MarsController {
    String value() default "";
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MarsRequestMapping {
    String value() default "";
}

充實(shí)Servlet功能

先列出框架在初始化的時(shí)候都要做那些事情

  1. 加載配置文件
  2. 掃描所有相關(guān)聯(lián)的類
  3. 初始化所有相關(guān)聯(lián)的類,并且將其保存在IOC容器里面
  4. 執(zhí)行依賴注入(把加了@Autowired注解的字段賦值)
  5. 構(gòu)造HandlerMapping语稠,將URL和Method進(jìn)行關(guān)聯(lián)

接下來(lái)我們一步步完成上面的操作

 @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println("===================");
        //1.加載配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        
        //2.掃描所有相關(guān)聯(lián)的類
        doScanner(contextConfig.getProperty("scanPackage"));
        
        //3.初始化所有相關(guān)聯(lián)的類宋彼,并且將其保存在IOC容器里面
        doInstance();
        
        //4.執(zhí)行依賴注入(把加了@Autowired注解的字段賦值)
        doAutowired();

        //Spring 和核心功能已經(jīng)完成 IOC、DI
        
        //5.構(gòu)造HandlerMapping仙畦,將URL和Method進(jìn)行關(guān)聯(lián)
        initHandlerMapping();

        System.out.println("Mars MVC framework initialized");

    }
        ```
        
##      加載配置文件
   
        private Properties contextConfig = new Properties();

    private void doLoadConfig(String location) {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);

        try {
            contextConfig.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
#   掃描所有相關(guān)聯(lián)的類
private void doInstance() {

        if(classNames.isEmpty()) return;

        for(String className: classNames) {

            try {
                Class<?> clazz = Class.forName(className);


                if(clazz.isAnnotationPresent(MarsController.class)) {

                    Object instance = clazz.newInstance();
                    String beanName = lowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);

                } else if (clazz.isAnnotationPresent(MarsService.class)) {

                    MarsService service = clazz.getAnnotation(MarsService.class);

                    //2.優(yōu)先使用自定義命名
                    String beanName = service.value();

                    if("".equals(beanName.trim())) {
                        //1.默認(rèn)使用類名首字母小寫
                        beanName = lowerFirstCase(clazz.getSimpleName());
                    }

                    Object instance = clazz.newInstance();

                    ioc.put(beanName, instance);

                    //3.自動(dòng)類型匹配(例如:將實(shí)現(xiàn)類賦值給接口)

                    Class<?> [] interfaces = clazz.getInterfaces();

                    for(Class<?> inter: interfaces) {
                        ioc.put(inter.getName(), instance);
                    }

                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    //利用ASCII碼的差值
    private String lowerFirstCase(String str) {
        char[] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
        

執(zhí)行依賴注入(把加了@Autowired注解的字段賦值)

private void doAutowired() {

        if(ioc.isEmpty()) return;

        for(Map.Entry<String, Object> entry: ioc.entrySet()) {
            //注入的意思就是把所有的IOC容器中加了@Autowired注解的字段賦值
            //包含私有字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for(Field field : fields) {

                //判斷是否加了@Autowired注解
                if(!field.isAnnotationPresent(MarsAutowired.class)) continue;

                MarsAutowired autowired = field.getAnnotation(MarsAutowired.class);

                String beanName = autowired.value();

                if("".equals(beanName)) {
                    beanName = field.getType().getName();
                }

                //如果這個(gè)字段是私有字段的話输涕,那么要強(qiáng)制訪問(wèn)
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
        

構(gòu)造HandlerMapping,將URL和Method進(jìn)行關(guān)聯(lián)

private void initHandlerMapping() {
        if(ioc.isEmpty()) return;

        for(Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();

            if(!clazz.isAnnotationPresent(MarsController.class)) continue;

            String baseUrl = "";

            if(clazz.isAnnotationPresent(MarsRequestMapping.class)) {
                MarsRequestMapping requestMapping = clazz.getAnnotation(MarsRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            Method[] methods = clazz.getMethods();

            for(Method method : methods) {

                if(!method.isAnnotationPresent(MarsRequestMapping.class)) continue;

                MarsRequestMapping requestMapping = method.getAnnotation(MarsRequestMapping.class);

                String regex = requestMapping.value();

                regex = (baseUrl + regex).replaceAll("/+", "/");

                Pattern pattern = Pattern.compile(regex);
                handlerMapping.add(new Handler(entry.getValue(), method, pattern));

                System.out.println("Mapping: " + regex + "," + method.getName());
            }
        }

    }
        

編寫業(yè)務(wù)代碼

新建一個(gè)Controller

@MarsController
@MarsRequestMapping("/demo")
public class DemoApi {

    @MarsAutowired
    private DemoService demoService;

    @MarsRequestMapping("/query")
    public void query(HttpServletRequest req,
                      HttpServletResponse resp,
                      @MarsRequestParam("name") String name) {
        System.out.println("name: " + name);
        String result = demoService.get(name);

        try{
            resp.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @MarsRequestMapping("/add")
    public void add(HttpServletRequest req,
                    HttpServletResponse resp,
                    @MarsRequestParam("a") Integer a,
                    @MarsRequestParam("b") Integer b) {
        try {
            resp.getWriter().write(String.format("%d+%d=%d", a, b, (a+b)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

提供兩個(gè)接口慨畸,一個(gè)通過(guò)請(qǐng)求名稱返回響應(yīng)的介紹內(nèi)容莱坎,另一個(gè)將請(qǐng)求的兩個(gè)Integer相加并返回。

創(chuàng)建一個(gè)Service

public interface DemoService {
    String get(String name);
}

@MarsService
public class DemoServiceImpl implements DemoService {
    public String get(String name) {
        return String.format("My name is %s.", name);
    }
}

添加Jetty插件

我們的項(xiàng)目運(yùn)行在Jetty中寸士,所以添加相關(guān)插件以及配置:

<plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>7.1.6.v20100715</version>
    <configuration>
        <stopPort>9988</stopPort>
        <stopKey>foo</stopKey>
        <scanIntervalSeconds>5</scanIntervalSeconds>
        <connectors>
            <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
                <port>8080</port>
                <maxIdleTime>60000</maxIdleTime>
            </connector>
        </connectors>
        <webAppConfig>
            <contextPath>/</contextPath>
        </webAppConfig>
    </configuration>
</plugin>

運(yùn)行

file

點(diǎn)擊 jetty:run 運(yùn)行項(xiàng)目

瀏覽器訪問(wèn): http://localhost:8080/demo/query?name=Mars

file




瀏覽器訪問(wèn):http://localhost:8080/demo/add?a=10&b=20

file

這樣一個(gè)完整的spring框架就已經(jīng)手寫出來(lái)了檐什,大家也可以關(guān)注下我的宮眾浩【java開發(fā)之路】
為大家準(zhǔn)備好了2019最新的面試教程和架構(gòu)師資料,感謝大家的支持與關(guān)注碉京!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末厢汹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谐宙,更是在濱河造成了極大的恐慌,老刑警劉巖界弧,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凡蜻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡垢箕,警方通過(guò)查閱死者的電腦和手機(jī)划栓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)条获,“玉大人忠荞,你說(shuō)我怎么就攤上這事。” “怎么了委煤?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵堂油,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我碧绞,道長(zhǎng)府框,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任讥邻,我火速辦了婚禮迫靖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兴使。我一直安慰自己系宜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布发魄。 她就那樣靜靜地躺著盹牧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪欠母。 梳的紋絲不亂的頭發(fā)上欢策,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音赏淌,去河邊找鬼踩寇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛六水,可吹牛的內(nèi)容都是我干的俺孙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼掷贾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼睛榄!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起想帅,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤场靴,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后港准,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旨剥,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年浅缸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了轨帜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衩椒,死狀恐怖蚌父,靈堂內(nèi)的尸體忽然破棺而出哮兰,到底是詐尸還是另有隱情,我是刑警寧澤苟弛,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布喝滞,位于F島的核電站,受9級(jí)特大地震影響嗡午,放射性物質(zhì)發(fā)生泄漏囤躁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一荔睹、第九天 我趴在偏房一處隱蔽的房頂上張望狸演。 院中可真熱鬧,春花似錦僻他、人聲如沸宵距。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)满哪。三九已至,卻和暖如春劝篷,著一層夾襖步出監(jiān)牢的瞬間哨鸭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工娇妓, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留像鸡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓哈恰,卻偏偏與公主長(zhǎng)得像只估,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子着绷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355