簡單的重構讓 MVC 的職責更加清晰

前面與你們分享了幾篇在 Smart 中有關數據持久層的重構解決方案,今天咱們把目光集中到 MVC 框架上來吧。衆所周知,Smart 是一款看起來還不錯的輕量級 Java Web 框架,若是連 MVC 框架都很差用或者不容易擴展,那豈不是本身給本身找麻煩嗎?前端

當我剛說完上面這句話時,咱們團隊中的一名帥哥 快槍手 同窗,他指出了我在 Smart MVC 上的嚴重問題,請容許我再次感謝個人小夥伴快槍手,感謝他深厚的功力與犀利的言語!解救我於水火之中,讓我學到了不少,受益不淺。java

重構前的 DispatcherServlet

咱們先來看看 Smart MVC 中一個很是重要的類 —— 前端控制器,它就是 DispatcherServlet,重構前是這樣的:git

<!-- lang: java -->
@WebServlet(urlPatterns = "/*", loadOnStartup = 0)
public class DispatcherServlet extends HttpServlet {

    private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 初始化相關配置
        ServletContext servletContext = config.getServletContext();
        UploadHelper.init(servletContext);
    }

    @Override
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 設置請求編碼方式
        request.setCharacterEncoding(FrameworkConstant.UTF_8);
        // 獲取當前請求相關數據
        String currentRequestMethod = request.getMethod();
        String currentRequestPath = WebUtil.getRequestPath(request);
        logger.debug("[Smart] {}:{}", currentRequestMethod, currentRequestPath);
        // 將「/」請求重定向到首頁
        if (currentRequestPath.equals("/")) {
            WebUtil.redirectRequest(FrameworkConstant.HOME_PAGE, request, response);
            return;
        }
        // 去掉當前請求路徑末尾的「/」
        if (currentRequestPath.endsWith("/")) {
            currentRequestPath = currentRequestPath.substring(0, currentRequestPath.length() - 1);
        }
        // 定義幾個變量(在下面的循環中使用)
        ActionBean actionBean = null;
        Matcher requestPathMatcher = null;
        // 獲取並遍歷 Action 映射
        Map<RequestBean, ActionBean> actionMap = ActionHelper.getActionMap();
        for (Map.Entry<RequestBean, ActionBean> actionEntry : actionMap.entrySet()) {
            // 從 RequestBean 中獲取 Request 相關屬性
            RequestBean requestBean = actionEntry.getKey();
            String requestMethod = requestBean.getRequestMethod();
            String requestPath = requestBean.getRequestPath(); // 正則表達式
            // 獲取請求路徑匹配器(使用正則表達式匹配請求路徑並從中獲取相應的請求參數)
            requestPathMatcher = Pattern.compile(requestPath).matcher(currentRequestPath);
            // 判斷請求方法與請求路徑是否同時匹配
            if (requestMethod.equalsIgnoreCase(currentRequestMethod) && requestPathMatcher.matches()) {
                // 獲取 ActionBean 及其相關屬性
                actionBean = actionEntry.getValue();
                // 若成功匹配,則終止循環
                break;
            }
        }
        // 若未找到 Action,則跳轉到 404 頁面
        if (actionBean == null) {
            WebUtil.sendError(HttpServletResponse.SC_NOT_FOUND, "", response);
            return;
        }
        // 初始化 DataContext
        DataContext.init(request, response);
        try {
            // 調用 Action 方法
            invokeActionMethod(request, response, actionBean, requestPathMatcher);
        } catch (Exception e) {
            // 處理 Action 異常
            handleActionException(request, response, e);
        } finally {
            // 銷燬 DataContext
            DataContext.destroy();
        }
    }

    private void invokeActionMethod(HttpServletRequest request, HttpServletResponse response, ActionBean actionBean, Matcher requestPathMatcher) throws Exception {
        // 獲取 Action 相關信息
        Class<?> actionClass = actionBean.getActionClass();
        Method actionMethod = actionBean.getActionMethod();
        // 從 BeanHelper 中建立 Action 實例
        Object actionInstance = BeanHelper.getBean(actionClass);
        // 獲取 Action 方法參數
        List<Object> paramList = createParamList(request, actionBean, requestPathMatcher);
        Class<?>[] paramTypes = actionMethod.getParameterTypes();
        if (paramTypes.length != paramList.size()) {
            throw new RuntimeException("因爲參數不匹配,沒法調用 Action 方法!");
        }
        // 調用 Action 方法
        actionMethod.setAccessible(true); // 取消類型安全檢測(可提升反射性能)
        Object actionMethodResult = actionMethod.invoke(actionInstance, paramList.toArray());
        // 處理 Action 方法返回值
        handleActionMethodReturn(request, response, actionMethodResult);
    }

    private List<Object> createParamList(HttpServletRequest request, ActionBean actionBean, Matcher requestPathMatcher) throws Exception {
        // 定義參數列表
        List<Object> paramList = new ArrayList<Object>();
        // 獲取 Action 方法參數類型
        Class<?>[] actionParamTypes = actionBean.getActionMethod().getParameterTypes();
        // 添加路徑參數列表(請求路徑中的帶佔位符參數)
        paramList.addAll(createPathParamList(requestPathMatcher, actionParamTypes));
        // 分兩種狀況進行處理
        if (UploadHelper.isMultipart(request)) {
            // 添加 Multipart 請求參數列表
            paramList.addAll(UploadHelper.createMultipartParamList(request));
        } else {
            // 添加普通請求參數列表(包括 Query String 與 Form Data)
            Map<String, Object> requestParamMap = WebUtil.getRequestParamMap(request);
            if (MapUtil.isNotEmpty(requestParamMap)) {
                paramList.add(new Params(requestParamMap));
            }
        }
        // 返回參數列表
        return paramList;
    }

    private List<Object> createPathParamList(Matcher requestPathMatcher, Class<?>[] actionParamTypes) {
        // 定義參數列表
        List<Object> paramList = new ArrayList<Object>();
        // 遍歷正則表達式中所匹配的組
        for (int i = 1; i <= requestPathMatcher.groupCount(); i++) {
            // 獲取請求參數
            String param = requestPathMatcher.group(i);
            // 獲取參數類型(支持四種類型:int/Integer、long/Long、double/Double、String)
            Class<?> paramType = actionParamTypes[i - 1];
            if (paramType.equals(int.class) || paramType.equals(Integer.class)) {
                paramList.add(CastUtil.castInt(param));
            } else if (paramType.equals(long.class) || paramType.equals(Long.class)) {
                paramList.add(CastUtil.castLong(param));
            } else if (paramType.equals(double.class) || paramType.equals(Double.class)) {
                paramList.add(CastUtil.castDouble(param));
            } else if (paramType.equals(String.class)) {
                paramList.add(param);
            }
        }
        // 返回參數列表
        return paramList;
    }

    private void handleActionMethodReturn(HttpServletRequest request, HttpServletResponse response, Object actionMethodResult) {
        // 判斷返回值類型
        if (actionMethodResult != null) {
            if (actionMethodResult instanceof Result) {
                // 分兩種狀況進行處理
                Result result = (Result) actionMethodResult;
                if (UploadHelper.isMultipart(request)) {
                    // 對於 multipart 類型,說明是文件上傳,須要轉換爲 HTML 格式並寫入響應中
                    WebUtil.writeHTML(response, result);
                } else {
                    // 對於其它類型,統一轉換爲 JSON 格式並寫入響應中
                    WebUtil.writeJSON(response, result);
                }
            } else if (actionMethodResult instanceof View) {
                // 轉發 或 重定向 到相應的頁面中
                View view = (View) actionMethodResult;
                if (view.isRedirect()) {
                    // 獲取路徑
                    String path = view.getPath();
                    // 重定向請求
                    WebUtil.redirectRequest(path, request, response);
                } else {
                    // 獲取路徑
                    String path = FrameworkConstant.JSP_PATH + view.getPath();
                    // 初始化請求屬性
                    Map<String, Object> data = view.getData();
                    if (MapUtil.isNotEmpty(data)) {
                        for (Map.Entry<String, Object> entry : data.entrySet()) {
                            request.setAttribute(entry.getKey(), entry.getValue());
                        }
                    }
                    // 轉發請求
                    WebUtil.forwardRequest(path, request, response);
                }
            }
        }
    }

    private void handleActionException(HttpServletRequest request, HttpServletResponse response, Exception e) {
        // 判斷異常緣由
        Throwable cause = e.getCause();
        if (cause instanceof AccessException) {
            // 分兩種狀況進行處理
            if (WebUtil.isAJAX(request)) {
                // 跳轉到 403 頁面
                WebUtil.sendError(HttpServletResponse.SC_FORBIDDEN, "", response);
            } else {
                // 重定向到首頁
                WebUtil.redirectRequest(FrameworkConstant.HOME_PAGE, request, response);
            }
        } else if (cause instanceof PermissionException) {
            // 跳轉到 403 頁面
            WebUtil.sendError(HttpServletResponse.SC_FORBIDDEN, "", response);
        } else {
            // 跳轉到 500 頁面
            logger.error("執行 Action 出錯!", e);
            WebUtil.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, cause.getMessage(), response);
        }
    }
}

代碼量雖然不算太多,可是你們能夠發現,這個類犯了一個嚴重的錯誤,或許應該說是編程中的一個大忌! —— 嚴重違反了「單一職責原則」。正則表達式

當快槍手指出這個問題時,我彷彿被一根針狠狠紮了一下,瞬間就清醒過來了,本身果真是犯了錯誤。編程

定義 MVC 接口

正所謂「知錯能改,善莫大焉」,因此我虛心地向快槍手請教,因而他開始舞弄筆墨,只需一分鐘就幫我畫了一張草圖:緩存

Smart MVC

這貌似哪裏見過呀,沒錯,Spring MVC 就長這樣子!安全

可見結構很是清晰:app

  1. 請求首先進入 DispatcherServlet(前端控制器)
  2. DispatcherServlet 調用 HandlerMapping 接口,獲取 Handler 對象(該對象是對 Action 方法的抽象)
  3. DispatcherServlet 調用 HandlerInvoker 接口,調用具體的 Action 方法(同時須要獲取 Action 參數)
  4. HandlerInvoker 調用 ViewResolver 接口,進行視圖解析(視圖能夠是 JSP、HTML 或 Velocity 模板等)
  5. DispatcherServlet 調用 HandlerExceptionResolver 接口,進行相關的異常處理(可能會跳轉到相應的錯誤頁面)

你們可能會問:爲何要使用如此之多的接口呢?框架

由於在每一個成功接口的背後,總會有一個默默支持它的實現,而這些實現都是 Smart 框架內部提供的默認實現,開發人員能夠根據實際須要進行定製,此時就須要與 InstanceFactory 打交道了。ide

<!-- lang: java -->
/**
 * 實例工廠
 *
 * @author huangyong
 * @since 2.3
 */
public class InstanceFactory {

    /**
     * 用於緩存對應的實例
     */
    private static final Map<String, Object> cache = new ConcurrentHashMap<String, Object>();

    ...

    /**
     * HandlerMapping
     */
    private static final String HANDLER_MAPPING = "smart.framework.custom.handler_mapping";

    /**
     * HandlerInvoker
     */
    private static final String HANDLER_INVOKER = "smart.framework.custom.handler_invoker";

    /**
     * HandlerExceptionResolver
     */
    private static final String HANDLER_EXCEPTION_RESOLVER = "smart.framework.custom.handler_exception_resolver";

    /**
     * ViewResolver
     */
    private static final String VIEW_RESOLVER = "smart.framework.custom.view_resolver";

    ...

    /**
     * 獲取 HandlerMapping
     */
    public static HandlerMapping getHandlerMapping() {
        return getInstance(HANDLER_MAPPING, DefaultHandlerMapping.class);
    }

    /**
     * 獲取 HandlerInvoker
     */
    public static HandlerInvoker getHandlerInvoker() {
        return getInstance(HANDLER_INVOKER, DefaultHandlerInvoker.class);
    }

    /**
     * 獲取 HandlerExceptionResolver
     */
    public static HandlerExceptionResolver getHandlerExceptionResolver() {
        return getInstance(HANDLER_EXCEPTION_RESOLVER, DefaultHandlerExceptionResolver.class);
    }

    /**
     * 獲取 ViewResolver
     */
    public static ViewResolver getViewResolver() {
        return getInstance(VIEW_RESOLVER, DefaultViewResolver.class);
    }

    @SuppressWarnings("unchecked")
    public static <T> T getInstance(String cacheKey, Class<T> defaultImplClass) {
        // 若緩存中存在對應的實例,則返回該實例
        if (cache.containsKey(cacheKey)) {
            return (T) cache.get(cacheKey);
        }
        // 從配置文件中獲取相應的接口實現類配置
        String implClassName = ConfigHelper.getString(cacheKey);
        // 若實現類配置不存在,則使用默認實現類
        if (StringUtil.isEmpty(implClassName)) {
            implClassName = defaultImplClass.getName();
        }
        // 經過反射建立該實現類對應的實例
        T instance = ObjectUtil.newInstance(implClassName);
        // 若該實例不爲空,則將其放入緩存
        if (instance != null) {
            cache.put(cacheKey, instance);
        }
        // 返回該實例
        return instance;
    }
}

好比說,當開發者想提供本身的 HandlerInvoker 實現,怎麼作到呢?

他只須要在 smart.properties 配置文件中,添加以下配置項:

<!-- lang: java -->
smart.framework.custom.handler_invoker=本身的 HandlerInvoker 實現類

abel533 同窗就實現了一個更強大的 HandlerInvoker,他的代碼地址以下:

http://git.oschina.net/free/Args/tree/smart_plugin_args/

很是感謝他的貢獻!

說到這裏,你們應該火燒眉毛想看看,快槍手這位大神到底是如何定義這些接口的。別急,下面的纔是精華!

這是 HandlerMapping 接口:

<!-- lang: java -->
/**
 * 處理器映射
 *
 * @author huangyong
 * @since 2.3
 */
public interface HandlerMapping {

    /**
     * 獲取 Handler
     *
     * @param currentRequestMethod 當前請求方法
     * @param currentRequestPath   當前請求路徑
     * @return Handler
     */
    Handler getHandler(String currentRequestMethod, String currentRequestPath);
}

這是 HandlerInvoker 接口:

<!-- lang: java -->
/**
 * Handler 調用器
 *
 * @author huangyong
 * @since 2.3
 */
public interface HandlerInvoker {

    /**
     * 調用 Handler
     *
     * @param request  請求對象
     * @param response 響應對象
     * @param handler  Handler
     * @throws Exception 異常
     */
    void invokeHandler(HttpServletRequest request, HttpServletResponse response, Handler handler) throws Exception;
}

這是 ViewResolver 接口:

<!-- lang: java -->
/**
 * 視圖解析器
 *
 * @author huangyong
 * @since 2.3
 */
public interface ViewResolver {

    /**
     * 解析視圖
     *
     * @param request            請求對象
     * @param response           響應對象
     * @param actionMethodResult Action 方法返回值
     */
    void resolveView(HttpServletRequest request, HttpServletResponse response, Object actionMethodResult);
}

這是 HandlerExceptionResolver 接口:

<!-- lang: java -->
/**
 * Handler 異常解析器
 *
 * @author huangyong
 * @since 2.3
 */
public interface HandlerExceptionResolver {

    /**
     * 解析 Handler 異常
     *
     * @param request  請求對象
     * @param response 響應對象
     * @param e        異常
     */
    void resolveHandlerException(HttpServletRequest request, HttpServletResponse response, Exception e);
}

可見,每一個接口中只包含一個方法,充分作到了「單一職責」,並且接口方法的參數很是簡單。

其中有一個 Handler 類須要說明一下,它就是曾經的 ActionBean,這裏只是把它改了一個名字,讓它看起來更加的專業,說白了,Handler 就是 Action 方法,沒什麼神奇的。

此外,快槍手建議將之前的 RequestBean 重命名爲 Requestor,用於封裝請求對象的相關信息。

有必要再對 Handler 的代碼說明一下:

<!-- lang: java -->
/**
 * 封裝 Action 方法相關信息
 *
 * @author huangyong
 * @since 1.0
 */
public class Handler {

    private Class<?> actionClass;
    private Method actionMethod;
    private Matcher requestPathMatcher;

    public Handler(Class<?> actionClass, Method actionMethod) {
        this.actionClass = actionClass;
        this.actionMethod = actionMethod;
    }

    public Class<?> getActionClass() {
        return actionClass;
    }

    public Method getActionMethod() {
        return actionMethod;
    }

    public Matcher getRequestPathMatcher() {
        return requestPathMatcher;
    }

    public void setRequestPathMatcher(Matcher requestPathMatcher) {
        this.requestPathMatcher = requestPathMatcher;
    }
}

該類中包括三個成員變量:

  1. Class<?> actionClass:表示該 Handler 所在的 Action 類
  2. Method actionMethod:表示當前的 Action 方法
  3. Matcher requestPathMatcher:表示請求路徑的正則表達式匹配器(用於根據請求 URL 匹配 Action 方法)

準備工做現已就緒,下面就來看看這些接口的默認實現吧。

提供接口的默認實現

接口定義清楚了,重構猶如複製粘貼,下面根據以上 MVC 相關接口的出現順序,分別展現具體的實現代碼。

這是 HandlerMapping 接口的默認實現:

<!-- lang: java -->
/**
 * 默認處理器映射
 *
 * @author huangyong
 * @since 2.3
 */
public class DefaultHandlerMapping implements HandlerMapping {

    /**
     * 用於緩存 Handler 實例
     */
    private static final Map<String, Handler> cache = new ConcurrentHashMap<String, Handler>();

    @Override
    public Handler getHandler(String currentRequestMethod, String currentRequestPath) {
        // 若緩存中存在對應的實例,則返回該實例
        String cacheKey = currentRequestMethod + ":" + currentRequestPath;
        if (cache.containsKey(cacheKey)) {
            return cache.get(cacheKey);
        }
        // 定義一個 Handler
        Handler handler = null;
        // 獲取並遍歷 Action 映射
        Map<Requestor, Handler> actionMap = ActionHelper.getActionMap();
        for (Map.Entry<Requestor, Handler> actionEntry : actionMap.entrySet()) {
            // 從 Requestor 中獲取 Request 相關屬性
            Requestor requestor = actionEntry.getKey();
            String requestMethod = requestor.getRequestMethod();
            String requestPath = requestor.getRequestPath(); // 正則表達式
            // 獲取請求路徑匹配器(使用正則表達式匹配請求路徑並從中獲取相應的請求參數)
            Matcher requestPathMatcher = Pattern.compile(requestPath).matcher(currentRequestPath);
            // 判斷請求方法與請求路徑是否同時匹配
            if (requestMethod.equalsIgnoreCase(currentRequestMethod) && requestPathMatcher.matches()) {
                // 獲取 Handler 及其相關屬性
                handler = actionEntry.getValue();
                // 設置請求路徑匹配器
                if (handler != null) {
                    handler.setRequestPathMatcher(requestPathMatcher);
                }
                // 若成功匹配,則終止循環
                break;
            }
        }
        // 若該實例不爲空,則將其放入緩存
        if (handler != null) {
            cache.put(cacheKey, handler);
        }
        // 返回該 Handler
        return handler;
    }
}

這是 HandlerInvoker 接口的默認實現:

<!-- lang: java -->
/**
 * 默認 Handler 調用器
 *
 * @author huangyong
 * @since 2.3
 */
public class DefaultHandlerInvoker implements HandlerInvoker {

    private ViewResolver viewResolver = InstanceFactory.getViewResolver();

    @Override
    public void invokeHandler(HttpServletRequest request, HttpServletResponse response, Handler handler) throws Exception {
        // 獲取 Action 相關信息
        Class<?> actionClass = handler.getActionClass();
        Method actionMethod = handler.getActionMethod();
        // 從 BeanHelper 中建立 Action 實例
        Object actionInstance = BeanHelper.getBean(actionClass);
        // 建立 Action 方法的參數列表
        List<Object> actionMethodParamList = createActionMethodParamList(request, handler);
        // 檢查參數列表是否合法
        checkParamList(actionMethod, actionMethodParamList);
        // 調用 Action 方法
        Object actionMethodResult = invokeActionMethod(actionMethod, actionInstance, actionMethodParamList);
        // 解析視圖
        viewResolver.resolveView(request, response, actionMethodResult);
    }

    public List<Object> createActionMethodParamList(HttpServletRequest request, Handler handler) throws Exception {
        // 定義參數列表
        List<Object> paramList = new ArrayList<Object>();
        // 獲取 Action 方法參數類型
        Class<?>[] actionParamTypes = handler.getActionMethod().getParameterTypes();
        // 添加路徑參數列表(請求路徑中的帶佔位符參數)
        paramList.addAll(createPathParamList(handler.getRequestPathMatcher(), actionParamTypes));
        // 分兩種狀況進行處理
        if (UploadHelper.isMultipart(request)) {
            // 添加 Multipart 請求參數列表
            paramList.addAll(UploadHelper.createMultipartParamList(request));
        } else {
            // 添加普通請求參數列表(包括 Query String 與 Form Data)
            Map<String, Object> requestParamMap = WebUtil.getRequestParamMap(request);
            if (MapUtil.isNotEmpty(requestParamMap)) {
                paramList.add(new Params(requestParamMap));
            }
        }
        // 返回參數列表
        return paramList;
    }

    private List<Object> createPathParamList(Matcher requestPathMatcher, Class<?>[] actionParamTypes) {
        // 定義參數列表
        List<Object> paramList = new ArrayList<Object>();
        // 遍歷正則表達式中所匹配的組
        for (int i = 1; i <= requestPathMatcher.groupCount(); i++) {
            // 獲取請求參數
            String param = requestPathMatcher.group(i);
            // 獲取參數類型(支持四種類型:int/Integer、long/Long、double/Double、String)
            Class<?> paramType = actionParamTypes[i - 1];
            if (ClassUtil.isInt(paramType)) {
                paramList.add(CastUtil.castInt(param));
            } else if (ClassUtil.isLong(paramType)) {
                paramList.add(CastUtil.castLong(param));
            } else if (ClassUtil.isDouble(paramType)) {
                paramList.add(CastUtil.castDouble(param));
            } else if (ClassUtil.isString(paramType)) {
                paramList.add(param);
            }
        }
        // 返回參數列表
        return paramList;
    }

    private Object invokeActionMethod(Method actionMethod, Object actionInstance, List<Object> actionMethodParamList) throws IllegalAccessException, InvocationTargetException {
        // 經過反射調用 Action 方法
        actionMethod.setAccessible(true); // 取消類型安全檢測(可提升反射性能)
        return actionMethod.invoke(actionInstance, actionMethodParamList.toArray());
    }

    private void checkParamList(Method actionMethod, List<Object> actionMethodResult) {
        // 判斷 Action 方法參數的個數是否匹配
        Class<?>[] actionMethodParameterTypes = actionMethod.getParameterTypes();
        if (actionMethodParameterTypes.length != actionMethodResult.size()) {
            throw new RuntimeException("因爲參數不匹配,沒法調用 Action 方法!");
        }
    }
}

這是 ViewResolver 接口的默認實現:

<!-- lang: java -->
/**
 * 默認視圖解析器
 *
 * @author huangyong
 * @since 2.3
 */
public class DefaultViewResolver implements ViewResolver {

    @Override
    public void resolveView(HttpServletRequest request, HttpServletResponse response, Object actionMethodResult) {
        if (actionMethodResult != null) {
            // Action 返回值可爲 View 或 Result
            if (actionMethodResult instanceof View) {
                // 若爲 View,則需考慮兩種視圖類型(重定向 或 轉發)
                View view = (View) actionMethodResult;
                if (view.isRedirect()) {
                    // 獲取路徑
                    String path = view.getPath();
                    // 重定向請求
                    WebUtil.redirectRequest(path, request, response);
                } else {
                    // 獲取路徑
                    String path = FrameworkConstant.JSP_PATH + view.getPath();
                    // 初始化請求屬性
                    Map<String, Object> data = view.getData();
                    if (MapUtil.isNotEmpty(data)) {
                        for (Map.Entry<String, Object> entry : data.entrySet()) {
                            request.setAttribute(entry.getKey(), entry.getValue());
                        }
                    }
                    // 轉發請求
                    WebUtil.forwardRequest(path, request, response);
                }
            } else {
                // 若爲 Result,則需考慮兩種請求類型(文件上傳 或 普通請求)
                Result result = (Result) actionMethodResult;
                if (UploadHelper.isMultipart(request)) {
                    // 對於 multipart 類型,說明是文件上傳,須要轉換爲 HTML 格式並寫入響應中
                    WebUtil.writeHTML(response, result);
                } else {
                    // 對於其它類型,統一轉換爲 JSON 格式並寫入響應中
                    WebUtil.writeJSON(response, result);
                }
            }
        }
    }
}

這是 HandlerExceptionResolver 接口的默認實現:

<!-- lang: java -->
/**
 * 默認 Handler 異常解析器
 *
 * @author huangyong
 * @since 2.3
 */
public class DefaultHandlerExceptionResolver implements HandlerExceptionResolver {

    @Override
    public void resolveHandlerException(HttpServletRequest request, HttpServletResponse response, Exception e) {
        // 判斷異常緣由
        Throwable cause = e.getCause();
        if (cause instanceof AccessException) {
            // 分兩種狀況進行處理
            if (WebUtil.isAJAX(request)) {
                // 跳轉到 403 頁面
                WebUtil.sendError(HttpServletResponse.SC_FORBIDDEN, "", response);
            } else {
                // 重定向到首頁
                WebUtil.redirectRequest(FrameworkConstant.HOME_PAGE, request, response);
            }
        } else if (cause instanceof PermissionException) {
            // 跳轉到 403 頁面
            WebUtil.sendError(HttpServletResponse.SC_FORBIDDEN, "", response);
        } else {
            // 跳轉到 500 頁面
            WebUtil.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, cause.getMessage(), response);
        }
    }
}

代碼量雖然有增無減,但這樣的結構更加清晰了,職責更加分明,便於往後的擴展與維護。看來快槍手真不是通常的神人啊!

這些實現類的代碼都來自於重構前的 DispatcherServlet,這裏只是根據職責進行了分類,那麼如今的 DispatcherServlet 又是啥樣子呢?

重構後的 DispatcherServlet

最後給你們奉上重構後的 DispatcherServlet

<!-- lang: java -->
/**
 * 前端控制器
 *
 * @author huangyong
 * @since 1.0
 */
@WebServlet(urlPatterns = "/*", loadOnStartup = 0)
public class DispatcherServlet extends HttpServlet {

    private static final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class);

    private HandlerMapping handlerMapping = InstanceFactory.getHandlerMapping();
    private HandlerInvoker handlerInvoker = InstanceFactory.getHandlerInvoker();
    private HandlerExceptionResolver handlerExceptionResolver = InstanceFactory.getHandlerExceptionResolver();

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 初始化相關配置
        ServletContext servletContext = config.getServletContext();
        UploadHelper.init(servletContext);
    }

    @Override
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 設置請求編碼方式
        request.setCharacterEncoding(FrameworkConstant.UTF_8);
        // 獲取當前請求相關數據
        String currentRequestMethod = request.getMethod();
        String currentRequestPath = WebUtil.getRequestPath(request);
        logger.debug("[Smart] {}:{}", currentRequestMethod, currentRequestPath);
        // 將「/」請求重定向到首頁
        if (currentRequestPath.equals("/")) {
            WebUtil.redirectRequest(FrameworkConstant.HOME_PAGE, request, response);
            return;
        }
        // 去掉當前請求路徑末尾的「/」
        if (currentRequestPath.endsWith("/")) {
            currentRequestPath = currentRequestPath.substring(0, currentRequestPath.length() - 1);
        }
        // 獲取 Handler
        Handler handler = handlerMapping.getHandler(currentRequestMethod, currentRequestPath);
        // 若未找到 Action,則跳轉到 404 頁面
        if (handler == null) {
            WebUtil.sendError(HttpServletResponse.SC_NOT_FOUND, "", response);
            return;
        }
        // 初始化 DataContext
        DataContext.init(request, response);
        try {
            // 調用 Handler
            handlerInvoker.invokeHandler(request, response, handler);
        } catch (Exception e) {
            // 處理 Action 異常
            handlerExceptionResolver.resolveHandlerException(request, response, e);
        } finally {
            // 銷燬 DataContext
            DataContext.destroy();
        }
    }
}

此時,只需在 DispatcherServlet 中調用 InstanceFactory 的相關方法,即可獲取相應的接口,代碼看起來也輕鬆了許多。

重構是一個永恆的話題,也是一件永無休止的事情,好代碼毫不是一開始就寫得出來的,必定是通過了反覆的重構,將不合理的地方變成更合理,之後有機會再與你們分享重構的心得與體會。


歡迎下載 Smart 源碼:

http://git.oschina.net/huangyong/smart

歡迎閱讀 Smart 博文:

http://my.oschina.net/huangyong/blog/158380

相關文章
相關標籤/搜索