1. 程式人生 > >深度解讀springMVC底層實現

深度解讀springMVC底層實現

使用JDK8 ,idea2018.2, maven3.5.4

使用XML 解析 + 反射來寫一個springMVC框架:
如下思路實現;

首先需要一個前置控制器 DispatcherServlet,作為整個流程的核心,由它去呼叫其他元件,共同完成業務。主要元件有兩個:
一是 Controller,呼叫其業務方法 Method,執行業務邏輯;
二是 ViewResolver 檢視解析器,將業務方法的返回值解析為物理檢視 + 模型資料,返回客戶端。

bu

Spring MVC 的實現流程:

客戶端請求被 DispatcherServlet(前端控制器)接收。
->根據 HandlerMapping 對映到 Handler。
->生成 Handler 和 HandlerInterceptor(如果有則生成)。
->Handler 和 HandlerInterceptor 以 HandlerExecutionChain 的形式一併返回給 DispatcherServlet。
->DispatcherServlet 通過 HandlerAdapter 呼叫 Handler 的方法做業務邏輯處理。
->返回一個 ModelAndView 物件給 DispatcherServlet。
->DispatcherServlet 將獲取的 ModelAndView 物件傳給 ViewResolver 檢視解析器,將邏輯檢視解析成物理檢視 View。
->ViewResolver 返回一個 View 給 DispatcherServlet。
->DispatcherServlet 根據 View 進行檢視渲染(將模型資料填充到檢視中)。
->DispatcherServlet 將渲染後的檢視響應給客戶端。

分析:

HTTP 請求是通過註解找到對應的 Controller 物件…;
Controller 的 Method 也是通過註解與 HTTP 請求對映的;
使用map 當做 ioC 容器,完成儲存所有引數與業務的class;
.

業務邏輯:

初始化工作完成,接下來處理 HTTP 請求,業務流程如下:
DispatcherServlet 接收請求,通過對映從 IoC 容器中獲取對應的 Controller 物件;
根據對映獲取 Controller 物件對應的 Method;
呼叫 Method,獲取返回值;
將返回值傳給檢視解析器,返回物理檢視;
完成頁面跳轉。

bu

doPost 方法處理 HTTP 請求:

解析 HTTP,分別得到 Controller 和 Method 對應的 URL
通過 URL 分別在 iocContainer 和 handlerMapping 中獲取對應的 Controller 及 Method
使用反射呼叫 Method,執行業務方法,獲取結果
結果傳給 MyViewResolver 進行解析,返回真正的物理檢視(JSP 頁面)
完成 JSP 的頁面跳轉


專案結構如圖

sdg

自定義註解:
@MyController
@MyRequestMapping

/**
 * @auther SyntacticSugar
 * @data 2018/11/13 0013下午 9:15
 */
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface MyController { String value() default ""; }
/**
 *   自定義  @RequestMapping  註解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
    String value() default "";
}

定義一個核心控制器MyDispatcherServlet ;

/**
 * @auther SyntacticSugar
 * @data 2018/11/13 0013下午 9:20
 * <p>
 * 建立控制器
 */
public class MyDispatcherServlet extends HttpServlet {
    //建立ioC  建立 handler存放容器

    private HashMap<String, Object> ioC = new HashMap<>();
    private HashMap<String, Method> handlerMapping = new HashMap<>();
    //自定義檢視解析
    private MyViewResolver myViewResolver;

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 把controller放到ioC中
        scanController(config);
        //初始化handler 對映
        initHandlerMapping();
        //載入檢視解析器
        loadViewResolver(config);
    }

    @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 {
        String handlerUri = req.getRequestURI().split("/")[2];
        String methodUri = req.getRequestURI().split("/")[3];
        //
        Object o = ioC.get(handlerUri);
        Method method = handlerMapping.get(methodUri);
        //  使用反射機制,呼叫執行 業務
        try {
            String value = (String) method.invoke(o);
            //  將邏輯檢視  轉化為   物理檢視,交給  view渲染返回前端
            String result  = myViewResolver.jspMapping(value);
            req.getRequestDispatcher(result).forward(req,resp );

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  /**
     * saxReader 解析springmvc.xml
     * @param config
     */
    private void scanController(ServletConfig config) {
        SAXReader saxReader = new SAXReader();
        try {
            String path = config.getServletContext().getRealPath("") + "\\WEB-INF\\classes\\" +
                    config.getInitParameter("contextConfigLocation");
            Document document = saxReader.read(path);
            // 獲取根元素
            Element rootElement = document.getRootElement();
            Iterator iterator = rootElement.elementIterator();
            // 遍歷 nodes  、sax解析每一行xml
            while (iterator.hasNext()) {
                Element next = (Element) iterator.next();
                // 把每一個元素的name和 component-scan  比較,獲取base-package值
                if (next.getName().equals("component-scan")) {
                    String packageName = next.attributeValue("base-package");
                    // 獲取包下 每個子包
                    List<String> classNames = getClassNames(packageName);
                    for (String className : classNames) {
                        /**
                        通過反射獲取clazz  ,判斷class上是否存在MyController註解
                         *   若存在 、獲取MyRequestMapping 的 value , 並將其 裝入 自定義的ioC
                          */
                        Class<?> clazz = Class.forName(className);
                        if (clazz.isAnnotationPresent(MyController.class)) {
                            MyRequestMapping annotation  = clazz.getAnnotation(MyRequestMapping.class);
                            String value = annotation.value().substring(1);
                            // 放入ioC
                            ioC.put(value,clazz.newInstance() );
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取 <context:component-scan base-package="com.baidu"/> 所有class 全路徑名
     * @param packageName
     * @return
     */
    private List<String> getClassNames(String packageName) {
        List<String> classNameList = new ArrayList<String>();
        String path = packageName.replace(".", "/");
        //已知存在包路徑,獲取每一級路徑下的file、 獲取類載入器,
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URL url = classLoader.getResource(path);
        //非空判斷
        if (url != null) {
            File[] files = new File(url.getPath()).listFiles();
            //遍歷取值
            for (File childFile : files) {
                String className = packageName + "." + childFile.getName().replace(".class", "");
                classNameList.add(className);
            }
        }
        // return
        return classNameList;
    }

    /**
     * 初始化 handler
     */
    private void initHandlerMapping() {
                //從ioC中取出 MyController註解的 class
        for (String s : ioC.keySet()) {
            Class<?> clazz = ioC.get(s).getClass();
            Method[] methods = clazz.getMethods();
            //遍歷
            for (Method method : methods) {
                // 判斷哪一個被 @MyRequestMapping 註解標識
                if (method.isAnnotationPresent(MyRequestMapping.class)) {
                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                    String value = annotation.value().substring(1);
                    //   存入 handler
                    handlerMapping.put(value,method );
                }
            }
        }
    }

    /**
     *   載入自定義檢視  saxReader 解析springmvc.xml
     * @param config
     */
    private void loadViewResolver(ServletConfig config) {
        SAXReader reader = new SAXReader();
        try {
            String path = config.getServletContext().getRealPath("")+"\\WEB-INF\\classes\\"+config.getInitParameter("contextConfigLocation");
            Document document = reader.read(path);
            Element root = document.getRootElement();
            Iterator iter = root.elementIterator();
            //遍歷
            while(iter.hasNext()){
                Element ele = (Element) iter.next();
                if(ele.getName().equals("bean")){
                    String className = ele.attributeValue("class");
                    Class clazz = Class.forName(className);
                    Object obj = clazz.newInstance();
                    //獲取 setter 方法
                    Method prefixMethod = clazz.getMethod("setPrefix", String.class);
                    Method suffixMethod = clazz.getMethod("setSuffix", String.class);
                    Iterator beanIter = ele.elementIterator();
                    //獲取 property 值
                    Map<String,String> propertyMap = new HashMap<String,String>();
                    while(beanIter.hasNext()){
                        Element beanEle = (Element) beanIter.next();
                        String name = beanEle.attributeValue("name");
                        String value = beanEle.attributeValue("value");
                        propertyMap.put(name, value);
                    }
                    for(String str:propertyMap.keySet()){
                        //反射機制呼叫 setter 方法,完成賦值
                        if(str.equals("prefix")){
                            prefixMethod.invoke(obj, propertyMap.get(str));
                        }
                        if(str.equals("suffix")){
                            suffixMethod.invoke(obj, propertyMap.get(str));
                        }
                    }
                    myViewResolver = (MyViewResolver) obj;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


自定義一個檢視解析器,MyViewResolver

/**
 * @auther SyntacticSugar
 * @data 2018/11/13 0013下午 9:31
 *
 * 自定義檢視解析器 MyViewResolver
 */
public class MyViewResolver {
    private  String prefix;
    private  String suffix;
    //  目標資源路徑
    public String jspMapping(String value){
        return this.prefix+value+this.suffix;
    }
    //setter  getter
    ......
}

建立測試 TestController ,對自定義的springmvc 進行測試;

/**
 * @auther SyntacticSugar
 * @data 2018/11/13 0013下午 10:47
 */
@MyController
@MyRequestMapping("/testController")
public class TestController {
    @MyRequestMapping("/test")
    public  String test(){
        System.out.println("執行test相關業務");
        return "index";
    }
}

springmvc.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <component-scan base-package="com.baidu"/>
    <!-- 配置檢視解析器 ,攔截器 -->
    <bean class="com.baidu.view.MyViewResolver">
        <property name="prefix" value="/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

啟動tomcat訪問:
http://localhost:8080/SpringMVCImitate-master/testController/test

xzhing
zhixng

原始碼下載:
https://download.csdn.net/download/weixin_42323802/10783231