1. 程式人生 > >自己寫個Spring MVC

自己寫個Spring MVC

Spring mvc流程圖:

請求流程:

⑴ 使用者傳送請求至前端控制器DispatcherServlet

⑵ DispatcherServlet收到請求呼叫HandlerMapping處理器對映器。

⑶ 處理器對映器根據請求url找到具體的處理器,生成處理器物件及處理器攔截器(如果有則生成)一併返回給DispatcherServlet。

⑷ DispatcherServlet通過HandlerAdapter處理器介面卡呼叫處理器

⑸ 執行處理器(Controller,也叫後端控制器)。

⑹ Controller執行完成返回ModelAndView

⑺ HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet

⑻ DispatcherServlet將ModelAndView傳給ViewReslover檢視解析器

⑼ ViewReslover解析後返回具體View

⑽ DispatcherServlet對View進行渲染檢視(即將模型資料填充至檢視中)。

⑾ DispatcherServlet響應使用者。從上面可以看出,DispatcherServlet有接收請求,響應結果,轉發等作用。有了DispatcherServlet之後,可以減少元件之間的耦合度。

DispatcherServlet類結構圖:

主要元件:

protected void initStrategies(ApplicationContext context) {  
//用於處理上傳請求。處理方法是將普通的request包裝成            MultipartHttpServletRequest,後者可以直接呼叫getFile方法獲取File.
 initMultipartResolver(context);  
//SpringMVC主要有兩個地方用到了Locale:一是ViewResolver檢視解析的時候;二是用到國際化資源或者主題的時候。
 initLocaleResolver(context); 
//用於解析主題。SpringMVC中一個主題對應 一個properties檔案,裡面存放著跟當前主題相關的所有資源、//如圖片、css樣式等。SpringMVC的主題也支援國際化, 
 initThemeResolver(context);  
//用來查詢Handler的。
 initHandlerMappings(context);  
//從名字上看,它就是一個介面卡。Servlet需要的處理方法的結構卻是固定的,都是以request和response為引數的方法。//如何讓固定的Servlet處理方法呼叫靈活的Handler來進行處理呢?這就是HandlerAdapter要做的事情
 initHandlerAdapters(context);  
//其它元件都是用來幹活的。在幹活的過程中難免會出現問題,出問題後怎麼辦呢?//這就需要有一個專門的角色對異常情況進行處理,在SpringMVC中就是HandlerExceptionResolver。
 initHandlerExceptionResolvers(context);  
//有的Handler處理完後並沒有設定View也沒有設定ViewName,這時就需要從request獲取ViewName了,//如何從request中獲取ViewName就是RequestToViewNameTranslator要做的事情了。
 initRequestToViewNameTranslator(context);
//ViewResolver用來將String型別的檢視名和Locale解析為View型別的檢視。//View是用來渲染頁面的,也就是將程式返回的引數填入模板裡,生成html(也可能是其它型別)檔案。
 initViewResolvers(context);  
//用來管理FlashMap的,FlashMap主要用在redirect重定向中傳遞引數。
 initFlashMapManager(context); 
}

接下來實現mvc

1.新建一個web專案,web.xml裡宣告自定義的DispatcherServlet以及配置檔案

<?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/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    <servlet>
        <servlet-name>MySpringMVC</servlet-name>
        <servlet-class>com.uiao.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>

application.properties裡只配置了掃描路徑

scanPackage=com.uiao.core

2.先來自定義幾個註解模仿Comtroller,RequestMapping,RequestParam註解

/**
 * 控制層註解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
    String value() default "";
}

/**
 * 方法路徑註解
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
    String value() default "";
}

/**
 * 引數註解
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
    String value();
}

3.自定義DispatcherServlet類

/**
 * 自定義DispatcherServlet
 */
public class MyDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();

    private List<String> classNames = new ArrayList<>();

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

    private Map<String, Method> handlerMapping = new HashMap<>();

    private Map<String, Object> controllerMapping = new HashMap<>();

    @Override
    public void init(ServletConfig config) {
        //載入配置檔案進properties
        doloadConfig(config.getInitParameter("contextConfigLocation"));

        //掃描使用者包下面所有的類放到classNames裡
        doScanner(properties.getProperty("scanPackage"));

        //將掃描到的類通過反射例項化,並存放進IOC容器內
        doInstance();

        //將url和對映存進handlerMapping和controllerMapping
        initHandlerMapping();

    }

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

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            resp.getWriter().write("500 Server Exception");
        }
    }

    private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (handlerMapping.isEmpty()) {
            return;
        }
        String url = request.getRequestURI();
        String contextPath = request.getContextPath();

        url = url.replace(contextPath, "").replaceAll("/+", "/");

        if (!this.handlerMapping.containsKey(url)) {
            response.getWriter().write("404 not found");
        }

        Method method = this.handlerMapping.get(url);

        //獲取方法的引數列表
        Class<?>[] parameterTypes = method.getParameterTypes();

        //獲取請求的引數列表(只有這一個)
        Map<String, String[]> parameterMap = request.getParameterMap();

        //儲存引數值
        Object[] paramValues = new Object[parameterTypes.length];

        for (int i = 0; i < parameterTypes.length; i++) {
            String requestParam = parameterTypes[i].getSimpleName();

            if (requestParam.equals("HttpServletRequest")) {
                paramValues[i] = request;
                continue;
            }

            if (requestParam.equals("HttpServletResponse")) {
                paramValues[i] = response;
                continue;
            }

            if (requestParam.equals("String")) {
                for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
                    String value = Arrays.toString(param.getValue()).replaceAll("[|]", "").replaceAll(",", ",");
                    paramValues[i] = value;
                }
            }
        }

        try {
            method.invoke(this.controllerMapping.get(url), paramValues);//第一個引數為method所對應的例項,在ioc容器中
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private void doloadConfig(String location) {
        InputStream inputStream = this.getClass().getResourceAsStream(location);
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doScanner(String packageName) {
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll(".", "/"));
        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                doScanner(packageName + "." + file.getName());
            } else {
                String className = packageName + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }
    }

    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }
        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(MyController.class)) {
                    ioc.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
                } else {
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }
        }
    }


    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        try {
            for (Map.Entry<String, Object> entry : ioc.entrySet()) {
                Class<? extends Object> clazz = entry.getValue().getClass();
                if (clazz.isAnnotationPresent(MyController.class)) {
                    continue;
                }

                String baseUrl = "";
                if (clazz.isAnnotationPresent(MyRequestMapping.class))//用在類上或直接用在方法上
                {
                    MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
                    baseUrl = annotation.value();
                }
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (!method.isAnnotationPresent(MyRequestMapping.class)) {//用在方法上
                        continue;
                    }
                    MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
                    String url = annotation.value();
                    url = (baseUrl + "/" + url).replace("/+", "/");
                    handlerMapping.put(url, method);
                    controllerMapping.put(url, clazz.newInstance());
                    System.out.println(url + "," + method);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 把字串的首字母小寫
     *
     * @param name
     * @return
     */
    private String toLowerFirstWord(String name) {
        char[] charArray = name.toCharArray();
        charArray[0] += 32;
        return String.valueOf(charArray);
    }
}

4.寫個請求測試

@MyController
@MyRequestMapping("/test")
public class TestController {

    @MyRequestMapping("/testa")
    public void testA(HttpServletRequest request, HttpServletResponse response, @MyRequestParam("name") String name) {
        System.out.println(name);
        try {
            response.getWriter().write("dotest methoda success ! param:" + name);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @MyRequestMapping("/testb")
    public void testB(HttpServletRequest request, HttpServletResponse response) {
        try {
            response.getWriter().write("dotestb method success ! ");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}