首先我們先看看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>* <p>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<string, ?> 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>&& !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<string, object> 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<viewresolver> 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<string, viewresolver> 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<viewresolver>(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><bean</code><code>class</code><code>=</code><code>"org.springframework.web.servlet.view.freemarker.freemarkerconfigurer"</code><code>></code>
<code> </code><code><property name=</code><code>"templateloaderpath"</code> <code>value=</code><code>"/web-inf/views"</code> <code>/></code>
<code> </code><code><property name=</code><code>"defaultencoding"</code> <code>value=</code><code>"utf-8"</code> <code>/></code>
<code> </code><code><property name=</code><code>"freemarkersettings"</code><code>></code>
<code> </code><code><props></code>
<code> </code><code><prop key=</code><code>"locale"</code><code>>zh_cn</prop></code>
<code> </code><code></props></code>
<code> </code><code></property></code>
<code> </code><code></bean></code>
<code> </code><code><bean</code><code>class</code><code>=</code><code>"org.springframework.web.servlet.view.freemarker.freemarkerviewresolver"</code><code>></code>
<code> </code><code><property name=</code><code>"suffix"</code> <code>value=</code><code>".html"</code> <code>/></code>
<code> </code><code><property name=</code><code>"contenttype"</code> <code>value=</code><code>"text/html;charset=utf-8"</code> <code>/></code>
<code> </code><code><property name=</code><code>"requestcontextattribute"</code> <code>value=</code><code>"request"</code> <code>/></code>
<code> </code><code><property name=</code><code>"exposerequestattributes"</code> <code>value=</code><code>"true"</code> <code>/></code>
<code> </code><code><property name=</code><code>"exposesessionattributes"</code> <code>value=</code><code>"true"</code> <code>/></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<object, view> viewaccesscache =</code><code>new</code> <code>concurrenthashmap<object, view>(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<object, view> viewcreationcache =</code>
<code> </code><code>new</code> <code>linkedhashmap<object, view>(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<object, view> eldest) {</code>
<code> </code><code>if</code> <code>(size() > 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>&&</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<object, view> viewaccesscache =</code><code>new</code> <code>concurrenthashmap<object, view>(default_cache_limit);</code>
viewcreationcache 的類型是linkedhashmap,但是它複寫了protected boolean removeeldestentry(map.entry<object, view> eldest)方法,當該方法傳回true時,linkedhashmap則會删除最老的key。在這裡我們可以看到,當viewcreationcache 的所存的view數量達到cachelimit時,就會删除最老的那個key和value,同時也會使viewaccesscache删除這個key和value。
viewaccesscache主要是用來高并發的通路,viewcreationcache 則是用來統計最老的key。他們所存儲的view都是一樣的。