ViewResolver Overview

The role of ViewResolver component is to resolve the corresponding view View object based on the view name and the localized Locale.

In the SpringMvc source code, ViewResolver is an interface. This interface defines one method.

  • View resolveViewName(String viewName, Locale locale)

The interface method takes two parameters viewName and locale, viewName is the view name and locale is the localization parameter. In general, we only need the viewName to find the corresponding view. In internationalization scenarios, the view can be displayed in different languages according to different locales.

ViewResolver class system

ViewResolver class system

As you can see from the above class diagram, the four classes AbstractCachingViewResolver, BeanNameViewResolver, ContentNegotiatingViewResolver and ViewResolverComposite inherit directly from the ViewResolver interface, all of them have only one level of inheritance structure.

Only the AbstractCachingViewResolver class has a relatively complex inheritance relationship, and the AbstractCachingViewResolver class can cache parsed views.

AbstractCachingViewResolver series

AbstractCachingViewResolver

AbstractCachingViewResolver class is an abstract class. It provides a caching function for the View, when the view is parsed once it is cached, until the cached view is deleted. View resolution is automatically fetched from the cache.

AbstractCachingViewResolver class inherits from the WebApplicationObjectSupport parent class and implements the ViewResolver interface.

AbstractCachingViewResolver class implements the resolveViewName method of the ViewResolver interface.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
    if (!isCache()) {
        return createView(viewName, locale);
    }
    else {
        Object cacheKey = getCacheKey(viewName, locale);
        View view = this.viewAccessCache.get(cacheKey);
        if (view == null) {
            synchronized (this.viewCreationCache) {
                view = this.viewCreationCache.get(cacheKey);
                if (view == null) {
                    // Ask the subclass to create the View object.
                    view = createView(viewName, locale);
                    if (view == null && this.cacheUnresolved) {
                        view = UNRESOLVED_VIEW;
                    }
                    if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
                        this.viewAccessCache.put(cacheKey, view);
                        this.viewCreationCache.put(cacheKey, view);
                    }
                }
            }
        }
        else {
            if (logger.isTraceEnabled()) {
                logger.trace(formatKey(cacheKey) + "served from cache");
            }
        }
        return (view != UNRESOLVED_VIEW ? view : null);
    }
}

The resolveViewName method does the following main things.

  • The first thing is to determine if caching is enabled. If caching is not enabled, we call createView(viewName, locale) method directly to create a view.
  • If caching is enabled, first get the key value of the view cache. Then use the key value to get the view in the viewAccessCache cache. If the view exists, it is returned directly.
  • If the view does not exist in the viewAccessCache cache, then go to the viewCreationCache cache to find the view, and return the view if found.
  • If the view does not exist in the viewCreationCache cache, call the createView(viewName, locale) method again to create a view and return it. And put the created view into the viewAccessCache and viewCreationCache cache.

In the createView method, the loadView method is actually called again. loadView is an abstract method in the AbstractCachingViewResolver class and is reserved for subclasses to implement their own logic.

View caching in AbstractCachingViewResolver

In the AbstractCachingViewResolver class, two HashMap, viewAccessCache and viewCreationCache, are used for caching.

viewCreationCache is of type LinkedHashMap. LinkedHashMap is a combination of HashMap and Doubly linked list, which ensures that the values are ordered by maintaining a Doubly linked list.

LinkedHashMap has a removeEldestEntry method, which returns a boolean value. When the return value is true, LinkedHashMap will automatically remove the top Map value. This method is triggered when the put and putAll methods are called.

In the AbstractCachingViewResolver class, the viewCreationCache is defined in this way. viewCreationCache overrides the parent class’s removeEldestEntry method. In this method, it determines whether the number of views exceeds the limit. If the limit is exceeded, the first cached view is removed from the viewAccessCache cache. Then it returns true, and the viewCreationCache brakes the deletion of the view. This combines the advantages of each of the two types of maps.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
/** Map from view key to View instance, synchronized for View creation. */
@SuppressWarnings("serial")
private final Map<Object, View> viewCreationCache =
        new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
                if (size() > getCacheLimit()) {
                    viewAccessCache.remove(eldest.getKey());
                    return true;
                }
                else {
                    return false;
                }
            }
        };

UrlBasedViewResolver

UrlBasedViewResolver inherits directly from the AbstractCachingViewResolver class. It is a more basic implementation of the viewResolvers interface. It provides a URL stitching approach to parsing views.

The UrlBasedViewResolver class can set a prefix and a suffix respectively through the prefix and suffix properties, and the URL of the final view is the prefix + logical view + suffix.

It mainly overrides the parent class’ getCacheKey, createView and loadView methods.

getCacheKey

1
2
3
4
5
6
7
8
/**
    * This implementation returns just the view name,
    * as this ViewResolver doesn't support localized resolution.
    */
@Override
protected Object getCacheKey(String viewName, Locale locale) {
    return viewName;
}

The getCacheKey method returns the viewName directly, discarding the locale parameter. It can be seen that UrlBasedViewResolver class does not support Locale.

createView

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
    * Overridden to implement check for "redirect:" prefix.
    * <p>Not possible in {@code loadView}, since overridden
    * {@code loadView} versions in subclasses might rely on the
    * superclass always creating instances of the required view class.
    * @see #loadView
    * @see #requiredViewClass
    */
@Override
protected View createView(String viewName, Locale locale) throws Exception {
    // If this resolver is not supposed to handle the given view,
    // return null to pass on to the next resolver in the chain.
    if (!canHandle(viewName, locale)) {
        return null;
    }

    // Check for special "redirect:" prefix.
    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
        String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
        RedirectView view = new RedirectView(redirectUrl,
                isRedirectContextRelative(), isRedirectHttp10Compatible());
        String[] hosts = getRedirectHosts();
        if (hosts != null) {
            view.setHosts(hosts);
        }
        return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
    }

    // Check for special "forward:" prefix.
    if (viewName.startsWith(FORWARD_URL_PREFIX)) {
        String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
        InternalResourceView view = new InternalResourceView(forwardUrl);
        return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
    }

    // Else fall back to superclass implementation: calling loadView.
    return super.createView(viewName, locale);
}

The createView method does the following four main things.

  • In the canHandle method, checks if the view can be resolved by the view name.
  • Determines if the view name starts with “redirect:”, and if so, creates a view of type RedirectView to return. redirectView views are used for redirection.
  • Then determine if the view name starts with “forward:”, if so, create a forward view of type InternalResourceView and return it. forwardView is used for redirection.
  • If it is not one of the above 3 cases, then the parent class’s createView method is called. The createView method of the parent class in turn calls the loadView method.

loadView

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
/**
    * Delegates to {@code buildView} for creating a new instance of the
    * specified view class. Applies the following Spring lifecycle methods
    * (as supported by the generic Spring bean factory):
    * <ul>
    * <li>ApplicationContextAware's {@code setApplicationContext}
    * <li>InitializingBean's {@code afterPropertiesSet}
    * </ul>
    * @param viewName the name of the view to retrieve
    * @return the View instance
    * @throws Exception if the view couldn't be resolved
    * @see #buildView(String)
    * @see org.springframework.context.ApplicationContextAware#setApplicationContext
    * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
    */
@Override
protected View loadView(String viewName, Locale locale) throws Exception {
    AbstractUrlBasedView view = buildView(viewName);
    View result = applyLifecycleMethods(viewName, view);
    return (view.checkResource(locale) ? result : null);
}

Three main things are done in the loadView method.

  • Create a view using the buildView method. The buildView method uses the org.springframework.beans.BeanUtils.instantiateClass method to create an instance of the AbstractUrlBasedView type, depending on the type of the view. After the instance of AbstractUrlBasedView is created, some properties are set for this instance.

    The url property of the view is set here. The url is a combination of the prefix plus the view name plus the suffix.

  • Use the applyLifecycleMethods method to set the initialization of the created view.

  • Determine if the view template exists, return if it exists, return the NULL value if it does not.

UrlBasedViewResolver implements the basic functionality of view parsing. As a subclass of UrlBasedViewResolver class, it only needs to override two methods of the parent class to implement its own functionality.

  • The class overrides the requiredViewClass method of the parent class, and returns its own type in this method.
  • The class overrides the buildView method of the parent class to first call the buildView method of the parent class to get a View object and then set its own unique properties for that pair.

InternalResourceViewResolver

The InternalResourceViewResolver class inherits from the UrlBasedViewResolver class, and the Spring MVC container initializes the InternalResourceViewResolver component when it initializes components of the ViewResolver type. InternalResourceViewResolver is also one of the most widely used view resolvers in practice. internalResourceViewResolver resolves all returned view names to InternalResourceView objects.

The InternalResourceViewResolver class overrides the requiredViewClass method of the parent class and returns the InternalResourceView type.

The InternalResourceViewResolver class overrides the buildView method of the parent class. In the buildView method, the parent class is first called to build an InternalResourceView object. Then set the values of the object InternalResourceView’s alwaysInclude, preventDispatchLoop properties.

ViewResolverComposite

The ViewResolverComposite class inherits from the ViewResolver interface. As you know from the ViewResolverComposite class name, it is a collection of ViewResolver.

One property of the ViewResolverComposite class is viewResolvers, which is a collection of ViewResolver types. We can set the value of this property.

The ViewResolverComposite class implements the resolveViewName method of the ViewResolver interface. In the resolveViewName method, the program loops through the collection of viewResolvers, targeting each instance of the ViewResolver in the collection. The resolveViewName method of each instance is called until a View is obtained. The code is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
    for (ViewResolver viewResolver : this.viewResolvers) {
        View view = viewResolver.resolveViewName(viewName, locale);
        if (view != null) {
            return view;
        }
    }
    return null;
}

Reference https://www.cnblogs.com/YaoxTao/p/16687005.html