Spring MVC: View resolver

Posted by tolutlou on Tue, 08 Feb 2022 02:43:18 +0100

1. Overview

Configure < MVC: annotation driven... / > After the element, it will configure three special beans, HandlerMapping, HandlerAdapter and HandlerExceptionResovler, for spring MVC, which solve the mapping of URL Controller processing methods.

When the processing method of the Controller is completed, the processing method can return: String (logical View name), View (View object), ModelAndView (including both Model and logical View or View), and the View object represents the specific View. Therefore, spring MVC must use ViewResolver to resolve the logical View name (String) into the actual View (View object).

Function diagram of ViewResolver:

ViewResolver itself is an interface, which provides the following common classes:

  • Abstractcacheingviewresolver: abstract view resolver, which is responsible for caching views. Many views need to be prepared before use, and its subclasses can cache views.
  • XmlViewResolver: a view parser that can receive XML configuration files. The DTD of the XML configuration file is the same as that of the Spring configuration file. The default configuration file is / WEB-INF / views xml.
  • BeanNameViewResolver: it will directly obtain the Bean with id of viewName from the existing container as the View.
  • Resourcebundeviewresolver: use Bean definition in ResourceBundle to implement ViewResolver. This ResourceBundle is specified by basename of bundle. This bundle is usually defined in a properties file located in CLASSPATH.
  • UrlBasedViewResolver: this view resolver allows the view name to be parsed into a URL. It does not need to display the configuration, just the view name.
  • InternalResourceViewResolver: a subclass of UrlBasedViewResolver, which can easily support Servlet and JSP views and subclasses such as jstview and TilesView. It is a view parser commonly used in practical development and the default view parser of spring MVC.
  • FreeMarkerViewResolver: a subclass of UrlBasedViewResolver, which can easily support FreeMarker views. Similar to it are GroovyMarkupViewResolver and TilesViewReoslver.
  • Content negotiatingviewresolver: it is not a specific view parser. It will "dynamically" select the appropriate view parser according to the requested MIME type, and then delegate the view parsing work to the selected view parser.

​​​​2,UrlBasedViewResolver

2.1. Function and usage of UrlBasedViewResolver

UrlBasedViewResolver inherits the AbstractCachingViewResolver base class and is a simple implementation class of the ViewResolver interface. UrlBasedViewResolver uses a way of splicing URLs to resolve views. It can specify a prefix through the prefix attribute or a suffix through the suffix attribute, and then add the specified prefix and suffix to the logical view name to get the URL of the actual view.

For example, specify prefix="/WEB-INF/content /", suffix=".jsp". When the view name returned by the processing method of the controller is "error", the view URL parsed by UrlBasedViewResolver is / WEB-INF / content / error jsp. The default prefix and suffix attribute values are empty strings.

When using URLBasedViewResolver as the view parser, it supports the use of forword: prefix or redirect: prefix in the logical view name, where:

  • forword: the prefix represents the same request forwarded to the specified view resource, so the request parameters and request attributes will not be lost after forwarding to the target resource.
  • redirect: the prefix represents redirection to the specified view resource. If you resend the request, the redirection will generate a new request. Therefore, the reset backward request parameters and request attributes will be lost.

When using the UrlBasedViewResolver, the viewClass attribute must be specified to indicate which view to parse into. The InternalResourceView is commonly used (not 100%) to render ordinary JSP views; If you want to use JSTL, you should specify the property value as JstlView.

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
    <!-- Using annotation driven -->
    <mvc:annotation-driven />
    <!-- Define scan loaded packages -->
    <context:component-scan base-package="com.ysy.springmvc.Controller" />
    <!-- Define view parser -->
    <bean class="org.springframework.web.servlet.view.UrlBasedViewResolver"
          p:prefix="/WEB-INF/content/"
          p:suffix=".jsp"
          p:viewClass="org.springframework.web.servlet.view.InternalResourceView"/>
</beans>
@Controller
public class UserController {
    @Resource(name = "userService")
    private UserService userService;

    @PostMapping("/login")
    public String login(String username, String pass, Model model) {
        if (userService.userLogin(username,pass)>0){
            model.addAttribute("tip","Welcome, login succeeded!");
            return "success";
        }
        model.addAttribute("tip","Sorry, the user name and password you entered are incorrect!");
        return "error";
    }
}

If you open the source code of the UrlBasedViewResolver class, you can see that it overrides the createView() method, and its code fragment is as follows:

protected View createView(String viewName, Locale locale) throws Exception {
    //Judge whether the processing of the view name is not supported. If null is returned, it indicates that it is passed to the next node of the next view parser chain
    if (!this.canHandle(viewName, locale)) {
        return null;
    } else {
        String forwardUrl;
        //Determine whether to start with redirect:
        if (viewName.startsWith("redirect:")) {
            //Remove the beginning of redirect
            forwardUrl = viewName.substring("redirect:".length());
            //Create RedirectView and perform redirection
            RedirectView view = new RedirectView(forwardUrl, this.isRedirectContextRelative(), this.isRedirectHttp10Compatible());
            String[] hosts = this.getRedirectHosts();
            if (hosts != null) {
                view.setHosts(hosts);
            }
            return this.applyLifecycleMethods("redirect:", view);
        } else if (viewName.startsWith("forward:")) {
            forwardUrl = viewName.substring("forward:".length());
            //Create an InternalResource and execute forwarding
            InternalResourceView view = new InternalResourceView(forwardUrl);
            return this.applyLifecycleMethods("forward:", view);
        } else {
            //In other cases, you can directly call the createView() method of the parent class for processing
            return super.createView(viewName, locale);
        }
    }
}

It can be seen from the source code that although the viewClass attribute is specified when configuring the UrlBasedViewResolver view parser, if the returned logical view name contains the prefix "forward:" it means that the UrlBasedViewResolver always uses the InternalResourceView view view; However, if the returned logical view name contains the prefix "redirect:" it means that the UrlBasedViewResolver always redirects using the RedirectView view.

The loadView() abstract method will be called inside the createView() method of AbstractCachingViewResolver (the parent class of UrlBasedViewResolver), which is implemented by UrlBasedViewResolver. The code is as follows:

protected View loadView(String viewName, Locale locale) throws Exception {
    AbstractUrlBasedView view = this.buildView(viewName);
    View result = this.applyLifecycleMethods(viewName, view);
    return view.checkResource(locale) ? result : null;
}

In loadView(), the buildView() method is actually called to create the View. The source code of buildView() method is as follows:

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
        Class<?> viewClass = this.getViewClass();
        Assert.state(viewClass != null, "No view class");
        AbstractUrlBasedView view = (AbstractUrlBasedView)BeanUtils.instantiateClass(viewClass);
        //According to prefix Use the suffix property and the view name to build the view URL
        view.setUrl(this.getPrefix() + viewName + this.getSuffix());
        view.setAttributesMap(this.getAttributesMap());
        String contentType = this.getContentType();
        //If the contentType property is set, set the contentType for the view
        if (contentType != null) {
            view.setContentType(contentType);
        }
        //If the contextattribute is set on the object, it is passed directly to the view
        String requestContextAttribute = this.getRequestContextAttribute();
        if (requestContextAttribute != null) {
            view.setRequestContextAttribute(requestContextAttribute);
        }
        //Set the exposePathVariables property for the view according to the configuration
        Boolean exposePathVariables = this.getExposePathVariables();
        if (exposePathVariables != null) {
            view.setExposePathVariables(exposePathVariables);
        }
        //Set the exposecontextbeansattributes attribute for the view according to the configuration
        Boolean exposeContextBeansAsAttributes = this.getExposeContextBeansAsAttributes();
        if (exposeContextBeansAsAttributes != null) {
            view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
        }
        //Set the exposedContextBeanName property for the view according to the configuration
        String[] exposedContextBeanNames = this.getExposedContextBeanNames();
        if (exposedContextBeanNames != null) {
            view.setExposedContextBeanNames(exposedContextBeanNames);
        }

        return view;
}

In addition to building the View URL according to prefix, suffix attribute and View name, the above source code also sets contentType, requestContextAttribute and attributesMap attributes for the View. These attributes are additional attributes that can be set when configuring UrlBasedViewResolver. contentType and reqeustContextAttribute are string attributes, attributesMap allows you to set a Map attribute, which will be directly passed to the View object created by UrlBasedViewResolver.

UrlBasedViewResolver also allows the following three properties to be set:

  • exposePathVariables: sets whether path variables are added to the Model corresponding to the view.
  • Exposecontextbeansattributes: if this attribute is set to true, it means that the Bean in the Spring container is exposed to the view page as a request attribute.
  • exposedContextBeanNames: set that only those beans in the Spring container (separated by English commas) are exposed to the view page as request attributes; If this attribute is not specified, all beans in the Spring container are exposed.
<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
    <!-- Using annotation driven -->
    <mvc:annotation-driven />
    <!-- Define scan loaded packages -->
    <context:component-scan base-package="com.ysy.springmvc.Controller" />
    <!-- Define view parser -->
    <bean class="org.springframework.web.servlet.view.UrlBasedViewResolver"
          p:prefix="/WEB-INF/content/"
          p:suffix=".jsp"
          p:viewClass="org.springframework.web.servlet.view.InternalResourceView"
          p:exposeContextBeansAsAttributes="true"
          p:exposedContextBeanNames="now,win"/>
</beans>
<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
    <bean id="userService" class="com.ysy.springmvc.Service.UserService"/>
    <bean id="now" class="java.util.Date"/>
    <bean id="win" class="javax.swing.JFrame" c:_0="My window"/>
</beans>

2.2 functions and usage of InternalResourceViewResolver

InternalResourceViewResolver is a subclass of UrlBasedViewResolver. The biggest difference between it and UrlBasedViewResolver is that when configuring InternalResourceViewResolver as a view parser, there is no need to specify viewClass attribute.

public InternalResourceViewResolver() {
    Class<?> viewClass = this.requiredViewClass();
    if (InternalResourceView.class == viewClass && jstlPresent) {
        viewClass = JstlView.class;
    }
    this.setViewClass(viewClass);
}
protected Class<?> requiredViewClass() {
    return InternalResourceView.class;
}

The advantage of InternalResourceView is that it can "intelligently" Select view classes.

  • If there is a JSTL class library in the class loading path, it uses JstlView as viewClass by default.
  • If there is no JSTL class library in the class loading path, it uses InternalResourceView as viewClass by default.

Although the InternalResourceViewResolver provides a "smart" default value for viewClass, the viewClass attribute can still be specified when configuring the view parser, and the explicitly configured viewClass attribute will override the default value of its "smart" selection.

In addition, you can also see the following code of buildView() method in the source code of InternalResourceViewResolver:

protected AbstractUrlBasedView buildView(String viewName) throws Exception {
    InternalResourceView view = (InternalResourceView)super.buildView(viewName);
    if (this.alwaysInclude != null) {
        view.setAlwaysInclude(this.alwaysInclude);
    }
    view.setPreventDispatchLoop(true);
    return view;
}

When using InternalResourceView, you can also configure an additional alwaysInclude attribute. If this attribute is set to true, it indicates that the program will include the view page instead of forward to the view page.

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
    <!-- Using annotation driven -->
    <mvc:annotation-driven />
    <!-- Define scan loaded packages -->
    <context:component-scan base-package="com.ysy.springmvc.Controller" />
    <!-- Define view parser -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/content/"
          p:suffix=".jsp"/>
</beans>

3. Redirection

3.1, redirect view

Both the UrlBasedView parser and the InternalResourceViewResolver parser support specifying a "redirect:" prefix for the view name, which allows the view parser to create a RedirectView to redirect the specified view.

In fact, you can also let the processing method of the controller directly return the RedirectView object, which forces the dispatcher servlet to perform redirection instead of using normal view parsing.

The function of the View parser is to parse the View object (View) according to the logical View name (String). If the processing method of the controller directly returns the View object (or encapsulates the View object with ModelAndView), it indicates that the processing method returns the View object, so the View parser is no longer needed to parse. Of course, it is not a good strategy for the processing method to directly return the View object, because this means that the processing method forms a hard coded coupling with the View, which is not conducive to project maintenance.

Whether redirection is performed using the prefix "redirect:" or explicitly using RedirectView, all data in the model will be appended to the URL and passed as request parameters. Since the request parameters appended to the URL can only be basic types or strings, complex types of data in the model cannot be passed normally.

In addition, the redirection of spring MVC is similar to the sendRedirect() method of HttpServletResponse. They both control the browser to regenerate a request, which is equivalent to re entering the URL address in the browser address bar to send the request. Therefore, the redirection cannot access the JSP page under / WEB-INF /.

@Controller
public class UserController {
    @Resource(name = "userService")
    private UserService userService;

    @PostMapping("/login")
    public View login(String username, String pass, Model model) {
        if (userService.userLogin(username,pass)>0){
            model.addAttribute("tip","Welcome, login succeeded!");
            return new RedirectView("success.jsp");
        }
        model.addAttribute("tip","Sorry, the user name and password you entered are incorrect!");
        return new RedirectView("error.jsp");
    }
}

Method uses RedirectView as the return value, which means that the controller will perform redirection. At this time, the data in the model will not be transmitted through the request attribute, but in the form of HTTP request parameters (appended to the redirected URL):

http://localhost:8080/success.jsp?tip=%3F%3F%3F%3F%3F%3F%3F%3F%21

From the results, we can see the characteristics of spring MVC redirection: resetting the address of the backward browser address bar to the redirected address, which indicates that it is a new request; The data in the model becomes the request parameter of the address in the address bar. Therefore, during redirection, the data in the model is no longer passed to the redirection target in the form of request attributes. You can't access any data using ${tip} on the page, and the output is blank.

3.2, transfer the data to the redirection target

Redirection solves the problem of "repeated submission" well, but it brings a new problem - it loses request attributes and request parameters. Although the redirection of Spring MVC improves one step: it will automatically splice the data in the model behind the forwarded URL. However, there are still two limitations to this improvement:

  • The model data appended to the URL can only be String or basic type and its wrapper class; Moreover, the length of model data appended to the URL is limited.
  • The model data appended after the URL is directly displayed in the address bar of the browser, which may cause security problems.

In order to solve the problem of data loss in the model after resetting, spring MVC proposes a solution of "Flash attribute". This solution is very simple: Spring MVC will temporarily save the data in the model before redirection, but then temporarily save the temporarily saved data after redirection, put the temporarily saved data into the new model, and then empty the temporarily stored data immediately.

Spring MVC uses FlashMap to save temporary data in the model. FlashMap inherits HashMap < string, Object >, which is actually a Map object; The FlashMapManager object is responsible for saving and putting model data. At present, spring MVC only provides a SessionFlashMapManager entity class for the FlashMapManager interface, which means that FlashMapManager will use session to temporarily store model data.

For each request, the FlashMapManager will maintain two FlashMap attributes, "input" and "output". The input attribute stores the data passed in by one request, while the output attribute stores the data to be passed to the next request.

Open SessionFlashMapManager and you can see the source code of the following two methods:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.web.servlet.support;

import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.util.WebUtils;

public class SessionFlashMapManager extends AbstractFlashMapManager {
    private static final String FLASH_MAPS_SESSION_ATTRIBUTE = SessionFlashMapManager.class.getName() + ".FLASH_MAPS";

    public SessionFlashMapManager() {
    }

    @Nullable
    protected List<FlashMap> retrieveFlashMaps(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        return session != null ? (List)session.getAttribute(FLASH_MAPS_SESSION_ATTRIBUTE) : null;
    }

    protected void updateFlashMaps(List<FlashMap> flashMaps, HttpServletRequest request, HttpServletResponse response) {
        WebUtils.setSessionAttribute(request, FLASH_MAPS_SESSION_ATTRIBUTE, !flashMaps.isEmpty() ? flashMaps : null);
    }

    protected Object getFlashMapsMutex(HttpServletRequest request) {
        return WebUtils.getSessionMutex(request.getSession());
    }
}

In actual development, there are two ways to transfer data by using "Flash attribute":

  • For the traditional controller method without annotation, the program can obtain the FlashMap object through getInputFlashMap() or getOutpuFlashMap() method of RequestContextUtils class.
FlashMap flashMap=RequestContextUtils.getOutputFlashMap(request);
FlashMap flashMap=RequestContextUtils.getInputFlashMap(request);
  • For the controller method decorated with annotations, spring MVC provides a RedirectAttributes interface to operate the "Flash attributes".

RedirectAttributes inherits the Model interface, which shows that it is a special and more powerful Model interface. In fact, RedirectAttribute mainly provides the following two methods:

  • addAttribute(Object attributeValue): equivalent to the addAttribute() method of Model, using the automatically generated attribute name.
  • addAttribute(String attributeName, Object attributeValue): equivalent to the addAttribute() method of Model.
  • addFlashAttribute(Object attributeValue): add the attribute to the "Flash attribute" and use the automatically generated attribute name.
  • addFlashAttribute(String attributeName, Object attributeValue): add the attribute to "Flash attribute", and use the attribute name specified by the attributeName parameter.

In terms of method, RedirectAttributes can completely replace model. When addAttribute() method is called to add attributes, its function is the same as that of the method in model interface, and it still only adds attributes to model; When the addFlashAttribute() method is called to add attributes, the FlashMapManager is responsible for storing and fetching these data, which can ensure that the attributes added through the addFlashAttribute() method will not be lost during redirection.

For developers, as long as they remember that RedirectAttribute is an enhanced model interface, they can completely replace the model interface with it; If you want to forward valid data only after calling the addmodel () method; If you want the model data to be valid after the reset, you need to call the addFlashAttribute() method.

The main function of the Model interface is to transfer data as a Model, and its relationship with ModelMap, RedirectAttributes and their implementation classes:

The Model interface is the whole inheritance system and interface. Whether it is the RedirectAttributes interface, or the implementation classes of RedirectAttributesModeMap and extendedmodemap, they either inherit the Model interface or implement the Model interface.

ModelMap is spring 2 0 defective products with insufficient introduction and design. It is a class in itself and implements the Model interface, but it also implements almost all the methods in the Model interface. From the perspective of interface oriented programming, it is recommended to use the Model interface or redirectattributes interface as the Model to transfer data.

In fact, if you use the Model interface or the ModelMap class as the Model, the bottom layer of spring MVC will use ExtendModelMap as the specific implementation. Therefore, the processing method of the controller is still recommended to be oriented to the Model interface programming, which has more flexibility.

@Controller
public class UserController {
    @Resource(name = "userService")
    private UserService userService;

    @PostMapping("/login")
    public View login(String username, String pass, RedirectAttributes attrs) {
        if (userService.userLogin(username,pass)>0){
            attrs.addFlashAttribute("tip","Welcome, login succeeded!");
            return new RedirectView("success");
        }
        attrs.addFlashAttribute("tip","Sorry, the user name and password you entered are incorrect!");
        return new RedirectView("error");
    }
}

4. Chain processing of other view parsers and view parsers

4.1. Chain processing of view parser

Although InternalResovler is very simple to use, it is a very "domineering" view Parser - it will try to parse all logical view names. For example, if the controller's processing method returns the "ysy" string (logical view name), it will always parse / WEB-INF / content / ysy JSP as the view resource of the view name - in fact, there may be no / WEB-INF/content/roma.jsp in the application at all JSP, the application wants to use other view pages to display the "ysy" logical view.

If you want multiple view parsing logic in the application, you can configure multiple view parsers in the spring MVC container, and multiple view parsers will form a chain process:

The function of View parser is to parse the incoming String object (logical View name) into View object (actual View). As can be seen from the above figure, only when the parsing result of View parser A is null, will the View name be passed to View parser B (next) to continue parsing - as long as any View parser on the View parser chain parses the String object into View object, The parsing is over, and the View name will not be passed to the next View parser for parsing.

All view parsers implement the Ordered interface and the getOrder() method in the interface. The return value of this method determines the order of the view Parser - the larger the order value is, the more it is ranked behind the parser chain. Therefore, view parsers are allowed to configure an order attribute, which represents the order value of the view parser.

Because the InternalResourceViewResolver is too "overbearing", you need to set the order attribute of the view parser to the maximum to ensure that the view parser is at the back; Otherwise, the view parser behind the InternalResourceViewResolver has no chance to execute at all.

4.2,XmlViewResolver

Next, add a link in the page. The link is used to view some of the author's books, but the request wants to generate an Excel document as a view, so the InternalResourceViewResolver must be confused, because it is not responsible for the resolution of a single view name, but for the resolution of most view names in the application. Therefore, it always performs parsing according to the "unified" rules, and always adds "/ WEB-INF/content /" prefix and ". jsp" suffix to the logical view name.

At this time, consider using XmlViewResolver and InternalResourceViewResolver to form a view parser chain, and let XmlViewResolver be responsible for resolving those special logical view names. For the logical view names that cannot be resolved by XmlViewResolver, it will be released to the later InternalResourceViewResolver for processing.

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
    <!-- Using annotation driven -->
    <mvc:annotation-driven/>
    <!-- Define scan loaded packages -->
    <context:component-scan base-package="com.ysy.springmvc.Controller"/>
    <!-- Define view parser -->
    <mvc:annotation-driven ignore-default-model-on-redirect="true"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/content/"
          p:suffix=".jsp"
          p:order="10"/>
    <bean class="org.springframework.web.servlet.view.XmlViewResolver"
          p:location="/WEB-INF/views.xml"
          p:order="1"/>
</beans>

So how exactly does XmlViewResolver resolve view names? Open the source code of this class and you can see the loadView() method:

protected View loadView(String viewName, Locale locale) throws BeansException {
    //Create a Spring container according to the configuration file specified by the location attribute
    BeanFactory factory = this.initFactory();
    try {
        //Directly find the Bean whose container id is viewName as the View
        return (View)factory.getBean(viewName, View.class);
    } catch (NoSuchBeanDefinitionException var5) {
        //If the corresponding Bean cannot be found, null is returned and released to the next view
        return null;
    }
}

XmlViewResolver needs to create a Spring container based on the location parameter -- therefore, the location parameter is set when configuring the XmlViewResolver parser above. The Spring container created by XmlViewResolver is a brand-new container. It is neither the Root container nor the Servlet container of Spring MVC. This brand-new container has no relationship with the Root container and the Servlet container of Spring MVC.

After creating this new Spring container, XmlViewResolver directly returns the Bean with id of viewName in the container as the parsed View -- which means that all beans in the Spring container should be View instances. If the controller's processing method returns the logical View name of "books", the XmlViewResolver will directly find the Bean with id of books from this container as the parsed View.

@GetMapping("/viewBooks")
public String viewBooks(Model model){
    List bookList = new LinkedList();
    bookList.add("Yan Shuangying");bookList.add("Duma");bookList.add("Step Eagle");
    model.addAttribute("books",bookList);
    return "books";
}
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans.xsd">
	<bean id="books" class="com.ysy.springmvc.View.BookExcelDoc"
		p:sheetName="XmlViewResolver"/>
</beans>

public class BookExcelDoc extends AbstractXlsView {
    private String sheetName;

    public void setSheetName(String sheetName) {
        this.sheetName = sheetName;
    }

    @SuppressWarnings("unchecked")
    public void buildExcelDocument(Map<String, Object> model,
                                   Workbook workbook, HttpServletRequest request,
                                   HttpServletResponse response) {
        // Create the first page and set the page label
        Sheet sheet = workbook.createSheet(this.sheetName);
        //Set default column width
        sheet.setDefaultColumnWidth(20);
        // Locate the first cell, A1
        Cell cell = sheet.createRow(0).createCell(0);
        cell.setCellValue("Spring-Excel test");
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        // Set to use red font
        font.setColor(Font.COLOR_RED);
        // Set double underline below font
        font.setUnderline(Font.U_DOUBLE);
        style.setFont(font);
        // Style cells
        cell.setCellStyle(style);
        // Get data in Model
        List<String> books = (List<String>) model.get("books");
        // Populate the Excel table with data from the Model
        for (int i = 0; i < books.size(); i++) {
            Cell c = sheet.createRow(i + 1).createCell(0);
            c.setCellValue(books.get(i));
        }
    }
}

4.3. Functions and usage of ResourceBundleViewResolver

The essence of ResourceBundleViewResolver is the same as that of XmlViewResolver. They both create a brand-new Spring container, and then obtain the Bean with id of viewName in the container as the parsed View - if you View the source code of ResourceBundleViewResolver, you will find that its loadView() method is almost the same as that of XmlViewResolver, This shows that their method of parsing View is exactly the same.

The difference between ResourceBundleViewResolver and XmlViewResolver is mainly reflected in the way of creating Spring containers - XmlViewResolver needs to provide a configuration file, so it can directly use the configuration file to create Spring containers; The ResourceBundleViewResolver requires a property file (*. properties file), so it needs to create a Spring container based on the property file, which is more complex.

The functions of ResourceBundleViewResource and XmlViewResource parsers are almost identical. The difference is that the format of the configuration file is different.

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
    <!-- Using annotation driven -->
    <mvc:annotation-driven/>
    <!-- Define scan loaded packages -->
    <context:component-scan base-package="com.ysy.springmvc.Controller"/>
    <!-- Define view parser -->
    <mvc:annotation-driven ignore-default-model-on-redirect="true"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/content/"
          p:suffix=".jsp"
          p:order="10"/>
    <bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver"
          p:basename="view"
          p:order="1"/>
</beans>
# Configure the implementation class of books Bean
books.(class)=com.ysy.springmvc.View.BookExcelDoc
# Specify a value for the sheetName property of books Bean
books.sheetName=ResourceBundleViewResolver

4.4 functions and usage of BeanNameViewResolver

BeanNameViewResolver is a crude version of XmlViewResolver: XmlViewResolver will create a new Spring container to manage all View objects, but BeanNameViewResolver is more lazy. It does not create a Spring container, but directly obtains the Bean with id viewName from the existing container as the parsed View.

The code of resolveViewName() method in beannameviewresolver class is as follows:

public View resolveViewName(String viewName, Locale locale) throws BeansException {
    //Get the existing Spring container directly
    ApplicationContext context = this.obtainApplicationContext();
    //If the container does not contain a Bean with id viewName
    if (!context.containsBean(viewName)) {
        //null is returned, which means that the view is handed over to the next view parser for processing
        return null;
    //The corresponding instance of beawview class is required
    } else if (!context.isTypeMatch(viewName, View.class)) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Found bean named '" + viewName + "' but it does not implement View");
        }
        return null;
    } else {
        //The Bean with id of viewName and type of View in the container is returned as the resolved View
        return (View)context.getBean(viewName, View.class);
    }
}

After understanding the principle of BeanNameViewResolver, it is not difficult to understand why it is called a crude version of XmlViewResolver. However, from the perspective of system design, BeanNameViewResolver is worse than XmlViewResolver. XmlViewResolver uses special configuration files and Spring containers to manage view beans, but BeanNameViewResolver directly uses the Spring configuration files and Spring containers of the whole application to manage beans, which is obviously a little confused.

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
    <!-- Using annotation driven -->
    <mvc:annotation-driven/>
    <!-- Define scan loaded packages -->
    <context:component-scan base-package="com.ysy.springmvc.Controller"/>
    <!-- Define view parser -->
    <mvc:annotation-driven ignore-default-model-on-redirect="true"/>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/content/"
          p:suffix=".jsp"
          p:order="10"/>
    <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"
          p:order="1"/>
    <bean id="books" class="com.ysy.springmvc.View.BookExcelDoc"
          p:sheetName="XmlViewResolver"/>
</beans>

4.5 function and usage of ContentNegotiatingViewResolver

Strictly speaking, content negotiatingviewresolver is not A real View parser, because it is not responsible for the actual View parsing. It is just A proxy for multiple View parsers. When A logical View name arrives, the ContentNegotiatingViewResolver does not directly parse the actual View, but intelligently "distributes" it to some View parsers A, B and C in the system Wait.

So how does the ContentNegotiatingViewResolver know how to "distribute" logical view names? It is distributed according to the content type of the request. For example, if the content type of the user's request is application/json, it will distribute the request to the parser that returns the JSON view for processing; The contentType of the user's request is text/html, which will distribute the request to the parser that returns the HTML view for processing.

This raises two more questions:

  • How does the ContentNegotiatingViewResolver determine the contentType of the request?
  • How does the content negotiatingviewresolver know which view parsers are included in the system?

For the first question, the ContentNegotiatingViewResolver judges the requested contentType in three ways:

  • According to the suffix of the request, for example, the suffix of the request is json, which determines that the contentType of the request is application/json; The suffix requested by the user is xls, which determines that the content type of the request is application / vnd ms-excel...... And so on.
  • According to the request parameter (usually the format parameter), for example, the request parameter is / aa?format=json, which determines that the contentType of the request is application/json; The requested parameter is / aa?format=xls, which determines that the contentType of the request is application / vnd ms-excel..... And so on. This method is turned off by default. You need to set the favorparameter parameter to true to turn on this judgment method.
  • According to the Accept request header of the request, for example, the Accept request header of the request contains text/html, it determines that the content type of the request is text/html. This judgment method may have problems, especially when the user sends a request using the browser. The Accept request header is completely controlled by the browser, and the user cannot change this request header.

For the second question, ContentNegotiatingViewResolver has two methods to determine which view parsers are included in the system:

  • The display is configured through the viewResolvers property, which can accept a List property value, so that the view resolvers forwarded by ContentNegotiatingViewResolver can be explicitly listed.
  • ContentNegotiatingViewResolver will also automatically scan all beans in the Spring container, and it will automatically treat the ViewResolver implementation classes as available for contentneigatingviewresolver.

Example: a user can add to viewbooks json,viewBooks.xls,viewBooks.pdf and viewbooks (no suffix) send requests. The addresses of these four requests are the same (only the suffixes are different), so the program will use the same processing method to process the request and return the same logical view name.

Because the user to viewbooks json,viewBooks.xls,viewBooks.pdf and viewbooks (no suffix) send requests. You must want to see JSON, Excel document, PDF document and JSP response respectively. At this time, it is the turn of ContentNegotiatingViewResolver, which will send the logical view name to different view resolvers according to the content type requested by the user.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<body>
<h2>Hello World!</h2>
    <a href="${pageContext.request.contextPath}/viewBooks.pdf">pdf</a>
    <a href="${pageContext.request.contextPath}/viewBooks.xls">excel</a>
    <a href="${pageContext.request.contextPath}/viewBooks.json">json</a>
    <a href="${pageContext.request.contextPath}/viewBooks">jsp</a>
</body>
</html>
@Controller
public class BookController {
    @GetMapping("/viewBooks")
    public String viewBooks(Model model){
        ArrayList bookList = new ArrayList();
        bookList.add("Yan Shuangying");bookList.add("Duma");bookList.add("Step Eagle");
        model.addAttribute("books",bookList);
        return "books";
    }
}

This processing method always returns the logical view name of "books". In order to make the logical view name correspond to different views, you need to configure the ContentNegotiatingViewResolver parser in the configuration file of spring MVC.

<?xml version='1.0' encoding='UTF-8' ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
    <!-- Using annotation driven -->
    <mvc:annotation-driven/>
    <!-- Define scan loaded packages -->
    <context:component-scan base-package="com.ysy.springmvc.Controller"/>
    <!-- Define view parser -->
    <mvc:annotation-driven ignore-default-model-on-redirect="true"/>
    <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="viewResolvers">
            <list>
                <ref bean="jspResovler"/>
                <bean class="com.ysy.springmvc.View.PdfViewResolver" p:viewPackage="com.ysy.springmvc.View"/>
                <bean class="com.ysy.springmvc.View.ExcelViewResolver" p:viewPackage="com.ysy.springmvc.View"/>
                <bean class="com.ysy.springmvc.View.JsonViewResolver" p:viewPackage="com.ysy.springmvc.View"/>
            </list>
        </property>
    </bean>
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
          p:prefix="/WEB-INF/content/"
          p:suffix=".jsp"/>
</beans>