文档章节

简单的重构让 MVC 的职责更加清晰

黄勇
 黄勇
发布于 2014/05/20 23:34
字数 3854
阅读 4512
收藏 66

前面与大家分享了几篇在 Smart 中有关数据持久层的重构解决方案,今天我们把目光集中到 MVC 框架上来吧。众所周知,Smart 是一款看起来还不错的轻量级 Java Web 框架,如果连 MVC 框架都不好用或者不容易扩展,那岂不是自己给自己找麻烦吗?

当我刚说完上面这句话时,我们团队中的一名帅哥 快枪手 同学,他指出了我在 Smart MVC 上的严重问题,请允许我再次感谢我的小伙伴快枪手,感谢他深厚的功力与犀利的言语!解救我于水火之中,让我学到了很多,受益匪浅。

重构前的 DispatcherServlet

我们先来看看 Smart MVC 中一个非常重要的类 —— 前端控制器,它就是 DispatcherServlet,重构前是这样的:

<!-- 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 就长这样子!

可见结构非常清晰:

  1. 请求首先进入 DispatcherServlet(前端控制器)
  2. DispatcherServlet 调用 HandlerMapping 接口,获取 Handler 对象(该对象是对 Action 方法的抽象)
  3. DispatcherServlet 调用 HandlerInvoker 接口,调用具体的 Action 方法(同时需要获取 Action 参数)
  4. HandlerInvoker 调用 ViewResolver 接口,进行视图解析(视图可以是 JSP、HTML 或 Velocity 模板等)
  5. DispatcherServlet 调用 HandlerExceptionResolver 接口,进行相关的异常处理(可能会跳转到相应的错误页面)

大家可能会问:为什么要使用如此之多的接口呢?

因为在每个成功接口的背后,总会有一个默默支持它的实现,而这些实现都是 Smart 框架内部提供的默认实现,开发人员可以根据实际需要进行定制,此时就需要与 InstanceFactory 打交道了。

<!-- 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

© 著作权归作者所有

黄勇

黄勇

粉丝 6610
博文 121
码字总数 216155
作品 1
浦东
CTO(技术副总裁)
私信 提问
加载中

评论(6)

L
LLPANDER
看完springmvc的源码再来看您的源码有一种通透的感觉~~感谢
Dead_knight
Dead_knight
越来越强大了呀~~
tyutNo4
tyutNo4
@快枪手 i love u!
HZ先生
HZ先生
79
蛙牛
蛙牛
79 42
D神
D神
追你好久了!!正在看你的文章!5
php框架--WindFramework

Wind Framework是phpwind团队开发的一款php框架。2010年10月份开始投入开发,目前为止已经内部发行了四个版本(0.5,0.8,0.9,1.0)。它源自phpwind社区产品的一次名为‘鹊桥’的重构计划。...

wuqiong
2012/03/29
10.3K
0
架构,改善程序复用性的设计~第四讲 方法的重载真的用不到吗?

在第三讲中我们主要关注了代码重构的思想,从方法重构到类重构再到项目重构,这是一个过程,一种思想上的升华,今天将继续我们“程序复用性设计”的旅程,说一下方法重载的重要性。 细心的朋...

mcy247
2017/12/05
0
0
构建更好的客户端 JavaScript 应用

你可能注意到了,最近的一段时间越来越多的Web应用有变复杂的趋势,重心从服务端慢慢向着客户端转移。 这是个正常的趋势么?我不知道。支持和反对者的讨论就像是在讨论复活者和圣诞节哪一个更...

oschina
2014/07/05
7.2K
50
31天重构学习笔记下载

前言 前两天写了一篇程序猿也爱学英语(上),有图有真相的文章,写作那篇文章只是自己一时兴起,或者说是自己的兴趣使然。文中的观点只是自己的学习心得和体会,属一家之言且鉴于本人不是学...

KnightsWarrior
2013/08/01
0
0
重构系统的套路-面向对象设计原则

前言 一讨论系统重构,很多人不明所以的就开始画各种架构图,写各种高可用,高并发设计方案,其实不知道很多系统的腐朽是从代码失控开始的,所以重构系统之前,架构师需要深谙面向对象设计之...

春哥大魔王的博客
2018/12/16
26
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring Cloud 笔记之Spring cloud config client

观察者模式它的数据的变化是被动的。 观察者模式在java中的实现: package com.hxq.springcloud.springcloudconfigclient;import org.springframework.context.ApplicationListener;i...

xiaoxiao_go
今天
4
0
CentOS7.6中安装使用fcitx框架

内容目录 一、为什么要使用fcitx?二、安装fcitx框架三、安装搜狗输入法 一、为什么要使用fcitx? Gnome3桌面自带的输入法框架为ibus,而在使用ibus时会时不时出现卡顿无法输入的现象。 搜狗和...

技术训练营
今天
4
0
《Designing.Data-Intensive.Applications》笔记 四

第九章 一致性与共识 分布式系统最重要的的抽象之一是共识(consensus):让所有的节点对某件事达成一致。 最终一致性(eventual consistency)只提供较弱的保证,需要探索更高的一致性保证(stro...

丰田破产标志
今天
7
0
docker 使用mysql

1, 进入容器 比如 myslq1 里面进行操作 docker exec -it mysql1 /bin/bash 2. 退出 容器 交互: exit 3. mysql 启动在容器里面,并且 可以本地连接mysql docker run --name mysql1 --env MY...

之渊
今天
7
0
python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部