手寫Spring框架之MVC

簡介
上一篇博客實現(xiàn)了Bean容器和IOC功能, 本篇博客來實現(xiàn)簡化版的 Spring MVC. 在看下面的內(nèi)容之前, 我們首先來回顧下 Spring MVC的架構(gòu)圖:

Spring MVC 最核心部分的就是前端控制器DispatcherServlet, 而DispatcherServlet其實就是一個Servlet, 所以我們有必要先了解下Servlet的知識點, 如下:
映射處理器
(1) Request類
請求類中的方法和路徑對應(yīng) @RequestMapping 注解里的方法和路徑.
public?class?Request {
????/**
?????* 請求方法
?????*/
????private?String?requestMethod;
????/**
?????* 請求路徑
?????*/
????private?String?requestPath;
????public?Request(String?requestMethod, String?requestPath) {
????????this.requestMethod = requestMethod;
????????this.requestPath = requestPath;
????}
????public?String?getRequestMethod() {
????????return?requestMethod;
????}
????public?String?getRequestPath() {
????????return?requestPath;
????}
????@Override
????public?int hashCode() {
????????int result = 17;
????????result = 31?* result + requestMethod.hashCode();
????????result = 31?* result + requestPath.hashCode();
????????return?result;
????}
????@Override
????public?boolean?equals(Object?obj) {
????????if?(this?== obj) return?true;
????????if?(!(obj instanceof?Request)) return?false;
????????Request request = (Request) obj;
????????return?request.getRequestPath().equals(this.requestPath) && request.getRequestMethod().equals(this.requestMethod);
????}
}(2) Handler類
Handler類為一個處理器, 封裝了Controller的Class對象和Method方法.
public?class?Handler?{
????/**
?????* Controller 類
?????*/
????private?Class> controllerClass;
????/**
?????* Controller 方法
?????*/
????private?Method controllerMethod;
????public?Handler(Class> controllerClass, Method controllerMethod)?{
????????this.controllerClass = controllerClass;
????????this.controllerMethod = controllerMethod;
????}
????public?Class> getControllerClass() {
????????return?controllerClass;
????}
????public?Method getControllerMethod()?{
????????return?controllerMethod;
????}
}(3) 實現(xiàn)映射處理器
ControllerHelper 助手類定義了一個"請求-處理器" 的映射 REQUEST_MAP, REQUEST_MAP 就相當(dāng)于Spring MVC里的映射處理器, 接收到請求后返回對應(yīng)的處理器.
REQUEST_MAP 映射處理器的實現(xiàn)邏輯如下:
首先通過 ClassHelper 工具類獲取到應(yīng)用中所有Controller的Class對象, 然后遍歷Controller及其所有方法, 將所有帶 @RequestMapping 注解的方法封裝為處理器, 將 @RequestMapping 注解里的請求路徑和請求方法封裝成請求對象, 然后存入 REQUEST_MAP 中.
public?final class?ControllerHelper?{
????/**
?????* REQUEST_MAP為 "請求-處理器" 的映射
?????*/
????private?static?final Map REQUEST_MAP = new?HashMap();
????static?{
????????//遍歷所有Controller類
????????Set> controllerClassSet = ClassHelper.getControllerClassSet();
????????if?(CollectionUtils.isNotEmpty(controllerClassSet)) {
????????????for?(Class> controllerClass : controllerClassSet) {
????????????????//暴力反射獲取所有方法
????????????????Method[] methods = controllerClass.getDeclaredMethods();
????????????????//遍歷方法
????????????????if?(ArrayUtils.isNotEmpty(methods)) {
????????????????????for?(Method method : methods) {
????????????????????????//判斷是否帶RequestMapping注解
????????????????????????if?(method.isAnnotationPresent(RequestMapping.class)) {
????????????????????????????RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
????????????????????????????//請求路徑
????????????????????????????String requestPath = requestMapping.value();
????????????????????????????//請求方法
????????????????????????????String requestMethod = requestMapping.method().name();
????????????????????????????//封裝請求和處理器
????????????????????????????Request request = new?Request(requestMethod, requestPath);
????????????????????????????Handler handler = new?Handler(controllerClass, method);
????????????????????????????REQUEST_MAP.put(request, handler);
????????????????????????}
????????????????????}
????????????????}
????????????}
????????}
????}
????/**
?????* 獲取 Handler
?????*/
????public?static?Handler getHandler(String requestMethod, String requestPath) {
????????Request request = new?Request(requestMethod, requestPath);
????????return?REQUEST_MAP.get(request);
????}
} 前端控制器
(1) Param類
Param類用于封裝Controller方法的參數(shù).
public?class?Param {
????private?Map<String, Object> paramMap;
????public?Param() {
????}
????public?Param(Map<String, Object> paramMap) {
????????this.paramMap = paramMap;
????}
????public?Map<String, Object> getParamMap() {
????????return?paramMap;
????}
????public?boolean?isEmpty(){
????????return?MapUtils.isEmpty(paramMap);
????}
}(2) Data類
Data類用于封裝Controller方法的JSON返回結(jié)果.
public?class?Data?{
????/**
?????* 模型數(shù)據(jù)
?????*/
????private?Object model;
????public?Data(Object model)?{
????????this.model = model;
????}
????public?Object getModel()?{
????????return?model;
????}
}(3) View類
Data類用于封裝Controller方法的視圖返回結(jié)果.
public?class?View {
????/**
?????* 視圖路徑
?????*/
????private?String?path;
????/**
?????* 模型數(shù)據(jù)
?????*/
????private?Map<String, Object> model;
????public?View(String?path) {
????????this.path = path;
????????model = new?HashMap<String, Object>();
????}
????public?View addModel(String?key, Object?value) {
????????model.put(key, value);
????????return?this;
????}
????public?String?getPath() {
????????return?path;
????}
????public?Map<String, Object> getModel() {
????????return?model;
????}
}(4) RequestHelper 助手類
前端控制器接收到HTTP請求后, 從HTTP中獲取請求參數(shù), 然后封裝到Param對象中.
public?final class?RequestHelper {
????/**
?????* 獲取請求參數(shù)
?????*/
????public?static?Param createParam(HttpServletRequest request) throws IOException {
????????Map<String, Object> paramMap = new?HashMap<>();
????????Enumeration<String> paramNames = request.getParameterNames();
????????//沒有參數(shù)
????????if?(!paramNames.hasMoreElements()) {
????????????return?null;
????????}
????????//get和post參數(shù)都能獲取到
????????while?(paramNames.hasMoreElements()) {
????????????String?fieldName = paramNames.nextElement();
????????????String?fieldValue = request.getParameter(fieldName);
????????????paramMap.put(fieldName, fieldValue);
????????}
????????return?new?Param(paramMap);
????}
}(5) HelperLoader 類
到目前為止, 我們創(chuàng)建了ClassHelper, BeanHelper, IocHelper, ControllerHelper這四個Helper類, 我們需要一個入口程序來加載他們(實際上是加載靜態(tài)代碼塊), 當(dāng)然就算沒有這個入口程序, 這些類也會被加載, 我們這里只是為了讓加載更加集中.
public?final?class?HelperLoader?{
????public?static?void?init()?{
????????Class>[] classList = {
????????????ClassHelper.class,
????????????BeanHelper.class,
????????????IocHelper.class,
????????????ControllerHelper.class
????????};
????????for?(Class> cls : classList) {
????????????ClassUtil.loadClass(cls.getName());
????????}
????}
}(6) 實現(xiàn)前端控制器
前端控制器實際上是一個Servlet, 這里配置的是攔截所有請求, 在服務(wù)器啟動時實例化.
當(dāng)DispatcherServlet實例化時, 首先執(zhí)行 init() 方法, 這時會調(diào)用 HelperLoader.init() 方法來加載相關(guān)的helper類, 并注冊處理相應(yīng)資源的Servlet.
對于每一次客戶端請求都會執(zhí)行 service() 方法, 這時會首先將請求方法和請求路徑封裝為Request對象, 然后從映射處理器 (REQUEST_MAP) 中獲取到處理器. 然后從客戶端請求中獲取到Param參數(shù)對象, 執(zhí)行處理器方法. 最后判斷處理器方法的返回值, 若為view類型, 則跳轉(zhuǎn)到j(luò)sp頁面, 若為data類型, 則返回json數(shù)據(jù).
@WebServlet(urlPatterns = "/*", loadOnStartup = 0)
public?class?DispatcherServlet extends?HttpServlet {
????@Override
????public?void?init(ServletConfig servletConfig) throws ServletException {
????????//初始化相關(guān)的helper類
????????HelperLoader.init();
????????//獲取ServletContext對象, 用于注冊Servlet
????????ServletContext servletContext = servletConfig.getServletContext();
????????//注冊處理jsp和靜態(tài)資源的servlet
????????registerServlet(servletContext);
????}
????/**
?????* DefaultServlet和JspServlet都是由Web容器創(chuàng)建
?????* org.apache.catalina.servlets.DefaultServlet
?????* org.apache.jasper.servlet.JspServlet
?????*/
????private?void?registerServlet(ServletContext servletContext) {
????????//動態(tài)注冊處理JSP的Servlet
????????ServletRegistration jspServlet = servletContext.getServletRegistration("jsp");
????????jspServlet.addMapping(ConfigHelper.getAppJspPath() + "*");
????????//動態(tài)注冊處理靜態(tài)資源的默認Servlet
????????ServletRegistration defaultServlet = servletContext.getServletRegistration("default");
????????defaultServlet.addMapping("/favicon.ico"); //網(wǎng)站頭像
????????defaultServlet.addMapping(ConfigHelper.getAppAssetPath() + "*");
????}
????@Override
????public?void?service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
????????String?requestMethod = request.getMethod().toUpperCase();
????????String?requestPath = request.getPathInfo();
????????//這里根據(jù)Tomcat的配置路徑有兩種情況, 一種是 "/userList", 另一種是 "/context地址/userList".
????????String[] splits = requestPath.split("/");
????????if?(splits.length > 2) {
????????????requestPath = "/"?+ splits[2];
????????}
????????//根據(jù)請求獲取處理器(這里類似于SpringMVC中的映射處理器)
????????Handler handler = ControllerHelper.getHandler(requestMethod, requestPath);
????????if?(handler != null) {
????????????Class> controllerClass = handler.getControllerClass();
????????????Object?controllerBean = BeanHelper.getBean(controllerClass);
????????????//初始化參數(shù)
????????????Param param = RequestHelper.createParam(request);
????????????//調(diào)用與請求對應(yīng)的方法(這里類似于SpringMVC中的處理器適配器)
????????????Object?result;
????????????Method actionMethod = handler.getControllerMethod();
????????????if?(param == null?|| param.isEmpty()) {
????????????????result = ReflectionUtil.invokeMethod(controllerBean, actionMethod);
????????????} else?{
????????????????result = ReflectionUtil.invokeMethod(controllerBean, actionMethod, param);
????????????}
????????????//跳轉(zhuǎn)頁面或返回json數(shù)據(jù)(這里類似于SpringMVC中的視圖解析器)
????????????if?(result instanceof?View) {
????????????????handleViewResult((View) result, request, response);
????????????} else?if?(result instanceof?Data) {
????????????????handleDataResult((Data) result, response);
????????????}
????????}
????}
????/**
?????* 跳轉(zhuǎn)頁面
?????*/
????private?void?handleViewResult(View view, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
????????String?path = view.getPath();
????????if?(StringUtils.isNotEmpty(path)) {
????????????if?(path.startsWith("/")) { //重定向
????????????????response.sendRedirect(request.getContextPath() + path);
????????????} else?{ //請求轉(zhuǎn)發(fā)
????????????????Map<String, Object> model = view.getModel();
????????????????for?(Map.Entry<String, Object> entry : model.entrySet()) {
????????????????????request.setAttribute(entry.getKey(), entry.getValue());
????????????????}
????????????????request.getRequestDispatcher(ConfigHelper.getAppJspPath() + path).forward(request, response);
????????????}
????????}
????}
????/**
?????* 返回JSON數(shù)據(jù)
?????*/
????private?void?handleDataResult(Data data, HttpServletResponse response) throws IOException {
????????Object?model = data.getModel();
????????if?(model != null) {
????????????response.setContentType("application/json");
????????????response.setCharacterEncoding("UTF-8");
????????????PrintWriter writer = response.getWriter();
????????????String?json = JSON.toJSON(model).toString();
????????????writer.write(json);
????????????writer.flush();
????????????writer.close();
????????}
????}
}handwritten-mvc-framwork 實例
到這里為止, handwritten-mvc-framwork ?框架已經(jīng)實現(xiàn)了Bean容器, IOC功能, MVC功能, 所以現(xiàn)在我們完全可以用 handwritten-mvc-framwork ?框架來寫一個實例了.
(1) 業(yè)務(wù)類
public?interface?IUserService?{
????List getAllUser();
}
@Service
public?class?UserService?implements?IUserService?{
????/**
?????* 獲取所有用戶
?????*/
????public?List getAllUser() {
????????List userList = new?ArrayList<>();
????????userList.add(new?User(1, "Tom", 22));
????????userList.add(new?User(2, "Alic", 12));
????????userList.add(new?User(3, "Bob", 32));
????????return?userList;
????}
} (2) 處理器
@Controller
public?class?UserController?{
????@Autowired
????private?IUserService userService;
????/**
?????* 用戶列表
?????* @return
?????*/
????@RequestMapping(value = "/userList", method = RequestMethod.GET)
????public?View getUserList()?{
????????List userList = userService.getAllUser();
????????return?new?View("index.jsp").addModel("userList", userList);
????}
} (3) JSP頁面
<%@?page?pageEncoding="UTF-8"?%>
<%@?taglib?prefix="c"?uri="http://java.sun.com/jsp/jstl/core"?%>
<c:set?var="BASE"?value="${pageContext.request.contextPath}"/>
<html>
<head>
????<title>用戶信息title>
head>
<body>
<h1>用戶信息h1>
<table>
????<tr>
????????<th>用戶idth>
????????<th>名稱th>
????????<th>年齡th>
????tr>
????<c:forEach?var="userinfo"?items="${userList}">
????????<tr>
????????????<td>${userinfo.id}td>
????????????<td>${userinfo.name}td>
????????????<td>${userinfo.age}td>
????????????<td>
????????????????<a?href="#">詳情a>
????????????????<a?href="#">編輯a>
????????????td>
????????tr>
????c:forEach>
table>
body>
html>(4) 結(jié)果
http://localhost:8081/handwritten/userList

