摘要:依賴于對(duì)請(qǐng)求的支持。使用解析兼容的沒(méi)有構(gòu)造器參數(shù),也沒(méi)有要設(shè)置的參數(shù),這樣,在應(yīng)用上下文中,將其聲明為就會(huì)非常簡(jiǎn)單。默認(rèn)是沒(méi)有限制的整個(gè)請(qǐng)求的容量。
Spring MVC 高級(jí)的技術(shù)
本章內(nèi)容:
Spring MVC配置的替代方案
處理文件上傳
在控制器中處理異常
使用flash屬性
稍等還沒(méi)結(jié)束
說(shuō)明如果你有幸能看到。后面的章節(jié)暫時(shí)不更新了,改變學(xué)習(xí)方式了。重要理解思想,這本書寫的太好了。記得要看作者的代碼,書上只是闡述了知識(shí)點(diǎn)。還有以后會(huì)把重點(diǎn)放在GitHub上,閱讀別人的代碼,自己理解的同時(shí)在模仿出來(lái),分享給大家。你們的點(diǎn)贊就是對(duì)我的支持,謝謝大家了。
1、本文參考了《Spring 實(shí)戰(zhàn)》重點(diǎn)內(nèi)容,參考了作者GitHub上的代碼,推薦使用chrome上的GitHub插件Insight.io,FireFox也有。
2、本文只為記錄作為以后參考,要想真正領(lǐng)悟Spring的強(qiáng)大,請(qǐng)看原書。跟著作者套路來(lái),先別瞎搗騰!!!
3、在一次佩服老外,國(guó)外翻譯過(guò)來(lái)的書,在GiuHub上大都有實(shí)例。看書的時(shí)候,跟著敲一遍,效果很好。
4、代碼和筆記在這里GitHub,對(duì)你有幫助的話,歡迎點(diǎn)贊。
5、每個(gè)人的學(xué)習(xí)方式不一樣,找到合適自己的就行。2018,加油。
6、Java 8 In Action 的作者M(jìn)ario Fusco、Spring In Action 、Spring Boot In Action的作者Craig Walls
7、知其然,也要知其所以然。有些是在Atom上手敲的,有拼寫錯(cuò)誤,還請(qǐng)見(jiàn)諒。
8、從Spring Web Flow 開(kāi)始,我要加快速度了,邏輯可能不完整,一切以原書為準(zhǔn),自己只是記錄。加深印象。
談一些個(gè)人感受
1、趕快學(xué)習(xí)Spring吧,Spring MVC 、Spring Boot 、微服務(wù)。
2、重點(diǎn)中的重點(diǎn),學(xué)習(xí)JDK 8 Lambda,Stream,Spring 5 最低要求JDK1.8.
3、還有Netty、放棄SH吧,不然你會(huì)落伍的。
4、多看一些國(guó)外翻譯過(guò)來(lái)的書,例如 Xxx In Action 系列。權(quán)威指南系列。用Kindle~
5、寫代碼之前先寫測(cè)試,這就是老外不同之處。學(xué)到了很多技巧。
6、再一次佩服老外對(duì)細(xì)節(jié)的處理。值得我們每一個(gè)人學(xué)習(xí)
在很多方面,Spirng MVC(整個(gè)Spirng也是如此),也有還沒(méi)結(jié)束這樣的感覺(jué)。
在第五章,我們學(xué)習(xí)了Sprng MVC的基礎(chǔ)知識(shí),以及如何編寫控制器來(lái)處理各種請(qǐng)求,基于這些知識(shí)。我們?cè)诘诹聦W(xué)習(xí)了如何創(chuàng)建JSP和Thymeleaf視圖,這些視圖會(huì)將模型數(shù)據(jù)展示給用戶。你可能認(rèn)為我們已經(jīng)掌握了Spring MVC的全部知識(shí),但是,稍等!還沒(méi)結(jié)束。
在本章中,我們將會(huì)看到如何編寫控制器處理文件上傳,如何處理控制器所拋出的異常,以及如何在模型中傳遞數(shù)據(jù),使其能夠在重定向(redirect)之后依然存活。
但,首先我要兌現(xiàn)一個(gè)承諾。在第5章中,我們快速展現(xiàn)了如何通過(guò)AbstractAnnotationConfigDispatcherServletInitializer搭建Spring MVC,當(dāng)時(shí),我們承諾會(huì)為讀者展現(xiàn)其他的配置方案。所以,在介紹文件上傳和異常處理之前,我們花時(shí)間探討一下如何使用其他方式來(lái)搭建DispatcherServlet和ContextLoaderListener
7.1 Spring MVC 配置的替代方案盡管對(duì)很多Spring應(yīng)用來(lái)說(shuō),這是一種安全的假設(shè),但是并不一定能滿足我們的要求。除了DispatcherServlet以外,我們還可能需要額外的DispatcherServlet和Filter,我們可能還需要對(duì)DispatcherServlet本身做一些額外的配置:或者,如果我們需要將應(yīng)用部署到Servlet3.0之前的容器中,那么還需要將DispatcherServlet配置到傳統(tǒng)的web.xml中。
7.1.1 自定義DispatcherServlet配置public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class>[] getRootConfigClasses() { return new Class>[] { RootConfig.class }; } @Override protected Class>[] getServletConfigClasses() { return new Class>[] { WebConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
AbstractAnnotationConfigDispatcherServletInitializer所完成的事其實(shí)比看上去要多,在SpitterWebInitializer中我們所編寫的三個(gè)方法僅僅是必須要重載的三個(gè)抽象方法,但實(shí)際上還有更多的方法可以進(jìn)行重載,從而實(shí)現(xiàn)額外的配置。
此類的方法之一就是customizeRegistration().在AbstractAnnotationConfigDispatcherServletInitializer將DispatcherServlet注冊(cè)到Servlet容器中就會(huì)調(diào)用customizeRegistration(),并將Servlet注冊(cè)后得到的Registration.Dynamic傳遞進(jìn)來(lái),通過(guò)重載customizeRegistration()方法,我們就可以對(duì)DispatcherServlet進(jìn)行額外的配置。
在本章稍后,我們將會(huì)看到如何在Spirng MVC中處理multiparty請(qǐng)求和文件上傳。如果計(jì)劃使用Servlet3.0對(duì)multiparty配置的支持,那么我們需要使用DispatcherServlet的registration來(lái)啟用multilpart請(qǐng)求。我們可以重載customizeRegistration()方法來(lái)設(shè)置MultipartConfigElement,
@Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig( new MultipartConfigElement("C:Temp")); //設(shè)置上傳文件目錄 }
借助customizeRegistration()方法中的ServletRegistration.Dynamic我們能夠完成更多的任務(wù),
包括通過(guò)調(diào)用setLoadOnstartup()設(shè)置load-on-startup 優(yōu)先級(jí),
通過(guò)setInitParameter()設(shè)置初始化參數(shù),
通過(guò)調(diào)用setMultipartConfig()配置Servlet3.0對(duì)multipart的支持,
7.1.2 添加其他的Servlet和Filter按照AbstractAnnotationConfigDispatcherServletInitializer的定義,它會(huì)創(chuàng)建DispatcherServlet和ContextLoaderListener.但是如果你想要注冊(cè)其他的Servlet、Filter、Listener的話,那該怎么辦?
基于Java的初始化器(initializer)的一個(gè)好處在于我們可以定義任意數(shù)量的初始化類。如果我們想要往Web容器中注冊(cè)其他組件的話,只需要?jiǎng)?chuàng)建一個(gè) 新的初始化類就可以了,最簡(jiǎn)單的方式就是實(shí)現(xiàn)Spring的WebApplicationInitializer并注冊(cè)一個(gè)Servlet。
public class MyServletInitializer implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { Dynamic myServlet = servletContext.addServlet("myServlet",myServlet.class); myServlet.addMapping("/custom/**"); } }
以上程序相當(dāng)基礎(chǔ)的Servlet注冊(cè)初始化器類,它注冊(cè)了一個(gè)Servlet并將其映射到了一個(gè)路徑上,我們也可以通過(guò)這種方式來(lái)手動(dòng)注冊(cè)DispatcherServlet.(但是沒(méi)必要,因?yàn)?b>AbstractAnnotationConfigDispatcherServletInitializer沒(méi)用太多代碼就將這項(xiàng)任務(wù)完成得很漂亮)
類似的,我們還可以創(chuàng)建新的WebApplicationInitializer來(lái)實(shí)現(xiàn)注冊(cè)Listener和 Filter,
@Override public void onStartup(ServletContext servletContext) throws ServletException { javax.servlet.FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter",myFilter.class); filter.addMappingForUrlPatterns(null,false,"/custom/*"); }
如果你將應(yīng)用部署到Servlet3.0的容器中,那么WebApplicationInitializer提供了一種通用的方法,實(shí)現(xiàn)在Java中注冊(cè)Servlet和Filter、Listener,如果你只是注冊(cè)Filter,并且該Filter只會(huì)映射到DispatcherServlet上的話,那么AbstractAnnotationConfigDispatcherServletInitializer還有一種快捷的方式。
為了注冊(cè)Filter并將其映射到DispatcherServlet,所需要做的僅僅是重載AbstractAnnotationConfigDispatcherServletInitializer的getServletFilter()方法。
@Override protected Filter[] getServletFilters() { return new Filter[] {new Myfilter()}; }
這個(gè)方法返回一個(gè)javax.servlet.filter數(shù)組。在這里沒(méi)有必要聲明它的映射路徑,getServletFilter()方法返回所有Filter都會(huì)被映射到DispatcherServlet上。
如果要將應(yīng)用部署到Servlet3.0上,那么Spring容器提供了多種注冊(cè)方式,而不必創(chuàng)建web.xml文件,但是,如果你不想采取上述方案的話,也是可以的,假設(shè)你將應(yīng)用部署到不支持Servlet3.0的容器中(或者你只希望使用web.xml),那么我們完全可以按照傳統(tǒng)的方式,通過(guò)web.xml配置Spirng MVC,
7.1.3 在web.xml中聲明DispatcherServlet在典型的Spirng MVC應(yīng)用中,我們會(huì)需要DispatcherServlet和ContextLoaderListener.AbstractAnnotationConfigDispatcherServletInitializer會(huì)自動(dòng)注冊(cè)它們,但如果需要在web.xml中注冊(cè)的話,那就需要我們自己動(dòng)手來(lái)完成了。
appServlet index.html contextConfigLocation classpath:spring/applicationContext-*.xml org.springframework.web.context.ContextLoaderListener CharacterEncodingFilter org.springframework.web.filter.CharacterEncodingFilter encoding utf-8 CharacterEncodingFilter /* appServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:spring/springmvc.xml 1 appServlet /
ContextLoaderListener和DispatcherServlet各自都會(huì)加載一個(gè)Spirng應(yīng)用上下文。上下文ContextLoaderLocation指定了一個(gè)XMl文件的地址,這個(gè)文件定義了根據(jù)應(yīng)用上下文,它會(huì)被ContextLoaderListener加載。根上下文會(huì)從"/WEB-INF/spring/applicationContext-*.xml"中加載bean的定義
DispatcherServlet會(huì)根據(jù)Servlet的名字找到一個(gè)文件,并基于該文件加載應(yīng)用上下文。
如果你希望指定DispatcherServlet配置文件的話,那么可以在Servlet指定一個(gè)ContextLoaderLocation初始化參數(shù)。
appServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:spring/springmvc.xml 1 appServlet /
現(xiàn)在我們已經(jīng)看到了如何以多種不同的方式來(lái)搭建Spring MVC,那么接下來(lái)我們看一下如何使用Spring MVC來(lái)處理文件上傳。
7.2 處理multipart形式的數(shù)據(jù)在Web應(yīng)用中,允許用戶上傳內(nèi)容是很常見(jiàn)的需求,在Facebook和Flickr這樣的網(wǎng)站中,允許用戶會(huì)上傳圖片和視頻,并與家人朋友分享。還有一些服務(wù)器中允許用戶上傳照片,然后按照傳統(tǒng)的方式將其打印在紙上,或者咖啡杯上。
Spittr應(yīng)用有兩個(gè)地方需要文件上傳。當(dāng)新用戶注冊(cè)的時(shí)候,我們希望能夠上傳一張照片,從而與他的個(gè)人信息相關(guān)聯(lián)。當(dāng)用戶提交新的Spittle時(shí),除了文本信息外,他們可能還會(huì)上傳一張照片。
一般表單提交所形成的請(qǐng)求結(jié)果是非常簡(jiǎn)單的,就是以"&"符號(hào)分割的多個(gè)name-value對(duì),盡管這種方式簡(jiǎn)單,并且對(duì)于典型的基于文本的表單提交也足夠滿足需求,但是對(duì)于二進(jìn)制數(shù)據(jù),就顯得力不從心了。與之不同的是multipart格式的數(shù)據(jù)會(huì)將一個(gè)表單拆分為多個(gè)部分(part),每個(gè)部分對(duì)應(yīng)一個(gè)輸入域。在一般的表單輸入域中,它所對(duì)應(yīng)的部分中會(huì)放置文本數(shù)據(jù),但是如果是上傳文件的話,它所對(duì)應(yīng)的部分可以是二進(jìn)制。
Content-Type 他表它的類型。盡管multipart請(qǐng)求看起來(lái)很復(fù)雜,但是在SpringMVC中處理它卻很容易,在編寫控制器方法處理文件上傳之前,我們必須配置一個(gè)multipart解析器,通過(guò)它來(lái)告訴DispatcherServlet該如何讀取multipart請(qǐng)求。
7.2.1 配置multipart解析器DispatcherServlet并沒(méi)有實(shí)現(xiàn)愛(ài)你任何解析multipart請(qǐng)求數(shù)據(jù)的功能。它將該任務(wù)委托給了Spring中的MultipartResolver策略接口的實(shí)現(xiàn),通過(guò)這個(gè)實(shí)現(xiàn)類來(lái)解析multipart請(qǐng)求中的內(nèi)容。從Spirng 3.1開(kāi)始,Spirng內(nèi)置了兩個(gè)MultipartResolver的實(shí)現(xiàn)供我們選擇。
CommonsMultipartResolver:使用Jakarta Commons FileUpload解析multipart請(qǐng)求。
StandardServletMultipartResolver:依賴于Servlet3.0對(duì)multipart請(qǐng)求的支持。
一般來(lái)講StandardServletMultipartResolver可能會(huì)是優(yōu)選方案,他使用Servlet所提供的功能支持。并不需要依賴任何其他的項(xiàng)目。如果,我們需要將項(xiàng)目部署到Sevrvlet3.0之前的容器中,或者還沒(méi)有使用Spring 3.1 或者更高的版本,那么可能就需要 CommonsMultipartResolver了。
使用Servlet3.0解析multipart
兼容Servlet3.0的StandardServletMultipartResolver沒(méi)有構(gòu)造器參數(shù),也沒(méi)有要設(shè)置的參數(shù),這樣,在Spring應(yīng)用上下文中,將其聲明為bean就會(huì)非常簡(jiǎn)單。
@Bean public MultipartResolver multipartResolver() throws IOException { return new StandardServletMultipartResolver(); }
如果我們采用Servlet初始化類的方式來(lái)配置DispatcherServlet的話,這個(gè)初始化類應(yīng)該已經(jīng)實(shí)現(xiàn)了WebApplicationInitializer,那么我們可以在ServletRegistration上調(diào)用setMultipartConfig()方法,傳入一個(gè)MultipartConfigElement實(shí)例,具體的配置如下:
@Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig( new MultipartConfigElement("C:Temp")); } }
通過(guò)重載customizeRegistration()方法(它會(huì)得到一個(gè)Dynamic作為參數(shù))類配置multipart的具體細(xì)節(jié)。
到目前為止,我們所使用的是只有一個(gè)參數(shù)的MultipartConfigElemenet構(gòu)造器,這個(gè)參數(shù)指定的是文件系統(tǒng)中一個(gè)絕對(duì)目錄,上傳文件將會(huì)臨時(shí)寫入該目錄,但是,我們還可以通過(guò)其他的構(gòu)造器來(lái)限制上傳文件的大小,除了臨時(shí)路徑的位置,其他的構(gòu)造器可以接受的參數(shù)如下:
上傳文件的最大容量(以字節(jié)為單位)。默認(rèn)是沒(méi)有限制的
整個(gè)multipart請(qǐng)求的容量。不會(huì)關(guān)心有多少個(gè)part以及每個(gè)part的大小。默認(rèn)是沒(méi)有限制的。
在上傳的過(guò)程中,如果文件大小達(dá)到了一個(gè)指定最大容量,將會(huì)寫入到臨時(shí)文件路徑中,默認(rèn)值為0,也就是所有上傳的文件都會(huì)寫入磁盤上。
例如,假設(shè)我們想要限制文件的大小不超過(guò)2MB,整個(gè)請(qǐng)求不超過(guò)4MB,而且所有的文件都要寫入磁盤,
@Override protected void customizeRegistration(Dynamic registration) { registration.setMultipartConfig( new MultipartConfigElement("C:Tempuploads",2097152, 4194304, 0)); }
如果使用更為傳統(tǒng)的web.xml來(lái)配置MultipartConfigResolver的話,那么可以使用
appServlet org.springframework.web.servlet.DispatcherServlet 1 C:Tempuploads 2097152 4194304
配置Jakarta Commons FileUpload Multipart解析器
Spring內(nèi)置了CommonsMultipartResolver,可以作為StandardServletMultipartResolver的替代方案。
7.2.2 處理multipart請(qǐng)求。已經(jīng)配置好multipart請(qǐng)求的處理器,那么接下來(lái)我們就編寫控制器方法來(lái)接收上傳的文件。要實(shí)現(xiàn)這一點(diǎn),最常見(jiàn)的方法就是在某個(gè)控制器方法參數(shù)上添加@RequestPart注解。
現(xiàn)在我們需要修改processRegistration()方法,使其能夠接受上傳的圖片。其中一種方式就是添加btye數(shù)組,并為其添加@RequestPart注解。
@RequestMapping(value="/register", method=POST) public String processRegistration( @RequestPart(value = "profilePictures") byte[] profilePicture, @Valid Spitter spitter, Errors errors) {
當(dāng)表單提交的時(shí)候,profilePicture屬性將會(huì)給定一個(gè)byte數(shù)組,這個(gè)數(shù)組中包含了請(qǐng)求中對(duì)應(yīng)的part的數(shù)據(jù)(通過(guò)@RequestPart指定的)。如果沒(méi)有選擇文件,那么這個(gè)數(shù)據(jù)為空(而不是null),獲取到圖片數(shù)據(jù)后,processRegistration()方法剩下的任務(wù)就是將文件保存到某個(gè)地方。
接受MultipartFile
使用上傳文件的原始byte比較簡(jiǎn)單,但是功能有限。因此,Spring提供了MultipartFile接口,它為處理multipart數(shù)據(jù)提供了內(nèi)容更為豐富的對(duì)象。
package org.springframework.web.multipart; /** * A representation of an uploaded file received in a multipart request. * @author Juergen Hoeller * @author Trevor D. Cook */ public interface MultipartFile { /** * Return the name of the parameter in the multipart form. * @return the name of the parameter (never {@code null} or empty) */ String getName(); /** * Return the original filename in the client"s filesystem. */ String getOriginalFilename(); /** * Return the content type of the file. */ String getContentType(); /** * Return whether the uploaded file is empty, that is, either no file has * been chosen in the multipart form or the chosen file has no content. */ boolean isEmpty(); /** * Return the size of the file in bytes. */ long getSize(); /** * Return the contents of the file as an array of bytes. */ byte[] getBytes() throws IOException; /** * Return an InputStream to read the contents of the file from. */ InputStream getInputStream() throws IOException; /** * Transfer the received file to the given destination file. */ void transferTo(File dest) throws IOException, IllegalStateException; }
可以看到,MultipartFile提供了獲取文件上傳文件byte的方式,還能獲取原始的文件名,大小以及內(nèi)容類型、還提供了一個(gè)InputStream用來(lái)將文件數(shù)據(jù)以流的方式進(jìn)行讀取。
除此之外,還提供了一個(gè)transferTo()方法,它能夠幫助我們將上傳文件寫入到文件系統(tǒng)中。作為樣例,我們?cè)诳梢栽?b>processRegistration()方法中添加如下的幾行代碼,從而將上傳的圖片文件寫入到文件系統(tǒng)中
profilePicture.transferTo( new File("date/spittr" + profilePicture.getOriginalFilename()));
將文件保存到本地文件系統(tǒng)中是非常簡(jiǎn)單的,但是這需要我們對(duì)這些文件進(jìn)行管理。我們需要確保有足夠的空間,確保當(dāng)出現(xiàn)硬件故障時(shí),文件進(jìn)行了備份,還需要在集群的多個(gè)服務(wù)器之間處理這些圖片文件的同步。
以Part的形式接受上傳的文件
Spring MVC接受javax.servlet.http.Part作為控制器方法的參數(shù),如果使用part來(lái)替換MultiFile的話,那么processRegistration()方法簽名會(huì)變成如下的形式。
@RequestMapping(value="/register", method=POST) public String processRegistration( @RequestPart(value="profilePictures", required=false) Part fileBytes, RedirectAttributes redirectAttributes, @Valid Spitter spitter, Errors errors) throws IOException { if (errors.hasErrors()) { return "registerForm"; }
Part接口
package javax.servlet.http; public interface Part { public InputStream getInputStream() throws IOException; public String getContentType(); public String getName(); public String getSubmittedFileName(); public long getSize(); public void write(String fileName) throws IOException; public void delete() throws IOException; public String getHeader(String name); public CollectiongetHeaders(String name); public Collection getHeaderNames(); }
很多情況下,Part方法的名稱與MultiPartFile方法的名稱是完全相同的。有一些比較類似,但是稍有差別。
比如getSubmittedFileName()方法對(duì)應(yīng)getOriginalFilename().類似的,write()方法對(duì)應(yīng)于transfer()方法,借助該方法我們能夠?qū)⑸蟼鞯奈募懭胛募到y(tǒng)中。
值得一提的是,如果沒(méi)有在編寫控制器方法的時(shí)候,通過(guò)Part參數(shù)的形式接受文件上傳,那就沒(méi)必要配置MultipartResolver了。只有使用MultipartFile的時(shí)候 ,我們才需要MultipartResolver.
7.3 處理異常Spring提供了多種方式將異常轉(zhuǎn)換為響應(yīng)
特定的Spring異常將會(huì)自動(dòng)映射為指定的HTTP狀態(tài)碼
異常上可以添加@RequestStatus注解,從而將其映射為某一個(gè)HTTP狀態(tài)
在方法上添加@ExceptionHandle注解,使其用來(lái)處理異常。
處理異常最簡(jiǎn)單的方式就是將其映射到HTTP狀態(tài)碼上。
7.3.1 將異常映射 為HTTP狀態(tài)碼異常一般由Spring自身拋出,作為DispatcherServlet處理過(guò)程中或執(zhí)行校驗(yàn)時(shí)出現(xiàn)問(wèn)題的結(jié)果。
Spring提供了一種機(jī)制,能夠通過(guò)使用@RequestStatus注解將其映射為HTTP狀態(tài)碼
@RequestMapping(value="/{spittleId}", method=RequestMethod.GET) public String spittle( @PathVariable("spittleId") long spittleId, Model model) { Spittle spittle = spittleRepository.findOne(spittleId); if (spittle == null) { throw new SpittleNotFoundException(); //這里會(huì)拋出異常 } model.addAttribute(spittle); return "spittle"; }
如果資源沒(méi)有找到的話,HTTP狀態(tài)碼404是最為精確的響應(yīng)狀態(tài)碼
@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Spittle Not Found") public class SpittleNotFoundException extends RuntimeException { }7.3.2 編寫異常處理方法
如果響應(yīng)中不僅包含狀態(tài)碼,還要包含所產(chǎn)生的錯(cuò)誤信息,需要按照請(qǐng)求的方式來(lái)處理異常。
@RequestMapping(method=RequestMethod.POST) public String saveSpittle(SpittleForm form, Model model) { try { spittleRepository.save(new Spittle(null, form.getMessage(), new Date(), form.getLongitude(), form.getLatitude())); return "redirect:/spittles"; } catch (DuplicateSpittleException e) { //捕獲異常 return "error/duplicate"; } }
@RequestMapping(method=RequestMethod.POST) public String saveSpittle(SpittleForm form, Model model) { spittleRepository.save(new Spittle(null, form.getMessage(), new Date(), form.getLongitude(), form.getLatitude())); return "redirect:/spittles"; return "error/duplicate"; }
它只關(guān)注成功保存Spittle的情況,所以只需要一個(gè)執(zhí)行路徑,很容易理解和測(cè)試。
@ExceptionHandler(DuplicateSpittleException.class) public String handleNotFound() { return "error/duplicate"; }
方法上加上@ExceptionHandler注解后,當(dāng)方法拋出異常的時(shí)候,將委托該方法來(lái)處理,它能夠處理同一個(gè)控制器中所有的方法拋出的異常。
7.4 為控制器添加通知如果控制器類的特定切面能夠 運(yùn)用到整個(gè)應(yīng)用程序的所有控制器中,那么這將會(huì)便利很多。,為了 避免重復(fù),我們會(huì)創(chuàng)建一個(gè)基礎(chǔ)的控制器,所有的控制器類要擴(kuò)展這個(gè)類,從而繼承通用的@ExceptionHandler方法。
Spring3.2 引入了一個(gè)新的解決方法:控制器通知。控制器通知(controllerAdvice)是任意帶有@ControllerAdvice注解的類,這個(gè)類會(huì)包含一個(gè)或多個(gè) 如下類型的方法:
@ExceptionHandle注解標(biāo)注的方法
@InitBinder注解標(biāo)注的方法
@ModelAttribute注解標(biāo)注的方法。
@ControllerAdvice public class AppWideExceptionHandler { @ExceptionHandler(DuplicateSpittleException.class) public String handleNotFound() { return "error/duplicate"; } }7.5 跨重定向請(qǐng)求傳遞數(shù)據(jù)
“redirect:”前綴能讓重定向功能變得更簡(jiǎn)單,但是,請(qǐng)稍等,Spirng為重定向功能還提供了一些其他的輔助功能。
當(dāng)一個(gè)處理器方法完成之后,該方法所指定的模型數(shù)據(jù)會(huì)將復(fù)制到請(qǐng)求中,并作為請(qǐng)求中的屬性,請(qǐng)求會(huì)轉(zhuǎn)發(fā)(forward)到視圖上進(jìn)行渲染。
對(duì)于 重定向來(lái)說(shuō),模型并不能徹底數(shù)據(jù),有一些其他方法,能夠從發(fā)起重定向的方法傳遞數(shù)據(jù)給處理重定向的方法
使用URL模板以路徑變量和/或查詢參數(shù)的形式傳遞數(shù)據(jù)
通過(guò)flash屬性發(fā)生數(shù)據(jù)
7.5.1 通過(guò)URL模板進(jìn)行重定向@RequestMapping(value="/register", method=POST) public String processRegistration( @Valid SpitterForm spitterForm, Errors errors) throws IllegalStateException, IOException { if (errors.hasErrors()) { return "registerForm"; } Spitter spitter = spitterForm.toSpitter(); spitterRepository.save(spitter); MultipartFile profilePicture = spitterForm.getProfilePicture(); profilePicture.transferTo(new File("/tmp/spittr/" + spitter.getUsername() + ".jpg")); return "redirect:/spitter/" + spitter.getUsername(); //根據(jù)名字重定向 }
通過(guò)路徑變量和查詢參數(shù)的形式跨重定向傳遞數(shù)據(jù)是很簡(jiǎn)單直接的方式,但是也有限制它只能發(fā)送簡(jiǎn)單的值,如String和數(shù)字的值。在URl中,并沒(méi)有 辦法發(fā)送更為復(fù)雜的值,但這正是flash屬性能夠提供幫助。
### 7.5.2 使用flash屬性。
有個(gè)方案是將Spittr放到會(huì)話中,會(huì)話能長(zhǎng)期存在,并且會(huì)話能夠跨多個(gè)請(qǐng)求,所以我們可以在重定向之前將Spittr放到會(huì)話中,并在重定向后,從會(huì)話中取出 ,當(dāng)然,我們需要負(fù)責(zé)在重定向之后在會(huì)話中將其清理掉。
Spring提供了將數(shù)據(jù)發(fā)送為flash屬性的功能,按照定義,flash屬性會(huì)一直攜帶這些數(shù)據(jù),直到下一次請(qǐng)求,然后才消失
Spring提供了通過(guò)RedirectAttributes設(shè)置flash屬性的方式,這是Spring3.1引入的Modwl的一個(gè)子接口 。 RedirectAttributes提供了Model的所有功能。除此之外,還有幾個(gè)方法用來(lái)設(shè)置flash屬性。
public String processRegistration( @RequestPart(value="profilePictures", required=false) Part fileBytes, RedirectAttributes redirectAttributes, @Valid Spitter spitter, Errors errors) throws IOException { if (errors.hasErrors()) { return "registerForm"; } spitterRepository.save(spitter); redirectAttributes.addAttribute("username", spitter.getUsername()); //調(diào)用方法,將spitter作為ket,Spitter作為值。也可以不設(shè)置值,根據(jù)值得類型自行判斷。 redirectAttributes.addFlashAttribute(spitter); return "redirect:/spitter/" + spitter.getUsername();
在重定向之前,所有的flash屬性都會(huì)復(fù)制到會(huì)話中,在重定向結(jié)束后,存在會(huì)話中的 flash屬性會(huì)被取出,并從會(huì)話中轉(zhuǎn)移到模型之中。
@RequestMapping(value="/{username}", method=GET) public String showSpitterProfile( @PathVariable String username, Model model) { if (!model.containsAttribute("spitter")) { model.addAttribute( spitterRepository.findByUsername(username)); } return "profile"; }
showSpitterProfile()方法首先檢查是否存在key為sptter的modle屬性。如果模型中包含的話,那就什么都不用做了。包含的Spitter對(duì)象將會(huì)傳遞到視圖中進(jìn)行渲染。如果不包含則從spitterRepository中查找Spitter,并將其放到模型中。
7.6 小節(jié)(最喜歡這里)在Spirng中,總是會(huì)有“還沒(méi)有結(jié)束”的感覺(jué)更多的特性,更多的選擇,以及實(shí)現(xiàn)開(kāi)發(fā)目標(biāo)的更多方式。Spring MVC有很多功能和技巧。
當(dāng)然,Spirng MVC的環(huán)境搭建是由多種可選方案的一個(gè)領(lǐng)域。在本章中,我們首先看來(lái)一下搭建Spring MVC中DispatcherServlet和ContextLoaderListener的多種方式。還看到了如何調(diào)整DispatcherServlet的注冊(cè)功能以及如何注冊(cè)自定義的Servlet和FIlterr。如果你需要將應(yīng)用部署到更老的服務(wù)器上,我們還快速了解了如何使用web.xml聲明DispatcherServlet和ContextLoaderListener.
然后我們了解 如何處理Spirng MVC控制器所拋出的異常,盡管帶有@Requestmapping注解的方法可以在自身的代碼中處理異常,但是如果將異常處理的代碼抽取到多帶帶的方法中,那么控制器的代碼會(huì)整潔很多。
為了采用一致的方式處理通用的任務(wù),包括在應(yīng)用中的所有控制器 中處理異常,Spirng 3.2 引入了@ControllerAdvice,他所創(chuàng)建的類能夠?qū)⒖刂破鞯耐ㄓ眯袨槌槿〉酵粋€(gè)方法。
最后,我們看了下如何跨重定向傳遞數(shù)據(jù),包括Spring對(duì)flash屬性的支持:類似于模板,但是能在重定向后存活下來(lái)。這樣的話,就能采用非常恰當(dāng)?shù)姆绞綖镻OST請(qǐng)求執(zhí)行一個(gè)重定向回應(yīng)。而且能夠?qū)⑻幚鞵OST請(qǐng)求時(shí)的模型數(shù)據(jù)傳遞過(guò)來(lái),然后再重定向后使用或展現(xiàn)這些模型數(shù)據(jù)。
如果你有疑惑的話,那么可以告訴你,這就是我所說(shuō)的“更多的功能”,其實(shí),我們并沒(méi)有討論到Spirng MVC 的每個(gè)方面。我們將會(huì)在16章中重新討論 Spirng MVC,到時(shí)你會(huì)看到如何使用它來(lái)創(chuàng)建REST API。
但現(xiàn)在,我們將會(huì)暫時(shí)放下Spring MVC,看一下Spirng web Flow,這是一個(gè)構(gòu)建在Spirng MVC 之上的流程框架,它能夠引導(dǎo)用戶執(zhí)行一系列向?qū)Р襟E。
納悶,你忘記總結(jié)文件上傳了。期待下一章。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/68576.html
摘要:?jiǎn)⒂冒踩赃@個(gè)簡(jiǎn)單的默認(rèn)配置指定了如何保護(hù)請(qǐng)求,以及客戶端認(rèn)證用戶的方案。基于數(shù)據(jù)庫(kù)進(jìn)行認(rèn)證用戶數(shù)據(jù)通常會(huì)存儲(chǔ)在關(guān)系型數(shù)據(jù)庫(kù)中,并通過(guò)進(jìn)行訪問(wèn)。必須經(jīng)過(guò)認(rèn)證其他所有請(qǐng)求都是允許的,不需要認(rèn)證。要求用戶不僅需要認(rèn)證,還要具備權(quán)限。 Spring Security Spring Security 是基于Spring 應(yīng)用程序提供的聲明式安全保護(hù)的安全框架。Spring Sercurity ...
摘要:請(qǐng)求旅程的第一站是的。的任務(wù)是將請(qǐng)求發(fā)送控制器控制器是一個(gè)用于處理請(qǐng)求的組件。處理映射器根據(jù)請(qǐng)求攜帶的信息來(lái)進(jìn)行決策。這樣的結(jié)果就是,只能找到顯示聲明在配置類中的控制器。 構(gòu)建Spring Web應(yīng)用 說(shuō)明 如果你有幸能看到。 1、本文參考了《Spring 實(shí)戰(zhàn)》重點(diǎn)內(nèi)容,參考了GitHub上的代碼 2、本文只為記錄作為以后參考,要想真正領(lǐng)悟Spring的強(qiáng)大,請(qǐng)看原書。 3、在一次...
摘要:是的簡(jiǎn)稱,運(yùn)行環(huán)境,為的運(yùn)行提供了所需的環(huán)境。分割字符串,返回分割后的字符串?dāng)?shù)組。當(dāng)計(jì)算的值相同時(shí),我們稱之為沖突,的做法是用鏈表和紅黑樹存儲(chǔ)相同的值的。迭代器取代了集合框架中的,迭代器允許調(diào)用者在迭代過(guò)程中移除元素。 Java基礎(chǔ)1.JDK和JRE有什么區(qū)別? JDK 是java development kit的簡(jiǎn)稱,java開(kāi)發(fā)工具包,提供java的開(kāi)發(fā)環(huán)境和運(yùn)行環(huán)境。JRE 是j...
摘要:是一個(gè)基于的框架。控制器將視圖響應(yīng)給用戶通過(guò)視圖展示給用戶要的數(shù)據(jù)或處理結(jié)果。有了減少了其它組件之間的耦合度。 相關(guān)閱讀: 本文檔和項(xiàng)目代碼地址:https://github.com/zhisheng17/springmvc 轉(zhuǎn)載請(qǐng)注明出處和保留以上文字! 了解 Spring: Spring 官網(wǎng):http://spring.io/ 一個(gè)好的東西一般都會(huì)有一個(gè)好的文檔解釋說(shuō)明,如果你...
閱讀 1310·2021-11-22 14:44
閱讀 2445·2021-09-30 09:47
閱讀 1221·2021-09-09 11:56
閱讀 2076·2021-09-08 09:45
閱讀 3953·2021-08-31 09:40
閱讀 1250·2019-08-30 15:52
閱讀 2044·2019-08-30 14:09
閱讀 1578·2019-08-26 17:04