In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article mainly introduces "how to understand ViewResolver components". In daily operation, I believe many people have doubts about how to understand ViewResolver components. The editor consulted all kinds of materials and sorted out simple and easy-to-use operation methods. I hope it will be helpful for you to answer the doubts about "how to understand ViewResolver components"! Next, please follow the editor to study!
1. Overview
First, let's take a look at what the ViewResolver interface looks like:
Public interface ViewResolver {@ Nullable View resolveViewName (String viewName, Locale locale) throws Exception;}
There is only one method in this interface, and as you can see, it is very simple to find the corresponding View and return it through the view name and Locale.
As shown in the figure, there are four classes that directly inherit from the ViewResolver interface, and the functions are as follows:
ContentNegotiatingViewResolver: a view parser that supports MediaType and suffixes.
BeanNameViewResolver: this is directly based on the view name to go to the Spring container to find the corresponding Bean and return.
AbstractCachingViewResolver: a view parser with caching capabilities.
ViewResolverComposite: this is a combined view parser that can then be used to proxy other work-specific view parsers.
Let's take a look at each of these four view parsers, starting with the simplest BeanNameViewResolver.
2.BeanNameViewResolver
The processing method of BeanNameViewResolver is very simple and rough. Directly look up the corresponding Bean in the Spring container according to viewName and return it, as follows:
@ Override@Nullablepublic View resolveViewName (String viewName, Locale locale) throws BeansException {ApplicationContext context = obtainApplicationContext (); if (! context.containsBean (viewName)) {return null;} if (! context.isTypeMatch (viewName, View.class)) {return null;} return context.getBean (viewName, View.class);}
First to determine whether there is a corresponding Bean, and then check whether the type of Bean is correct, no problem, just look for it and return it.
3.ContentNegotiatingViewResolver
ContentNegotiatingViewResolver is actually a widely used view parser, mainly adding support for MediaType. ContentNegotiatingViewResolver, a view parser introduced in Spring3.0, is not responsible for specific view parsing, but selects an appropriate view parser from the context and delegates the request work to it according to the MIME type of the current request.
Let's first take a look at the ContentNegotiatingViewResolver#resolveViewName method:
Public View resolveViewName (String viewName, Locale locale) throws Exception {RequestAttributes attrs = RequestContextHolder.getRequestAttributes (); List requestedMediaTypes = getMediaTypes (ServletRequestAttributes) attrs). GetRequest ()); if (requestedMediaTypes! = null) {List candidateViews = getCandidateViews (viewName, locale, requestedMediaTypes); View bestView = getBestView (candidateViews, requestedMediaTypes, attrs); if (bestView! = null) {return bestView }} if (this.useNotAcceptableStatusCode) {return NOT_ACCEPTABLE_VIEW;} else {return null;}}
The code logic here is also relatively simple:
The first step is to get the current request object, which can be obtained directly from the RequestContextHolder. The MediaType is then extracted from the current request object.
If MediaType is not null, the appropriate view parser is found based on MediaType, and the parsed View is returned.
If MediaType is null, there are two cases. If useNotAcceptableStatusCode is true, the NOT_ACCEPTABLE_VIEW view is returned. This view is actually a 406 response, indicating a client error, and the server cannot provide a response that matches the values specified in the Accept-Charset and Accept-Language message headers. If useNotAcceptableStatusCode is false, null is returned.
Now the core of the problem is actually the getCandidateViews method and the getBestView method. You can see by the name that the former is to get all the candidate View, while the latter is to choose the best View from these candidate View. Let's look at it one by one.
Let's first take a look at getCandidateViews:
Private List getCandidateViews (String viewName, Locale locale, List requestedMediaTypes) throws Exception {List candidateViews = new ArrayList (); if (this.viewResolvers! = null) {for (ViewResolver viewResolver: this.viewResolvers) {View view = viewResolver.resolveViewName (viewName, locale) If (view! = null) {candidateViews.add (view);} for (MediaType requestedMediaType: requestedMediaTypes) {List extensions = this.contentNegotiationManager.resolveFileExtensions (requestedMediaType) For (String extension: extensions) {String viewNameWithExtension = viewName +'.'+ extension; view = viewResolver.resolveViewName (viewNameWithExtension, locale) If (view! = null) {candidateViews.add (view) } if (! CollectionUtils.isEmpty (this.defaultViews)) {candidateViews.addAll (this.defaultViews);} return candidateViews;}
There are two steps to get all candidate View:
Call the resolveViewName method in each ViewResolver to load the corresponding View object.
Extract the extension according to MediaType, and then load the View object according to the extension. In practical application, we rarely configure this step, so one step basically can not load the View object, mainly depends on the first step.
The first step to load the View object is to find the corresponding View according to your viewName, combined with the prefix, suffix, templateLocation and other properties configured in ViewResolver. The method execution process is resolveViewName- > createView- > loadView.
I won't post the specific implementation methods one by one. The only point I need to say is the final loadView method. Let's take a look at this method:
Protected View loadView (String viewName, Locale locale) throws Exception {AbstractUrlBasedView view = buildView (viewName); View result = applyLifecycleMethods (viewName, view); return (view.checkResource (locale)? Result: null);}
In this method, after View is loaded, it calls its checkResource method to determine whether the View exists, returns View if it exists, and returns null if it does not exist.
This is a critical step, but our common views handle it differently:
FreeMarkerView: will check honestly.
ThymeleafView: this is not checked (the entire View architecture of Thymeleaf is different from FreeMarkerView and JstlView).
JstlView: check results always return true.
So far, we have found all the candidate View, but we need to note that this candidate View does not necessarily exist, in the case of Thymeleaf, the returned candidate View may not be available, and in JstlView, the candidate View may not really exist.
Next, call the getBestView method to find the best View from all the candidate View. The logic of the getBestView method is relatively simple, that is, look at the MediaType of all the View, and then match with the requested MediaType array, the first match is the best View, this process does not check whether the view really exists, so it is possible to select a view that does not exist at all, resulting in 404.
This is how the ContentNegotiatingViewResolver#resolveViewName method works.
So here's another question: where does the ViewResolver in ContentNegotiatingViewResolver come from? This comes from two sources: default and manually configured. Let's look at the following initialization code:
@ Overrideprotected void initServletContext (ServletContext servletContext) {Collection matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors (obtainApplicationContext (), ViewResolver.class) .values (); if (this.viewResolvers = = null) {this.viewResolvers = new ArrayList (matchingBeans.size ()) For (ViewResolver viewResolver: matchingBeans) {if (this! = viewResolver) {this.viewResolvers.add (viewResolver);} else {for (int I = 0; I < this.viewResolvers.size () ViewResolver vr +) {ViewResolver vr = this.viewResolvers.get (I); if (matchingBeans.contains (vr)) {continue;} String name = vr.getClass () .getName () + I ObtainApplicationContext (). GetAutowireCapableBeanFactory (). InitializeBean (vr, name);}} AnnotationAwareOrderComparator.sort (this.viewResolvers); this.cnmFactoryBean.setServletContext (servletContext);}
First get the matchingBeans, which gets all the view parsers in the Spring container.
If the viewResolvers variable is null, that is, the developer has not configured the view parser for ContentNegotiatingViewResolver, the found matchingBeans will be assigned to viewResolvers at this time.
If the developer has configured the relevant view parsers for ContentNegotiatingViewResolver, check to see if these view parsers exist in matchingBeans, and if not, initialize them.
That's what ContentNegotiatingViewResolver does.
4.AbstractCachingViewResolver
A feature of this kind of file is that once it is developed, it does not change much, so it is particularly important to cache it to improve the loading speed. In fact, most of the view parsers we use support caching, which means that AbstractCachingViewResolver actually has a lot of opportunities to use its talents.
Let's take a quick look at AbstractCachingViewResolver and then learn about its subclasses.
@ Override@Nullablepublic 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) {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 {} return (view! = UNRESOLVED_VIEW? View: null);}}
First of all, if caching is not turned on, the createView method is called directly to create the view and return.
Call the getCacheKey method to get the cached key.
Go to viewAccessCache to find the cache View, and return it directly if you find it.
Go to viewCreationCache to find the cache View, find it and return it directly. If you don't find it, call the createView method to create a new View and put the View in two cache pools.
There are two cache pools. The difference between the two cache pools is that the type of viewAccessCache is ConcurrentHashMap and the type of viewCreationCache is LinkedHashMap. The former supports concurrent access and is very efficient, while the latter limits the maximum number of caches and is less efficient than the former. When the number of caches of the latter reaches the limit, the elements in it will be deleted automatically, and in the process of deleting its own elements, the corresponding elements in the former viewAccessCache will also be deleted.
So there's another method involved here, which is createView. Let's take a look at it a little bit:
@ Nullableprotected View createView (String viewName, Locale locale) throws Exception {return loadView (viewName, locale);} @ Nullableprotected abstract View loadView (String viewName, Locale locale) throws Exception
As you can see, loadView is called in createView, while loadView is an abstract method, and the concrete implementation needs to be checked in the subclass.
This is the lookup process of caching View.
There are four kinds of view parsers that directly inherit AbstractCachingViewResolver: ResourceBundleViewResolver, XmlViewResolver, UrlBasedViewResolver and ThymeleafViewResolver, the first two of which have been abandoned since Spring5.3, so Song GE won't introduce much here, let's mainly take a look at the latter two.
4.1 UrlBasedViewResolver
UrlBasedViewResolver overrides the getCacheKey, createView, and loadView methods of the parent class:
GetCacheKey
@ Overrideprotected Object getCacheKey (String viewName, Locale locale) {return viewName;}
The getCacheKey of the parent class is viewName +'_'+ locale, which is now viewName.
CreateView
@ Overrideprotected View createView (String viewName, Locale locale) throws Exception {if (! canHandle (viewName, locale)) {return null;} 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);} 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);} return super.createView (viewName, locale);}
First, call the canHandle method to determine whether the logical view here is supported.
Next, determine whether the logical view name prefix is redirect:,. If so, it means that this is a redirected view, and a RedirectView is constructed to process it.
Next, determine whether the logical view name prefix is forward:,. If so, it means that this is a server jump, and an InternalResourceView is constructed to process it.
If none of the above, the createView method of the parent class is called to build the view, which eventually calls the loadView method of the child class.
LoadView
@ Overrideprotected View loadView (String viewName, Locale locale) throws Exception {AbstractUrlBasedView view = buildView (viewName); View result = applyLifecycleMethods (viewName, view); return (view.checkResource (locale)? Result: null);}
Three things have been done here:
Call the buildView method to build the View.
Call the applyLifecycleMethods method to complete the initialization of the View.
Check whether the car View exists and return.
The third step is relatively simple, there is nothing to say, the main thing is to check whether the view file exists, like our commonly used Jsp view parser and Freemarker view parser will check, but Thymeleaf will not check (for more information: how multiple view parsers exist at the same time in SpringMVC). Here are mainly the first two steps. Brother Song would like to focus on it with you. Here, two more methods, buildView and applyLifecycleMethods, are involved.
4.1.1 buildView
This method is used to build views:
Protected AbstractUrlBasedView buildView (String viewName) throws Exception {AbstractUrlBasedView view = instantiateView (); view.setUrl (getPrefix () + viewName + getSuffix ()); view.setAttributesMap (getAttributesMap ()); String contentType = getContentType (); if (contentType! = null) {view.setContentType (contentType);} String requestContextAttribute = getRequestContextAttribute () If (requestContextAttribute! = null) {view.setRequestContextAttribute (requestContextAttribute);} Boolean exposePathVariables = getExposePathVariables (); if (exposePathVariables! = null) {view.setExposePathVariables (exposePathVariables);} Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes (); if (exposeContextBeansAsAttributes! = null) {view.setExposeContextBeansAsAttributes (exposeContextBeansAsAttributes) } String [] exposedContextBeanNames = getExposedContextBeanNames (); if (exposedContextBeanNames! = null) {view.setExposedContextBeanNames (exposedContextBeanNames);} return view;}
First call the instantiateView method to build a View object return based on the viewClass we provided when we configured the view parser.
Configuring view with url is the prefix + viewName+ suffix, where the prefix and suffix are provided when we configure the view parser.
Similarly, if the user provides content-type when configuring the view parser, it is also set to the View object.
Configure the property name of the requestContext.
Configure exposePathVariables, that is, the parameter information marked with @ PathVaribale annotations.
Configuring exposeContextBeansAsAttributes indicates whether the Bean in the container can be used in View, which we can provide when configuring the view parser.
Configure exposedContextBeanNames to indicate which Bean in the container can be used in View, a parameter that we can provide when configuring the view parser.
In this way, the view is built, isn't it very easy!
4.1.2 applyLifecycleMethodsprotected View applyLifecycleMethods (String viewName, AbstractUrlBasedView view) {ApplicationContext context = getApplicationContext (); if (context! = null) {Object initialized = context.getAutowireCapableBeanFactory (). InitializeBean (view, viewName); if (initialized instanceof View) {return (View) initialized;}} return view;}
This is the initialization of Bean, there is nothing to say.
There are still many subclasses of UrlBasedViewResolver, two of which are representative, namely, the InternalResourceViewResolver we use when we use JSP and the FreeMarkerViewResolver we use when we use Freemarker. Because these two are common to us, Songge is here to introduce these two components again.
4.2 InternalResourceViewResolver
This view parser may be used when we use JSP.
InternalResourceViewResolver does four main things:
The view is specified by the requiredViewClass method.
@ Overrideprotected Class requiredViewClass () {return InternalResourceView.class;}
The requiredViewClass method is called in the constructor to determine the view, and if JSTL is introduced in the project, the view is adjusted to JstlView.
The instantiateView method is overridden to initialize different View according to the actual situation:
@ Overrideprotected AbstractUrlBasedView instantiateView () {return (getViewClass () = = InternalResourceView.class? New InternalResourceView (): (getViewClass () = = JstlView.class? New JstlView (): super.instantiateView ());}
The InternalResourceView or JstlView is initialized according to the actual situation, or the method of the parent class is called to complete the initialization of the View.
The buildView method is also rewritten as follows:
@ Overrideprotected 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;}
Here, we first call the parent class method to build the InternalResourceView, then configure the alwaysInclude to indicate whether or not to allow the use of include if forward is used, and the setPreventDispatchLoop method of the last side is to prevent circular calls.
4.3 FreeMarkerViewResolver
It's easy to have an AbstractTemplateViewResolver,AbstractTemplateViewResolver between FreeMarkerViewResolver and UrlBasedViewResolver, but there are only five more attributes. Brother Song has already said these five attributes when he shared the usage of Freemarker with you (see: twists and turns in Spring Boot + Freemarker! ), let me talk to you again:
ExposeRequestAttributes: whether to expose RequestAttributes to View use.
AllowRequestOverride: whether to allow parameters in RequestAttributes to override parameters with the same name in Model when the data in RequestAttributes and Model have the same name.
ExposeSessionAttributes: whether to expose SessionAttributes to View use.
AllowSessionOverride: whether to allow parameters in SessionAttributes to override parameters with the same name in Model when the data in SessionAttributes and Model have the same name.
ExposeSpringMacroHelpers: whether to expose RequestContext for SpringMacro use.
This is the AbstractTemplateViewResolver feature, which is relatively simple. Let's take a look at FreeMarkerViewResolver.
Public class FreeMarkerViewResolver extends AbstractTemplateViewResolver {public FreeMarkerViewResolver () {setViewClass (requiredViewClass ());} public FreeMarkerViewResolver (String prefix, String suffix) {this (); setPrefix (prefix); setSuffix (suffix);} @ Override protected Class requiredViewClass () {return FreeMarkerView.class } @ Override protected AbstractUrlBasedView instantiateView () {return (getViewClass () = = FreeMarkerView.class? New FreeMarkerView (): super.instantiateView ();}}
The source code of FreeMarkerViewResolver is very simple. Configure the prefix, rewrite the requiredViewClass method to provide FreeMarkerView, and rewrite the instantiateView method to complete the initialization of the View.
ThymeleafViewResolver inherits from AbstractCachingViewResolver, and the specific workflow is similar to the previous one, so I won't talk too much about it here. It is important to note that the ThymeleafViewResolver#loadView method does not check whether a view template exists, so it is possible that it will eventually return a view that does not exist (see: how multiple view parsers exist at the same time in SpringMVC).
5.ViewResolverComposite
Finally, let's take a look at ViewResolverComposite,ViewResolverComposite. In fact, we have seen this pattern many times in the previous source code analysis. We use ViewResolverComposite to proxy other ViewResolver. The difference is that the ViewResolverComposite here also does some initialization operations for other ViewResolver. ApplicationContext and servletContext are configured for the corresponding ViewResolver. The code here is relatively simple, so I won't post it. Finally, in the ViewResolverComposite#resolveViewName method, I traverse other view parsers to deal with it:
@ Override@Nullablepublic 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 At this point, the study on "how to understand ViewResolver components" is over. I hope to be able to solve your doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.