Spring MVC是個經得起考驗的一個成熟框架,在企業內也是一個不少開發者選擇的框架。html
SpringMVC是一種基於Java,實現了Web MVC設計模式,請求驅動類型的輕量級Web框架,即便用了MVC架構模式的思想,將Web層進行職責解耦。基於請求驅動指的就是使用請求-響應模型,框架的目的就是幫助咱們簡化開發,SpringMVC也是要簡化咱們平常Web開發。設計模式
MVC設計模式的任務是將包含業務數據的模塊與顯示模塊的視圖解耦。這是怎樣發生的?在模型和視圖之間引入重定向層能夠解決問題。此重定向層是控制器,控制器將接收請求,執行更新模型的操做,而後通知視圖關於模型更改的消息。架構
概念很複雜,無論他先,可是咱們如今知道Spring MVC經過註解能夠分發請求。咱們從這裏入手,大概思路就是,一個HttpServlet接受全部的請求,而後根據路由,判斷具體通知那個方法執行,從而達到相似的功能。mvc
OK,那麼咱們如今首先要有一個接受全部請求的HttpServlet,咱們定義一個BaseServlet,並繼承HttpServlet;app
/** * 請求訪問入口(全部請求都會來到這裏進行分發) * @author zhanglr * */ @WebServlet(name = "BaseServlet", urlPatterns = "/") public final class BaseServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.service(req, resp); } } }
咱們先不去管service()的具體實現,咱們如今有一個總的HttpServlet了,全部請求都會被攔截在這裏,那麼具體通知哪些方法呢,首先,咱們須要有註解,相似Spring MVC的@RequestMapping、@PostMapping等;框架
那麼咱們如今定義兩個註解,一個是定義在類上的註解,一個是定義具體方法的註解;ide
/** * 類註解映射,映射使用的類 * * @author zhanglr * */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) public @interface RequestType { String url(); /** * 請求方式,get或post,大小寫不敏感 * @return */ String type(); }
/** * 方法註解映射,映射調用的方法 * @author zhanglr * */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface RequestMethod { String name() default ""; }
好了,咱們如今有兩個註解,一個是註解在類上的,也是路由請求地址的前一段地址,咱們以模塊來區分;第二個註解是註解在方法上的,也就是路由請求地址的後面一段地址,兩段地址的意義我是用 【模塊/功能】來組合;工具
解析:@RequestType中有兩個屬性,type是請求方式;url是路由地址,也是上面全部我習慣用的模塊地址;post
Ok,咱們如今將這兩個註解用起來,相似Spring MVC,咱們先來建立一個UserController;this
/** * 用戶邏輯控制器 * @author zhanglr * */ @RequestType(type = "POST", url = "/user") public class UserController { private UserService userService = new UserService(); @RequestMethod(name = "/login.do") public IActionResult login(HttpServletRequest request, HttpServletResponse response) { // 登陸邏輯 return ErrorCodeResponse.Success.getSuccess("success"); } /** * 用戶註冊 * @param request * @param response * @return */ @RequestMethod(name = "/register.do") public IActionResult registerUser(HttpServletRequest request, HttpServletResponse response) { UserRegisterRequest userRegisterRequest = ClassUtil.getObjectFromResquest(request, UserRegisterRequest.class); if (Types.isEmpty(userRegisterRequest.getAccount())) { return ErrorCodeResponse.AccountIsNull.getError(); } if (Types.isEmpty(userRegisterRequest.getPassword())) { return ErrorCodeResponse.PasswordIsNull.getError(); } if (Types.isEmpty(userRegisterRequest.getAgainPwd())) { return ErrorCodeResponse.ParamIsNull.getError("二次密碼"); } return userService.registerUser(userRegisterRequest); } )
以上新建了一個用戶邏輯控制器,@RequestType即模塊功能地址爲「/user」,請求方式爲「Post」,用戶類中定義了兩個方法,分別是登陸註冊,@RequestMethod分別註解在兩個方法中,相似登陸功能即@RequestMethod(name = "/login")。那麼咱們預想中的請求登陸地址就是「/user/login.do」,一樣,註冊地址就是「/user/register.do」;
OK,咱們如今有SpringMVC的Controller了。其中有一些工具類待會再貼出來吧。
返回咱們的BaseServlet,如今須要思考的就是接受到的請求,怎樣準備的通知對應的方法,咱們經過請求地址匹配註解上的內容便可;
/** * 請求訪問入口(全部請求都會來到這裏進行分發) * @author zhanglr * */ @WebServlet(name = "BaseServlet", urlPatterns = "/") public final class BaseServlet extends HttpServlet { private static final long serialVersionUID = 1L; @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.service(req, resp); req.setCharacterEncoding("utf-8"); resp.setContentType("text/html"); resp.setCharacterEncoding("utf-8"); String contextPath = req.getContextPath(); String requestUri = req.getRequestURI(); // 去除contextPath爲真實請求路徑 String realUri = requestUri.substring(contextPath.length(), requestUri.length()); // 獲取入口包下的全部controller類 List<Class<?>> classes = ClassUtil.getAllClassByPackageName(this.getClass().getPackage()); for (Class<?> currClass : classes) { // 獲取當前類的註解 RequestType requestType = currClass.getAnnotation(RequestType.class); String methodType = req.getMethod().toLowerCase().trim(); // 當前類註解不爲空,請求路徑以該註解url開頭且請求類型爲註解type則獲取該類 if (requestType != null && requestType.type().toLowerCase().trim().equals(methodType) && realUri.contains(requestType.url()) && realUri.startsWith(requestType.url())) { // 獲取當前類的全部方法 Method[] methods = currClass.getDeclaredMethods(); for (Method currMethod : methods) { // 獲取當前方法的註解 RequestMethod requestMethod = currMethod.getAnnotation(RequestMethod.class); // 當前方法註解不爲空且請求路徑以該註解name結尾,則獲取該方法 if (requestMethod != null && realUri.contains(requestMethod.name()) && realUri.endsWith(requestMethod.name())) { try { IActionResult result = (IActionResult) currMethod.invoke(currClass.newInstance(), req, resp); if (result != null && result.getData() != null) { resp.getWriter().print(result.getData()); resp.getWriter().flush(); } } catch (Exception e) { e.printStackTrace(); } break; } } break; } } } }
在Spring MVC中,咱們能夠經過配置找到對應的包下的Controller類,咱們這裏就不作這個了,將Controller跟BaseServlet置於同一個包目錄下,在同一個包目錄下進行查找,具體的實現上面註釋也寫的挺詳細的,
就不在多解釋了。
而後,咱們來看看其中的一些小細節吧,我將返回的數據封裝到一個接口內,IActionResult接口爲:
public interface IActionResult { String getData(); }
咱們Controller類都返回該接口的實現便可,相似我寫了個公用的返回數據類;
public enum ErrorCodeResponse implements IActionResult { Success(0), Error(1), // 公共錯誤 10000+ ParamIsNull(10001, " %s 不能爲空 "), AccountIsNull(10002, "用戶帳號不能爲空"), PasswordIsNull(10003, "用戶密碼不能爲空"); private int code; private String msg; private ErrorCodeResponse(int code) { this.code = code; } private ErrorCodeResponse(int code, String msg) { this.code = code; this.msg = msg; } public IActionResult getError() { return this; } public IActionResult getError(String param) { this.msg = String.format(this.msg, param); return this; } public IActionResult getError(int code, String param) { this.code = code; this.msg = param; return this; } public IActionResult getSuccess(String param) { this.msg = param; return this; } // 拓展參數value private Object data = null; public ErrorCodeResponse setExtraData(Object data) { this.data = data; return this; } @Override public String getData() { Gson gson = new Gson(); Map<String, Object> params = new HashMap<String, Object>(); params.put("code", this.code); params.put("msg", this.msg); if (Types.isNotEmpty(data)) { if (data instanceof String) { params.put("data", data); } else { params.put("data", gson.toJson(data)); } } else { params.put("data", ""); } return gson.toJson(params); } }
經過這個工具類,咱們返回的結果會以JSON字符串進行返回;
上面還有個ClassUtil工具類,其實能夠封裝到BaseServlet裏面,就像是Spring MVC那樣,Controller的參數能夠是一個JavaBean,可是我沒去搞了,寫這個也是閒的蛋疼而已。
public final class ClassUtil { /** * 將request請求數據轉換爲實體 * @param request * @param clazz * @return */ public static <T> T getObjectFromResquest(HttpServletRequest request, Class<T> clazz) { try { T t = clazz.newInstance(); Map<String, Method> methods = new HashMap<>(); for (Method method : clazz.getMethods()) { methods.put(method.getName(), method); } Enumeration<String> keys = request.getParameterNames(); while (keys.hasMoreElements()) { String key = (String) keys.nextElement(); String value = request.getParameter(key); if (value.isEmpty()) continue; String fieldName = key.substring(0, 1).toUpperCase() + key.substring(1); Method setMethod = methods.get("set" + fieldName); if (setMethod == null) continue; invokeSetMethod(t, setMethod, value); } return t; } catch (InstantiationException | IllegalAccessException | SecurityException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } return null; } private static void invokeSetMethod(Object obj, Method method, Object value) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { Class<?>[] types = method.getParameterTypes(); if (types != null && types.length == 1) { Class<?> typeClass = types[0]; if (value.getClass() == typeClass) { method.invoke(obj, value); return; } if (typeClass == String.class) method.invoke(obj, String.valueOf(value)); else if (typeClass == Integer.class || typeClass == int.class) { method.invoke(obj, Integer.valueOf(String.valueOf(value))); } else if (typeClass == Long.class || typeClass == long.class) { method.invoke(obj, Long.valueOf(String.valueOf(value))); } } } }
以上,就大概就是全部的流程,這樣的話,咱們就不用像使用HttpServlet那樣,每一個請求都新建一個Servlet了,經過註解,咱們能夠很方便的將每個請求寫好;
大概是閒的蛋疼吧,認真的態度隨便的心情寫的,多多指教,不喜勿噴。