1. 程式人生 > >spring之mvc原理分析及簡單模擬實現

spring之mvc原理分析及簡單模擬實現

subst request 配置文件 location dap tro build classes getname

  在之前的一篇博客中已經簡單的實現了spring的IOC和DI功能,本文將在之前的基礎上實現mvc功能。

一 什麽是MVC

  MVC簡單的說就是一種軟件實現的設計模式,將整個系統進行分層,M(model 數據模型,業務邏輯層) 、V(view 視圖層)、C(controller 控制器調度),實現應用程序的分層開發。實現原理如下圖:

技術分享圖片

主要執行步驟:

  1 用戶在發起request請求給前端控制器;

  2 控制器接收到請求後,經過一系統的過濾器,找到對應的請求處理映射;

  3 根據請求映射獲得請求處理適配器;

  4 適配器將對請求進行處理並將處理結果(ModelAndView)返回給前端控制器;

  5 前端處理器將處理結果交給視圖解析器解析;

  6 視圖解析器將解析的結果返回給控制器;

  7 控制器將結果返回給用戶。

二 簡單模擬實現

  創建一個核心控制器(DispatcherServlet)繼承HttpServlet,配置在web.xml中,並指定要初始化的參數

 1 <!-- 核心servlet -->
 2     <servlet>
 3         <servlet-name>dispatcherServlet</servlet-name>
 4         <servlet-class>org.wl.test.spring.mvc.DispatcherServlet</servlet-class
> 5 <!-- 初始化參數 --> 6 <init-param> 7 <param-name>contextConfigLocation</param-name> 8 <param-value>classpath:application.properties</param-value> 9 </init-param> 10 <!-- 啟動時加載 --> 11 <load-on-startup>0</load-on-startup> 12
</servlet> 13 <servlet-mapping> 14 <servlet-name>dispatcherServlet</servlet-name> 15 <url-pattern>/</url-pattern> 16 </servlet-mapping>

  核心控制器,在web容器啟動時執行init方法進行文件的初始化

  1 public class DispatcherServlet extends HttpServlet {
  2 
  3     private List<HandlerMapping> handlerMappingList = new ArrayList<HandlerMapping>();
  4 
  5     private Map<HandlerMapping, HandlerAdapter> adapterMap = new HashMap<>();
  6 
  7     @Override
  8     public void init(ServletConfig config) throws ServletException {
  9         // web.xml 配置核心servlet 獲取配置的信息
 10         String configFile = config.getInitParameter("contextConfigLocation");
 11         //定義一個當前上下文對象,實現基礎包的掃描、IOC、DI
 12         ApplicationContext context = new ApplicationContext(configFile.replace("classpath:", ""));
 13         //獲取掃描到的有Controller註解的類
 14         List<Object> controllerList = context.getControllerList();
 15         //初始化HandlerMapping
 16         initHandlerMapping(controllerList);
 17         //初始化HandlerAdapter
 18         initHandlerAdapter();
 19     }
 20 
 21 
 22     private void initHandlerAdapter() {
 23         if (handlerMappingList.size() == 0) {
 24             return;
 25         }
 26 
 27         handlerMappingList.forEach(handlerMapping -> {
 28             Method method = handlerMapping.getMethod();
 29             //方法的參數  <參數索引,參數名字>
 30             Map<Integer, String> paramMap = new HashMap<>();
 31 
 32             //使用了註解參數
 33             Annotation[][] annos = method.getParameterAnnotations();
 34             if(annos.length > 0){
 35                 for(int i=0; i<annos.length; i++){
 36                     for(Annotation anno : annos[i]){
 37                         if(anno instanceof RequestParam){
 38                             RequestParam requestParam = (RequestParam) anno;
 39                             String paramName = requestParam.value();
 40 
 41                             paramMap.put(i, paramName);
 42                         }
 43                     }
 44                 }
 45             }
 46             //直接用的servlet參數,如HttpServletRequest
 47             Class<?>[] paramTypes = method.getParameterTypes();
 48             if(paramTypes.length > 0){
 49                 for(int i=0; i<paramTypes.length; i++){
 50                     Class<?> typeClass = paramTypes[i];
 51                     if (typeClass == HttpServletRequest.class || typeClass == HttpServletResponse.class) {
 52                         String paramName = typeClass.getName();
 53 
 54                         paramMap.put(i, paramName);
 55                     }
 56                 }
 57             }
 58 
 59             HandlerAdapter handlerAdapter = new HandlerAdapter(paramMap);
 60             adapterMap.put(handlerMapping, handlerAdapter);
 61         });
 62     }
 63 
 64     /**
 65      * 完成請求方法與請求處理實例的映射關系
 66      * @param controllerList
 67      */
 68     private void initHandlerMapping(List<Object> controllerList) {
 69         if(controllerList.size() == 0){
 70             return;
 71         }
 72 
 73         controllerList.forEach(controllerObj -> {
 74             //類上的請求路徑
 75             String classRequestUrl = "";
 76             if (controllerObj.getClass().isAnnotationPresent(RequestMapping.class)) {
 77                 RequestMapping classRequestMapping = controllerObj.getClass().getAnnotation(RequestMapping.class);
 78                 if(classRequestMapping != null){
 79                     classRequestUrl += urlHandler(classRequestMapping.value());
 80                 }
 81             }
 82             //方法上的請求路徑
 83             Method[] methods = controllerObj.getClass().getDeclaredMethods();
 84             if(methods.length > 0){
 85                 for(int i=0; i<methods.length; i++){
 86                     String methodRequestUrl = "";
 87                     Method method = methods[i];
 88                     //必須是public修飾的方法
 89                     if(method.getModifiers() == Modifier.PUBLIC){
 90                         if (method.isAnnotationPresent(RequestMapping.class)) {
 91                             RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
 92                             if(methodRequestMapping != null){
 93                                 methodRequestUrl += urlHandler(methodRequestMapping.value());
 94                             }
 95 
 96                             String requestUrl = classRequestUrl + methodRequestUrl;
 97 
 98                             HandlerMapping handlerMapping = new HandlerMapping();
 99                             handlerMapping.setMethod(method);
100                             handlerMapping.setUrl(requestUrl);
101 
102                             handlerMapping.setControllerInstance(controllerObj);
103                             handlerMappingList.add(handlerMapping);
104                         }
105                     }
106 
107                 }
108             }
109 
110         });
111 
112     }
113 
114     /**
115      * url處理
116      * @param url
117      * @return
118      */
119     public String urlHandler( String url){
120         if(!url.startsWith("/")){
121             url = "/" + url;
122         }
123         if(url.endsWith("/")){
124             url = url.substring(0, url.length() - 1);
125         }
126         return url;
127     }
128 
129     @Override
130     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
131         this.doPost(req, resp);
132     }
133 
134     @Override
135     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
136         doDispatcher(req, resp);
137     }
138 
139     /**
140      * 請求處理
141      * @param req
142      * @param resp
143      */
144     private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
145         req.setCharacterEncoding("utf-8");
146         resp.setContentType("text/html;charset=UTF-8");
147 
148         String contextUrl = req.getContextPath();
149         String requestUrl = req.getRequestURI();
150 
151         String url = requestUrl.replace(contextUrl, "");
152         HandlerMapping handlerMapping = null;
153         for(int i=0; i<handlerMappingList.size(); i++){
154             if(url.equals(handlerMappingList.get(i).getUrl())){
155                 handlerMapping = handlerMappingList.get(i);
156                 break;
157             }
158         }
159         if(handlerMapping == null){
160             resp.getWriter().write("404, 未知的請求!");
161         }else{
162             HandlerAdapter adapter = adapterMap.get(handlerMapping);
163             try {
164                 Object result = adapter.handler(req, resp, handlerMapping);
165 
166                 viewResolve(req, resp, result);
167             } catch (Exception e) {
168                 e.printStackTrace();
169                 resp.getWriter().write("500, 服務器發生異常!");
170             }
171         }
172 
173     }
174 
175     /**
176      * 視圖解析 返回
177      * @param result
178      */
179     private void viewResolve(HttpServletRequest request, HttpServletResponse response, Object result) throws Exception{
180         if (result.getClass() == ModelAndView.class) {
181             ModelAndView mv = (ModelAndView) result;
182             String view = mv.getViewName();
183             Map<String, Object> dataMap = mv.getData();
184             if(dataMap.size() > 0){
185                 for(String key : dataMap.keySet()){
186                     request.setAttribute(key, dataMap.get(key));
187                 }
188             }
189             request.getRequestDispatcher(view).forward(request, response);
190         }
191     }
192 
193 }

  ApplicationContext的具體實現如下,主要實現對執行資源文件的掃描,並完成IOC和DI

public class ApplicationContext {

    /**
     * 配置文件
     */
    private static String PROPERTIES_FILE = "";

    /**
     * 初始化一個集合,存放掃描到的class對象
     */
    private List<Class<?>> classList = Collections.synchronizedList(new ArrayList<>());

    /**
     * 初始化map 存放別名與對象實例
     */
    private Map<String, Object> aliasInstanceMap = new HashMap<>();

    public ApplicationContext(String fileName) {
        PROPERTIES_FILE = fileName;
        try {
            String basePackage = getBasePackage(PROPERTIES_FILE);

            buildAliasInstanceMap(basePackage);

            doAutowired();

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

    /**
     * 完成別名與實例的映射
     */
    public void buildAliasInstanceMap(String basePackage) throws Exception {

        scanClasses(basePackage);

        if(classList.size() == 0){return;}

        for(Class<?> clazz : classList){
            if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)
                || clazz.isAnnotationPresent(Autowired.class)) {
                String alias = getAlias(clazz);
                Object obj = aliasInstanceMap.get(alias);

                //如果別名實例映射關系已經存在,則給出提示
                if(obj != null){
                    throw new Exception("alias is exist!");
                }else{
                    aliasInstanceMap.put(alias, clazz.newInstance());
                }
            }
        }

        System.out.println(aliasInstanceMap);
    }

    /**
     * 屬性對象的註入
     */
    public void doAutowired(){
        if (aliasInstanceMap.size() == 0) {
            return;
        }

        aliasInstanceMap.forEach((k, v)->{

            Field[] fields = v.getClass().getDeclaredFields();

            for(Field field : fields){
                if (field.isAnnotationPresent(Autowired.class)) {
                    String alias = "";

                    Autowired autowired = field.getAnnotation(Autowired.class);
                    if(autowired != null){
                        //註入的對象是接口時,由於不知道接口有幾個實現類,所以就必須在Autowired或者Qualifier上指定要註解的具體的實現類
                        if(!"".equals(autowired.value())){
                            alias = autowired.value();
                        }else{
                            Qualifier qualifier = field.getAnnotation(Qualifier.class);
                            if(qualifier != null){
                                alias = qualifier.value();
                            }
                        }
                    }

                    if ("".equals(alias)) {
                        alias = getAlias(field.getType());
                    }

                    Object instance = null;
                    if(!"".equals(alias)){
                        instance = aliasInstanceMap.get(alias);
                    }

                    field.setAccessible(true);

                    try {
                        field.set(v, instance);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }

            }
        });

    }

    /**
     * 獲取對象的別名,如果註解中配置了別名,別使用配置的別名,否則默認使用類名首字母小寫
     * @param clazz
     * @return
     */
    public String getAlias(Class<?> clazz){
        String alias = "";
        Controller controller = clazz.getAnnotation(Controller.class);
        if(controller != null){
            alias = controller.value();
        }
        Service service = clazz.getAnnotation(Service.class);
        if (service != null) {
            alias = service.value();
        }
        Autowired autowired = clazz.getAnnotation(Autowired.class);
        if(autowired != null){
            alias = autowired.value();
        }

        //註解中沒有配置別名
        if("".equals(alias)){
            String simpleName = clazz.getSimpleName();
            alias = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
        }
        return alias;
    }

    /**
     * 跟據基礎包名讀取包及子包中的類對象
     * @param basePackage
     */
    public void scanClasses(String basePackage){
        if(basePackage == null || "".equals(basePackage)){return;}

        doScan(basePackage);
        System.out.println(classList);
    }

    private void doScan(String basePackage) {
        String path = basePackage.replaceAll("\\.","/");
        URL url = this.getClass().getClassLoader().getResource(path);
        File file = new File(url.getFile());
        file.listFiles(new FileFilter() {
            @Override
            public boolean accept(File childFile) {
                String fileName = childFile.getName();
                if(childFile.isDirectory()){
                    //當前文件是目錄,遞歸 掃描下級子目錄下的class文件
                    doScan(basePackage + "." + fileName);
                }else{
                    if(fileName.endsWith(".class")){
                        String className = basePackage + "." + fileName.replace(".class", "");
                        try {
                            Class<?> clazz = this.getClass().getClassLoader().loadClass(className);
                            classList.add(clazz);
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
                return false;
            }
        });
    }

    /**
     * 從配置的屬性文件中讀取要掃描的包
     * @return
     */
    public String getBasePackage(String fileName) throws IOException {
        String basePackage;
        Properties prop = new Properties();
        InputStream in = this.getClass().getClassLoader().getResourceAsStream(fileName);
        prop.load(in);
        basePackage = prop.getProperty("basePackage");
        return basePackage;
    }

    /**
     * 根據beanName 獲取
     * @param beanName
     * @return
     */
    public Object getBean(String beanName){
        return aliasInstanceMap.get(beanName);
    }

    /**
     * 獲取所有標註了controller的註解
     * @return
     */
    public List<Object> getControllerList(){
        List<Object> controllerList = new ArrayList<>();
        if(aliasInstanceMap.size() > 0) {
            aliasInstanceMap.values().forEach(obj -> {
                if(obj.getClass().isAnnotationPresent(Controller.class)){
                    controllerList.add(obj);
                }
            });
        }
        return controllerList;
    }

    public static void main(String[] args) throws Exception {
        String fileName = "application.properties";
        ApplicationContext context = new ApplicationContext(fileName);
        String basePackage = context.getBasePackage(PROPERTIES_FILE);

        context.buildAliasInstanceMap(basePackage);

        context.doAutowired();
        //測試
        UserController controller = (UserController) context.getBean("userController");
        controller.save();
    }


}

  請求映射HandlerMapping,用來存放請求url和要執行的方法和方法所在對象實例,構建請求與請求處理的映射關系

1 public class HandlerMapping {
2 
3     private String url;
4     private Method method;
5     private Object controllerInstance;
6 
7     //此處省去getter和setter
8 }

  處理適配器HandlerAdapter,每一個請求映射都有一個請求處理適配器來完成請求的處理(handler)

 1 public class HandlerAdapter {
 2 
 3     private Map<Integer, String> paramMap;
 4 
 5     public HandlerAdapter(Map<Integer, String> paramMap){
 6         this.paramMap = paramMap;
 7     }
 8 
 9     public Object handler(HttpServletRequest request, HttpServletResponse response, HandlerMapping handlerMapping) throws Exception {
10         Method method = handlerMapping.getMethod();
11         Object classInstance = handlerMapping.getControllerInstance();
12 
13         int paramNum = method.getParameterCount();
14         Object[] paramObj = new Object[paramNum];
15         for(int i=0; i<paramNum; i++){
16             String paramName = paramMap.get(i);
17             if(paramName.equals(HttpServletRequest.class.getName())){
18                 paramObj[i] = request;
19             }else if(paramName.equals(HttpServletResponse.class.getName())){
20                 paramObj[i] = response;
21             } else {
22                 paramObj[i] = request.getParameter(paramName);
23             }
24         }
25         Object result = method.invoke(classInstance, paramObj);
26         return result;
27     }
28 
29 
30     public Map<Integer, String> getParamMap() {
31         return paramMap;
32     }
33 
34     public void setParamMap(Map<Integer, String> paramMap) {
35         this.paramMap = paramMap;
36     }
37 }

  處理結果ModelAndView,用來存放當前請求要返回的視圖和數據

 1 public class ModelAndView {
 2 
 3     private String viewName;
 4     private Map<String, Object> data = new HashMap<>();
 5 
 6     public ModelAndView(String viewName) {
 7         this.viewName = viewName;
 8     }
 9 
10     public void addAttribute(String name, Object value){
11         data.put(name, value);
12     }
13 
14     //此處省略getter和setter
15 }

  視圖展示返回的結果

<html>
<head>
    <title>Title</title>
</head>
<body>
<h3>hello mvc...</h3>
<hr/>
用 戶 信 息:<%=request.getAttribute("user") %>
</body>
</html>

  瀏覽器端顯示信息

技術分享圖片

以上是模擬實現springmvc的主要代碼,實現的源代碼我已經上傳在github,感興趣的朋友可以訪問

https://github.com/wlzq/spring

spring之mvc原理分析及簡單模擬實現