手動寫個相似的Spring MVC框架試試

Spring MVC是個經得起考驗的一個成熟框架,在企業內也是一個不少開發者選擇的框架。html

SpringMVC是一種基於Java,實現了Web MVC設計模式,請求驅動類型的輕量級Web框架,即便用了MVC架構模式的思想,將Web層進行職責解耦。基於請求驅動指的就是使用請求-響應模型,框架的目的就是幫助咱們簡化開發,SpringMVC也是要簡化咱們平常Web開發。設計模式

MVC設計模式

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了,經過註解,咱們能夠很方便的將每個請求寫好;

大概是閒的蛋疼吧,認真的態度隨便的心情寫的,多多指教,不喜勿噴。

相關文章
相關標籤/搜索