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.java
,HandlerMapping.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.java
,LoginController
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!