回調模式——讓你的controller不再繁瑣

在Java後臺編程中,大家一般會使用MVC設計模式,即便使用的具體框架不盡相同。今天,我們來說說MVC中的這個C,也就是Controller。Controller是web程序中最先接觸到用戶request的地方,當然,前提是該request經過了身份認證和權限檢查等重重考驗,這一部分建議在框架的Interceptor中進行。詳細內容請看筆者之前的博客玩轉Spring!從拒絕Filter開始。好了,話不多說,進入正題。


一般來說,Controller的作用是爲每個URI提供一個處理方法,比如有一組User的增刪改查操作,我們會寫一個UserController,然後其中有四個方法,對應User的增刪改查操作。好了,這部分沒什麼不同。我們來看看這四個方法的內部邏輯,比如增加用戶addUser方法,我們需要接受一些關於User的元信息,然後檢查這些元信息是否非空,是否合法等等,接着調用對應的service方法,然後返回一個執行正確或者執行錯誤的結果。再比如updateUser方法,我們仍然需要接受一個User的元信息,然後檢查這些元信息是否非空,是否合法等等,接着調用對應的service方法,然後返回一個執行正確或者執行錯誤的結果。


根據Donnot repeat yourself原則,我們自然想到:

1. 可否做一個抽象,將所有的參數合法性檢查放到一個地方來做,業務代碼僅僅需要指定傳入的參數類,框架會幫助檢查參數的合法性。如果不合法,直接返回異常。

2. 可否做一個抽象,將所有的返回值放到一個地方來做,業務代碼可以將任意的結果類返回,而無需關心該結果類和JsonString的轉化。


針對第一個問題,我們設計一種模式,讓業務developer每次實現一個URI,就新增一個對應該URI的Param類,然後框架會幫助檢查這個Param類。

針對第二個問題,我們設計一個包含泛型結果的類,讓業務developer每次僅僅需要將某一個處理結果類放入即可。


至於重用代碼的抽象,我們使用回調模式,來看代碼:

public interface CommonExecute<T extends BaseParam> {
    HttpResult execute(T param);
}

public class CommonExecutor {
    public static String execute(HttpServletRequest request, HttpServletResponse response, Class<? extends BaseParam> paramClass, CommonExecute commonExecute) {
        //根據paramClass的原信息檢查參數的是否存在以及各式是否正確
        BaseParam baseParam = null;
        try {
            baseParam = paramClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new SystemException(ErrorEnum.RUNTIME_EXCEPTION);
        }
        //inject the values into the baseParam
        new ServletRequestDataBinder(baseParam).bind(request);
        HttpResult result = commonExecute.execute(baseParam);
        return JSON.toJSONString(result);
    }
}

先來看第一個問題,有了上面的這兩個類以後,以後的Controller是這樣的:

    @Resource(name = "userService")
    IUserService userService;

    @RequestMapping(value = CommonUrl.UrlConstant.GET_USER_BY_ACCOUNT, produces = CommonUtils.CONTENT_TYPE)
    @ResponseBody
    public String getUserByAccount(HttpServletRequest request, HttpServletResponse response) {
        return CommonExecutor.execute(request, response, CommonUrl.GET_USER_BY_ACCOUNT.getParamClass(), new CommonExecute<GetUserByAccountParam>() {
            @Override
            public HttpResult execute(GetUserByAccountParam param) {
                HttpResult<UserDTO> result = userService.getUserByAccount(param.getAccount());
                return result;
            }
        });
    }

public class GetUserByAccountParam extends BaseParam {
    private String account;

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }
}

我們來看上面的代碼,當我需要寫一個getUserByAccount的API時,僅僅需要在對應的controller中添加一個如下的方法

public String getUserByAccount(HttpServletRequest request, HttpServletResponse response) {}

當我想添加比如名爲xxx方法的時候,僅僅需要添加同樣的方法:

public String xxx(HttpServletRequest request, HttpServletResponse response) {}

在方法體的內部,直接調用CommonExecutor的靜態execute方法,該方法有四個參數,前兩個參數是request和response參數,傳入皆可,無需感知。第三個方法是該方法對應的URI對應的參數類,可以看到,他是一個繼承自BaseParam子類的元類。說白了,就是你新添加的方法,需要傳入那些參數,把這些參數寫成一個類。


Class<? extends BaseParam>


第四個參數是一個實現了CommonExecute接口的匿名類,在這裏進行具體的業務處理。讀者可以看到,該類的一個傳入的參數就是你自定義的傳入參數類,只要用戶在發送請求的時候傳入和該參數類的字段域同名的參數,框架就會自動幫你注入到該傳入參數類中。這樣業務developer無需感知這個過程,在寫CommonExecute接口的匿名類時,直接使用自定義的參數類即可。

對於第二個問題,框架定義了一個HttpResult類:

public class HttpResult<T> implements Serializable {
    private static final long serialVersionUID = -3404886040638951329L;

    protected boolean success;

    protected T model;

    protected String msgCode;

    protected String msgInfo;

    public HttpResult() {

    }

    public HttpResult(boolean success, String msgCode, String msgInfo) {
        this.success = success;
        this.msgCode = msgCode;
        this.msgInfo = msgInfo;
    }

    public static <T> HttpResult<T> successResult(T t) {
        HttpResult<T> result = new HttpResult<T>();
        result.setSuccess(true);
        result.setModel(t);
        return result;
    }

    public static <T> HttpResult<T> failedResult(String msgCode, String msgInfo) {
        HttpResult<T> result = new HttpResult<T>();
        result.setSuccess(false);
        result.setMsgCode(msgCode);
        result.setMsgInfo(msgInfo);
        return result;
    }

    public static <T> HttpResult<T> failedResult(ErrorEnum errorEnum) {
        HttpResult<T> result = new HttpResult<T>();
        result.setSuccess(false);
        if (errorEnum != null) {
            result.setMsgCode(errorEnum.getErrCode());
            result.setMsgInfo(errorEnum.getErrDesc());
        }
        return result;
    }

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public T getModel() {
        return model;
    }

    public void setModel(T model) {
        this.model = model;
    }

    public String getMsgCode() {
        return msgCode;
    }

    public void setMsgCode(String msgCode) {
        this.msgCode = msgCode;
    }

    public String getMsgInfo() {
        return msgInfo;
    }

    public void setMsgInfo(String msgInfo) {
        this.msgInfo = msgInfo;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
}

可以看見,HttpResult類是框架定義好的返回格式,在業務Developer實現CommonExecute接口的匿名類時,僅僅需要返回一個HttpResult類,框架會幫助把該HttpResult類格式化成爲Json返回給用戶,十分方便。


覺得文章不錯的話,別忘了關注我哦!

關於更將詳細的代碼,請參見筆者的github。點擊打開鏈接

筆者開設了一個知乎live,詳細的介紹的JAVA從入門到精通該如何學,學什麼?

提供給想深入學習和提高JAVA能力的同學,歡迎收聽https://www.zhihu.com/lives/93219220424868249



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