一、SpringMVC配置代替方案
1自定DispatcherServlet
按照AbstractAnnotationConfigDispatcherServletInitializer的定義,它會創建DispatcherServlet和ContextLoaderListener。
AbstractAnnotationConfigDispatcherServletInitializer有三個方法是必須要重載的abstract方法。但是實際上還有更多的方法可以進行重載,從而實現額外的配置。
此類的方法之一就是customizeRegistration()。在AbstractAnnotationConfigDispatcherServletInitializer將DispatcherServlet注冊到Servlet容器中之后,就會調用customizeRegistration(),并將Servlet注冊后得到的Registration.Dynamic傳遞進來。通過重載customizeRegistration()方法,我們可以對DispatcherServlet進行額外的配置。
2、添加其他的Servlet和Filter
如果你想注冊其他的Servlet、Filter或Listener的話,基于Java的初始化器(initializer)的一個好處就在于我們可以定義任意數量的初始化器類。因此,如果我們想往Web容器中注冊其他組件的話,只需創建一個新的初始化器就可以了。最簡單的方式就是實現Spring的WebApplicationInitializer接口。
?
如何創建WebApplicationInitializer實現并注冊一個Servlet。
import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration.Dynamic;import org.springframework.web.WebApplicationInitializer;import spittr.servlet.MyServlet;public class MySevletInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);//注冊servletmyServlet.addMapping("/custom/**"); //添加映射Servlet } }
?它注冊了一個Servlet并將其映射到一個路徑上。
?還可以創建新的WebApplicationInitializer實現來注冊Listener和Filter。
import javax.servlet.FilterRegistration.Dynamic; import javax.servlet.ServletContext; import javax.servlet.ServletException;import org.springframework.web.WebApplicationInitializer;import spittr.servlet.MyFilter;public class MyFilterInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletContext) throws ServletException {Dynamic filter = servletContext.addFilter("MyFilter", MyFilter.class); 注冊filter.addMappingForUrlPatterns(null, false, "/custom/*"); 添加Filter的映射路徑}}
?
?如果你只是注冊Filter,并且該Filter只會映射到DispatcherServlet上的話,那么在AbstractAnnotationConfigDispatcherServletInitializer中還有一種快捷方式。
為了注冊Filter并將其映射到DispatcherServlet,所需要做的僅僅是重載AbstractAnnotationConfigDispatcherServletInitializer的getServletFilters()方法。
@Overrideprotected Filter[] getServletFilters() { return new Filter[]{new MyFilter()};}
?
?這個方法返回的是一個javax.servlet.Filter的數組。在這里它只返回了一個Filter,但它實際上可以返回任意數量的Filter。在這里沒有必要聲明它的映射路徑,getServletFilters()方法返回的所有Filter都會映射到DispatcherServlet上。
3、在web.xml中聲明DispatcherServlet
在典型的Spring MVC應用中,我們會需要DispatcherServlet和ContextLoaderListener。AbstractAnnotationConfigDispatcherServletInitializer會自動注冊它們,但是如果需要在web.xml中注冊的話,那就需要我們自己來完成這項任務了。
3.1讓DispatcherServlet和ContextLoaderListener從XML中加載各自的應用上下文。
搭建DispatcherServlet和ContextLoaderListener。
?
<?xml version="1.0" encoding="UTF-8"?> <web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1"><!-- 設置root上下文配置文件位置 --> <context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param><!-- 注冊ContextLoaderListener --> <listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener><!-- 注冊DispatcherServlet --> <servlet><servlet-name>appServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name> <!-- 指定的路徑上加載應用上下文--><param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value></init-param><load-on-startup>1</load-on-startup> </servlet><servlet-mapping><servlet-name>appServlet</servlet-name><url-pattern>/</url-pattern> </servlet-mapping> </web-app>
?
?其中對于ContextLoaderListener,上下文參數contextConfigLocation指定了一個XML文件的地址,這個文件定義了根應用上下文,它會被ContextLoaderListener加載。
DispatcherServlet會根據Servlet的名字找到一個文件,并基于該文件加載應用上下文。Servlet的名字是appServlet,因此DispatcherServlet會從“/WEB-INF/appServlet-context.xml”文件中加載其應用上下文。但是上面改變了加載的配置文件的路勁(在Servlet上指定一個contextConfigLocation初始化參數。)。
?3.2在java配置類中加載
要在Spring MVC中使用基于Java的配置,需要告訴DispatcherServlet和ContextLoaderListener使用AnnotationConfigWebApplicationContext,這是一個WebApplicationContext的實現類,它會加載Java配置類,而不是使用XML。要實現這種配置,可以設置contextClass上下文參數以及DispatcherServlet的初始化參數。在下面文件中,它所搭建的Spring MVC使用基于Java的Spring配置:
<?xml version="1.0" encoding="UTF-8"?> <web-app
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
id="WebApp_ID" version="3.1"><context-param><param-name>contextClass</param-name> <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> </context-param><!-- 設置root上下文配置文件位置 --> <context-param> <param-name>contextConfigLocation</param-name><param-value>spittr.config.RootConfig</param-value> </context-param><!-- 注冊ContextLoaderListener --> <listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener><!-- 注冊DispatcherServlet --> <servlet><servlet-name>appServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param><param-name>contextClass</param-name><param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value></init-param> <init-param><param-name>contextConfigLocation</param-name> <!-- 指定應用上下文的加載路徑 --><param-value>spittr.config.WebConfig</param-value></init-param><load-on-startup>1</load-on-startup> </servlet><servlet-mapping><servlet-name>appServlet</servlet-name><url-pattern>/</url-pattern> </servlet-mapping> </web-app>
?
?二、處理multipart形式數據
?multipart對于處理文件的上傳有很好的處理。
multipart格式的數據會將一個表單拆分為多個部分(part),每個部分對應一個輸入域。在一般的表單輸入域中,它所對應的部分中會放置文本型數據,但是如果上傳文件的話,它所對應的部分可以是二進制,下面展現了multipart的請求體:
在這個multipart的請求中,我們可以看到profilePicture部分與其他部分明顯不同。除了其他內容以外,它還有自己的Content-Type頭,表明它是一個JPEG圖片。profilePicture部分的請求體是二進制數據,而不是簡單的文本。
在編寫控制器方法處理文件上傳之前,我們必須要配置一個multipart解析器,通過它來告訴DispatcherServlet該如何讀取multipart請求。
DispatcherServlet沒有實現任何解析multipart請求數據的功能。它將該任務委托給了Spring中MultipartResolver策略接口的實現,通過這個實現類來解析multipart請求中的內容。從Spring 3.1開始,Spring內置了兩個MultipartResolver的實現供我們選擇:
CommonsMultipartResolver:使用Jakarta Commons?FileUpload解析multipart請求;
StandardServletMultipartResolver:依賴于Servlet 3.0對multipart請求的支持(始于Spring 3.1)。
2.1使用StandardServletMultipartResolver
其沒有構造器參數,也沒有要設置的屬性。
在Spring應用上下文中配置:
public class WebConfig extends WebMvcConfigurerAdapter{
@Bean
public MultipartResolver multipartResolver()throws IOException{
return new StandardServletMultipartResolver();
}
}
?
雖然沒辦法直接通過StandardServletMultipartResolver配置限制條件的。但在Servlet中能指定multipart的配置。具體來講,我們必須要在web.xml或Servlet初始化類中,將multipart的具體細節作為DispatcherServlet配置的一部分。
方法一:(沒用過)
如果采用Servlet初始化類的方式來配置DispatcherServlet的話,這個初始化類應該已經實現了WebApplicationInitializer,那我們可以在Servlet registration上調用setMultipartConfig()方法,傳入一個MultipartConfig-Element實例。如下是最基本的DispatcherServlet multipart配置,它將臨時路徑設置為“/tmp/spittr/uploads”:
方法二:
如果配置DispatcherServlet的Servlet初始化類繼承了Abstract AnnotationConfigDispatcherServletInitializer或AbstractDispatcherServletInitializer的話,那么我們不會直接創建DispatcherServlet實例并將其注冊到Servlet上下文中。這樣的話,將不會有對Dynamic Servlet registration的引用供我們使用了。但是,我們可以通過重載customizeRegistration()方法(它會得到一個Dynamic作為參數)來配置multipart的具體細節:
public class SpittrWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
@Overrideprotected void customizeRegistration(Dynamic registration) {registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads"));}
}
?
MultipartConfigElement構造器,這個參數指定的是文件系統中的一個絕對目錄,上傳文件將會臨時寫入該目錄中。還可以通過其他的構造器來限制上傳文件的大小。除了臨時路徑的位置,其他的構造器所能接受的參數如下:
上傳文件的最大容量(以字節為單位)。默認是沒有限制的。
整個multipart請求的最大容量(以字節為單位),不會關心有多少個part以及每個part的大小。默認是沒有限制的。
在上傳的過程中,如果文件大小達到了一個指定最大容量(以字節為單位),將會寫入到臨時文件路徑中。默認值為0,也就是所有上傳的文件都會寫入到磁盤上。
假設我們想限制文件的大小不超過2MB,整個請求不超過4MB,而且所有的文件都要寫到磁盤中。
@Overrideprotected void customizeRegistration(Dynamic registration) {registration.setMultipartConfig(new MultipartConfigElement("/tmp/spittr/uploads",2097152,4194304,0));}
?
或者在xml中配置:
使用<servlet>中的<multipart-config>元素
2.2處理multipart請求
例如傳一張圖片:
<body><div id="content"><h1>Register</h1><form method="POST" enctype="multipart/form-data" action="upload/up"><label>Profile Picture</label>:<input type="file"name="profilePicture"accept="image/jpeg,image/png,image/gif" /><br/><input type="submit" value="Register" /></form></div></body>
?
注意:<form>標簽現在將enctype屬性設置為multipart/form-data,這會告訴瀏覽器以multipart數據的形式提交表單,而不是以表單數據的形式進行提交。在multipart中,每個輸入域都會對應一個part。
添加了一個新的<input>域,其type為file。這能夠讓用戶選擇要上傳的圖片文件。accept屬性用來將文件類型限制為JPEG、PNG以及GIF圖片。根據其name屬性,圖片數據將會發送到multipart請求中的profilePicture part之中。
2.2.1MultipartFile接收
Spring還提供了MultipartFile接口,它為處理multipart數據提供了內容更為豐富的對象。
Spring所提供的MultipartFile接口,用來處理上傳的文件
MultipartFile提供了獲取上傳文件byte的方式,還能獲得原始的文件名、大小以及內容類型。它還提供了一個InputStream,用來將文件數據以流的方式進行讀取。
MultipartFile還提供了一個便利的transferTo()方法,它能夠幫助我們將上傳的文件寫入到文件系統中。
?在processRegistration()方法中添加如下的幾行代碼,從而將上傳的圖片文件寫入到文件系統中:
@RequestMapping(value="/up", method=RequestMethod.POST)public String processRegistration(@RequestPart("profilePicture")MultipartFile profilePicture,Model model) throws Exception{ // 文件保存路徑 String filePath = "E:/spittr/image/";System.out.println(filePath+profilePicture.getSize()+".jpg");profilePicture.transferTo(new File(filePath+profilePicture.getSize()+".jpg"));model.addAttribute("imgSrc","/spittr/image/"+profilePicture.getSize()+".jpg");return "displayImg";}
?讀取存儲的內容:
<body><img src="${imgSrc}"></body>
?2.3以part形式處理
?將應用部署到Servlet 3.0的容器中,那么會有MultipartFile的一個替代方案。Spring MVC也能接受javax.servlet.http.Part作為控制器方法的參數。
Part接口與MultipartFile并沒有太大的差別。
Part方法的名稱與MultipartFile方法的名稱是完全相同的。有一些比較類似,但是稍有差異,比如getSubmittedFileName()對應于getOriginalFilename()。類似地,write()對應于transferTo(),借助該方法我們能夠將上傳的文件寫入文件系統中.
如果在編寫控制器方法的時候,通過Part參數的形式接受文件上傳,那么就沒有必要配置MultipartResolver了。只有使用MultipartFile的時候,我們才需要MultipartResolver。
?
@RequestMapping(value="/up", method=RequestMethod.POST)public String processRegistration(@RequestPart("profilePicture") Part profilePicture,Model model) throws Exception{ // 文件保存路徑 String filePath = "E:/spittr/image/";System.out.println(filePath+profilePicture.getSize()+".jpg"); profilePicture.write(filePath+profilePicture.getSize()+".jpg");model.addAttribute("imgSrc","/spittr/image/"+profilePicture.getSize()+".jpg");return "displayImg";}
?三、處理異常
不管發生什么事情,不管是好的還是壞的,Servlet請求的輸出都是一個Servlet響應。如果在請求處理的時候,出現了異常,那它的輸出依然會是Servlet響應。異常必須要以某種方式轉換為響應。
Spring提供了多種方式將異常轉換為響應:
特定的Spring異常將會自動映射為指定的HTTP狀態碼;
異常上可以添加@ResponseStatus注解,從而將其映射為某一個HTTP狀態碼;
在方法上可以添加@ExceptionHandler注解,使其用來處理異常。
3.1將異常映射為HTTP狀態碼
在默認情況下,Spring會將自身的一些異常自動轉換為合適的狀態碼。
Spring的一些異常會默認映射為HTTP狀態碼
Spring異常 | HTTP狀態碼 |
BindException | 400 - Bad Request |
ConversionNotSupportedException | 500 - Internal Server Error |
HttpMediaTypeNotAcceptableException | 406 - Not Acceptable |
HttpMediaTypeNotSupportedException | 415 - Unsupported Media Type |
HttpMessageNotReadableException | 400 - Bad Request |
MissingServletRequestParameterException | 400 - Bad Request |
MissingServletRequestPartException | 400 - Bad Request |
NoSuchRequestHandlingMethodException | 404 - Not Found |
TypeMismatchException | 400 - Bad Request |
HttpMessageNotWritableException | 500 - Internal Server Error |
HttpRequestMethodNotSupportedException | 405 - Method Not Allowed |
異常一般會由Spring自身拋出,作為DispatcherServlet處理過程中或執行校驗時出現問題的結果。
如果DispatcherServlet無法找到適合處理請求的控制器方法,那么將會拋出NoSuchRequestHandlingMethodException異常,最終的結果就是產生404狀態碼的響應(Not Found)。
3.2@ResponseStatus注解
Spring提供了一種機制,能夠通過@ResponseStatus注解將異常映射為HTTP狀態碼。
public String spittle(@PathVariable("spittleId") long spittledId ,Model model){Spittle spittle = sipttleRepository.findOne(spittleId);if(spittle == null){throw new SpittleNotFoundException();}model.addAttribute(spittle);return "spittle";}
?通過ID檢索Spittle對象。如果findOne()方法能夠返回Spittle對象的話,那么會將Spittle放到模型中,然后名為spittle的視圖會負責將其渲染到響應之中。但是如果findOne()方法返回null的話,那么將會拋出SpittleNotFoundException異常。
public class SpittleNotFoundException extends RuntimeException {}
如果調用spittle()方法來處理請求,并且給定ID獲取到的結果為空,那么SpittleNotFoundException(默認)將會產生500狀態碼(Internal Server Error)的響應。實際上,如果出現任何沒有映射的異常,響應都會帶有500狀態碼,故返回的不精確,可以修改。
使用@ResponseStatus注解將SpittleNotFoundException映射為HTTP狀態碼404。
@ResponseStatus(value=HttpStatus.NOT_FOUND,reason="Spittle Not Found") public class SpittleNotFoundException extends RuntimeException {}
在引入@ResponseStatus注解之后,如果控制器方法拋出SpittleNotFoundException異常的話,響應將會具有404狀態碼,這是因為Spittle Not Found。
3.3異常處理的方法
若在響應中不僅要包括狀態碼,還要包含所產生的錯誤,此時的話,就不能將異常視為HTTP錯誤了,而是要按照處理請求的方式來處理異常了。
假設用戶試圖創建的Spittle與已創建的Spittle文本完全相同,那么SpittleRepository的save()方法將會拋出DuplicateSpittle Exception異常。這意味著SpittleController的saveSpittle()方法可能需要處理這個異常。
程序的處理:
@RequestMapping(method=RequestMethod.POST)public String saveSpittle(SpittleForm form,Model model){try{spittleRepository.save(new Spittle(null,form,getMessage(),new Date()));return "redirect:/spittles";}catch(DuplicateSpittleException e){return "err/duplicate";}}
?
如果能讓saveSpittle()方法只關注正確的路徑,而讓其他方法處理異常的話,那么它就能簡單一些。
修改:
@RequestMapping(method=RequestMethod.POST)public String saveSpittle(SpittleForm form,Model model){spittleRepository.save(new Spittle(null,form,getMessage(),new Date()));return "redirect:/spittles";}@ExceptionHandler(DuplicateSpittleException.class)public String handlerDuplicateSpittle(){return "err/duplicate";}
handleDuplicateSpittle()方法上添加了@ExceptionHandler注解,當拋出DuplicateSpittleException異常的時候,將會委托該方法來處理。它返回的是一個String,這與處理請求的方法是一致的,指定了要渲染的邏輯視圖名,它能夠告訴用戶他們正在試圖創建一條重復的條目。
對于@ExceptionHandler注解標注的方法來說,比較有意思的一點在于它能處理同一個控制器中所有處理器方法所拋出的異常。所以,盡管我們從saveSpittle()中抽取代碼創建了handleDuplicateSpittle()方法,但是它能夠處理SpittleController中所有方法所拋出的DuplicateSpittleException異常。我們不用在每一個可能拋出DuplicateSpittleException的方法中添加異常處理代碼,這一個方法就涵蓋了所有的功能。
3.4為控制器添加通知
如果多個控制器類中都會拋出某個特定的異常,那么你可能會發現要在所有的控制器方法中重復相同的@ExceptionHandler方法。或者,為了避免重復,我們會創建一個基礎的控制器類,所有控制器類要擴展這個類,從而繼承通用的@ExceptionHandler方法。
但是:Spring 3.2為這類問題引入了一個新的解決方案:控制器通知。控制器通知(controller advice)是任意帶有@ControllerAdvice注解的類,這個類會包含一個或多個如下類型的方法:
@ExceptionHandler注解標注的方法;
@InitBinder注解標注的方法;
@ModelAttribute注解標注的方法。
在帶有@ControllerAdvice注解的類中,以上所述的這些方法會運用到整個應用程序所有控制器中帶有@RequestMapping注解的方法上。
@ControllerAdvice注解本身已經使用了@Component,因此@ControllerAdvice注解所標注的類將會自動被組件掃描獲取到,就像帶有@Component注解的類一樣。
@ControllerAdvice最為實用的一個場景就是將所有的@ExceptionHandler方法收集到一個類中,這樣所有控制器的異常就能在一個地方進行一致的處理。
如果任意的控制器方法拋出了DuplicateSpittleException,不管這個方法位于哪個控制器中,都會調用這個duplicateSpittleHandler()方法來處理異常。
四:跨重定向請求傳遞數據
當控制器方法返回的String值以“redirect:”開頭的話,那么這個String不是用來查找視圖的,而是用來指導瀏覽器進行重定向的路徑。
具體來講,正在發起重定向功能的方法該如何發送數據給重定向的目標方法呢?一般來講,當一個處理器方法完成之后,該方法所指定的模型數據將會復制到請求中,并作為請求中的屬性,請求會轉發(forward)到視圖上進行渲染。同一個請求,所以在轉發的過程中,請求屬性能夠得以保存。
當控制器的結果是重定向的話,原始的請求就結束了,并且會發起一個新的GET請求。原始請求中所帶有的模型數據也就隨著請求一起消亡了。在新的請求屬性中,沒有任何的模型數據,這個請求必須要自己計算數據。
有一些其他方案,能夠從發起重定向的方法傳遞數據給處理重定向方法中:
使用URL模板以路徑變量和/或查詢參數的形式傳遞數據;
通過flash屬性發送數據。
4.1通過URL模板進行重定向
通過路徑變量和查詢參數傳遞數據看起來非常簡單。以路徑變量的形式傳遞了新創建Spitter的username。但是按照現在的寫法,username的值是直接連接到重定向String上的。這能夠正常運行,但是還遠遠不能說沒有問題。當構建URL或SQL查詢語句的時候,使用String連接是很危險的。
Spring還提供了使用模板的方式來定義重定向URL。
username作為占位符填充到了URL模板中,而不是直接連接到重定向String中,所以username中所有的不安全字符都會進行轉義。這樣會更加安全,這里允許用戶輸入任何想要的內容作為username,并會將其附加到路徑上。
模型中所有其他的原始類型值都可以添加到URL中作為查詢參數。作為樣例,假設除了username以外,模型中還要包含新創建Spitter對象的id屬性,那processRegistration()方法可以改寫為如下的形式:
所返回的重定向String并沒有太大的變化。但是,因為模型中的spitterId屬性沒有匹配重定向URL中的任何占位符,所以它會自動以查詢參數的形式附加到重定向URL上。
如果username屬性的值是habuma并且spitterId屬性的值是42,那么結果得到的重定向URL路徑將會是“/spitter/habuma?spitterId=42”。
通過路徑變量和查詢參數的形式跨重定向傳遞數據是很簡單直接的方式,但它也有一定的限制。它只能用來發送簡單的值,如String和數字的值。
?4.2使用flash屬性
Spitter對象要比String和int更為復雜。因此,我們不能像路徑變量或查詢參數那么容易地發送Spitter對象。它只能設置為模型中的屬性。
模型數據最終是以請求參數的形式復制到請求中的,當重定向發生的時候,這些數據就會丟失。因此,我們需要將Spitter對象放到一個位置,使其能夠在重定向的過程中存活下來。有個方案是將Spitter放到會話中。會話能夠長期存在,并且能夠跨多個請求。所以我們可以在重定向發生之前將Spitter放到會話中,并在重定向后,從會話中將其取出。當然,我們還要負責在重定向后在會話中將其清理掉。
Spring認為我們并不需要管理這些數據,相反,Spring提供了將數據發送為flash屬性(flash attribute)的功能。按照定義,flash屬性會一直攜帶這些數據直到下一次請求,然后才會消失。
Spring提供了通過RedirectAttributes設置flash屬性的方法,這是Spring 3.1引入的Model的一個子接口。RedirectAttributes提供了Model的所有功能。
具體來講,RedirectAttributes提供了一組addFlashAttribute()方法來添加flash屬性。重新看一下processRegistration()方法
調用了addFlashAttribute()方法,并將spitter作為key,Spitter對象作為值。另外,我們還可以不設置key參數,讓key根據值的類型自行推斷得出:因為我們傳遞了一個Spitter對象給addFlashAttribute()方法,所以推斷得到的key將會是spitter
在重定向執行之前,所有的flash屬性都會復制到會話中。在重定向后,存在會話中的flash屬性會被取出,并從會話轉移到模型之中。
?
?showSpitterProfile()方法所做的第一件事就是檢查是否存有key為spitter的model屬性。如果模型中包含spitter屬性,那就什么都不用做了。這里面包含的Spitter對象將會傳遞到視圖中進行渲染。但是如果模型中不包含spitter屬性的話,那么showSpitterProfile()將會從Repository中查找Spitter,并將其存放到模型中。
?
?