zoukankan      html  css  js  c++  java
  • SpringMVC源码阅读:视图解析器

    1.前言

    SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧

    本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC如何完成视图解析的

    2.源码分析

    SpringMVC源码阅读:拦截器分析过doDispatch的运行过程,这里再分析一遍

    回到DispatcherServlet类的doDispatch方法,看看doDispatch如何获取ModelAndView

    HandlerMapping根据request获得HandlerExecutionChain

    根据HandlerExecutionChain获取HandlerAdapter

    HandlerAdapter根据request,response和HandlerExecutionChain调用handle方法返回ModelAndView

    然后交由processDispatchResult处理

    1023行检测从doDispatch方法运行到此是否有异常

    1037行根据ModelAndView渲染视图

    1259根据视图名称解析成View对象

    1282行调用AbstractView的render方法进行渲染

    点进去,这里以解析ftl做测试

    301行创建包含动态值和静态属性的Map

    302为待渲染的响应做准备

    303行调用子类实现方法渲染这个包含动态值和静态属性的Map

    接下来会进入AbstractTemplateView的renderMergedOutputModel方法,AbstractTemplateView是AbstractView的子类

    AbstractTemplateView的renderMergedOutputModel方法会调用内部renderMergedTemplateModel方法,该方法被子类FreemarkerView实现

    打开FreemarkerView的renderMergedTemplateModel方法

    进入doRender方法,该方法根据response渲染FreeMarker视图

    275行exposeModelAsRequestAttributes方法在父类AbstractView被实现,该方法把Model设置到request中

    277行创建Freemarker模板模型

    284行处理、加工Freemarker模板给response

    再看看FreeMarkerViewResolver做了什么,打开类接口继承图

    构造函数设置了前缀和后缀,再看看AbstractTemplateViewResolver

    104~108行给视图设置属性

    我们发现103行super引用了父类,点开进入UrlBasedViewResolver的buildView方法

    532行拼接前缀+视图名称+后缀,例如"employee/form.ftl",并设置url

    534~553行判断属性是否为空,非空则设置到View中

    再看createView方法

    460行如果以"redirect:"开头,交给RedirectView处理

    467行如果以"forward:"开头,交给InternalResourceView处理

    Freemarker解析逻辑至此分析完毕

    现在看JSP的解析流程

    回到AbstractView类的render方法,刚刚我们分析过

    304行此时renderMergedOutputModel方法被子类InternalResourceView实现

     

    139行将Model设置到Request中

    142将辅助性参数设置到Request中

    145行获取视图文件路径

    148行获取请求分发器

    155到170行如果response已经提交,则把资源文件纳入到response中,否则调用forward方法转发

    再看下InternalResourceViewResolver,该类辅助InternalResourceView,为其设置属性,其余的ViewResolver的实现类分析同Freemarker

    3.自定义视图解析

    3.1 自定义支持Freemarker和Jsp的视图解析器

    自定义视图解析器,"jsp:"开头构造InternalResourceView解析jsp,以"freemarker:"开头则构造FreemarkerView解析ftl

    这样做的好处是可以区分同名的jsp和ftl

    public class CustomViewResolver extends UrlBasedViewResolver {
    
        public static final String JSP_URL_PREFIX = "jsp:";
        public static final String FTL_URL_PREFIX = "freemarker:";
    
        private static final boolean jstlPresent = ClassUtils.isPresent(
                "javax.servlet.jsp.jstl.core.Config", CustomViewResolver.class.getClassLoader());
    
        private Boolean exposePathVariables = false;
    
        private boolean exposeRequestAttributes = false;
    
        private boolean allowRequestOverride = false;
    
        private boolean exposeSessionAttributes = false;
    
        private boolean allowSessionOverride = false;
    
        private boolean exposeSpringMacroHelpers = true;
    
        public CustomViewResolver() {
            this.setViewClass(FreeMarkerView.class);
        }
    
        @Override
        protected AbstractUrlBasedView buildView(String viewName) throws Exception {
                if(viewName.startsWith(FTL_URL_PREFIX)) {
                return buildFreemarkerView(viewName.substring(FTL_URL_PREFIX.length()));
            } else if(viewName.startsWith(JSP_URL_PREFIX)) {
                Class viewCls = jstlPresent ? JstlView.class : InternalResourceView.class;
                return build(viewCls, viewName.substring(JSP_URL_PREFIX.length()), getPrefix(), ".jsp");
            } else {
                //默认以freemarker处理
                return buildFreemarkerView(viewName);
            }
        }
    
        /**
         * @Author 谷天乐
         * @Description 使用UrlBasedViewResolver的buildView方法
         * 因为CustomViewResolver重写了buildView,不再执行UrlBasedViewResolver的buildView方法
         * @Date 2019/1/17 11:26
         * @Param [viewClass, viewName, prefix, suffix]
         * @return org.springframework.web.servlet.view.AbstractUrlBasedView
         **/
        private AbstractUrlBasedView build(Class viewClass, String viewName, String prefix, String suffix) {
            AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
            view.setUrl(prefix + viewName + suffix);
            String contentType = getContentType();
            if (contentType != null) {
                view.setContentType(contentType);
            }
            view.setRequestContextAttribute(getRequestContextAttribute());
            view.setAttributesMap(getAttributesMap());
            if (this.exposePathVariables != null) {
                view.setExposePathVariables(exposePathVariables);
            }
            return view;
        }
    
        /**
         * @Author 谷天乐
         * @Description 使用AbstractTemplateViewResolver的buildView,为view设置属性
         * Freemarker解析所需
         *
         * @Date 2019/2/7 10:22
         * @Param [viewName]
         * @return org.springframework.web.servlet.view.AbstractUrlBasedView
         **/
        private AbstractUrlBasedView buildFreemarkerView(String viewName) throws Exception {
            AbstractTemplateView view = (AbstractTemplateView) build(FreeMarkerView.class, viewName, "", getSuffix());
            view.setExposeRequestAttributes(this.exposeRequestAttributes);
            view.setAllowRequestOverride(this.allowRequestOverride);
            view.setExposeSessionAttributes(this.exposeSessionAttributes);
            view.setAllowSessionOverride(this.allowSessionOverride);
            view.setExposeSpringMacroHelpers(this.exposeSpringMacroHelpers);
            return view;
        }
    
        public boolean isExposeRequestAttributes() {
            return exposeRequestAttributes;
        }
    
        public void setExposeRequestAttributes(boolean exposeRequestAttributes) {
            this.exposeRequestAttributes = exposeRequestAttributes;
        }
    
        public boolean isExposeSessionAttributes() {
            return exposeSessionAttributes;
        }
    
        public void setExposeSessionAttributes(boolean exposeSessionAttributes) {
            this.exposeSessionAttributes = exposeSessionAttributes;
        }
    
        public boolean isExposeSpringMacroHelpers() {
            return exposeSpringMacroHelpers;
        }
    
        public void setExposeSpringMacroHelpers(boolean exposeSpringMacroHelpers) {
            this.exposeSpringMacroHelpers = exposeSpringMacroHelpers;
        }
    }

    在dispatcher-servlet.xml加入自定义视图解析器

       <bean class="org.format.demo.custom.CustomViewResolver">
            <property name="prefix" value="/WEB-INF/view/"/>
            <property name="suffix" value=".ftl"/>
            <property name="contentType" value="text/html;charset=utf-8"/>
            <property name="exposeRequestAttributes" value="true"/>
            <property name="exposeSessionAttributes" value="true"/>
            <property name="exposeSpringMacroHelpers" value="true"/>
            <property name="requestContextAttribute" value="request"/>
        </bean>

    引入freemarker和jsp的配置,配置在前遇到同名情况具备更高的优先级,比如index.ftl和index.jsp会优先解析index.ftl

        <!--遇到同名的ftl和jsp文件,配置在前的解析器优先级更高-->
        <import resource="classpath:springConfig/viewConfig/freemarker.xml"/>
        <import resource="classpath:springConfig/viewConfig/jsp.xml"/>

    测试Controller

    @Controller
    @RequestMapping(value = "/tvrc")
    public class TestViewResolverController {
    
    
        @RequestMapping("jsp")
        public ModelAndView jsp(ModelAndView view) {
            view.setViewName("jsp:tvrc/test");
            return view;
        }
    
        @RequestMapping("/ftl")
        public ModelAndView freemarker(ModelAndView view) {
            view.setViewName("freemarker:tvrc/test");
            return view;
        }
    
    }

    浏览器输入http://localhost:8080/springmvcdemo/tvrc/jsp

    浏览器输入http://localhost:8080/springmvcdemo/tvrc/ftl

    3.2 自定义Pdf视图

    测试Controller

    @Controller
    @RequestMapping("otherview")
    public class OtherViewController {
        @RequestMapping("/pdf")
        public ModelAndView mypdf() {
            ModelAndView mav = new ModelAndView();
            //添加自定义视图
            mav.setView(new MyPdfView());
            List<String> list = new ArrayList<String>();
            for (int i = 0; i < 10; i++) {
                list.add(i+"");
            }
            mav.addObject("list", list);
            return mav;
        }
    
        @RequestMapping("/excel")
        public ModelAndView myexcle() {
            ModelAndView mav = new ModelAndView();
            //添加视图
            mav.setView(new MyExcelView());
            List list = new ArrayList<String>();
            for (int i = 0; i < 10; i++) {
                list.add(i+"");
            }
            mav.addObject("list", list);
            return mav;
        }
    }

    创建测试类继承AbstractPdfView,重写buildPdfDocument方法

    public class MyPdfView extends AbstractPdfView {
    
        @Override
        protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer,
                                        HttpServletRequest request, HttpServletResponse response) throws Exception {
            List list = (List) model.get("list");
            for (int i = 0; i < list.size(); i++) {
                //将数据加载到视图上
                document.add(new Paragraph((String)(list.get(i))));
            }
        }
    }

    浏览器输入http://localhost:8080/springmvcdemo/otherview/pdf

    3.3 自定义Excel视图

    创建测试类继承AbstractExcelView,重写buildExcelDocument方法

    public class MyExcelView extends AbstractExcelView {
    
        @Override
        protected void buildExcelDocument(Map<String, Object> model, HSSFWorkbook workbook, HttpServletRequest request,
                                          HttpServletResponse response) throws Exception {
            HSSFSheet sheet;//工作簿的名字
            HSSFCell cell;//单元格
    
            sheet = workbook.createSheet("spring");
            sheet.setDefaultColumnWidth(12);
    
            List list = (List) model.get("list");
            for (int i = 0; i < list.size(); i++) {
                cell = getCell(sheet, 0, i);
                setText(cell, (String) list.get(i));
            }
        }
    }

     浏览器输入http://localhost:8080/springmvcdemo/otherview/excel

    4.总结

    View作为视图接口,AbstractView的render方法创建待渲染的model,调用子类,如果待解析视图类型是jsp,则调用InternalResourceView,如果是ftl,则调用FreemarkerView,执行渲染

    ViewResolver作为视图解析接口,主要为View提供支持,createView方法创建View,buildView根据路径构造View,并为View设置一系列属性

    DispatcherServlet根据ModelAndView调用View实现类的render方法进行视图渲染,ViewResolver实现类起到辅助作用,为View设置属性(前缀、后缀等)

    5.参考

    https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#beans-beans-conversion

    https://docs.spring.io/spring/docs/current/javadoc-api/

    https://github.com/spring-projects/spring-framework

    文中难免有不足,欢迎指出

  • 相关阅读:
    C#序列化和反序列化开发者在线 Builder.com.cn 更新时间:20080904
    提高C#编程水平不可不读的50个要诀 开发者在线 Builder.com.cn 更新时间:20080805作者: 来源:开发者在线
    CollapsiblePanelExtender这Ajax控件
    100个冷笑话,越往后越冷(郁闷时专用……)赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞赞
    [转]eclipse java build path
    [转]线程间操作无效: 从不是创建控件“”的线程访问它~~~的解决方法~
    水晶报表最后空页解决方法
    JQuery资源
    Windows Explorer中对所选文件增加右键菜单并关联自己程序的例子
    [转]Oracle如何复制表的sql语句
  • 原文地址:https://www.cnblogs.com/Java-Starter/p/10353996.html
Copyright © 2011-2022 走看看