Spring源碼學習(一)從一個簡單的servlet開始

前言

磕磕碰碰,終於開始翻看spring源碼了。spring使用已經很多,之前一直想學習spring源碼,但是一直沒有開始。這篇博客開始學習spring源碼,手動實現一個簡單的基於servlet的mvc框架。

廣義的spring mvc調用流程

這個之前我們看過了很多資料,但是一直沒有真實理解,畢竟沒有一個實際的感受。MVC調用具體流程圖如下(個人理解)

要幹什麼

簡單點說,手寫一個mvc調用框架(超級簡單的版本)。更通俗點說,用代碼實現上述流程圖。

初始工作準備

新建一個web項目,這一步就不詳細說了。

1、模仿編寫controller,requestMapping,autowired,requestParam,service註解

自己的SelfController註解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelfController {
    String value() default "";
}

自己的RequestMapping註解

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelfRequestMapping {
    String value() default "";
}

自己的RequestParam註解

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelfRequestParam {
    String value() default "";
}

 自己的Autowired註解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelfAutowired {
    String value() default "";
}

 自己的service註解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SelfService {
    String value() default "";
}

 2、編寫自己的servlet

package com.learn.springmvc.servlet.V2;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * autor:liman
 * comment: 自己的servlet
 */
public class SelfServlet extends HttpServlet {

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

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

    /**
     * 初始化方法
     *
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
    }
}

 3、web.xml中配置servlet的映射

    <servlet>
        <servlet-name>selfServlet</servlet-name>
        <!--<servlet-class>com.learn.springmvc.servlet.SelfServlet</servlet-class>-->
        <servlet-class>com.learn.springmvc.servlet.V2.SelfServletV2</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>selfServlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

其中的init-param是給servlet一個初始化的參數,這個後面會詳細談到。 

開始有點複雜的代碼編寫

Servlet中的init方法中,會完成HandlerMapping,IOC相關內容的初始化,具體如下所示。

    public void init(ServletConfig config) throws ServletException {
        //1.加載配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2.掃描相關的類
        doScanner(contextConfig.getProperty("scanPackage"));
        //3.初始化掃描的類
        doInstance();
        //4.完成依賴注入
        doAutowired();
        //5.初始化handleMapping
        initHandlerMapping();
        System.out.println("自己寫的spring mvc 初始化完成");
    }

1、加載配置文件

加載配置文件,在這裏面是最簡單的操作,只是將配置文件讀取到內存的Properties對象中即可。

    //用於保存application.properties配置文件中的內容,這個實例使用properties文件代替xml文件
    private Properties contextConfig = new Properties();

    /**
     * 加載配置文件
     */
    private void doLoadConfig(String contextConfigLocation) {
        InputStream fis = null;
        fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(fis);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fis) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

這裏只是簡單的一個mvc簡單的示例,配置文件中只有一項配置: 

scanPackage=com.learn.springmvc

上述的工作只是將該配置屬性讀取到屬性contextConfig中。該配置中指定了我們需要掃描的基礎包名

2、掃描出相關的類

 

    //用於保存所有掃描出來的類名
    private List<String> classNames = new ArrayList<String>();

    /**
     * 掃描出相關的類
     *
     * @param scanPackage
     */
    private void doScanner(String scanPackage) {

        //主要是將包路徑轉換爲文件路徑
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classPath = new File(url.getFile());
        for (File file : classPath.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                classNames.add(className);
            }
        }
    }

掃描出所有的類名,將類名放入到List<String> 集合中。 

3、初始化掃描的類

    //傳說中的IOC容器,爲了簡化程序,這裏不用ConcurrentHashMap
    private Map<String, Object> ioc = new HashMap<String, Object>();

    /**
     * 初始化,爲DI做準備
     * 加了註解的類才能初始化,這裏只列出加了@Controller和@Service註解的類
     */
    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }

        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                //如果有Controller註解
                if (clazz.isAnnotationPresent(SelfController.class)) {//如果是Controller
                    Object instance = clazz.newInstance();
                    //首字母小寫,並放入ioc容器中
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);
                } else if (clazz.isAnnotationPresent(SelfService.class)) {//如果有Service註解
                    //獲取自定義的beanName——@Service("test")獲取其中的test
                    SelfService service = clazz.getAnnotation(SelfService.class);
                    String beanName = service.value();//獲得註解的值(一般自己制定service的名稱的時候)
                    if ("".equals(beanName.trim())) {
                        //如果沒有指定名稱,service 默認首字母小寫
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.newInstance();
                    //放入IOC容器
                    ioc.put(beanName, instance);

                    //注入的時候是接口注入的方式,投機取巧,就將接口作爲key,實例作爲值
                    for (Class<?> i : clazz.getInterfaces()) {
                        if (ioc.containsKey(i.getName())) {//這樣就限制了,一個接口只有一個實現類
                            throw new Exception("the " + i.getName() + " is exists");
                        }
                        ioc.put(i.getName(), instance);
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 將首字母小寫
     *
     * @param simpleName
     * @return
     */
    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        //之所以加,是因爲大小寫字母的ASCII碼相差32,
        // 而且大寫字母的ASCII碼要小於小寫字母的ASCII碼
        //在Java中,對char做算學運算,實際上就是對ASCII碼做算學運算
        chars[0] += 32;
        return String.valueOf(chars);
    }

這一步完成了IOC容器的初始化,這一步也算是瞭解了IOC的基本結構 ——一個類名與類實例的鍵值對。

4、完成依賴注入(DI)

    /**
     * 完成依賴注入
     */
    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //獲取本類中所有的字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for (Field field : fields) {
                if (!field.isAnnotationPresent(SelfAutowired.class)) {
                    continue;
                }
                //獲取有@Autowired註解的屬性上的註解對象
                SelfAutowired selfAutowired = field.getAnnotation(SelfAutowired.class);
                //獲取註解的值
                String beanName = selfAutowired.value().trim();
                if ("".equals(beanName)) {
                    //如果沒有自定義,默認根據類型注入
                    beanName = field.getType().getName();
                }
                //設置屬性的訪問屬性
                field.setAccessible(true);
                try {
                    //設置屬性的值
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

依賴注入其實也沒有想象中的複雜,只是遍歷IOC容器中的實體,並遍歷每個實體中的屬性,如果屬性上有註解,則從IOC中取出指定類型的實例,完成初始化。

5、 初始化HandlerMapping

這是最關鍵的一步,也是最複雜的一步,同時這一步也引出了Handler與HandlerMapping的關係,HandlerMapping其實就是一個映射,主要完成url與對應的controller中method的映射關係。

    private List<HandlerMapping> handlerMapping = new ArrayList<HandlerMapping>();

    /**
     * 初始化HandlerMapping
     * HandlerMapping——url和method一對一的映射
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }

        for(Map.Entry entry:ioc.entrySet()){
            Class<?> clazz = entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(SelfController.class)){
                continue;
            }

            String baseUrl = "";
            if(clazz.isAnnotationPresent(SelfRequestMapping.class)){
                SelfRequestMapping selfRequestMapping = clazz.getAnnotation(SelfRequestMapping.class);
                baseUrl = selfRequestMapping.value();
            }

            //默認獲取所有的public方法
            for(Method method:clazz.getMethods()){
                if(!method.isAnnotationPresent(SelfRequestMapping.class)){
                    continue;
                }

                SelfRequestMapping requestMapping = method.getAnnotation(SelfRequestMapping.class);
                String url = ("/"+baseUrl+"/"+requestMapping.value()).replaceAll("/+","/");
                this.handlerMapping.add(new HandlerMapping(url,method,entry.getValue()));
                System.out.println("Mapped:"+url+":"+method);
            }
        }
    }

 爲了方便後面的操作,如果HandlerMapping中只有url屬性和Method屬性是遠遠不夠的,在實際編寫代碼過程中爲了方便通過反射獲取方法的參數,需要維護方法所在的類類型,因此最終確定的HandlerMapping的屬性如下:

    /**
     * 暫時將HandlerMapping寫成一個靜態內部類
     */
    public static class HandlerMapping {
        private String url;
        private Method method;
        private Object controller;
        private Class<?>[] paramTypes;//參數類型列表
        private Map<String, Integer> paramIndexMapping = new HashMap<>();//參數索引位置

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public Method getMethod() {
            return method;
        }

        public void setMethod(Method method) {
            this.method = method;
        }

        public Object getController() {
            return controller;
        }

        public void setController(Object controller) {
            this.controller = controller;
        }

        public Class<?>[] getParamTypes() {
            return paramTypes;
        }

        /**
         * 構造函數
         *
         * @param url
         * @param method
         * @param controller
         */
        public HandlerMapping(String url, Method method, Object controller) {
            this.url = url;
            this.method = method;
            this.controller = controller;
            this.paramTypes = method.getParameterTypes();
            putParamIndexMapping(method);
        }

        /**
         * 初始化參數的索引位置
         *
         * @param method
         * @return
         */
        private void putParamIndexMapping(Method method) {
            //提取方法中加了註解的參數
            Annotation[][] pa = method.getParameterAnnotations();
            for (int i = 0; i < pa.length; i++) {
                for (Annotation a : pa[i]) {
                    if (a instanceof SelfRequestParam) {
                        String paramName = ((SelfRequestParam) a).value();
                        if (!"".equals(paramName.trim())) {
                            this.paramIndexMapping.put(paramName, i);
                        }
                    }
                }
            }

            //提取方法中的request和response參數
            Class<?>[] paramsTypes = method.getParameterTypes();
            for (int i = 0; i < paramsTypes.length; i++) {
                Class<?> type = paramsTypes[i];
                if (type == HttpServletRequest.class
                        || type == HttpServletResponse.class) {
                    this.paramIndexMapping.put(type.getName(), i);
                }
            }
        }
    }

paramIndexMapping中維護了參數與參數索引的對應關係。 

    /**
     * 找到指定的一致的handlercMapping處理對象
     * @param req
     * @return
     */
    private HandlerMapping getHandler(HttpServletRequest req){
        if(handlerMapping.isEmpty()){
            return null;
        }
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath,"").replaceAll("/+","/");

        for(HandlerMapping handler:this.handlerMapping){
            if(handler.getUrl().equals(url)){
               return handler;
            }
        }
        return null;
    }

 上述其實就是從HandlerMapping集合中獲取指定的HandlerMapping。至此,上一步就完成Spring mvc的初始化,IOC容器,DI的注入,以及初始化HandlerMapping,接下來就差最後一步了,完成調用,Method對象的調用需要三個屬性:1、方法所在的對象,2、所有的參數值,3、對應的Method。

6、最後的調用邏輯

    /**
     * 開始調用自己寫的mvc框架
     *
     * @param req
     * @param resp
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        //獲取指定的handler
        HandlerMapping handler = getHandler(req);
        if(handler == null){//沒有找到對應的處理器
            resp.getWriter().write("404 Not Found!!!");
            return;
        }
        //獲取方法的形參列表
        Class<?> [] paramTypes = handler.getParamTypes();
        Object [] paramValues = new Object[paramTypes.length];
        Map<String,String[]> params= req.getParameterMap();
        for(Map.Entry<String,String[]> param:params.entrySet()){
            String value = Arrays.toString(param.getValue()).
                    replaceAll("\\[|\\]","").replaceAll("\\s",",");

            if(!handler.paramIndexMapping.containsKey(param.getKey())){continue;}

            int index = handler.paramIndexMapping.get(param.getKey());
            paramValues[index] = convert(paramTypes[index],value);
        }
        if(handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())){
            int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
        }
        if(handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())){
            int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex] = resp;
        }
        //調用handler的方法,也就是目標方法
        Object returnValue = handler.method.invoke(handler.controller,paramValues);
        if(returnValue == null || returnValue instanceof Void){
            return;
        }
        resp.getWriter().write(returnValue.toString());
    }

走到最後一步,也就沒有什麼了,主要是組裝參數列表然後直接調用method對象的invoke方法即可。

7、servlet中的調用

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //6.調用
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

測試 

對應的測試controller

package com.learn.springmvc.controller;

import com.learn.springmvc.Annotation.SelfAutowired;
import com.learn.springmvc.Annotation.SelfController;
import com.learn.springmvc.Annotation.SelfRequestMapping;
import com.learn.springmvc.Annotation.SelfRequestParam;
import com.learn.springmvc.service.IDemoService;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//雖然,用法一樣,但是沒有功能
@SelfController
@SelfRequestMapping("/demo")
public class DemoController {

  	@SelfAutowired
	private IDemoService demoService;

	@SelfRequestMapping("/query")
	public void query(HttpServletRequest req, HttpServletResponse resp,
					  @SelfRequestParam("name") String name){
		String result = "My name is " + name;
		try {
			resp.getWriter().write(result);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@SelfRequestMapping("/add")
	public void add(HttpServletRequest req, HttpServletResponse resp,
					@SelfRequestParam("a") Integer a, @SelfRequestParam("b") Integer b){
		try {
			resp.getWriter().write(a + "+" + b + "=" + (a + b));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

這個就是自己編寫的Controller,啓動之後在瀏覽器中輸入:localhost:8080/demo/add?name=test,可以得到以下結果

 總結

在這個實例中反射用的非常多,無非就是利用反射獲取註解的值,然後利用反射獲取方法的參數列表,處理調用參數,整個過程不管如何總結,其實就是文章開頭的流程圖。每一步對應的就是指定的函數,但是函數中針對request和response都做了處理,顯得些許臃腫。代碼書寫完之後,博客整理的異常凌亂,一下沒理清整理的思路,具體源碼可以參見如下地址:一個簡單的mvc框架源碼地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章