SpringMVC框架
轉載請注明出處
目錄
一:配置springMVC開發環境 1.1.配置文件的helloworld 1.2.基於注解的helloworld 二:@RequestMapping詳解 2.1.類的@RequestMapping注解 2.2.請求方式的設置 2.3.參數的規定與請求頭的規定設置 2.4.@RequestMapping映射中的通配符 2.5.@RequestMapping與@PathVariable注解的一起使用 2.6.Rest風格的URL 三:獲取http請求中的信息 3.1.獲取請求中的參數,@RequestParam 3.2.獲取請求頭的信息,@RequestHeader 3.3.獲取Cookie的信息,@CookieValue 3.4.使用Pojo來獲取請求中的大量參數 四:Servlet原生API 4.1.使用Servlet原生API 4.2.使用Servlet原生API的原理(部分springMVC的源代碼) 4.3.springMVC支持哪些原生API 五:頁面中的傳值 5.1.簡單的使用ModelAndView來向頁面傳遞參數 5.2.使用參數Map來向頁面傳值 5.3.使用@SessionAttributes注解,把值放到session域中 5.4.@SessionAtrribute引發的異常 六:@ModelAtrribute注解詳解 6.1.簡單介紹@ModelAtrribute及其運行流程 6.2.@ModelAtrribute源代碼分析 6.3.解決@ModelAtrribute注解中,map集合的鍵與執行目標方法的參數名不一致的情況 七:轉發與重定向 7.1<mvc:view-controller>標簽 7.2.自定義視圖 7.4.視圖的重定向操作 八:數據的格式化 8.1.日期的格式化 8.2.數字的格式化 8.3.數據的校驗 8.4.JSR303的驗證類型 8.5.傳遞json類型的數據 8.6.文件上傳 九:攔截器 9.1.第一個攔截器 9.2.攔截器的指定范圍 9.3.攔截器的使用順序 十:異常的處理 10.1.使用springMVC的注解@ExceptionHandler來處理異常 10.2.如何把異常傳遞到頁面顯示 10.3.處理的優先級 10.4.全局異常處理
一:配置SpringMVC開發環境
任何mvc的框架,都是需要在web.xml中進行核心的配置的,struts2是一個過濾器,而SpringMVC則是一個servlet,但是配置方法大同小異,都是在web.xml中進行配置,配置方法如下:
<servlet> <servlet-name>springMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置DispatcherServlet的一個初始化參數:配置SpringMVC配置文件位置和名稱,這個也可以不配置,如果不配置,則默認是WEB-INFO下的springMVC-servlet.xml --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:abc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springMVC</servlet-name> <!-- 攔截所有url --> <url-pattern>/</url-pattern> </servlet-mapping>
再然后就是配置springMVC-servlet.xml,這個文件名之前說過,可以自定義路徑與名稱,如果不自定義,則一定要放在WEB-INFO下,注意引入正確的命名空間,然后加入核心分解解析器,就是解析url的請求,類似於stuts的配置文件,配置如下:
<!-- 視圖分解解析器 --> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- 這是前綴 --> <property name="prefix" value="/"></property> <!-- 這是后綴 --> <property name="suffix" value=".jsp"></property> <!-- 在spring的控制器中,返回的是一個字符串,那么請求的路徑則是,前綴+返回字符串+后綴 --> </bean>
然后就是正式使用SpringMVC了,我們通常是用注解的方式,因為注解真的太好用了,省事好多,但是這里也簡單介紹一下使用xml配置文件的方式來寫一個簡單的helloworld
1.1.配置文件的helloworld
首先新建一個類,這個類要實現Controller接口,然后實現它的一個ModelAndView方法,代碼如下:
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; public class HelloWroldController implements Controller { public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("spring ModelAndView"); return new ModelAndView("/welcome"); } }
然后在springMVC-servlet.xml中配置一個bean:配置方法如下:
<bean name="/test" class="controller.HelloWroldController"></bean>
這種配置和struts2其實是一樣的,name屬性就是瀏覽器請求的url,而class就是它處理的類,而運行的方法,就是上面所繼承的接口,然后在瀏覽器輸入http://localhost:8080/springMVC/test,就可以跳轉到welcome.jsp下,這是根據之前配置的視圖分解解析器的前后綴前該方法返回的字符串拼接而成的。
1.2.基於注解的helloworld
使用注解就要簡單得多了,首先是在springMVC-servlet.xml中配置掃描的包,這樣spring就會自動根據注解來描述一些特定的注解,然后把這些bean裝載進入spring容器中,配置如下:
<!-- 定義掃描的包 --> <context:component-scan base-package="zxj"></context:component-scan>
然后,在指定的包,或者其子包下新建一個類,代碼如下:
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * @Controller是控制器的注解 * @author jie */ @Controller public class Control { /** * 這里配置了RequestMapping注解,就相當於配置了struts2的action,也就相當於url地址 * 其返回的地址為spingMVC-servlet.xml中,前綴+spinrg+后綴 * @return */ @RequestMapping("/helloworld") public String helloworld(){ return "spring"; } }
這樣就不用再寫配置文件,只要加上相應的注解就行,比如說這里的helloworld方法,只要在瀏覽器輸入http://localhost:8080/project/hellowrold,就可以進入spring.jsp頁面
二:@RequestMapping詳解
2.1.類的@RequestMapping注解
剛才看到了@RequestMapping這個注解是用於給一個方法加上url的請求地址,不過,這個注解不僅僅可以加在方法上面,也可以加在類上面,代碼如下圖所示:
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @RequestMapping("/springMVC") @Controller public class SpringMVCTest { private static final String SUCCESS = "success"; @RequestMapping("/testRequestMapping") public String testRequestMapping(){ System.out.println("testRequestMapping"); return "spring"; } }
如上代碼如示,如果同時在類與方法上面同時加上@RequestMapping注解的話,那么URL肯定不會是之前的那種寫法了,現在要寫成類的注解加上方法的注解,就是有點類似struts2中,<package>的nameplace屬性,那如如上代碼,URL應該為http://localhost:8080/project/springMVC/testReuqestMapping
2.2.請求方式的設置
像我們平常提交一個表單,肯定會有get,與post這兩個常用的請求,那么在springMVC中要如何設置呢?很簡單,也是在@RequestMapping中使用,不過需要在方法的注解上面使用,代碼如下:
/** * 使用method屬性來指定請求方式 * @return */ @RequestMapping(value="/testMethod",method=RequestMethod.POST) public String testMethod(){ System.out.println("testMethod"); return "spring"; }
這下在from表單中,或者異步請求中,就需要以post做為請求,不然會報錯的,因為這里已經設置為了post,如果客戶端再請求get,將會報錯。
2.3.參數的規定與請求頭的規定設置
我們都知道http請求會有着請求參數與請求頭消息,那么在springMVC里面,是可以規范這些信息的,首先給一段代碼示例:
/** * 代表着這個請求中參數必須包含username參數,而且age不能等於10 * 而且請求頭中,請求的語言為中文,時區為東八區,否則就報錯,不允許請求 * @return */ @RequestMapping(value="testParamsAndHeader",params={"username","age!=10"},headers={"Accept-Language=zh-CN,zh;q=0.8"}) public String testParamsAndHeader(){ System.out.println("testParamHeader"); return "spring"; }
無論是params,還是headers,都可以包含多個參數,以逗號相隔就行,如果不滿足寫了的條件,則就會報錯,不允許請求資源。其次這兩個屬性還支持一些簡單的表達式:
user 表示請求中,必須包含user的參數 !user 表示請求中,不能包含user的參數 user!=admin 表示請示中,user參數不能為admin user,age=10 表示請求中必須包含user,age這兩個參數,而且age要等於10
2.4.@RequestMapping映射中的通配符
在@RequestMapping的value屬性中,還支持Ant格式的能配符:
? 匹配url中的任意一個字符 * 匹配url中的任意多個字符 ** 匹配url中的多層路徑
下面舉幾個例子:
/user/*/createUser 匹配/user/abcd/createUser /user/**/createUser 匹配/user/aa/bb/cc/createUser /user/?/createUser 匹配/user/a/createUser
這里就不作代碼演示了,相信大家一看就懂,因為這種通配符真的太常見了
2.5.@RequestMapping與@PathVariable注解的一起使用
springMVC很靈活,它可以獲取URL地址中的值,然后當作變量來輸出,這里要使用@PathVariable注解,故名思意,就是路徑變量的意思,通常的話,@PathVariable要使用,是一定要與@RequestMapping一起使用的,下面給出代碼示例:
/** * 先在方法上面映射URL,這里面可以用一個占位符,然后在參數中用@PathVariable來獲取此占位符的值 * @param id * @return */ @RequestMapping("testPathVariable/{id}") public String testPathVariable(@PathVariable("id") Integer id){ System.out.println("PathVariable:"+id); return "spring"; }
先在括號中加上注解,其中value就是@RequestMapping中占位符的聲明,然后加上數據類型和定義的變量,這樣就可以對其進行使用了
2.6.Rest風格的URL
通常的話,表單有着post,與get提交方式,而rest風格的URL,則有着get,post,put,delete這四種請求方式,但是瀏覽器卻是只支持get與post,所以我們可以使用springMVC,把它進行轉換,我們要利用org.springframework.web.filter.HiddenHttpMethodFilter這個類,這是一個過濾器,我們首先要在web.xml中配置它,請配置在第一個位置,不然的話,可能會先進入其它的過濾器,配置代碼如下:
<!-- 配置org.springframework.web.filter.HiddenHttpMethodFilter,可以把post請求轉為delete或put請求 --> <filter> <filter-name>HiddenHttpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>HiddenHttpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
如上的配置,這個過濾器則會攔截所有的請求,我們可以看一下org.springframework.web.filter.HiddenHttpMethodFilter的部分源代碼:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String paramValue = request.getParameter(this.methodParam);//this.methodParam是一個final常量,為_method if (("POST".equals(request.getMethod())) && (StringUtils.hasLength(paramValue))) { String method = paramValue.toUpperCase(Locale.ENGLISH); HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method); filterChain.doFilter(wrapper, response); } else { filterChain.doFilter(request, response); } }
當這個過濾器攔截到一個請求時,就會先拿到這個請求的參數,它要滿足兩個條件,第一,瀏覽器發出的請求為post請示,第二,它還要有一個參數,參數名為_method,而它的值,則可以為get,post,delete,put,此時過濾器就會把post請求轉換成相應的請求,不然的話就不進行轉換,直接請求。至於添加_method參數的話,則可以使用hidden隱藏域,或者使用?拼接參數都可以。
下面就該是控制器的方法了,在本處第2小點中,有講到@RequestMapping的請求方式的設置,只要把這個請求方式設置成對應的請求就行,比如說轉換成了DELETE請求,則@RequestMapping也要寫成對應的DELETE請求,不然會出錯,示例代碼如下:
@RequestMapping(value="/testRest/{id}",method=RequestMethod.DELETE) public String testRestDelete(@PathVariable("id") Integer id){ System.out.println("test DELETE:"+id); return "spring"; } @RequestMapping(value="/testRest/{id}",method=RequestMethod.PUT) public String testRestPut(@PathVariable("id") Integer id){ System.out.println("test put:"+id); return "spring"; }
此時,就可以正確轉換請求方式了。
三:獲取http請求中的信息
3.1.獲取請求中的參數,@RequestParam
在獲取類似這種:http://localhost:8080/project/test?user=a&password=2這種請求參數時,就需要用@RequestParam這個注解來獲取,代碼如下:
/** * 使用@RequestParam注解來獲取請求參數 * value屬性 參數名 * required 是否非空,默認為true,如果請求中無此參數,則報錯,可以設置為false * defaultValue 默認值,當瀏覽器沒有帶此參數時,該值會有一個默認值 * @param username * @param password * @return */ @RequestMapping(value="/testRequestParam") public String testRequestParam(@RequestParam("username") String username, @RequestParam(value="password",required=false,defaultValue="我是默認值") String password ){ System.out.println(username); System.out.println(password); return "spring"; }
如上面注釋所寫,它常用這三個屬性,value是參數名,但是如果只寫了參數名的話,請求時,就必須帶此參數,不然就會報錯。如果把required屬性設置為false,就可以使得該參數不傳,還有defaultValue屬性,此屬性可以當瀏覽器沒有傳此參數時,給這個參數一個默認值
3.2.獲取請求頭的信息,@RequestHeader
之前也有提到過http的請求頭的信息,那么這里就可以使用@RequestHeader注解來獲取請求頭的信息,它的使用方法與上面的@RequestParam是完全一樣的,同樣擁有value,requied,defaultValue這三個屬性,而有代表的作用也是一樣的,下面給出代碼示例:
/** * 使用@RequestHeader注解來獲取請求頭的信息 * 它與@RequestParam的使用方法是一樣的,都有着三個參數,value,required,defaultValue * @param header * @return */ @RequestMapping("testRequestHeader") public String testRequestHeader(@RequestHeader(value="Accept-Language",required=false,defaultValue="null") String header){ System.out.println(header); return "spring"; }
因為它的用法與本章第一點的獲取請求參數的用法一樣,所以這里就不作過多的說明,詳細可以查看@RequestParam的用法
3.3.獲取Cookie的信息,@CookieValue
在開發中,有很多情況都會用到Cookie,這里可以使用@CookieValue注解來獲取,其使用方法與@RequestParam與@RequestHeader一樣,這里就不過多敘述,給出示例代碼:
/** * 使用@CookieValue注解來獲取瀏覽器傳過來的Cookie * 它與@RequestHeader與@RequestParam的用法一樣,也是有着required與default兩個屬性來指定是否為空與默認值 * @param value * @return */ @RequestMapping("testCookieValue") public String testCookieValue(@CookieValue(value="JSESSIONID",required=true,defaultValue="no") String value){ System.out.println("CookieValue:"+value); return "spring"; }
它也有着三個屬性,value,required,defaultValue,分別對應Cookie名,是否非空,默認值。
3.4.使用Pojo來獲取請求中的大量參數
如果http請求中只有一兩個參數,那么使用@RequestParam還可以,但是如果一個請求中帶有着大量的參數,那么這樣就有點麻煩了,那么springMVC就可以使用Pojo對象來獲取這次請求中的所有參數,並且全部封裝到這個對象里面,這種方式類似struts2的ModelDriver<T>,相信使用過struts2的同學都清楚,這種方式極其簡便,下面一邊給代碼,一邊解釋,首先給出請求的處理方法:
/** * 使用Pojo對象來傳遞參數,因為如果一個請求中包含了大量的參數,那么全部用@RequestParam來做肯定太麻煩<br> * 這里可以在參數中定義一個實體類,實體類中對應着屬性,springMVC就會把從瀏覽器獲取到的參數全部封裝到這個對象里面<br> * 而且這里面的參數可以為空,而且還支持級聯參數(就是指下面User類中的屬性還對應了一個Address的類) * @param user * @return */ @RequestMapping("testPojo") public String testPojo(User user){ System.out.println(user); return "spring"; }
這里無需使用其它的注解,只需要在這個處理方法中加上一個類就行,那么springMVC就會自動把請求參數封裝到你寫好的類中,而且這種封裝還支持級聯操作,什么是級聯操作呢?就是User類中的屬性還有着另外的一個類,下面給出User類的代碼:
public class User { private String username; private String password; private String email; private String age; private Address address; ...省略get,set方法 }
由上面可見,其中不僅有普通的屬性,還有着一個Address的類,我們再來看一下Address類的代碼:
public class Address { private String province; private String city; public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } ...省略get,set方法 }
可以很清楚的看清User類與Address類的關系,那么像這種關系的對象,在瀏覽器form表單中的name屬性該如何寫呢?Address類中的字段,要加上address,比如address.province,或者address.city,其它的屬性,就直接寫User類中的屬性就可以了。而這里為什么Address變成了小寫的呢?其實這並不是什么命名規則,而是我在User類中就是這么定義的
private Address address;
這下沒有什么問題了吧,我們再來看一下瀏覽器表單是怎么寫的:
<form action="${pageContext.request.contextPath}/testPojo" method="post"> username:<input type="text" name="username"/><br/> password:<input type="password" name="password"/><br/> email:<input type="text" name="email"/><br/> age:<input type="text" name="age"/><br/> <!-- POJO支持級聯屬性,所以name屬性是如下的寫法,address.province,意思就是有一個address的類,類里面有province屬性 --> province:<input type="text" name="address.province"/><br/> city:<input type="text" name="address.city"/><br/> <input type="submit" value="提交" /> </form>
如上表單元素就可以看到表單的name屬性是如何與User類對應的,其Address類中的屬性,就以address.city。
下面來說一下這種請求方式的特點:
1.簡便,不需要大量的@RequestParam注解。
2.與struts2的ModelDriver的用法差不多,只不過ModelDriver是接口,整個類里面所有的方法都可以使用。而springMVC中這個Pojo對象的作用僅僅是當前的處理方法中。
3.這種Pojo的使用中,瀏覽器的參數可以為空,就是可以不傳參數,也不會報錯,不像@RequestParam,如果不指定requried=false的話,還會報錯。
四:Servlet原生API
4.1.使用Servlet原生API
在我們平常使用springMVC中,雖然說springMVC已經幫我們做了很多工作了,但是我們實際中還是會要用到Servlet的原生API,那這個時候要如何得到Servlet的原生對象呢?這里與struts2不同,springMVC是在方法中聲明對應的參數來使用這些對象,而struts2則是調用相應的方法來得到這些對象。當然,對於沒有學過struts2的同學,可以忽略,下面給出代碼示例:
@RequestMapping("testServletAPI") public String testServletAPI(HttpServletRequest request,HttpServletResponse response,User user){ System.out.println(request+" "+response); System.out.println(user); System.out.println("testServletAPI"); return "spring"; }
如上代碼所示,直接在對應的處理方法里面聲明這些需要使用的對象就可以了,那如果同時要使用Pojo來獲得請求參數怎么辦呢?這個不用擔心,照常使用就行了,如上代碼所示,同樣聲明了一個User類來接收參數,並不會有任何的影響。
4.2.使用Servlet原生API的原理(部分springMVC的源代碼)
如果你想問,springMVC中的處理方法,里面可以支持哪些Servlet的原生API對象呢?或者你又想問,為什么可以照常的使用Pojo來獲取請求參數呢?那么這里,我們先來看一下springMVC的源代碼,然后再作解釋:
protected Object resolveStandardArgument(Class<?> parameterType, NativeWebRequest webRequest) throws Exception { HttpServletRequest request = (HttpServletRequest) webRequest .getNativeRequest(HttpServletRequest.class); HttpServletResponse response = (HttpServletResponse) webRequest .getNativeResponse(HttpServletResponse.class); if ((ServletRequest.class.isAssignableFrom(parameterType)) || (MultipartRequest.class.isAssignableFrom(parameterType))) { Object nativeRequest = webRequest .getNativeRequest(parameterType); if (nativeRequest == null) { throw new IllegalStateException( "Current request is not of type [" + parameterType.getName() + "]: " + request); } return nativeRequest; } if (ServletResponse.class.isAssignableFrom(parameterType)) { this.responseArgumentUsed = true; Object nativeResponse = webRequest .getNativeResponse(parameterType); if (nativeResponse == null) { throw new IllegalStateException( "Current response is not of type [" + parameterType.getName() + "]: " + response); } return nativeResponse; } if (HttpSession.class.isAssignableFrom(parameterType)) { return request.getSession(); } if (Principal.class.isAssignableFrom(parameterType)) { return request.getUserPrincipal(); } if (Locale.class.equals(parameterType)) { return RequestContextUtils.getLocale(request); } if (InputStream.class.isAssignableFrom(parameterType)) { return request.getInputStream(); } if (Reader.class.isAssignableFrom(parameterType)) { return request.getReader(); } if (OutputStream.class.isAssignableFrom(parameterType)) { this.responseArgumentUsed = true; return response.getOutputStream(); } if (Writer.class.isAssignableFrom(parameterType)) { this.responseArgumentUsed = true; return response.getWriter(); } return super.resolveStandardArgument(parameterType, webRequest); }
從這里就可以說明了一個問題了,springMVC首先會通過反射技術得到這個方法里面的參數(源代碼沒有貼上,有興趣的可以自行查看springMVC的源代碼),然后比較這些參數的類型,是否與上面的九個類型想匹配,如果匹配成功,則返回這個對象,請注意,是與對象類型相匹配,而不是與形參名作匹配,所以這樣,就不會使得Pojo無法工作了
4.3.springMVC支持哪些原生API
其實從4.2中的源代碼中也是可以看到了,這里支持九種個對象,對應關系分別是:
ServletRequest -- ServletRequest
ServletResponse -- ServletResponse
HttpSession -- HttpSession Principal -- request.getUserPrincipal() Locale -- RequestContextUtils.getLocale(request); InputStream -- request.getInputStream(); Reader -- request.getReader(); OutputStream -- response.getOutputStream(); Writer -- response.getWriter();
五:頁面中的傳值
5.1.簡單的使用ModelAndView來向頁面傳遞參數
實際開發中,總要大量的向頁面中傳遞后台的數據,那么存放數據的方法也有多種多樣,比如說存到request中,大家都知道是使用request.setAtrribute()來向request域中傳遞值,但是實際上springMVC給我們封裝了更好的方法,那就是使用ModelAndView。
首先,方法的返回值,該由String變成ModelAndView,然后在處理方法中new一個ModelAndView對象,然后返回這個對象就可以了,對象中可以增加返回頁面的字符,也可以向這個對象里面傳遞參數,現在給出一個簡單的示例代碼:
/** * 使用ModelAndView來對頁面傳值<br> * 注意:org.springframework.web.servlet.ModelAndView; * 而不是org.springframework.web.portlet.ModelAndView; * @return */ @RequestMapping("testModelAndView") public ModelAndView testModelAndView(){ String viewName = "spring"; ModelAndView modelAndView = new ModelAndView(viewName); //添加模型數據到ModelAndView中,數據存在request中 modelAndView.addObject("time", new Date()); modelAndView.setViewName("spring"); return modelAndView; }
如上代碼如示,我們可以使用構造方法給它傳一個值,那就是它最終要返回的頁面的值,或者使用setViewName方法來給它一個返回頁面的名字。使用addObject方法來給這個模型添加數據,這是一個鍵值對的數據,然后返回這個ModelAndView對象。
5.2.使用參數Map來向頁面傳值
可以在執行方法中定義一個Map參數,然后在方法中,向map添加內容,然后在頁面中根據map的鍵來取對應的值,也是存在request域中,下面給出代碼示例:
/** * 使用參數map來向頁面傳值<br> * 頁面中取值的方法${requestScope.names},${requestScope.nation} * @param map * @return */ @RequestMapping("testMap") public String testMap(Map<String, Object> map ){ map.put("names", Arrays.asList("zxj","lj","ly")); map.put("nation", "han"); return "spring"; }
這里面就是在方法中有着一個Map類型的參數,其實不僅僅可以是Map類型,還可以是Model與ModelMap類型的,因為最終傳入的根本就不是Map,而是
org.springframework.validation.support.BindingAwareModelMap
5.3.使用@SessionAttributes注解,把值放到session域中
其實5.1和5.2所訴的內容,雖然是把后台數據傳遞到前台,但是全部都是放置到request域中,這里講訴一下使用@SessionAtrributes注解,把后台數據同時放到session中。
首先介紹一下這個注解,這個注解只能放在類上面,而不能放在方法上面。它有兩個屬性,一個是value屬性,一個是types屬性,這兩個數據都是數組,所以可以聲明多個,其實不論是value屬性,還是types屬性,都可以把數據同時放到session域中,只不過value屬性對應的是執行方法中的map集合的鍵,只要是對應的鍵,那么其值,也會同時被傳遞到session域中(這里之所以加上“同時”這個詞,是因為它會同時存到request與session中),而types屬性,則是放置類的集合,只要map集合中存的是該類的數據,則也會同時被放到request中,下面給示例代碼:
/** * @Controller是控制器的注解 * @SessionAtrributes是session的注解,使用它,就能把map中對應的值,放到session域中 * @author 朱小傑 */ @SessionAttributes(value={"user"},types={String.class}) @Controller public class Control { /** * 在類上面使用@SessionAtrributes注解,把map集合中的值放到session域中 * @param map * @return */ @RequestMapping("testSessionAtrributes") public String testSessionAtrributes(Map<String,Object> map) { User user = new User("zxj","123","1@qq.com","11"); map.put("user", user); map.put("school", "xinxi"); return "spring"; } }
如上代碼所示,先是在類上面使用了@SessionScope注解,然后同時使用了value與types屬性,第一個value屬性的值"user",則是testSessionAtrributes方法中map集合中的"user"的鍵,所以這一個鍵值對會被同時放入session域中,而第二個types的屬性中的String.class,則是代表着這個類了,意思就是說,只要是map集合中放的String類型的數據,都會被放到session中。
注意:使用了value屬性后,這個屬性也就必須存在,意思就是說,必須有一個map,然后有一個user的鍵,不然會報錯,當然,這個是可以解決的,后面會詳細講到。
5.4.@SessionAtrribute引發的異常
上一講說到使用@SessionAtrribute來修飾一個類,就可以把一些值存放到session域中,但是如果找不到對應的值,就會報異常,這里可以用@ModelAtrribute來進行修飾,對它改名就可以了,代碼如下:
@RequestMapping("testModelAtrribute") public String testModelAtrribute(@ModelAttribute("abc") User user){ System.out.println("修改:"+user); return "spring"; }
或者加上一個有着@ModelAtrribute所修飾的方法,至於@ModelAtrribute注解,將會在第六章講到。
六:@ModelAtrribute注解詳解
6.1.簡單介紹@ModelAtrribute及其運行流程
在我們開發中,會有這樣一種情況,比如說我要修改一個人的信息,但是用戶名是不讓修改的,那么我在瀏覽器頁面中肯定會有一個表單,里面有一個隱藏域的id,可以改密碼,可以改郵箱,但是用戶名不讓修改,所以我們不能給用戶名的輸入框,然后用戶修改完數據后,點擊提交保存,然后發現這個時候用戶名不見了!當然大家都會想到就是重新取出用戶名,然后給這個對象賦值,或者先從數據庫里面找到這個用戶的對象,然后再來修改這個對象,而不是自己來創建對象,@ModelAtrribute注解就是基於這種情況的。
那么這種情況在springMVC中要如何實現呢?
首先給出執行的目標方法的代碼:
@RequestMapping("testModelAtrribute") public String testModelAtrribute(User user){ System.out.println("修改:"+user); return "spring"; }
這個代碼很簡單,只是使用Pojo來獲取表單的參數,但是User類是不可能從表單得到用戶名的,所以這個類就缺少了一個屬性,如果這樣存到數據庫里面,是肯定要出問題的,那么按照之前所說,我們可以先得到這個User對象,然后給這個對象賦值,但是我們有着簡化的方法,下面給出@ModelAtrribute注解的用法:
/** * 有@ModelAtrribute注解的方法,會在每個目標方法執行前被springMVC調用 * @param id * @param map */ @ModelAttribute public void getUser(@RequestParam(value="id",required=false) Integer id,Map<String,Object> map){ if(id!=null){ System.out.println("每個目錄方法調用前,都會先調用我!"); //模擬從數據庫中獲取對象 User user = new User(1L, "tom", "123456", "123456", "12"); System.out.println("從數據庫中獲取一個對象 "); map.put("user", user); } }
可以發現,上面的類是沒有返回值的,但是經有一個map集合,我們把這個從數據庫查出來的user對象放到了Map集合中,然后就不需要做什么了,然后上面的
testModelAtrribute方法執行的時候,就會自動把用戶名給填充進去。
下面講一個@ModelAtrribute注解的執行流程
1.執行@ModelAtrribute注解修飾的方法:從數據庫中取出對象,並把對象放到了Map中,鍵為user
2.springMVC從Map集合中取出User對象,並把表單的請求參數賦值給user對象相應的屬性
3.springMVC把上述對象傳入目標方法的參數
4.這個user對象是存在request中,如果jsp表單中有對應的字段,還會自動填充表單
注意:在@ModelAtrribute修飾的方法中,放入Map時的鍵要和目標方法的參數名一致
6.2.@ModelAtrribute源代碼分析
可以一邊調試一邊查看源代碼,這里的源代碼有點多,我就不貼出來了,有興趣的同學可以自己看,我這里講訴一下原理:
1.調用@ModelAtrribute注解修飾的方法,實際上是把@ModelAtrribute中的Map放到了implicitModel中。
2.解析請求處理器的標參數,實際上目標參數來自於WebDataBinder對象的target屬性
(1).創建WebDataBinder對象
>確定objectName屬性,若傳入的attrName屬性為"",則objectName為類名第一個字母小寫
>注意:atrributeName,若目標方法的Pojo屬性使用了@ModelAtrribute注解來修飾,則atrributeName值即為@ModelAtrribute的value屬性值
(2).確定target屬性
>在implicitModel中查找atrrName對應的屬性值,若存在ok,若不存在,則驗證Hander,是否使用了@SessionAtrributes進行修飾,若使用了,則嘗試從session中獲取attrName所對應的屬性值,若session中沒有對應的值,則拋出異常
>若Hander沒有使用@SessionAtrributes進行修飾,或@SessionAtrributes中沒有使用value值指定的鍵和attrName相互匹配,則通過反射創建了Pojo對象,這個時候target就創建好了。
3.springMVC把表單的請求參數賦給了WebDataBinder的target屬性
4.springMVC會把WebDataBinder的attrName和target給到implicitModel
5.把WebDataBinder的target作為參數傳遞給目標方法的入參
63.解決@ModelAtrribute注解中,map集合的鍵與執行目標方法的參數名不一致的情況
其實我們可以在目標方法里面的參數中,定義一個@ModelAtrribute注解,並把其值指定為@ModelAtrribute注解修飾的方法中的map的鍵,下面給出代碼示例:
@RequestMapping("testModelAtrribute") public String testModelAtrribute(@ModelAttribute("abc") User user){ System.out.println("修改:"+user); return "spring"; }
可以看到,目標方法的參數中,有着@ModelAtrribute修飾,其value屬性為"abc",再來看一下@ModelAtrribute所修飾的方法:
/** * 有@ModelAtrribute注解的方法,會在每個目標方法執行前被springMVC調用 * @param id * @param map */ @ModelAttribute public void getUser(@RequestParam(value="id",required=false) Integer id,Map<String,Object> map){ if(id!=null){ System.out.println("每個目錄方法調用前,都會先調用我!"); //模擬從數據庫中獲取對象 User user = new User(1L, "tom", "123456", "123456", "12"); System.out.println("從數據庫中獲取一個對象 "); map.put("abc", user); } }
在這里就可是很顯示的看到map是存放了一個"abc"的鍵。
七:轉發與重定向
7.1<mvc:view-controller>標簽
在springMVC的配置文件中使用<mvc:view-controller>標簽可以使得url不需要進入handler處理方法,就可以直接跳轉頁面,配置方法如下
<!-- 配置直接進行轉發的頁面,無須進入handler方法 --> <mvc:view-controller path="good" view-name="spring"/>
根據如上的配置,就可以直接在瀏覽器中輸入http://localhost:8080/project/good,就可以跳轉至success.jsp頁面,而無需進入handler處理方法,更不需要進行@RequestMapping映射。
但是如果僅僅是這樣的配置,會有一個很大的問題,就是之前所寫的handler處理方法全部都不能使用了,全部會進行報錯,那么要怎么解決呢?可以在springMVC的配置文件中,寫一個下面的標簽,就不會有這樣的問題了:
<!-- 這個標簽在實際開發中,通常要進行配置 --> <mvc:annotation-driven/>
只要寫上這樣的一個標簽,那么就可以解決上面的問題,而且也不要寫任何參數。不過這個標簽具體有什么用呢?后面會作介紹。
7.2.自定義視圖
下面來講一下自定義視圖,使用它可以很好的和jfreechar或excel整合,下面來具體說明。
首先新建一個視圖,新建一個類,繼承view接口,然后覆蓋里面的方法,代碼如下:
@Component public class HelloView implements View { @Override public String getContentType() { //這個方法是設置返回的類型,根據自己的需要設置 return "text/html"; } @Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { //這里顯示要打印的內容 response.getWriter().print("hello view ,time:"+new Date()); } }
如上所示,寫一個類,繼承View接口,然后覆蓋里面的方法,就可以自己自定義視圖了,但是目前這個視圖還沒有用,需要在springMVC的配置文件中進行配置,才能使用這個視圖,配置方法如下:
<!-- 視圖解析器 BeanNameViewResolver:使用視圖的名字來解析視圖 --> <!-- 定義property屬性來定義視力的優先級,order值越小,越優先,InternalResourceViewResolver視圖的order最高為int最大值 --> <bean class="org.springframework.web.servlet.view.BeanNameViewResolver"> <property name="order" value="10"></property> </bean>
只要在springMVC的配置文件中寫如上的配置,那么這個視圖就可以使用了,然后我們寫一個handler處理方法,代碼如下:
@RequestMapping("testView") public String testView(){ System.out.println("hello view"); return "helloView"; }
然后的話,我們輸入如下url,http://localhost:8080/project/testView,就不會進行helloView.jsp,因為配置的解析視圖的order值為最高,也就代表着它的優先級是最低的,所以會先執行我們自定義的視圖,那么就會在瀏覽器中顯示之前視圖中向瀏覽器寫的數據。
7.4.視圖的重定向操作
上面所說的全部都是視圖的轉發,而不是重定向,這次我來講一下重定向是怎么操作的。
只要字符串中以forward或者redirect開頭,那么springMVC就會把它解析成關鍵字,然后進行相應的轉發,或者重定向操作,下面給出示例代碼:
/** * 測試視圖的重定向 * 只要在字符串中加了foword或者redirect開頭,springMVC就會把它解析成關鍵字,進行相應原轉發重定向操作 * @return */ @RequestMapping("testRedirect") public String testRedirect(){ return "redirect:/spring.jsp"; } /** * 測試視圖的轉發 * 只要在字符串中加了foword或者redirect開頭,springMVC就會把它解析成關鍵字,進行相應原轉發重定向操作 * @return */ @RequestMapping("testForward") public String testForward(){ return "forward:/spring.jsp"; }
上面就分別是重定向與轉發操作,其實不止java代碼,<mvc:view-controller>標簽中的返回視圖,也可以加上redirect或者forward字符串,也會進行相應的操作。
八:數據的格式化
8.1.日期的格式化
form表單向后台處理方法提交一個參數的時候,如果提交一個日期的數據,而后台接收的數據類型則是Date類型,那么springMVC肯定無法將其轉換成,因為springMVC不知道你傳的數據的格式是怎么樣的,所以需要為接收的字段指定日期的格式,使用@DateTimeFormat注解,使用方法如下:
使用前提:需要在springMVC-servlet.xml的配置文件中配置<mvc:annotation-driven/>,這個在開發中肯定會配置的,因為它有好多作用,如果不配置,則下面代碼無效:
<mvc:annotation-driven/>
下面是目標方法的代碼:
@RequestMapping("dateFormat") public void initBinder(@RequestParam("date") @DateTimeFormat(pattern="yyyy-MM-dd") Date date){ System.out.println(date); }
上面就是在接收的參數前面加了一個@DateTimeFormat注解,注解中寫明pattern屬性,寫上日期的格式,然后在瀏覽器輸入:http://localhost:8080/project/dateFormat?date=19951029,這樣springMVC就可以把這個字符串轉成Date日期了。
如果是使用Pojo,使用一個對象來接收參數,那么也是一樣的,同樣是在字段的上方,加上一個@DateTimeFormat注解,如下:
public class User { private Long id; private String username; private String password; private String email; private String age; private Address address; @DateTimeFormat(pattern="yyyy-MM-dd") private Date birthday; ..省略get,set方法 }
8.2.數字的格式化
除了日期的格式化,我們可能還會遇到數字的格式化,比如會計人員作賬時,數字喜歡這樣寫 1,234,567.8 因為這樣簡單明了,但是如果由java直接來解析的話,肯定是不行的,因為這根本就不是一個Float類型的數據,肯定是要報錯的,那么要如何呢?我們可以使用@NumberFormat()注解,這個注解的使用方式,和使用前提和8.1章節,日期的格式化是一樣的,請先查看8.1章節,再看本章。
和之前一樣,<mvc:annotation-driven/>是肯定要配置的,不過這里就不詳細說明了,下面給出執行方法的代碼:
@RequestMapping("numFormat") public String numFormat(@RequestParam("num") @NumberFormat(pattern="#,###,###.#") Float f){ System.out.println(f); return "spring"; }
其使用方法,其實是和@DateTimeFormat是一樣的,但是這里的參數有必要說明一樣,”#“是代表數字,那么這個時候,就可以對其進行解析了。如果你傳入的是一個正確的Float類型的數據,那么它會被正確的接收這個數字,如果不是一個Float類型的數據,那么springMVC會嘗試使用@NumberFoamat注解的參數來嘗試解析。
如輸入的是http://locathost:8080/project?num=123,那么springMVC會解析成123.0,如果是http://locathost:8080/project?num=123.1,則會正常顯示成123.1,那如果是http://locathost:8080/project?num=1,234,567.8這種特殊的寫法,則也會正確的解析成1234567.8
8.3.數據的校驗
數據的檢驗是使用Hibernate-validator的擴展注解,它是javaee6.0提出的JSR303的實現,使用它,可以用注解對數據進行校驗,下面來說一下使用方法。
1.首先要導入jar包,這不是spring的jar包,需要下載Hibernate-validator的jar包,然后添加到項目工程中
2.其次是要在springMVC的配置文件中,即springMVC-servlet.xml中配置<mvc:annotation-driven/>,不需要寫其它的屬性:
<mvc:annotation-driven/>
3.然后在字段面前加上對應的校驗注解,下面以一個bean作一個簡單的例子:
public class User { private Long id; //用戶名不能為空 @NotEmpty private String username; private String password; //必須是email格式 @Email private String email; private String age; private Address address; //生日必須是以前的時間,而不能是未來的時間 @Past @DateTimeFormat(pattern="yyyy-MM-dd") private Date birthday; ..當然,省去了get,set方法 }
這樣就會對使用了注解的字段進行校驗,然后這樣還不行,還需要指定目標執行方法,所以需要在執行方法上面也加上一個注解@Valid,這樣加了這個注解的執行方法就會校驗數據,下面給出目標方法的注解:
@RequestMapping("testPojo") public String testPojo(@Valid User user,BindingResult result){ //程序較驗的結果,以衣類型轉換的結果,都會存放在bindingResult對象中,可以把這個參數定義到方法中使用 //當從這個對象中得到的錯誤的個數大於0的時候進行操作 if(result.getErrorCount()>0){ //遍歷所有的錯誤,把錯誤字段和錯誤消息打印出來 for(FieldError error : result.getFieldErrors()){ System.out.println(error.getField()+" "+error.getDefaultMessage() ); } } System.out.println(user); return "spring"; }
只要在這個方法上面加上@Valid注解,然后這個執行方法就會校驗數據,校驗的結果可以使用BindingResult對象顯示,這個對象會不僅僅會保存數據校驗的結果,還會保存數據類型轉換的結果,所以都可以使用這個對象得到相應的信息,顯示的方法如下面的代碼如示。
注意:需要校驗的bean對象與其綁定結果對象或錯誤對象是成對出現的,他們中間不允許聲明其它的參數
8.4.JSR303的驗證類型
通過上面的例子我們知道可以使用注解來驗證數據類型,但是具體可以使用哪些注解呢,下面給出說明:
@Null 被注釋的元素必須為 null @NotNull 被注釋的元素必須不為 null @AssertTrue 被注釋的元素必須為 true @AssertFalse 被注釋的元素必須為 false @Min(value) 被注釋的元素必須是一個數字,其值必須大於等於指定的最小值 @Max(value) 被注釋的元素必須是一個數字,其值必須小於等於指定的最大值 @DecimalMin(value) 被注釋的元素必須是一個數字,其值必須大於等於指定的最小值 @DecimalMax(value) 被注釋的元素必須是一個數字,其值必須小於等於指定的最大值 @Size(max, min) 被注釋的元素的大小必須在指定的范圍內 @Digits (integer, fraction) 被注釋的元素必須是一個數字,其值必須在可接受的范圍內 @Past 被注釋的元素必須是一個過去的日期 @Future 被注釋的元素必須是一個將來的日期 @Pattern(value) 被注釋的元素必須符合指定的正則表達式
//-----------------下面是hibernate-valitor新增加的 @Email 被注釋的元素必須是電子郵箱地址 @Length 被注釋的字符串的大小必須在指定的范圍內 @NotEmpty 被注釋的字符串的必須非空 @Range 被注釋的元素必須在合適的范圍內
8.5.傳遞json類型的數據
而在springMVC中,使用json非常的簡單,但是首先需要引進其它的一些jar包,那就是jackson,這是一個解析json的jar包,然后就可以直接使用了,下面給出代碼示例:
/** * 打印json字符串 * @return */ @ResponseBody @RequestMapping("testjson") public List testJson(){ List list = new ArrayList(); list.add("good"); list.add("12"); list.add("dgdgd"); list.add("99999999999"); return list; }
如上如示,只要在執行方法的上面加上@ResponseBody注解,然后定義目標方法的返回值,其返回值可以是任意集合,也可以是任意對象,然后springMVC會自動將其轉換成json
8.6.文件上傳
springMVC也封裝了文件上傳,變的極其簡單,但是需要引入common-io.jar與common.upload.jar包,然后需要在spinrgMVC-serlvet.xml中作如下的配置:
<!-- 配置nultipartresolver,注意:id名必須這樣寫,不然會報錯 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8"></property> <property name="maxInMemorySize" value="10240000"></property> </bean>
<!-- 配置nultipartresolver,注意:id名必須這樣寫,不然會報錯 --> <!-- defaultEncoding:表示用來解析request請求的默認編碼格式,當沒有指定的時候根據Servlet規范會使用默認值ISO-8859-1。當request自己指明了它的編碼格式的時候就會忽略這里指定的defaultEncoding。 --> <!-- uploadTempDir:設置上傳文件時的臨時目錄,默認是Servlet容器的臨時目錄。 --> <!-- maxUploadSize:設置允許上傳的最大文件大小,以字節為單位計算。當設為-1時表示無限制,默認是-1。 --> <!-- maxInMemorySize:設置在文件上傳時允許寫到內存中的最大值,以字節為單位計算,默認是10240。 -->
注意:id必須如上寫法,不然會報錯
然后給出處理方法的代碼:
@RequestMapping("testFileUpload") public String testFileUpload(@RequestParam(value="desc",required=false) String desc, @RequestParam(value="file",required=false) MultipartFile files[]) throws IOException{ for(MultipartFile file : files){ System.out.println(desc); System.out.println(file.getOriginalFilename());//得到文件的原始名字 System.out.println(file.getName());//得到文件的字段的名字”file InputStream in = file.getInputStream(); OutputStream out = new FileOutputStream("d:/"+file.getOriginalFilename()); int len=0; byte[] buf =new byte[1024]; while((len=in.read(buf))!=-1){ out.write(buf); out.flush(); } out.close(); in.close(); } return "spring"; }
如果是多個文件上傳,則改為數組,如果是單個,方式也是一樣,與struts2的文件的上傳極其的類似。
九:攔截器
9.1.第一個攔截器
編寫攔截器極其簡單,只要編寫一個類,實現HandlerInterceptor的方法,然后在springMVC的配置文件中配置這個類,就可以使用這個攔截器了。
首先給出配置文件的寫法:
<!-- 配置自定義的攔截器 --> <mvc:interceptors> <!-- 這個bean就是自定義的一個類,攔截器 --> <bean class="zxj.intercepter.FirstInterceptor"></bean> </mvc:interceptors>
然后再來寫FirstInterceptor這個攔截器,代碼如下:
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; /** * 寫一個攔截器,需要實現HandlerInterceptor接口 * @author jie * */ public class FirstInterceptor implements HandlerInterceptor { /** * 當目標方法執行之前,執行此方法,如果返回false,則不會執行目標方法,同樣的,后面的攔截器也不會起作用 * 可以用來做權限,日志等 */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("這個方法會最先執行.."); return true; } /** * 執行目標方法之后調用,但是在渲染視圖之前,就是轉向jsp頁面之前 * 可以對請求域中的屬性,或者視圖進行修改 */ public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("執行目標方法之后調用,但是在渲染視圖之前,就是轉向jsp頁面之前"); } /** * 在渲染視圖之后被調用 * 釋放資源 */ public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("在渲染視圖之后被調用"); } }
然后在每個執行方法調用之前,都會先進攔截器,這就是一個簡單的攔截器的寫法了。
9.2.攔截器的指定范圍
在使用攔截器時候,並不一定要對所有的目標方法都進行攔截,所以我們可以只對指定的方法進行攔截,這就需要更改配置文件了,下面給出配置文件的寫法:
<!-- 配置自定義的攔截器 --> <mvc:interceptors> <!-- 這個bean就是自定義的一個類,攔截器 --> <bean class="zxj.intercepter.FirstInterceptor"></bean> <!-- 這個配置可以配置攔截器作用(不作用)的路徑,不起作用的用<mvc:exclude-mapping path=""/> --> <mvc:interceptor> <!-- 這個path就是起作用的路徑,可以使用通配符 --> <mvc:mapping path="/test*"/> <bean class="zxj.intercepter.SecondInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
只需要在<mvc:interceptors>中配置一個<mvc:interceptor>,然后指定其路徑,就可以了,這個路徑可以指定一個URL,也可以使用通配符。
9.3.攔截器的使用順序
當同時定義了多個攔截器的時候,那么它的使用順序是怎么樣的呢?
preHandle是按配置文件中的順序執行的
postHandle是按配置文件中的倒序執行的
afterCompletion是按配置文件中的倒序執行的
十:異常的處理
10.1.使用springMVC的注解@ExceptionHandler來處理異常
現在來說一下springMVC的處理異常的方法,在目標方法中,可以不處理異常,全部拋出去,交由springMVC來統一處理,而且處理方式要比struts2要簡單的多,下面則簡單的介紹一下怎么用springMVC來處理異常。
首先定義一個會拋出異常的處理方法:
/** * 模擬一個異常,然后交由springMVC來處理 * @param i * @return */ @RequestMapping("byZero") public String byZero(@RequestParam("i") int i){ System.out.println("go."); System.out.println(10/i); return "spring"; }
看到上面的代碼,很顯示,如果瀏覽器提交出來的i是一個0的話,就會拋出一個算術的異常,因為0不能為除數,拋出的異常為ArithmeticException,如果不經過任何處理的話,異常就會往瀏覽器拋,而瀏覽器是無法處理的,所以我們要在springMVC中將其進行處理,下面就是處理方式的代碼,很簡單,只要定義一個方法,使有用@ExceptionHandler注解,代碼如下:
/** * 使用@ExceptionHandler注解,其value值是一個Class數組,且這個Class就是要拋出的那個異常的類,然后springMVC只要遇到這個異常,就會自行處理 * 參數中可以定義一個Exception,里面包含出錯的內容 * 注意:參數中不能定義Map,不然會報錯,所以如果要把異常存到request中,要用modelAndView * @param e * @return */ @ExceptionHandler({ArithmeticException.class}) public String hException(Exception e){ System.out.println(e); return "spring"; }
這就是springMVC的處理方式,首先來看一個@ExceptionHandler注解,它的value值是一個Class的數組,存放的就是要拋出的異常的類,比如除數為0就會拋出ArithmeticException異常,那么就在這個注解里面的value值中聲明這個Class,那以后只要有出現這個異常,springMVC就會對其進行處理,當然如果覺得寫太多異常類太麻煩的話,可以統一用Exception,因為它是所有異常類的父親類,所以它會處理所有的異常。
在這個異常的處理方法中,可以聲明一個Exception參數,這個對象里面封裝了引發異常的信息,可以由這個對象查看是哪些異常出現。
10.2.如何把異常傳遞到頁面顯示
很多人會想,得到了Exception對象,然后直接把它存在request中就可以了,但是如果在參數中聲明Map,是會報錯的。因為@ExceptionHandler修飾的方法中,參數不能有Map,既然不能使用Map往request中存值,那么要如何解決呢?使用ModelAndView,可以參照5.1章節的內容,下面給出示例代碼:
/** * 使用@ExceptionHandler注解,其value值是一個Class數組,且這個Class就是要拋出的那個異常的類,然后springMVC只要遇到這個異常,就會自行處理 * 參數中可以定義一個Exception,里面包含出錯的內容 * 注意:參數中不能定義Map,不然會報錯,所以如果要把異常存到request中,要用modelAndView * @param e * @return */ @ExceptionHandler({ArithmeticException.class}) public ModelAndView toException(Exception e){ ModelAndView mv = new ModelAndView(); mv.setViewName("error"); //雖然不能使用Map往request中存值,但是可以使用下面的方法 mv.addObject("error", e); System.out.println(e); return mv; }
這樣就可以往request中存放錯誤內容了。
10.3.處理的優先級
異常是可以聲明多個的,那么如果同時聲明了多個異常,而且都能匹配上拋出的異常,那么spirngMVC會怎么處理呢?
假設同時定義了兩個異常,一個是ArithmeticException,還有一個是Exception,那么肯定會執行ArithmeticException,因為它的匹配度更高一點,或者說離這個異常的真實原因更近一點,但是如果沒有定義ArithmeticException,那么就會走Exception異常了。
10.4.全局異常處理
細心的朋友也會發現,這上面定義的異常處理,都是只能處理一個類的異常,那在實際開發中,肯定會有着很多個處理類,那么要怎么定義一個全局的異常處理方法呢?
我們可以單獨寫一個類出來,使用@ControllerAdvice修飾這個類,然后再寫上異常的處理方法,然后它就是全局的一個異常處理了,代碼如下:
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.servlet.ModelAndView; /** * 定義全局的異常處理
* 如果在處理方法的當前類找不到異常的處理方法,則會在全局找有着@ControllerAdvice修飾的類中的異常處理方法 * 只要使用@ControllerAdvice修飾這個類,然后再定義異常的處理方法,那么它就是一個全局的異常處理了 * @author jie * */ @ControllerAdvice public class MyExceptionHandler { /** * 使用@ExceptionHandler注解,其value值是一個Class數組,且這個Class就是要拋出的那個異常的類,然后springMVC只要遇到這個異常,就會自行處理 * 參數中可以定義一個Exception,里面包含出錯的內容 * 注意:參數中不能定義Map,不然會報錯,所以如果要把異常存到request中,要用modelAndView * @param e * @return */ @ExceptionHandler({ArithmeticException.class}) public ModelAndView toException(Exception e){ ModelAndView mv = new ModelAndView(); System.out.println("gobal handler exception"); mv.setViewName("error"); //雖然不能使用Map往request中存值,但是可以使用下面的方法 mv.addObject("error", e); System.out.println(e); return mv; } }
如上代碼可以看到,方法體的內容完全沒有變化,只是在類上面加了一個@ControllerAdvice,而且不需要在springMVC的配置文件中作任何的配置,就可以使用這個全局的異常了。
注意:這個類里面的異常的處理的優先級低於直接定義在處理方法的類中