300行代碼實現Spring核心原理(徹底搞懂IOC、DI)


我們都知道spring的執行原理,甚至爲了面試倒背如流(http請求–>dispatcherServlet–>HandlerMapping–>Handler–>…),我之前雖然是知道這些東西,但是感覺對它又很模糊,dispatcherServlet具體是什麼?HandlerMapping又是幹什麼的?IOC、DI是怎麼實現的?這些東西在腦海中都是似懂非懂的。百度查資料什麼的其實都不如自己手寫一遍來的實在。也就幾百行代碼,手寫一遍,相信你一定會變得更自信。

注意:代碼中註釋都很全。源碼地址:https://gitee.com/isczy/mySpringCore.git

1.準備工作

配置application.properties,就和xml中配置的一個性質
在這裏插入圖片描述
配置web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:javaee="http://java.sun.com/xml/ns/javaee"
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
	version="2.4">

    <display-name>WebApplication</display-name>
    <servlet>
        <servlet-name>MySpringMvc</servlet-name>
        <servlet-class>com.czy.project.springMVCframework.servlet.MyDispatcherServlet</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>MySpringMvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

2.編寫自定義註解

自定義一些常用的註解:如
@Autowired

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

@Controller

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

@RequestMapping

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

@RequestParam

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

@Service

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

3.編寫簡單的Controller、Service

@MyController
@MyRequestMapping("/demo")
public class DemoController {

    @MyAutowired
    private DemoService demoService;

    @MyRequestMapping("/query")
    public void query(HttpServletRequest req, HttpServletResponse resp,
                      @MyRequestParam("name") String name,@MyRequestParam("age") int age){

        String result = demoService.query(name)+" and age is "+age;
        try {
            resp.setHeader("Content-type", "text/html;charset=UTF-8");
            resp.setCharacterEncoding("utf-8");
            resp.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @MyRequestMapping("/add")
    public void add(HttpServletRequest req, HttpServletResponse resp,
                    @MyRequestParam("name") String name){
        String result = demoService.add(name);
        try {
            resp.setHeader("Content-type", "text/html;charset=UTF-8");
            resp.setCharacterEncoding("utf-8");
            resp.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @MyRequestMapping("/remove")
    public void remove(HttpServletRequest req,HttpServletResponse resp,
                       @MyRequestParam("name") String name){
        try {
            String result = demoService.remove(name);
            resp.setHeader("Content-type", "text/html;charset=UTF-8");
            resp.setCharacterEncoding("utf-8");
            resp.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
public interface DemoService {

    String query (String name);
    String add(String name);
    String remove(String name);
}
@MyService
public class DemoServiceImpl implements DemoService {

    @Override
    public String query(String name) {
        return "query success :My name is " + name ;
    }

    @Override
    public String add(String name) {
        return "添加成功:新增用戶 " + name ;
    }

    @Override
    public String remove(String name) {
        return "刪除成功:刪除用戶 " + name ;
    }
}

4.編寫核心重點:DispatcherServlet

/**
 * 入口類
 * 即spring中的DispatcherServlet
 * @author czy
 */
public class MyDispatcherServlet extends HttpServlet {

    private static final String LOCATION = "contextConfigLocation";//即web.xml中配置的init-param-name

    private Properties properties = new Properties();//用於保存配置文件application.properties的內容

    private List<String> classNames = new ArrayList<String>();//保存所有掃描出的全限定類名的集合

    private Map<String,Object> ioc = new HashMap<String,Object>();//ioc容器

    //保存所有的Url和方法的映射關係
    private List<Handler> handlerMapping = new ArrayList<Handler>();

    /**
     * 初始化一系列操作
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加載配置文件
        //根據contextConfigLocation名稱從web.xml中獲取出配置文件全稱:application.properties

        String initParameter = config.getInitParameter(LOCATION);
        doLoadConfig(initParameter);

        /**
         * 2.掃描指定包下的類:spring中在application.xml中配置scanPackage指定掃描的包路徑
         * 這裏直接用application.properties代替
         */
        String scanPackage = properties.getProperty("scanPackage");//通過scanPackage鍵獲取值,即獲取要掃描的包路徑
        doScanner(scanPackage);
        //3.初始化所有相關類的實例(這裏就是初始化加@MyController,@MyService的類),並保存到IOC容器中
        doInstance();
        //4.依賴注入
        doAutowired();
        //5.構造HandlerMapping
        initHandlerMapping();
        //******************到此爲止,spring相關ioc、di等初始化完成**********************
        //******************等待請求,匹配URL,定位方法, 反射調用執行*******************
        //******************調用doGet或者doPost方法*************************************
        System.out.println("MyDispatcherServlet[初始化完成]。。。等待調用。。。");
    }

    /**
     * doGet和doPost方法大家應該相當瞭解了
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);//doGet的操作交個doPost來執行
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try{
            doDispatch(req,resp); //開始匹配到對應的方法
        }catch (Exception e){
            //如果匹配過程出現異常,將異常信息打印出去
            resp.getWriter().write("500 Exception,Details:\r\n" + Arrays.toString(e.getStackTrace()).
                    replaceAll("\\[|\\]", "").
                    replaceAll(",\\s", "\r\n"));//將‘[’或者‘]’替換成"",將空白字符替換成換行符
        }

    }

    /**
     *匹配URL
     * @param req
     * @param resp
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        Handler handler = getHandler(req);
        if (null == handler){
        //如果沒有匹配上,返回404錯誤
            resp.getWriter().write("404 Not Found");//常見操作。。。
            return;
        }
        System.out.println("已匹配到處理器["+handler+"]");
        //獲取方法的參數列表
        Class<?>[] paramTypes = handler.method.getParameterTypes();
        //保存所有需要自動賦值的參數值
        Object [] paramValues = new Object[paramTypes.length];
        Map<String,String[]> parameterMap = req.getParameterMap();//獲取用戶傳入的參數map
        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            //獲取所有參數即數組,並將數組轉化成string,並替換[]爲""、空白字符爲","
            String value = new String(Arrays.toString(param.getValue()).getBytes("iso8859-1"),"utf-8").
                    replaceAll("\\[|\\]", "").replaceAll("\\s", ",");

            //如果handler的參數列表沒有用戶傳入的key則跳出本次循環
            if (!handler.paramIndexMapping.containsKey(param.getKey()))continue;
            //找到匹配的參數,則開始填充參數值
            int index = handler.paramIndexMapping.get(param.getKey());//根據參數名獲取參數對應的序號
            //url傳過來的參數都是String類型的,HTTP是基於字符串協議
            //只需要把String轉換爲所需類型就好
            paramValues[index] = convert(paramTypes[index],value);
        }
        //設置方法中的request和response對象
        int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
        paramValues[reqIndex] = req;
        int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
        paramValues[respIndex] = resp;
        //執行url對應的方法
        System.out.println("開始執行url對應的方法:"+handler.method.getName());
        handler.method.invoke(handler.controller, paramValues);
    }

    /**
     *獲取處理器Handler
     * @param request
     * @return
     */
    private Handler getHandler(HttpServletRequest request) {
        if (handlerMapping.isEmpty())return null;
        String url = request.getRequestURI();
        String contextPath = request.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/+","/");

        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.pattern.matcher(url);//根據用戶的url獲取一個匹配器matcher
            //matches方法用於全字符串匹配也就是100%匹配
            if (!matcher.matches())continue;//如果沒有匹配到則跳過本次循環
            return handler;
        }
        return null;
    }

    /**
     * 初始化HandlerMapping(處理器映射器):
     * HandlerMapping其實就是一個包含Handler的集合
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty())return;
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            //如果該類上沒有MyController註解則跳過本次循環
            if (!clazz.isAnnotationPresent(MyController.class))continue;
            String url = "";
            if (clazz.isAnnotationPresent(MyRequestMapping.class)){
                //如果類上有MyRequestMapping註解,獲取Controller的url配置
                url =clazz.getAnnotation(MyRequestMapping.class).value();
            }

            //獲取Method的url配置
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                //沒有加RequestMapping註解的直接忽略
                if(!method.isAnnotationPresent(MyRequestMapping.class))continue;
                //映射URL
                MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);
                String regex =("/"+url+"/"+requestMapping.value()).replaceAll("/+","/");
                Pattern pattern = Pattern.compile(regex);
                handlerMapping.add(new Handler(entry.getValue(),method,pattern));
                System.out.println("HandlerMapping初始化完成【url:" + regex +"】" +"【method:" + method+"】");
            }
        }
    }

    /**
     * 依賴注入:就是拿到ioc容器中的類,然後訪問類中的字段屬性,是否包含Autowired註解
     * 然後從ioc容器中拿到實例並初始化該字段
     */
    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) {
                //如果該屬性上沒有MyAutowired註解則跳過本次循環
                if (!field.isAnnotationPresent(MyAutowired.class))continue;

                MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                String beanName = autowired.value();
                if ("".equals(beanName)){//用戶沒有指定注入的beanName
                    beanName = field.getType().getName();//那麼beanName就是該屬性的名稱
                }
                field.setAccessible(true);//暴力訪問私有屬性
                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    continue ;
                }
            }
        }
    }

    /**
     * 初始化所有相關類的實例,並保存到IOC容器中
     */
    private void doInstance() {
        if(classNames.size() == 0)return;
        //遍歷全限定類名集合,拿到各類,並初始化
        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(MyController.class)){ //如果類上聲明瞭MyController註解
                   //將類名作爲beanName,即ioc容器的key
                    String beanName = clazz.getName();
                    ioc.put(clazz.getName(),clazz.newInstance());
                    System.out.println("["+beanName+"]已添加到IOC容器");
                }else if (clazz.isAnnotationPresent(MyService.class)){//如果類上聲明瞭MyService註解
                    MyService myService = clazz.getAnnotation(MyService.class);
                    String beanName = myService.value();//獲取自定義的beanName
                    if (!"".equals(beanName.trim())){//如果用戶設置了自定義的beanName,就用用戶自己設置
                        ioc.put(beanName,clazz.newInstance());
                        System.out.println("["+beanName+"]已添加到IOC容器");
                        continue;
                    }
                    //如果自己沒設,就按接口類型創建一個實例
                    Class<?>[] interfaces = clazz.getInterfaces();//獲取該類實現的接口類
                    for (Class<?> c : interfaces) {
                        ioc.put(c.getName(),clazz.newInstance());
                        System.out.println("["+c.getName()+"]已添加到IOC容器");
                    }
                }else{
                    continue;
                }
            }

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


    /**
     * 掃描指定包下的類
     * @param scanPackage:包掃描路徑
     */
    private void doScanner(String scanPackage) {
        //將包路徑轉換爲文件路徑:即將com.czy.project.demo轉化爲 com/czy/project/demo
        String s = scanPackage.replace(".", "/");
        URL url = this.getClass().getClassLoader().getResource(s);
        File dir = new File(url.getFile());//獲取該路徑的文件
        //遍歷該文件夾下的所有文件
        for (File file : dir.listFiles()) {
            if (file.isDirectory()){
                doScanner(scanPackage+"."+file.getName());//如果該文件是個文件夾,繼續遞歸
            }else {
                //將全限定類名添加到集合
                classNames.add(scanPackage+"."+file.getName().replace(".class","").trim());
            }
        }
        for (String className : classNames) {
            System.out.println("已掃描到:["+className+"]");
        }
    }

    /**
     * 加載配置文件:application.properties
     * @param initParameter
     */
    private void doLoadConfig(String initParameter){
        //將application.properties加載到輸入流
        try(InputStream is = this.getClass().getClassLoader().getResourceAsStream(initParameter)) {
            properties.load(is);//讀取配置文件
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 內部類
     * Handler記錄Controller中的RequestMapping和Method的對應關係
     * 即spring中一個url請求對應一個方法
     */
    private class Handler{
        protected Object controller;	//保存方法對應的實例
        protected Method method;		//保存映射的方法
        protected Pattern pattern;      //url對應的正則表達式,用於映射匹配
        protected Map<String,Integer> paramIndexMapping;	//參數名以及序號

        /**
         *構造一個Handler基本的參數
         * @param controller
         * @param method
         * @param pattern
         */
        public Handler(Object controller, Method method, Pattern pattern) {
            this.controller = controller;
            this.method = method;
            this.pattern = pattern;

            paramIndexMapping = new HashMap<String, Integer>();
            putParamIndexMapping(method);
        }

        /**
         * 處理方法中的參數
         * @param method
         */
        private void putParamIndexMapping(Method method) {
            //獲取方法參數上的註解,注意Annotation是一個二維數組
            Annotation[][] p = method.getParameterAnnotations();
            for (int i = 0; i < p.length; i++) {
                for (Annotation a : p[i]) {
                    if (a instanceof MyRequestParam){//如果這個註解是MyRequestParam
                        String param = ((MyRequestParam) a).value();
                        if (!"".equals(param)){
                            paramIndexMapping.put(param,i);
                        }

                    }
                }
            }

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

        @Override
        public String toString() {
            return "Handler{" +
                    "controller=" + controller +
                    ", method=" + method +
                    ", pattern=" + pattern +
                    ", paramIndexMapping=" + paramIndexMapping +
                    '}';
        }
    }

    /**
     *  HTTP是基於字符串協議,所以url傳過來的參數都是String類型的,
     *  只需要把String轉換爲對應方法中的類型就好
     * @param type
     * @param value
     * @return
     */
    private Object convert(Class<?> type,String value){
        if(Integer.class == type||type == int.class){
            return Integer.valueOf(value);
        }
        //如果還有double或者其他類型,繼續加if
        //這時候,我們應該想到策略模式了
        return value;
    }

}

5.測試

啓動項目後可以看到,初始化MyDispatcherServlet都做了什麼
在這裏插入圖片描述
瀏覽器地址輸入路徑:http://localhost:8088/demo/query?name=zhangsan&age=18
訪問結果:query success :My name is zhangsan and age is 18

在這裏插入圖片描述

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