SpringMVC: 策略模式下的視圖解析

上一篇中經過前端控制器實現了接收請求. 控制器在收到請求後進行業務邏輯處理, 須要將視圖返回至前端控制器, 由前端控制器並將結果返回至客戶端.前端

常見的返回結果有如下兩種:web

  • HTML或其餘數據格式
  • 重定向

前端控制器中須要接收控制器的返回結果, 在返回結果中約定: 若是以"redirect:"開始, 則爲返回結果是重定向, 反之爲視圖, 前端控制器渲染視圖後返回客戶端.算法

private void doService(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    // 獲取對應的方法
    // 參數綁定
    // ...
    
    // 執行控制器方法並接收返回值
    String view = (String) method.invoke(classInstance, methodParams);

    // 根據返回值判斷重定向或渲染視圖(JSP)
    if (view.startsWith("redirect:")) {
        resp.sendRedirect(view.substring(9));
    } 
    else {
        // 將控制器中保持的變量設置到Request中
        for (Map.Entry<String, Object> entry : model.entrySet()) {
            req.setAttribute(entry.getKey(), entry.getValue());
        }
        // 跳轉至相應的JSP中
        req.getRequestDispatcher(view + ".jsp").forward(req, resp);
    }

}
複製代碼

支持多視圖

隨着模板的興起, 如今有愈來愈多的模板技術取代JSP成爲視圖. 做爲負責渲染視圖的前端控制器也應該支持多種視圖方式供用戶選擇.設計模式

配置視圖類型

在前端控制器中定義視圖類型, 並在初始化時加載.bash

// 視圖類型(jsp,freemarker,velocity...)
private String viewType = "jsp";

/**
 * 初始化Servlet. 容器初始化Servlet時調用, 加載配置文件初始化MVC相關組件(控制器,視圖解析器等)
 */
@Override
public void init() throws ServletException {

    // 獲取用戶自定義的視圖類型
    String viewTypeConfig = getInitParameter("viewType");
    if (viewTypeConfig != null) {
        this.viewType = viewTypeConfig;
    }

}
複製代碼

web.xml中配置視圖類型mvc

<servlet>
	<servlet-name>mvc</servlet-name>
	<servlet-class>com.atd681.xc.ssm.framework.DispatcherServlet</servlet-class>
	<!-- 視圖類型 -->
	<init-param>
		<param-name>viewType</param-name>
		<param-value>freemarker</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>
複製代碼

根據視圖類型渲染視圖

在解析視圖時, 根據配置的視圖類型使用對應的方式解析視圖jsp

private void doService(HttpServletRequest req, HttpServletResponse resp) throws Exception {

    // 返回的視圖
    String view = (String) method.invoke(classInstance, methodParams);

    // 根據返回值判斷重定向或渲染視圖
    if (view.startsWith("redirect:")) {
        resp.sendRedirect(view.substring(9));
    }
    // 根據配置的視圖類型使用對應的方式渲染視圖
    else {

        // 使用JSP做爲視圖
        if ("jsp".equals(this.viewType)) {

            // 將控制器中保持的變量設置到Request中
            for (Map.Entry<String, Object> entry : model.entrySet()) {
                req.setAttribute(entry.getKey(), entry.getValue());
            }
            // 跳轉至相應的JSP中
            req.getRequestDispatcher(view + ".jsp").forward(req, resp);

        }
        // 使用Freemarker做爲視圖
        else if ("freemarker".equals(this.viewType)) {
            
            Template template = new Configuration(Configuration.VERSION_2_3_0).getTemplate(view + ".ftl");
            template.process(model, resp.getWriter());
            
        }
        // 使用Velocity做爲視圖
        else if ("velocity".equals(this.viewType)) {

        }
        // 其餘各類視圖...

    }

}
複製代碼

策略模式

上述方式是將全部視圖解析的操做都在前端控制器中實現. 缺點顯而易見:ide

  • 隨着所支持視圖的增長, 前端控制器中的視圖渲染部分須要不斷的增長分支.
  • 某個類型的視圖須要修改或升級時, 須要在前端控制器代碼進行修改
  • 前端控制器代碼量巨大, 愈來愈難維護.

設計模式中有一種模式叫作策略模式: 將算法獨立封裝, 使得算法能夠自由切換. 換成視圖的方式就是: 將每種類型視圖的渲染方式封裝成獨立的策略類(對外提供渲染視圖的方法). 前端控制器根據配置找到對應的視圖策略類, 調用渲染視圖方法.this

定義視圖策略統一接口

// 策略接口
public interface ViewResolver {
    
    // 渲染視圖方法
    void render(HttpServletRequest req, HttpServletResponse resp, String viewName, Map<String, Object> model)
            throws Exception;

}
複製代碼

實現各個視圖策略類

JSP

// JSP視圖解析策略
public class JSPViewResolver implements ViewResolver {

    // 使用JSP渲染視圖
    @Override
    public void render(HttpServletRequest req, HttpServletResponse resp, String viewName, Map<String, Object> model)
            throws Exception {

        // 將控制器中保持的變量設置到Request中
        for (Map.Entry<String, Object> entry : model.entrySet()) {
            req.setAttribute(entry.getKey(), entry.getValue());
        }

        // 跳轉至相應的JSP中
        req.getRequestDispatcher(viewName + ".jsp").forward(req, resp);

    }

}
複製代碼

Freemarker

// Freemarker視圖解析策略
public class FreemarkerViewResolver implements ViewResolver {

    // Freemarker配置
    private Configuration config = new Configuration(Configuration.VERSION_2_3_0);

    // 使用Freemarker渲染視圖
    @Override
    public void render(HttpServletRequest req, HttpServletResponse resp, String viewName, Map<String, Object> model)
            throws Exception {

        Template template = config.getTemplate(viewName + ".ftl");
        template.process(model, resp.getWriter());

    }

}

複製代碼

前端控制器找到對應的策略

前端控制器如何可以根據配置的視圖類型找到對應的策略類而且實例化呢?spa

web.xml中配置視圖類型時使用對應的視圖策略類的名稱, 前端控制器經過JAVA發射就能夠實例化策略類並調用其渲染視圖的方法.

web.xml中配置視圖策略類

<servlet>
	<servlet-name>mvc</servlet-name>
	<servlet-class>com.atd681.xc.ssm.framework.DispatcherServlet</servlet-class>
	<!-- 視圖類型 -->
	<init-param>
		<param-name>viewClass</param-name>
		<param-value>com.atd681.xc.ssm.framework.view.FreemarkerViewResolver</param-value>
	</init-param>
</servlet>
複製代碼

在前端控制器中定義視圖類型, 並在初始化時加載.

// 視圖策略類路徑(jsp,freemarker,velocity...)
private String viewClass = "com.atd681.xc.ssm.framework.view.JSPViewResolver";

/**
 * 初始化Servlet. 容器初始化Servlet時調用, 加載配置文件初始化MVC相關組件(控制器,視圖解析器等)
 */
@Override
public void init() throws ServletException {

    // 獲取用戶自定義的視圖策略類路徑
    String viewClassConfig = getInitParameter("viewClass");
    if (viewClassConfig != null) {
        this.viewClass = viewClassConfig;
    }

}
複製代碼

實例化視圖策略類並渲染視圖

在解析視圖時, 根據配置的視圖類型使用對應的方式解析視圖

private void doService(HttpServletRequest req, HttpServletResponse resp) throws Exception {

    // 返回的視圖
    String view = (String) method.invoke(classInstance, methodParams);

    // 根據返回值判斷重定向或渲染視圖
    if (view.startsWith("redirect:")) {
        resp.sendRedirect(view.substring(9));
    }
    // 根據配置的視圖類型使用對應的方式渲染視圖
    else {

        // 實例化對應的策略類
        ViewResolver viewResolver = (ViewResolver) Class.forName(this.viewClass).newInstance();
        // 使用視圖策略類封裝的渲染方法
        viewResolver.render(req, resp, view, model);

    }

}
複製代碼

後續章節講到Spring IOC容器時, 實例化視圖策略能夠放到IOC容器.

視圖解析使用策略模式後, 大大簡化了代碼的複雜度. 當須要增長視圖或修改視圖時只須要增長或修改相應的視圖策略類便可. 對前端控制器的邏輯沒有任何影響.

總結

策略模式是一種很常見的設計模式, 結合Spring IOC使用很是的簡單便捷. 使用策略模式能夠大大下降代碼的複雜度, Spring IOC下的策略模式天生就是爲了解決過多的if/else

SpringMVC的視圖解析爲了支持多視圖解析, 增長了View的概念, View負責解析視圖. ViewResolver負責獲取對應的視圖. 但總體思路 有興趣的讀者能夠看下Spring的源碼.

相關文章
相關標籤/搜索