自己開發一個簡單的MVC框架-SmartMVC

SmartMVC是什麼?

是一個用來簡化基於MVC架構的web應用程序開發框架(類似於SpringMVC)

其核心是一個通用的控制器(DispatcherServlet)

使用該框架,只需要寫視圖和模型。

該框架使前端控制器成爲可複用的代碼,主要完成轉發動作,處理器(其中含有少量代碼)封裝具體業務邏輯。DispatchServlet:核心前端控制器,處理任何 *.do的請求,前端控制器處理全部 Web 功能。

SmartMVC架構

在這裏插入圖片描述

在這裏插入圖片描述

創建一個maven工程(war包),項目關聯tomcat->Targeted Runtimes

框架的核心類全部在src/main/java下的base包中

在這裏插入圖片描述

框架具體代碼如下:

  • pom.xml中添加dom4j依賴(用來解析xml文件)

    <dependencies>
        <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
    
    </dependencies>
    
  • 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/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
      <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>base.web.DispatcherServlet</servlet-class>
        <!-- 指定配置文件的名稱及路徑 -->
        <init-param>
        	<param-name>configLocation</param-name>
        	<param-value>smartmvc.xml</param-value>
        </init-param>
        <!-- 配置啓動加載 
        	容器啓動之後,會立即將這個Servlet實例化和初始化
        -->
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
      </servlet-mapping>
    </web-app>框架的核心類如下
    
  • src/main/resources創建smartmvc.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    
    <beans>
    	<!-- 配置處理器 class屬性用於指定處理器類名 -->
    	<bean class="controller.HelloController" />
    	<bean class="controller.LoginController" />
    </beans>
    

該框架的所有代碼全部在src/main/java下的base包中

  • base包下新建annotation包,該包有一個類RequestMapping.java,自定義@RequestMapping註解

    package base.annotation;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    /**
     * 開發RequestMapping註解
     * @Retention 是一個元註解(即由系統提供,專門用來解釋其他的註解的註解)
     */
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RequestMapping {
    	/*
    	 * value是註解的屬性(不是方法),類型是String
    	 * 如果註解名爲value,且只有一個屬性,則該註解在使用時,不需要寫屬性名
    	 */
    	public String value();
    }
    
    
  • base包下新建common包,該包有兩個類Handler.javaHandlerMapping.java

    Handler.java

    package base.common;
    
    import java.lang.reflect.Method;
    
    /**
     * 爲了方便利用java反射機制去調用處理器的方法而設計的一個輔助類 
     * obj:處理器實例 
     * mh:處理器方法所對應的Method對象
     * (mh.invoke(obj))
     */
    public class Handler {
    	private Object obj;
    	private Method mh;
    
    	public Object getObj() {
    		return obj;
    	}
    
    	public void setObj(Object obj) {
    		this.obj = obj;
    	}
    
    	public Method getMh() {
    		return mh;
    	}
    
    	public void setMh(Method mh) {
    		this.mh = mh;
    	}
    
    }
    
    

    HandlerMapping.java

    package base.common;
    
    import java.lang.reflect.Method;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import base.annotation.RequestMapping;
    
    /**
     * 映射處理器類:
     * 負責提供請求路徑與處理器及方法的對應關係
     * (如:請求路徑爲"/hello.do",由HelloController的hello方法來處理)
     */
    public class HandlerMapping {
    	// handlerMap存放有請求路徑與處理器及方法的的對應關係(Handler是處理器及方法的封裝)
    	private Map<String,Handler> handlerMap = new HashMap<>();
    	
    	/**
    	 * 依據請求路徑返回對應的Handler對象
    	 * @param path 請求路徑
    	 * @return 對應的Handler對象
    	 */
    	public Handler getHandler(String path) {
    		return handlerMap.get(path);
    	}
    	
    	/**
    	 * 負責建立請求路徑與處理器及方法的的對應關係
    	 * @param beans 處理器實例組成的集合
    	 */
    	public void process(List beans) {
    		for (Object bean : beans) {
    			// 獲取加載處理器類名前的@RequestMapping()註解(註解是可選的,可能沒有,則得到的root爲null)
    			RequestMapping root = bean.getClass().getAnnotation(RequestMapping.class);
    			// 加在類名前的註解路徑
    			String rootPath = "";
    			if(root != null) {
    				// 獲取根註解(類名前)的請求路徑
    				rootPath = root.value();
    				System.out.println("rootPath:"+rootPath);
    			}
    			// 獲得處理器的所有方法
    			Method[] methods = bean.getClass().getDeclaredMethods();
    			// 遍歷這些方法
    			for(Method mh : methods) {
    				// 獲得加在方法前的@RequestMapping註解
    				RequestMapping rm = mh.getAnnotation(RequestMapping.class);
    				// 處理器方法有可能沒有添加@RequestMapping註解
    				if(rm != null) {
    					// 獲取請求路徑
    					String path = rm.value();
    					System.out.println("路徑:"+path);
    					// 將處理器實例及方法對象封裝成Handler對象
    					Handler handler = new Handler();
    					handler.setObj(bean);
    					handler.setMh(mh);
    					// 將請求路徑與處理器及方法的的對應關係存放入map
    					handlerMap.put(rootPath+path, handler);
    				}
    			}
    		}
    		System.out.println("handlerMap:"+handlerMap);
    	}
    }
    
    
  • base包下新建web包,該包中有一個類DispatherServlet.java,控制器類

    package base.web;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.dom4j.Document;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    
    import base.common.Handler;
    import base.common.HandlerMapping;
    
    /**
     * 
     */
    public class DispatcherServlet extends HttpServlet {
    	private static final long serialVersionUID = 1L;
    	
    	private HandlerMapping handlerMapping; // 請求路徑與處理器及方法的對應關係
    	
    
    	/**
    	 * 1.讀取smartmvc的配置文件,比如(smartmvc.xml),獲得處理器的類名 
    	 * 2.將處理器實例化
    	 * 3.將處理器實例交給HandlerMapping來處理
    	 */
    	@Override
    	public void init() throws ServletException {
    		try {
    			// 1.創建SAXReader
    			SAXReader reader = new SAXReader();
    			
    			/*
    			 *  讀取配置文件名及路徑(讀取初始化參數)
    			 *  getInitParameter方法來自於GenericServlet
    			 *  GenericServlet是HttpServlet的父類
    			 */
    			String configLocation = getInitParameter("configLocation");
    			
    			// 2.使用SAXReader讀取要解析的XML文檔
    			// 構造一個指向配置文件輸入流
    			InputStream in = getClass().getClassLoader().getResourceAsStream(configLocation);
    
    			Document doc = reader.read(in);
    			// 3.通過Document獲取根元素
    			Element root = doc.getRootElement();
    			// 4.按照XML文檔的結構逐級獲取子元素達到遍歷XML文檔的目的
    			List<Element> list = root.elements();
    			// 創建集合beans,用於存放處理器實例
    			List beans = new ArrayList<>();
    			for(Element ele : list) {
    				// 獲得處理器類名
    				String className = ele.attributeValue("class");
    				System.out.println("className:"+className);
    				// 將處理器實例化
    				Object obj = Class.forName(className).newInstance();
    				// 爲了方便管理,將其添加入集合beans中
    				beans.add(obj);
    				
    			}
    			System.out.println("beans:"+beans);
    			// 創建映射處理器實例
    			handlerMapping = new HandlerMapping();
    			// 將處理器實體交給映射處理器來處理
    			handlerMapping.process(beans);
    		} catch (Exception e) {
    			System.out.println("初始化失敗:" + e);
    		}
    
    	}
    
    	protected void service(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		// 設置字符集編碼
    		request.setCharacterEncoding("utf-8");
    		// 獲得請求資源路徑(如:/smartmvc/login.do)
    		String uri = request.getRequestURI();
    		System.out.println("uri:"+uri);
    		// 獲得應用名(如:/smartmvc)
    		String contextPath = request.getContextPath();
    		System.out.println("contextPath:"+contextPath);
    		// 將請求資源路徑中的應用名截取掉(如:/login.do)
    		String path = uri.substring(contextPath.length());
    		System.out.println("path:"+path);
    		// 依據請求路徑來獲得相應的Handler對象
    		Handler handler = handlerMapping.getHandler(path);
    		System.out.println("handler:"+handler);
    		if(handler == null) {
    			// 沒有對應的處理器
    			System.out.println("與請求路徑"+path+"沒有對應的處理器!");
    			response.sendError(404);
    			return;
    		}else {
    			// 有對應的處理器,獲得處理器實例 
    			Object obj = handler.getObj();
    			// 獲得處理器方法所對應的Method對象
    			Method mh = handler.getMh(); 
    			// 處理器方法的返回值
    			Object rv = null;
    			try {
    				// 調用處理器的方法
    				// 先活動處理器方法的參數類型信息(看有無參數)
    				Class[] types = mh.getParameterTypes();
    				if(types.length > 0) {
    					// 帶參數,params用於存放實際參數值
    					Object[] params = new Object[types.length];
    					// 根據參數類型進行相應的賦值
    					for(int i=0;i<types.length;i++) {
    						// 這裏我們設計暫時只支持request,response
    						if(types[i] == HttpServletRequest.class) {
    							params[i] = request;
    						}
    						if(types[i] == HttpServletResponse.class) {
    							params[i] = response;
    						}
    						// 目前SmartMVC只支持兩種類型,以後可以在此擴展
    					}
    					rv = mh.invoke(obj,params);
    				}else {
    					// 處理器的方法不帶參
    					// 調用處理器的方法
    					rv = mh.invoke(obj);
    				}
    				// 獲得視圖名
    				String viewName = rv.toString();
    				System.out.println("viewName:"+viewName);
    				/*
    				 *  處理視圖名.
    				 *  如果視圖名是以"redirect:"開頭,則重定向,
    				 *  否則 轉發到"/WEB-INF/"+視圖名+".jsp"
    				 */
    				if(viewName.startsWith("redirect:")) {
    					// 生成重定向地址(使用絕對路徑)
    					String redirectPath = contextPath + "/" 
    							+ viewName.substring("redirect:".length());
    					// 重定向
    					response.sendRedirect(redirectPath);
    				}else {
    					// 生成轉發的地址
    					String forwardPath = "/WEB-INF/" + viewName + ".jsp";
    					// 轉發
    					request.getRequestDispatcher(forwardPath).forward(request, response);
    				}
    				
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    

    至此,框架的核心代碼完成!


  • 測試使用。在src/main/java下新建controller包,包中有兩個類HelloController.javaLoginController

    package controller;
    
    import base.annotation.RequestMapping;
    
    /**
     * 這是一個處理器類,是負責業務邏輯的處理,也可以調用其他類一起處理業務邏輯
     */
    public class HelloController {
    	@RequestMapping("/hello.do")
    	public String hello() {
    		System.out.println("HelloController.hello()");
    		// 返回視圖名
    		return "hello";
    	}
    }
    
    
    package controller;
    
    import javax.servlet.http.HttpServletRequest;
    
    import base.annotation.RequestMapping;
    
    @RequestMapping("/demo")
    public class LoginController {
    	@RequestMapping("/toLogin.do")
    	public String toLogin() {
    		System.out.println("LoginController.toLogin()");
    		// 返回視圖名
    		return "login";
    	}
    	
    	@RequestMapping("/login.do")
    	public String login(HttpServletRequest request) {
    		System.out.println("LoginController.login()");
    		String username = request.getParameter("username");
    		String pwd = request.getParameter("pwd");
    		System.out.println("\t userinfo:"+username+","+pwd);
    		if("root".equals(username) && "1234".equals(pwd)) {
    			// 登錄成功
    			// 如果視圖名是以“redirect:”開頭,表示重定向
    			return "redirect:demo/toWelcome.do";
    		}else {
    			// 登錄失敗
    			request.setAttribute("login_failed", "用戶名或密碼錯誤");
    			return "login";
    		}
    	}
    	
    	@RequestMapping("/toWelcome.do")
    	public String toWelcome() {
    		System.out.println("LoginController.toWelcome()");
    		// 返回視圖名
    		return "welcome";
    	}
    }
    
    
  • src/main/webapp/WEB-INF下創建對應的jsp文件

    hello.jsp

    <%@ page contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <html>
    <head>
    	<title></title>
    </head>
    <body style="font-size:30px">
    	Hello SmartMVC!
    </body>
    </html>
    

    login.jsp

    <%@ page contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <html>
    <head>
    	<title></title>
    </head>
    <body style="font-size:30px">
    	<form action="login.do" method="post">
    		<fieldset>
    			<legend>登錄</legend>
    			用戶名:<input name="username">
    			${login_failed}
    			<br>
    			密碼:<input name="pwd" type="password"><br>
    			<input type="submit" value="登錄">
    		</fieldset>
    	</form>
    </body>
    </html>
    

    welcome.jsp

    <%@ page contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <html>
    <head>
    	<title></title>
    </head>
    <body style="font-size:30px">
    	<h1>登錄成功</h1>
    </body>
    </html>
    



如何使用SmartMVC?

新建一個Maven工程(war包),項目關聯tomcat->Targeted Runtimes

  • 導包(在pom.xml中導入dom4j)

    <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    
  • 將SmartMVC項目核心類拷貝到新的工程裏面

在這裏插入圖片描述

  • web.xml文件中配置DispatherServlet

    示例:以下配置文件是smartmvc.xml,只能接收以*.do結尾的請求

    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>base.web.DispatcherServlet</servlet-class>
        <!-- 指定配置文件的名稱及路徑 -->
        <init-param>
            <param-name>configLocation</param-name>
            <param-value>smartmvc.xml</param-value>
        </init-param>
        <!-- 配置啓動加載 容器啓動之後,會立即將這個Servlet實例化和初始化 -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    
    
  • src/main/resources下創建配置文件smartmvc.xml(跟上面指定配置文件的名稱及路徑一致)(文件名及路徑可以自定義)

    <?xml version="1.0" encoding="UTF-8"?>
    
    <beans>
    	<!-- 配置處理器 class屬性用於指定處理器類名 -->
        <!-- 示例如下
    	<bean class="controller.HelloController" />
    	-->
    </beans>
    
  • 添加jsp文件(放到/WEB-INF/下)

    示例:創建hello.jsp文件

    <%@ page contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <html>
    <head>
    	<title></title>
    </head>
    <body style="font-size:30px">
    	<h1>hello smartmvc!</h1>
    </body>
    </html>
    
  • 添加處理器

    注:

    ​ 1. 處理器類名前面或者方法前面添加@RequestMapping註解

    ​ 2. 方法要求返回一個視圖名,如果視圖名以“redirect:”開頭,則表示重定向

    示例:在src/main/java下創建一個包controller,包下創建一個類HelloController

    package controller;
    
    import base.annotation.RequestMapping;
    
    @RequestMapping("/test")
    public class HelloController {
    	@RequestMapping("/hello.do")
    	public String hello() {
    		return "hello";
    	}
    }
    
  • 在SmartMVC的配置文件smartmvc.xml中指定處理器的類名

    <!-- 配置處理器 class屬性用於指定處理器類名 -->
    <bean class="controller.HelloController" />
    
  • 測試

    開啓服務:訪問http://localhost:8080/smartmvc-exec/test/hello.do,可以看到hello smartmvc!

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