天天看點

SpringMVC源碼總結(十二)ViewResolver介紹

首先我們先看看modelandview中重要的view接口。 

view接口: 

<a href="http://my.oschina.net/pingpangkuangmo/blog/376346#">?</a>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<code>string getcontenttype();</code>

<code>    </code><code>/**</code>

<code>     </code><code>* render the view given the specified model.</code>

<code>     </code><code>* &lt;p&gt;the first step will be preparing the request: in the jsp case,</code>

<code>     </code><code>* this would mean setting model objects as request attributes.</code>

<code>     </code><code>* the second step will be the actual rendering of the view,</code>

<code>     </code><code>* for example including the jsp via a requestdispatcher.</code>

<code>     </code><code>* @param model map with name strings as keys and corresponding model</code>

<code>     </code><code>* objects as values (map can also be {@code null} in case of empty model)</code>

<code>     </code><code>* @param request current http request</code>

<code>     </code><code>* @param response http response we are building</code>

<code>     </code><code>* @throws exception if rendering failed</code>

<code>     </code><code>*/</code>

<code>//上面說的很清楚,對于jsp來說,第一步就是将model作為request的attributes;第二步才開始渲染view</code>

<code>    </code><code>void</code> <code>render(map&lt;string, ?&gt; model, httpservletrequest request, httpservletresponse response)</code><code>throws</code> <code>exception;</code>

再看下viewresolver接口: 

<code>view resolveviewname(string viewname, locale locale)</code><code>throws</code> <code>exception;</code>

它是對給定的viewname找到對應的view對象,然後使用該view對象的render方法将本身的内容寫到response中。 

然後就看下,當我們的處理函數傳回一個viewname時,springmvc是如何渲染的。 

17

18

19

20

21

22

23

24

25

<code>try</code> <code>{</code>

<code>                    </code><code>// actually invoke the handler.</code>

<code>                    </code><code>mv = ha.handle(processedrequest, response, mappedhandler.gethandler());</code>

<code>                </code><code>}</code>

<code>                </code><code>finally</code> <code>{</code>

<code>                    </code><code>if</code> <code>(asyncmanager.isconcurrenthandlingstarted()) {</code>

<code>                        </code><code>return</code><code>;</code>

<code>                    </code><code>}</code>

<code>                </code><code>applydefaultviewname(request, mv);</code>

<code>                </code><code>mappedhandler.applyposthandle(processedrequest, response, mv);</code>

<code>            </code><code>}</code>

<code>            </code><code>catch</code> <code>(exception ex) {</code>

<code>                </code><code>dispatchexception = ex;</code>

<code>//這裡是我們的關注重點,就是進行視圖渲染的過程</code>

<code>            </code><code>processdispatchresult(processedrequest, response, mappedhandler, mv, dispatchexception);</code>

<code>        </code><code>}</code>

<code>        </code><code>catch</code> <code>(exception ex) {</code>

<code>            </code><code>triggeraftercompletion(processedrequest, response, mappedhandler, ex);</code>

<code>        </code><code>catch</code> <code>(error err) {</code>

<code>            </code><code>triggeraftercompletionwitherror(processedrequest, response, mappedhandler, err);</code>

繼續看下processdispatchresult是如何來渲染的 

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

<code>private</code> <code>void</code> <code>processdispatchresult(httpservletrequest request, httpservletresponse response,</code>

<code>            </code><code>handlerexecutionchain mappedhandler, modelandview mv, exception exception)</code><code>throws</code> <code>exception {</code>

<code>        </code><code>boolean</code> <code>errorview =</code><code>false</code><code>;</code>

<code>        </code><code>if</code> <code>(exception !=</code><code>null</code><code>) {</code>

<code>            </code><code>if</code> <code>(exception</code><code>instanceof</code> <code>modelandviewdefiningexception) {</code>

<code>                </code><code>logger.debug(</code><code>"modelandviewdefiningexception encountered"</code><code>, exception);</code>

<code>                </code><code>mv = ((modelandviewdefiningexception) exception).getmodelandview();</code>

<code>            </code><code>else</code> <code>{</code>

<code>                </code><code>object handler = (mappedhandler !=</code><code>null</code> <code>? mappedhandler.gethandler() :</code><code>null</code><code>);</code>

<code>                </code><code>mv = processhandlerexception(request, response, handler, exception);</code>

<code>                </code><code>errorview = (mv !=</code><code>null</code><code>);</code>

<code>        </code><code>// did the handler return a view to render?</code>

<code>//這裡是我們關注的重點</code>

<code>        </code><code>if</code> <code>(mv !=</code><code>null</code> <code>&amp;&amp; !mv.wascleared()) {</code>

<code>            </code><code>render(mv, request, response);</code>

<code>            </code><code>if</code> <code>(errorview) {</code>

<code>                </code><code>webutils.clearerrorrequestattributes(request);</code>

<code>        </code><code>else</code> <code>{</code>

<code>            </code><code>if</code> <code>(logger.isdebugenabled()) {</code>

<code>                </code><code>logger.debug(</code><code>"null modelandview returned to dispatcherservlet with name '"</code> <code>+ getservletname() +</code>

<code>                        </code><code>"': assuming handleradapter completed request handling"</code><code>);</code>

<code>        </code><code>if</code> <code>(webasyncutils.getasyncmanager(request).isconcurrenthandlingstarted()) {</code>

<code>            </code><code>// concurrent handling started during a forward</code>

<code>            </code><code>return</code><code>;</code>

<code>        </code><code>if</code> <code>(mappedhandler !=</code><code>null</code><code>) {</code>

<code>            </code><code>mappedhandler.triggeraftercompletion(request, response,</code><code>null</code><code>);</code>

<code>    </code><code>}</code>

<code>protected</code> <code>void</code> <code>render(modelandview mv, httpservletrequest request, httpservletresponse response)</code><code>throws</code> <code>exception {</code>

<code>        </code><code>// determine locale for request and apply it to the response.</code>

<code>        </code><code>locale locale =</code><code>this</code><code>.localeresolver.resolvelocale(request);</code>

<code>        </code><code>response.setlocale(locale);</code>

<code>        </code><code>view view;</code>

<code>        </code><code>if</code> <code>(mv.isreference()) {</code>

<code>            </code><code>// we need to resolve the view name.</code>

<code>            </code><code>view = resolveviewname(mv.getviewname(), mv.getmodelinternal(), locale, request);</code>

<code>            </code><code>if</code> <code>(view ==</code><code>null</code><code>) {</code>

<code>                </code><code>throw</code> <code>new</code> <code>servletexception(</code><code>"could not resolve view with name '"</code> <code>+ mv.getviewname() +</code>

<code>                        </code><code>"' in servlet with name '"</code> <code>+ getservletname() +</code><code>"'"</code><code>);</code>

<code>            </code><code>// no need to lookup: the modelandview object contains the actual view object.</code>

<code>            </code><code>view = mv.getview();</code>

<code>                </code><code>throw</code> <code>new</code> <code>servletexception(</code><code>"modelandview ["</code> <code>+ mv +</code><code>"] neither contains a view name nor a "</code> <code>+</code>

<code>                        </code><code>"view object in servlet with name '"</code> <code>+ getservletname() +</code><code>"'"</code><code>);</code>

<code>        </code><code>// delegate to the view object for rendering.</code>

<code>        </code><code>if</code> <code>(logger.isdebugenabled()) {</code>

<code>            </code><code>logger.debug(</code><code>"rendering view ["</code> <code>+ view +</code><code>"] in dispatcherservlet with name '"</code> <code>+ getservletname() +</code><code>"'"</code><code>);</code>

<code>        </code><code>try</code> <code>{</code>

<code>            </code><code>view.render(mv.getmodelinternal(), request, response);</code>

<code>                </code><code>logger.debug(</code><code>"error rendering view ["</code> <code>+ view +</code><code>"] in dispatcherservlet with name '"</code> <code>+</code>

<code>                        </code><code>getservletname() +</code><code>"'"</code><code>, ex);</code>

<code>            </code><code>throw</code> <code>ex;</code>

這裡可以看到整體的處理流程。首先判斷view是不是一個視圖的名稱,若是需要找到這個視圖名稱對應的view對象,然後便是調用view對象的render方法,渲染到response中。 

由于我們的處理函數經常僅僅是傳回一個view名稱,是以我們重點要看看它是如何根據視圖名稱來找到對應的view對象的,即resolveviewname方法内容。其實上文已經說明了view接口和viewresolver 接口,viewresolver 接口就是根據view名稱來找到對應的view對象的,是以看下面就會很清晰明白 

<code>protected</code> <code>view resolveviewname(string viewname, map&lt;string, object&gt; model, locale locale,</code>

<code>            </code><code>httpservletrequest request)</code><code>throws</code> <code>exception {</code>

<code>        </code><code>for</code> <code>(viewresolver viewresolver :</code><code>this</code><code>.viewresolvers) {</code>

<code>            </code><code>view view = viewresolver.resolveviewname(viewname, locale);</code>

<code>            </code><code>if</code> <code>(view !=</code><code>null</code><code>) {</code>

<code>                </code><code>return</code> <code>view;</code>

<code>        </code><code>return</code> <code>null</code><code>;</code>

這裡就是對dispatcherservlet的private list&lt;viewresolver&gt; viewresolvers屬性進行周遊找到一個能夠擷取view對象的viewresolver,并傳回這個view對象。 

至此整個流程便走通了,接下來就是要看看有哪些viewresolver以及它們的注冊來源是什麼? 

常用的viewresolver有:freemarkerviewresolver、internalresourceviewresolver、velocityviewresolver等。 

接下來就是如何來注冊這些viewresolver: 

<code>protected</code> <code>void</code> <code>initstrategies(applicationcontext context) {</code>

<code>        </code><code>initmultipartresolver(context);</code>

<code>        </code><code>initlocaleresolver(context);</code>

<code>        </code><code>initthemeresolver(context);</code>

<code>        </code><code>inithandlermappings(context);</code>

<code>        </code><code>inithandleradapters(context);</code>

<code>        </code><code>inithandlerexceptionresolvers(context);</code>

<code>        </code><code>initrequesttoviewnametranslator(context);</code>

<code>//我們關注的重點</code>

<code>        </code><code>initviewresolvers(context);</code>

<code>        </code><code>initflashmapmanager(context);</code>

還是在dispatcherservlet的初始化政策中,調用了initviewresolvers,如下: 

<code>private</code> <code>void</code> <code>initviewresolvers(applicationcontext context) {</code>

<code>        </code><code>this</code><code>.viewresolvers =</code><code>null</code><code>;</code>

<code>        </code><code>if</code> <code>(</code><code>this</code><code>.detectallviewresolvers) {</code>

<code>            </code><code>// find all viewresolvers in the applicationcontext, including ancestor contexts.</code>

<code>            </code><code>map&lt;string, viewresolver&gt; matchingbeans =</code>

<code>                    </code><code>beanfactoryutils.beansoftypeincludingancestors(context, viewresolver.</code><code>class</code><code>,</code><code>true</code><code>,</code><code>false</code><code>);</code>

<code>            </code><code>if</code> <code>(!matchingbeans.isempty()) {</code>

<code>                </code><code>this</code><code>.viewresolvers =</code><code>new</code> <code>arraylist&lt;viewresolver&gt;(matchingbeans.values());</code>

<code>                </code><code>// we keep viewresolvers in sorted order.</code>

<code>                </code><code>ordercomparator.sort(</code><code>this</code><code>.viewresolvers);</code>

<code>            </code><code>try</code> <code>{</code>

<code>                </code><code>viewresolver vr = context.getbean(view_resolver_bean_name, viewresolver.</code><code>class</code><code>);</code>

<code>                </code><code>this</code><code>.viewresolvers = collections.singletonlist(vr);</code>

<code>            </code><code>catch</code> <code>(nosuchbeandefinitionexception ex) {</code>

<code>                </code><code>// ignore, we'll add a default viewresolver later.</code>

<code>        </code><code>// ensure we have at least one viewresolver, by registering</code>

<code>        </code><code>// a default viewresolver if no other resolvers are found.</code>

<code>        </code><code>if</code> <code>(</code><code>this</code><code>.viewresolvers ==</code><code>null</code><code>) {</code>

<code>            </code><code>this</code><code>.viewresolvers = getdefaultstrategies(context, viewresolver.</code><code>class</code><code>);</code>

<code>                </code><code>logger.debug(</code><code>"no viewresolvers found in servlet '"</code> <code>+ getservletname() +</code><code>"': using default"</code><code>);</code>

這和handlemapping和handleradapter的初始化過程基本類似。this.detectallviewresolvers是dispatcherservlet的一個boolean屬性,可以在web.xml檔案中修改這個值,預設是true。 

<code>/** detect all viewresolvers or just expect "viewresolver" bean? */</code>

<code>    </code><code>private</code> <code>boolean</code> <code>detectallviewresolvers =</code><code>true</code><code>;</code>

當detectallviewresolvers為true,意味着就會擷取從xml檔案中解析出來的viewresolver。如果為false,則直接去找bean name為"viewresolver"并且是viewresolver類型的作為dispatcherservlet的viewresolver。 

當上述兩種情況都沒有找到,則會啟用預設的viewresolver,在this.viewresolvers = getdefaultstrategies(context, viewresolver.class)中,這個過程已經多次說過,可以見本系列第一篇handlemapping的來源。它就是依據dispatcherservlet.properties檔案中所配置的viewresolver,如下: 

<code>org.springframework.web.servlet.viewresolver=org.springframework.web.servlet.view.internalresourceviewresolver</code>

也就是預設采用的是internalresourceviewresolver。 

再說說在xml檔案中配置viewresolver的情況,如下: 

<code>&lt;bean</code><code>class</code><code>=</code><code>"org.springframework.web.servlet.view.freemarker.freemarkerconfigurer"</code><code>&gt;</code>

<code>        </code><code>&lt;property name=</code><code>"templateloaderpath"</code> <code>value=</code><code>"/web-inf/views"</code> <code>/&gt;</code>

<code>        </code><code>&lt;property name=</code><code>"defaultencoding"</code> <code>value=</code><code>"utf-8"</code> <code>/&gt;</code>

<code>        </code><code>&lt;property name=</code><code>"freemarkersettings"</code><code>&gt;</code>

<code>            </code><code>&lt;props&gt;</code>

<code>                </code><code>&lt;prop key=</code><code>"locale"</code><code>&gt;zh_cn&lt;/prop&gt;</code>

<code>            </code><code>&lt;/props&gt;</code>

<code>        </code><code>&lt;/property&gt;</code>

<code>    </code><code>&lt;/bean&gt;</code>

<code>    </code><code>&lt;bean</code><code>class</code><code>=</code><code>"org.springframework.web.servlet.view.freemarker.freemarkerviewresolver"</code><code>&gt;</code>

<code>        </code><code>&lt;property name=</code><code>"suffix"</code> <code>value=</code><code>".html"</code> <code>/&gt;</code>

<code>        </code><code>&lt;property name=</code><code>"contenttype"</code> <code>value=</code><code>"text/html;charset=utf-8"</code> <code>/&gt;</code>

<code>        </code><code>&lt;property name=</code><code>"requestcontextattribute"</code> <code>value=</code><code>"request"</code> <code>/&gt;</code>

<code>        </code><code>&lt;property name=</code><code>"exposerequestattributes"</code> <code>value=</code><code>"true"</code> <code>/&gt;</code>

<code>        </code><code>&lt;property name=</code><code>"exposesessionattributes"</code> <code>value=</code><code>"true"</code> <code>/&gt;</code>

這裡是以freemarkerviewresolver為例來說明,它的配置内容還是需要有待繼續研究。這裡隻是粗略的說下它的繼承情況。 

freemarkerviewresolver繼承abstracttemplateviewresolver繼承urlbasedviewresolver繼承abstractcachingviewresolver。 

首先是抽象類abstractcachingviewresolver:它加入了緩存功能,它有幾個重要的屬性。 

<code>/** default maximum number of entries for the view cache: 1024 */</code>

<code>    </code><code>public</code> <code>static</code> <code>final</code> <code>int</code> <code>default_cache_limit =</code><code>1024</code><code>;</code>

<code>    </code><code>/** the maximum number of entries in the cache */</code>

<code>    </code><code>private</code> <code>volatile</code> <code>int</code> <code>cachelimit = default_cache_limit;</code>

<code>    </code> 

<code>    </code><code>/** fast access cache for views, returning already cached instances without a global lock */</code>

<code>    </code><code>private</code> <code>final</code> <code>map&lt;object, view&gt; viewaccesscache =</code><code>new</code> <code>concurrenthashmap&lt;object, view&gt;(default_cache_limit);</code>

<code>    </code><code>/** map from view key to view instance, synchronized for view creation */</code>

<code>    </code><code>@suppresswarnings</code><code>(</code><code>"serial"</code><code>)</code>

<code>    </code><code>private</code> <code>final</code> <code>map&lt;object, view&gt; viewcreationcache =</code>

<code>            </code><code>new</code> <code>linkedhashmap&lt;object, view&gt;(default_cache_limit,</code><code>0</code><code>.75f,</code><code>true</code><code>) {</code>

<code>                </code><code>@override</code>

<code>                </code><code>protected</code> <code>boolean</code> <code>removeeldestentry(map.entry&lt;object, view&gt; eldest) {</code>

<code>                    </code><code>if</code> <code>(size() &gt; getcachelimit()) {</code>

<code>                        </code><code>viewaccesscache.remove(eldest.getkey());</code>

<code>                        </code><code>return</code> <code>true</code><code>;</code>

<code>                    </code><code>else</code> <code>{</code>

<code>                        </code><code>return</code> <code>false</code><code>;</code>

<code>            </code><code>};</code>

屬性一:cachelimit 最大的緩存數量,預設為1024。 

屬性二:viewaccesscache 是concurrenthashmap類型的,适合高并發。 

屬性三:viewcreationcache是linkedhashmap類型的 

我們再來看下,由view名稱來解析到view視圖對象的具體過程: 

<code>public</code> <code>view resolveviewname(string viewname, locale locale)</code><code>throws</code> <code>exception {</code>

<code>//這裡進行了是否進行緩存的判斷,即cachelimit是否大于0</code>

<code>        </code><code>if</code> <code>(!iscache()) {</code>

<code>                        </code><code>//不進行緩存,始終每次都建立</code>

<code>            </code><code>return</code> <code>createview(viewname, locale);</code>

<code>                        </code><code>//viewaccesscache viewcreationcache兩者的key</code>

<code>            </code><code>object cachekey = getcachekey(viewname, locale);</code>

<code>            </code><code>view view =</code><code>this</code><code>.viewaccesscache.get(cachekey);</code>

<code>                </code><code>synchronized</code> <code>(</code><code>this</code><code>.viewcreationcache) {</code>

<code>                    </code><code>view =</code><code>this</code><code>.viewcreationcache.get(cachekey);</code>

<code>                    </code><code>if</code> <code>(view ==</code><code>null</code><code>) {</code>

<code>                        </code><code>// ask the subclass to create the view object.</code>

<code>                        </code><code>view = createview(viewname, locale);</code>

<code>                        </code><code>if</code> <code>(view ==</code><code>null</code> <code>&amp;&amp;</code><code>this</code><code>.cacheunresolved) {</code>

<code>                            </code><code>view = unresolved_view;</code>

<code>                        </code><code>}</code>

<code>                        </code><code>if</code> <code>(view !=</code><code>null</code><code>) {</code>

<code>                            </code><code>this</code><code>.viewaccesscache.put(cachekey, view);</code>

<code>                            </code><code>this</code><code>.viewcreationcache.put(cachekey, view);</code>

<code>                            </code><code>if</code> <code>(logger.istraceenabled()) {</code>

<code>                                </code><code>logger.trace(</code><code>"cached view ["</code> <code>+ cachekey +</code><code>"]"</code><code>);</code>

<code>                            </code><code>}</code>

<code>            </code><code>return</code> <code>(view != unresolved_view ? view :</code><code>null</code><code>);</code>

對于object cachekey = getcachekey(viewname, locale);預設為viewname + "_" + locale; 

但是可以被子類覆寫,子類urlbasedviewresolver覆寫了它,變成隻有viewname。 

先從viewaccesscache中看能否找到已緩存的view視圖,若能找到則傳回。若未找到則加上同步鎖synchronized (this.viewcreationcache),進入這個方法之後,最關鍵的是仍需要進行一次判斷view = this.viewcreationcache.get(cachekey),看看是否已經建立過了,并不是viewaccesscache和viewcreationcache他們所緩存的内容不一樣而是如果沒有這個判斷,則會有多線程問題。 

如線程1和線程2同時要解析相同的view名稱,他們都來到同步鎖synchronized (this.viewcreationcache)之前,線程2先拿到鎖,線程1等待,線程2建立好view視圖後,加入viewcreationcache和viewaccesscache,并釋放鎖。此時線程1獲得鎖,進入同步鎖synchronized (this.viewcreationcache)内部,若不進行判斷,則線程1又會去建立一次view視圖。是以view = this.viewcreationcache.get(cachekey)并判斷view是否為null這一步驟是十分有用的。 

建立view視圖的任務就交給了子類來實作。resolveviewname這個方法基本上就分析完了,應該還會想到,它的那個cachelimit限制好像還沒發揮出作用。 

繼續回看 

<code>private</code> <code>final</code> <code>map&lt;object, view&gt; viewaccesscache =</code><code>new</code> <code>concurrenthashmap&lt;object, view&gt;(default_cache_limit);</code>

viewcreationcache 的類型是linkedhashmap,但是它複寫了protected boolean removeeldestentry(map.entry&lt;object, view&gt; eldest)方法,當該方法傳回true時,linkedhashmap則會删除最老的key。在這裡我們可以看到,當viewcreationcache 的所存的view數量達到cachelimit時,就會删除最老的那個key和value,同時也會使viewaccesscache删除這個key和value。 

viewaccesscache主要是用來高并發的通路,viewcreationcache 則是用來統計最老的key。他們所存儲的view都是一樣的。