1.1 引言1.2 MVC架構(gòu)1.2.1 概念1.2.2 好處
2.1 導(dǎo)入依賴2.2 配置核心(前端)控制器2.3 后端控制器2.4 配置文件2.5 訪問
3.1 基本類型參數(shù)3.2 實體收參【重點】3.3 數(shù)組收參3.4 集合收參 【了解】3.5 路徑參數(shù)3.6 中文亂碼
4.1 轉(zhuǎn)發(fā)4.2 重定向4.3 跳轉(zhuǎn)細節(jié)
5.1 Request和Session5.2 JSP中取值5.3 Model5.4 ModelAndView5.5 @SessionAttributes
6.1 靜態(tài)資源問題6.2 解決方案16.3 解決方案26.4 解決方案3
7.1 導(dǎo)入依賴7.2 使用@ResponseBody7.3 使用@RestController7.4 使用@RequestBody7.4.1 定義Handler7.4.2 Ajax發(fā)送json7.5 Jackson常用注解7.5.1 日期格式化7.5.2 屬性名修改7.5.3 屬性忽略7.5.4 null和empty屬性排除7.5.5 自定義序列化7.6 FastJson7.6.1 導(dǎo)入依賴7.6.2 安裝FastJson7.6.3 使用7.6.4 常用注解
8.1 現(xiàn)有方案,分散處理8.2 異常解析器,統(tǒng)一處理
10.1 導(dǎo)入jar10.2 表單10.3 上傳解析器10.4 Handler
12.1 作用12.2 導(dǎo)入jar12.3 聲明驗證碼組件12.4 Page
13.1 開發(fā)風(fēng)格13.2 優(yōu)點13.3 使用13.3.1 定義Rest風(fēng)格的 Controller13.3.2 Ajax請求
一、SpringMVC
1.1 引言
java開源框架,Spring Framework的一個獨立模塊。
MVC框架,在項目中開辟MVC層次架構(gòu)? ?
對控制器中的功能 包裝 簡化 擴展踐行工廠模式,功能架構(gòu)在工廠之上
1.2 MVC架構(gòu)
1.2.1 概念
名稱職責(zé)
Model模型:即業(yè)務(wù)模型,負責(zé)完成業(yè)務(wù)中的數(shù)據(jù)通信處理,對應(yīng)項目中的 service和dao
View視圖:渲染數(shù)據(jù),生成頁面。對應(yīng)項目中的Jsp
Controller控制器:直接對接請求,控制MVC流程,調(diào)度模型,選擇視圖。對應(yīng)項目中的Servlet
1.2.2 好處
MVC是現(xiàn)下軟件開發(fā)中的最流行的代碼結(jié)構(gòu)形態(tài);
人們根據(jù)負責(zé)的不同邏輯,將項目中的代碼分成 M V C 3個層次;
層次內(nèi)部職責(zé)單一,層次之間耦合度低;
符合低耦合 高內(nèi)聚的設(shè)計理念。也實際有利于項目的長期維護。
二、開發(fā)流程
2.1 導(dǎo)入依賴
????<dependency>
????????????<groupId>org.springframework</groupId>
????????????<artifactId>spring-webmvc</artifactId>
????????????<version>5.1.6.RELEASE</version>
????</dependency>
2.2 配置核心(前端)控制器
作為一個MVC框架,首先要解決的是:如何能夠收到請求!
所以MVC框架大都會設(shè)計一款前端控制器,選型在 Servlet 或 Filter兩者之一,在框架最前沿率先工作,接收所有請求。
此控制器在接收到請求后,還會負責(zé)springMVC的核心的調(diào)度管理,所以既是前端又是核心。
<servlet>
????<servlet-name>mvc</servlet-name>
????<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
????<!-- 局部參數(shù):聲明配置文件位置 -->
????<init-param>
????????<param-name>contextConfigLocation</param-name>
????????<param-value>classpath:mvc.xml</param-value>
????</init-param>
????<!-- Servlet啟動時刻:可選 -->
????<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
????<servlet-name>mvc</servlet-name>
????<url-pattern>/</url-pattern>
</servlet-mapping>
2.3 后端控制器
等價于之前定義的Servlet
@Controller//聲明這是一個控制器
@RequestMapping("/hello")//訪問路徑 ,等價于url-pattern
publicclassHelloController{
? ? @RequestMapping("/test1")//訪問路徑
? ? publicStringhello1(){
? ? ? ? System.out.println("hello world");
? ? ? ? return"index";// 跳轉(zhuǎn):/index.jsp ?
? ? }
? ? @RequestMapping("/test2")//訪問路徑
? ? publicStringhello2(){
? ? ? ? System.out.println("hello c9");
? ? ? ? return"views/users";//? 跳轉(zhuǎn):/views/user.jsp
? ? }
}
2.4 配置文件
默認名稱:核心控制器名-servet.xml? ? 默認位置:WEB-INF
隨意名稱:mvc.xml? ? ? ? ? 隨意位置:resources? ? 但需要配置在核心控制器中
<beans xmlns="http://www.springframework.org/schema/beans"
? ? ? ? xmlns:context="http://www.springframework.org/schema/context"
? ? ? ? xmlns:mvc="http://www.springframework.org/schema/mvc"
? ? ? ? xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
? ? ? ? xsi:schemaLocation="http://www.springframework.org/schema/beans
? ? ? ? ? ? ? ? ? ? ? ? ? ? http://www.springframework.org/schema/beans/spring-beans.xsd
? ? ? ? ? ? ? ? ? ? ? ? ? ? http://www.springframework.org/schema/context
? ? ? ? ? ? ? ? ? ? ? ? ? ? http://www.springframework.org/schema/context/spring-context.xsd
? ? ? ? ? ? ? ? ? ? ? ? ? ? http://www.springframework.org/schema/mvc
? ? ? ? ? ? ? ? ? ? ? ? ? ? http://www.springframework.org/schema/mvc/spring-mvc.xsd">
?
? ? <!-- 告知springmvc? 哪些包中 存在 被注解的類 -->
? ? <context:component-scanbase-package="com.qf.controller"></context:component-scan>
? ? <!-- 注冊注解開發(fā)驅(qū)動 -->
? ? <mvc:annotation-driven></mvc:annotation-driven>
? ? <!-- 視圖解析器
? ? 作用:1.捕獲后端控制器的返回值="index"
? ? 2.解析: 在返回值的前后 拼接 ==> "/index.jsp"
? ? -->
? ? <beanclass="org.springframework.web.servlet.view.InternalResourceViewResolver">
? ? ? ? <!-- 前綴 -->
? ? ? ? <propertyname="prefix"value="/"></property>
? ? ? ? <!-- 后綴 -->
? ? ? ? <propertyname="suffix"value=".jsp"></property>
? ? </bean>
</beans>
2.5 訪問
http://localhost:8989/hello/test1
http://localhost:8989/hello/test2
三、接收請求參數(shù)
3.1 基本類型參數(shù)
請求參數(shù)和方法的形參 同名即可
springMVC默認可以識別的日期字符串格式為: YYYY/MM/dd HH:mm:ss通過@DateTimeFormat可以修改默認日志格式
// id? name gender
// http://localhost:8989/xxx/../test1?id=1&name=zzz&gender=false&birth=2018-12-12 12:20:30
@RequestMapping("/test1")
publicStringtestParam1(Integerid,
Stringname,
Booleangender,
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")Datebirth){
System.out.println("test param1");
return"index";
}
3.2 實體收參【重點】
請求參數(shù)和實體的屬性 同名即可
publicclassUser{
? ? privateIntegerid;
? ? privateStringname;
? ? @DateTimeFormat(pattern="yyyy-MM-dd")
? ? privateDatebirth;
? ? privateBooleangender;
? ? //set/get ...
}
?
//http://localhost:8989/.../test2?id=1&name=zzz&gender=false&birth=2018-12-12 12:20:30
@RequestMapping("/test2")
publicStringtestParam2(Useruser){
System.out.println("test param2");
System.out.println("user:"+user);
return"index";
}
3.3 數(shù)組收參
簡單類型的 數(shù)組
<form>
?? ......
<inputtype="checkbox"name="hobby"value="fb"/>足球
<inputtype="checkbox"name="hobby"value="bb"/>籃球
<inputtype="checkbox"name="hobby"value="vb"/>排球
</form>
//http://localhost:8989/.../test3?hobby=football&hobby=basketball
@RequestMapping("/test3")
publicStringtestParam3(String[]hobby){
for(Stringh:hobby){
System.out.print(h+" ");
?? }
return"index";
}
3.4 集合收參 【了解】
publicclassUserList{
? ? //private User[] users;
? ? privateList<User>users;
? ? //set/get..
}
?
// <input type="text" name="users[0].id"/>
// post請求:http://...?users[0].id=1&users[0].name=zhangsan&users[0].birth=2018-12-12&users[1].id=2&....
@RequestMapping("/test4")
publicStringtestParam4(UserListuserList){
for(Useruser:userList.getUsers()){
System.out.println(user);
?? }
return"index";
}
3.5 路徑參數(shù)
// {id} 定義名為id的路徑;【/hello/{id}】的匹配能力和【/hello/*】等價
// http://localhost:8989/.../hello/10 ? {id}匹配到10
@RequestMapping("/hello/{id}")
// @PathVariable將{id}路徑匹配到值賦給id參數(shù)
// 路徑名和參數(shù)名相同則@PathVariable("id")可簡寫為 @PathVariable
publicStringtestParam5(@PathVariable("id")Integerid){
System.out.println("id:"+id);
return"index";
}
?
// http://localhost:8989/.../hello/tom ? {username}匹配到tom
@RequestMapping("/hello/{username}")
publicStringtestParam6(@PathVariable("username")Stringname){//將{username}路徑匹配到的值賦給name參數(shù)
System.out.println("username:"+name);
return"index";
}
3.6 中文亂碼
首先,頁面中字符集統(tǒng)一
????????JSP : <%@pagepageEncoding="utf-8"%>
????????HTML :<metacharset="UTF-8">
其次,tomcat中字符集設(shè)置,對get請求中,中文參數(shù)亂碼有效
????????Tomcat配置:URIEncoding=utf-8
最后,設(shè)置此filter,對post請求中,中文參數(shù)亂碼有效
<!-- 此過濾器會進行:request.setCharactorEncoding("utf-8"); -->
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
四、跳轉(zhuǎn)
4.1 轉(zhuǎn)發(fā)
@RequestMapping("/forw")
class ForwardController{
? ? @RequestMapping("/test1")
? ? public String testForward(){
? ? ? ? System.out.println("test forward1");
? ? ? ? // 轉(zhuǎn)發(fā)跳轉(zhuǎn) /views/users.jsp
? ? ? ? // return "views/users";//和下一行等價
? ? ? ? return "forward:/views/users.jsp";
? ? }
? ? @RequestMapping("/test2")
? ? public String testForward2(){
? ? ? ? System.out.println("test forward2");
? ? ? ? //轉(zhuǎn)發(fā)到? /forw/test1
? ? ? ? //return "forward:test1";//相對路徑(轉(zhuǎn)發(fā)到本類中的test1)
? ? ? ? //轉(zhuǎn)發(fā)到? /forw/test1
? ? ? ? return "forward:/forw/test1"; //絕對路徑
? ? }
}
4.2 重定向
@RequestMapping("/redir")
classRedirectController{
@RequestMapping("/test1")
publicStringtestRedirect1(){
System.out.println("test redirect1");
//重定向到 /redir/test1
//return "redirect:test1"; //相對路徑(轉(zhuǎn)發(fā)到本類中的test1)
return"redirect:/redir/test1";//絕對路徑
?? }
@RequestMapping("/test2")
publicStringtestRedirect2(){
System.out.println("test redirect2");
//重定向到 /views/users.jsp
return"redirect:/view/user.jsp";
?? }
}
4.3 跳轉(zhuǎn)細節(jié)
????在增刪改之后,為了防止請求重復(fù)提交,重定向跳轉(zhuǎn)
????在查詢之后,可以做轉(zhuǎn)發(fā)跳轉(zhuǎn)
五、傳值
????C得到數(shù)據(jù)后,跳轉(zhuǎn)到V,并向V傳遞數(shù)據(jù)。進而V中可以渲染數(shù)據(jù),讓用戶看到含有數(shù)據(jù)的頁面
????轉(zhuǎn)發(fā)跳轉(zhuǎn):Request作用域
????重定向跳轉(zhuǎn):Session作用域
5.1 Request和Session
//形參中 即可獲得 request 和 session對象
@RequestMapping("/test1")
publicStringtestData(HttpSessionsession,HttpServletRequestreq,Integerid){
session.setAttribute("user",newUser());
req.setAttribute("age",18);
req.setAttribute("users",Arrays.asList(newUser(),newUser()));
//return "test2";
return"forward:/WEB-INF/test2.jsp";
}
5.2 JSP中取值
建議:重點復(fù)習(xí) EL? JSTL
????//jsp中用EL表達式 取值即可
????<fmt:formatDatevalue="${sessionScope.user.birth}"pattern="yyyy-MM-dd"/><br/>
????${sessionScope.user.birth}<br>
? ? ${requestScope.age}
5.3 Model
//model中的數(shù)據(jù),會在V渲染之前,將數(shù)據(jù)復(fù)制一份給request
@RequestMapping("/test")
publicStringtestData(Modelmodel){
model.addAttribute("name","張三");
return"index";
}
?
//jsp中用EL表達式 取值即可
${requestScope.name}
5.4 ModelAndView
//modelandview 可以集中管理 跳轉(zhuǎn)和數(shù)據(jù)
@RequestMapping("/test")
publicModelAndViewtestData(){//返回值類型為ModelAndView
//新建ModelAndView對象
ModelAndViewmv=newModelAndView();
// 設(shè)置視圖名,即如何跳轉(zhuǎn)
mv.setViewName("forward:/index.jsp");
// 增加數(shù)據(jù)
mv.addObject("age",18);
returnmv;
}
?
//jsp中用EL表達式 取值即可
${requestScope.age}
5.5 @SessionAttributes
@SessionAttributes({"gender","name"})? :model中的 name和gender 會存入session中
SessionStatus 移除session
@Controller
@SessionAttributes({"gender","name"})// model中的 name和gender 會存入session中
publicclassUserController{
?
@RequestMapping("/hello")
publicStringhello(Modelm){
m.addAttribute("gender",true);// 會存入session
mv.addObject("name","zhj");// 會存入session
return"index";
?? }
@RequestMapping("/hello2")
publicStringhello(SessionStatusstatus){
// 移除通過SessionAttributes存入的session
status.setComplete();
return"index";
?? }
}
六、靜態(tài)資源
6.1 靜態(tài)資源問題
靜態(tài)資源:html,js文件,css文件,圖片文件
靜態(tài)文件沒有url-pattern,所以默認是訪問不到的,之所以可以訪問,是因為,tomcat中有一個全局的servlet:org.apache.catalina.servlets.DefaultServlet,它的url-pattern是 "/",是全局默認的Servlet.? 所以每個項目中不能匹配的靜態(tài)資源的請求,有這個Servlet來處理即可。
但,在SpringMVC中DispatcherServlet也采用了 “/” 作為url-pattern, 則項目中不會再使用全局的Serlvet,則靜態(tài)資源不能完成訪問。
6.2 解決方案1
DispathcerServlet采用其他的url-pattern
此時,所有訪問handler的路徑都要以 action結(jié)尾?。?/p>
????<servlet>
? ????????<servlet-name>mvc9</servlet-name>
? ????????<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
????</servlet>
????<servlet-mapping>
????????<servlet-name>mvc9</servlet-name>
????????<url-pattern>*.action</url-pattern>
????</servlet-mapping>
6.3 解決方案2
DispathcerServlet的url-pattern依然采用 "/",但追加配置
<!--
額外的增加一個handler,且其requestMapping:? "/**" 可以匹配所有請求,但是優(yōu)先級最低
所以如果其他所有的handler都匹配不上,請求會轉(zhuǎn)向 "/**" ,恰好,這個handler就是處理靜態(tài)資源的
處理方式:將請求轉(zhuǎn)會到tomcat中名為default的Servlet
-->
<mvc:default-servlet-handler/>
6.4 解決方案3
mapping是訪問路徑,location是靜態(tài)資源存放的路徑
將/html/** 中 /**匹配到的內(nèi)容,拼接到 /hhh/后http://..../html/a.html? 訪問 /hhh/a.html
<mvc:resourcesmapping="/html/**"location="/hhh/"/>
七、Json處理
7.1 導(dǎo)入依賴
<!-- Jackson springMVC默認的Json解決方案選擇是 Jackson,所以只需要導(dǎo)入jackson的jar,即可使用。-->
????<dependency>
????????<groupId>com.fasterxml.jackson.core</groupId>
????????<artifactId>jackson-databind</artifactId>
????<version>2.9.8</version>
</dependency>
7.2 使用@ResponseBody
@Controller
publicclassJsonController{
? ? @RequestMapping("/test1")
@ResponseBody//將handler的返回值,轉(zhuǎn)換成json(jackson),并將json響應(yīng)給客戶端。
publicUserhello1(){
System.out.println("hello world");
Useruser=newUser();
returnuser;
?? }
// @ResponseBody還可以用在handler的返回值上
@RequestMapping("/test2")
public@ResponseBodyList<User>hello2(){
System.out.println("hello world");
List<User>users=Arrays.asList(newUser(),newUser());
returnusers;
?? }
// 如果返回值已經(jīng)是字符串,則不需要轉(zhuǎn)json,直接將字符串響應(yīng)給客戶端
@RequestMapping(value="/test3",produces="text/html;charset=utf-8")//produces 防止中文亂碼
@ResponseBody
publicStringhello2(){
System.out.println("hello world");
return"你好";
?? }
}
7.3 使用@RestController
Controller類上加了@RestController注解,等價于在類中的每個方法上都加了@ResponseBody
@Controller
@RestController
publicclassJsonController{
@RequestMapping("/test1")
publicUserhello1(){
System.out.println("hello world");
Useruser=newUser();
returnuser;
?? }
//@ResponseBody還可以用在handler的返回值上
@RequestMapping("/test2")
publicList<User>hello2(){
System.out.println("hello world");
List<User>users=Arrays.asList(newUser(),newUser());
returnusers;
?? }
}
7.4 使用@RequestBody
? ??@RequestBody, 接收Json參數(shù)
7.4.1 定義Handler
classUser{
privateIntegerid;
privateStringname;
privateBooleangender;
//set get
}
@RequestMapping("/users")
publicStringaddUser(@RequestBodyUseruser){//@RequestBody將請求體中的json數(shù)據(jù)轉(zhuǎn)換為java對象
????System.out.println("cap2");
????System.out.println("Post user :"+user);
????return"index";
}
7.4.2 Ajax發(fā)送json
varxhr=newXMLHttpRequest();
xhr.open("post","${pageContext.request.contextPath}/users?"+newDate().getTime());
xhr.setRequestHeader("content-type","application/json");//設(shè)置請求頭
xhr.send('{"id":1,"name":"shine","gender":"true"}');//傳遞json串
//ajax
varuser={id:1,name:"shine"};
$.ajax({
url:'${pageContext.request.contextPath}/json2/test4',
type:'post',
contentType:"application/json",//聲明請求參數(shù)類型為 json
data:JSON.stringify(user),// 轉(zhuǎn)換js對象成json
success:function(ret){
console.log(ret);
?? }
});
7.5 Jackson常用注解
7.5.1 日期格式化
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
publicclassUser{
? ? privateIntegerid;
? ? privateStringname;
? ? @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
? ? privateDatebirth;
?? ....
get/set
}
7.5.2 屬性名修改
@JsonProperty("new_name")
publicclassUser{
? ? @JsonProperty("new_id")//不再使用原屬性名,而是 "new_id"
privateIntegerid;
? ? privateStringname;
?? ....
get/set
}
輸出的json:{“new_id”:xx,"name":"xx"}
7.5.3 屬性忽略
@JsonIgnore
publicclassUser{
privateIntegerid;
@JsonIgnore// 生成json時,忽略此屬性
? ? privateStringname;
?? ....
get/set
}
輸出json時: {"id":xx}
7.5.4 null和empty屬性排除
Jackson 默認會輸出null值的屬性,如果不需要,可以排除。
@JsonInclude(JsonInclude.Include.NON_NULL) //null值 屬性不輸出@JsonInclude(value= JsonInclude.Include.NON_EMPTY) // empty屬性不輸出( 空串,長度為0的集合,null值)
publicclassUser{
privateIntegerid;
@JsonInclude(JsonInclude.Include.NON_NULL)// 若"name==null" 忽略此屬性
? ? privateStringname;
@JsonInclude(value=JsonInclude.Include.NON_EMPTY)// 若hobby長度為0或==null 忽略此屬性
privateList<String>hobby;
?? ....
get/set
}
如果name=null,且hobby長度為0,則輸出json時:{"id":xx}
7.5.5 自定義序列化
@JsonSerialize(using = MySerializer.class) // 使用MySerializer輸出某屬性
publicclassUser{
privateIntegerid;
privateStringname;
@JsonSerialize(using=MySerializer.class)
privateDoublesalary=10000.126;//在輸出此屬性時,使用MySerializer輸出
?? ....
get/set
}
則輸出json時:{"id":xx,"name":"xxx","salary":10000.13}
publicclassMySerializerextendsJsonSerializer<Double>{
?
// value即 Double salary的值
@Override
publicvoidserialize(Doublevalue,JsonGeneratorgen,SerializerProviderserializers)throwsIOException{
// 將Double salary的值 四舍五入
Stringnumber=BigDecimal.valueOf(value).setScale(2,BigDecimal.ROUND_HALF_UP).toString();
// 輸出 四舍五入后的值
gen.writeNumber(number);
?? }
}
7.6 FastJson
7.6.1 導(dǎo)入依賴
<!-- FastJson -->
<dependency>
????<groupId>com.alibaba</groupId>
????<artifactId>fastjson</artifactId>
????<version>1.2.54</version>
</dependency>
7.6.2 安裝FastJson
<mvc:annotation-driven>
<!-- 安裝FastJson,轉(zhuǎn)換器 -->
<mvc:message-converters>
????<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
????<!-- 聲明轉(zhuǎn)換類型:json -->
????<propertyname="supportedMediaTypes">
? ? ? ? ?<list>
????????????????<value>application/json</value>
????????</list>
? ? ?</property>
????</bean>
</mvc:message-converters>
</mvc:annotation-driven>
7.6.3 使用
@ResponseBody? @RequestBody @RestController 使用方法不變
7.6.4 常用注解
日期格式化:@JSONField(format="yyyy/MM/dd")
屬性名修改:@JSONField(name="birth")
忽略屬性:@JSONField(serialize = false)
包含null值:@JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue)? 默認會忽略所有null值,有此注解會輸出null
@JSONField(serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty)? null的String輸出為""
自定義序列化:@JSONField(serializeUsing = MySerializer2.class)
publicclassUserimplementsSerializable{
? ? @JSONField(serialize=false)
privateIntegerid;
@JSONField(name="NAME",serialzeFeatures=SerializerFeature.WriteNullStringAsEmpty)
? ? privateStringname;
@JSONField(serialzeFeatures=SerializerFeature.WriteMapNullValue)
privateStringcity;
? ? @JSONField(format="yyyy/MM/dd")
? ? privateDatebirth;
@JSONField(serializeUsing=MySerializer2.class)
privateDoublesalary;
? ? ...
}
publicclassMySerializer2implementsObjectSerializer{
@Override
publicvoidwrite(JSONSerializerserializer,Objectobject,ObjectfieldName,TypefieldType,
intfeatures)throwsIOException{
Doublevalue=(Double)object;// salary屬性值
Stringtext=value+"元";// 在salary后拼接 “元”
serializer.write(text);// 輸出拼接后的內(nèi)容
?? }
}
newUser(1,null,null,newDate(),100.5);
// 如上對象,轉(zhuǎn)換json:
{NAME:"",city:null,"birth":"2020/12/12","salary":"100.5元"}
?
八、異常解析器
8.1 現(xiàn)有方案,分散處理
Controller中的每個Handler自己處理異常
此種處理方案,異常處理邏輯,分散在各個handler中,不利于集中管理
publicStringxxx(){
try{
? ? ...
}catch(Exception1e){
? ? e.printStackTrace();
return"redirect:/xx/error1";
}catch(Exception2e){
? ? e.printStackTrace();
return"redirect:/xx/error2";
?? }
}
8.2 異常解析器,統(tǒng)一處理
Controller中的每個Handler不再自己處理異常,而是直接throws所有異常。
定義一個“異常解析器” 集中捕獲處理 所有異常
此種方案,在集中管理異常方面,更有優(yōu)勢!
publicclassMyExResolverimplementsHandlerExceptionResolver{
? ? /**
? ? * 異常解析器:主體邏輯
? ? * 執(zhí)行時刻:當(dāng)handler中拋出異常時,會執(zhí)行:捕獲異常,并可以跳到錯誤頁面
? ? */
? ? @Override
? ? publicModelAndViewresolveException(HttpServletRequestrequest,
? ? ? ? ? ? HttpServletResponseresponse,Objecthandler,Exceptionex) {
? ? ? ? ex.printStackTrace();//打印異常棧
? ? ? ? //創(chuàng)建一個ModelAndView
? ? ? ? ModelAndViewmv=newModelAndView();
? ? ? ? //識別異常
? ? ? ? if(exinstanceofException1) {
? ? ? ? ? ? mv.setViewName("redirect:/xxx/error1");
? ? ? ? }elseif(exinstanceofException2){
? ? ? ? ? ? mv.setViewName("redirect:/xxx/error2");
? ? ? ? }else{
? ? ? ? ? ? mv.setViewName("redirect:/xxx/error");
? ? ? ? }
? ? ? ? returnmv;
? ? }
}
<!-- 聲明異常解析器 -->? ?
<beanclass="com.baizhi.exception.resolver.MyExResolver"></bean>
九、攔截器
9.1 作用
作用:抽取handler中的冗余功能
9.2 定義攔截器
執(zhí)行順序: preHandle--postHandle--afterCompletion
publicclassMyInter1implementsHandlerInterceptor{
? ? //主要邏輯:在handler之前執(zhí)行:抽取handler中的冗余代碼
? ? @Override
? ? publicbooleanpreHandle(HttpServletRequestrequest,
? ? ? ? ? ? HttpServletResponseresponse,Objecthandler)throwsException{
? ? ? ? System.out.println("pre~~~");
/*
response.sendRedirect("/springMVC_day2/index.jsp");//響應(yīng)
return false;//中斷請求
*/
? ? ? ? returntrue;//放行,后續(xù)的攔截器或handler就會執(zhí)行
? ? }
? ? //在handler之后執(zhí)行:進一步的響應(yīng)定制
? ? @Override
? ? publicvoidpostHandle(HttpServletRequestrequest,
? ? ? ? ? ? HttpServletResponseresponse,Objecthandler,
? ? ? ? ? ? ModelAndViewmodelAndView)throwsException{
? ? ? ? System.out.println("post~~");
? ? }
? ? //在頁面渲染完畢之后,執(zhí)行:資源回收
? ? @Override
? ? publicvoidafterCompletion(HttpServletRequestrequest,
? ? ? ? ? ? HttpServletResponseresponse,Objecthandler,Exceptionex)
? ? ? ? ? ? throwsException{
? ? ? ? System.out.println("after~~");
? ? }
}
9.3 配置攔截路徑
<mvc:interceptors>
<mvc:interceptor>
<mvc:mappingpath="/inter/test1"/>
<mvc:mappingpath="/inter/test2"/>
<mvc:mappingpath="/inter/test*"/><!-- test開頭 -->
<mvc:mappingpath="/inter/**"/><!-- /** 任意多級任意路徑 -->
<mvc:exclude-mappingpath="/inter/a/**"/><!--不攔截此路徑-->
<beanclass="com.baizhi.interceptor.MyInter1"></bean><!--攔截器類-->
</mvc:interceptor>
</mvc:interceptors>
十、上傳
10.1 導(dǎo)入jar
<dependency>
????<groupId>commons-io</groupId>
????<artifactId>commons-io</artifactId>
????<version>2.4</version>
</dependency>
?
<dependency>
????<groupId>commons-fileupload</groupId>
????<artifactId>commons-fileupload</artifactId>
????<version>1.3.3</version>
<exclusions>
<exclusion>
????<groupId>javax.servlet</groupId>
????<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
10.2 表單
<formaction="${pageContext.request.contextPath }/upload/test1"method="post" enctype="multipart/form-data">
????file:<inputtype="file"name="source"/><br>
????<inputtype="submit"value="提交"/>
</form>
10.3 上傳解析器
<!-- 上傳解析器? id必須是:“multipartResolver” -->
<beanid="multipartResolver"? class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 最大可上傳的文件大小? 單位:byte? 超出后會拋出MaxUploadSizeExceededException異常,可以異常解析器捕獲 -->
<propertyname="maxUploadSize"value="1048576"></property>
</bean>
10.4 Handler
@RequestMapping("/test1")
publicStringhello1(Stringusername,MultipartFilesource,HttpSessionsession) {
//文件的原始名稱
Stringfilename=source.getOriginalFilename();
//定制全局唯一的命名
Stringunique=UUID.randomUUID().toString();
//獲得文件的后綴
Stringext=FilenameUtils.getExtension(filename);//abc.txt ? txt ?? hello.html? html
//定制全局唯一的文件名
StringuniqueFileName=unique+"."+ext;
System.out.println("唯一的文件名:"+uniqueFileName);
?
//文件的類型
Stringtype=source.getContentType();
System.out.println("filename:"+filename+" type:"+type);
?
//獲得 upload_file的磁盤路徑 ==> 在webapp目錄下創(chuàng)建一個目錄"upload_file",且此目錄初始不要為空,否則編譯時被忽略
Stringreal_path=session.getServletContext().getRealPath("/upload_file");
System.out.println("real_path:"+real_path);
?
//將上傳的文件,存入磁盤路徑中
//source.transferTo(new File("d:/xxxx/xxxx/xx.jpg"))
source.transferTo(newFile(real_path+"\\"+uniqueFileName));
return"index";
}
十一、下載
11.1 超鏈
<ahref="${pageContext.request.contextPath}/download/test1?name=Koala.jpg">下載</a>
11.2 Handler
@RequestMapping("/test1")
publicvoidhello1(Stringname,HttpSessionsession,HttpServletResponseresponse){
System.out.println("name:"+name);
//獲得要下載文件的絕對路徑
Stringpath=session.getServletContext().getRealPath("/upload_file");
//文件的完整路徑
Stringreal_path=path+"\\"+name;
?
//設(shè)置響應(yīng)頭? 告知瀏覽器,要以附件的形式保存內(nèi)容 ? filename=瀏覽器顯示的下載文件名
response.setHeader("content-disposition","attachment;filename="+name);
?
//讀取目標(biāo)文件,寫出給客戶端
IOUtils.copy(newFileInputStream(real_path),response.getOutputStream());
?
//上一步,已經(jīng)是響應(yīng)了,所以此handler直接是void
}
十二、驗證碼
12.1 作用
防止暴力攻擊,前端安全保障
12.2 導(dǎo)入jar
<!-- Kaptcha -->
<dependency>
????<groupId>com.github.penggle</groupId>
????<artifactId>kaptcha</artifactId>
????<version>2.3.2</version>
????<exclusions>
????????<exclusion>
????????????????<groupId>javax.servlet</groupId>
????????????????<artifactId>javax.servlet-api</artifactId>
????????</exclusion>
????</exclusions>
</dependency>
12.3 聲明驗證碼組件
<servlet>
????<servlet-name>cap</servlet-name>
????<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
????<init-param>
????????<param-name>kaptcha.border</param-name>
????????<param-value>no</param-value>
????</init-param>
????<init-param>
????????<param-name>kaptcha.textproducer.char.length</param-name>
????????<param-value>4</param-value>
????</init-param>
????<init-param>
????????<param-name>kaptcha.textproducer.char.string</param-name>
????????<param-value>abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789</param-value>
????</init-param>
????<init-param>
????????<param-name>kaptcha.background.clear.to</param-name>
????????<param-value>211,229,237</param-value>
????</init-param>
????<init-param>
????<!-- session.setAttribute("captcha","驗證碼") -->
????????<param-name>kaptcha.session.key</param-name>
????????<param-value>captcha</param-value>
????</init-param>
</servlet>
<servlet-mapping>
????????<servlet-name>cap</servlet-name>
????????<url-pattern>/captcha</url-pattern>
</servlet-mapping>
12.4 Page
<imgsrc="${pageContext.request.contextPath}/captcha"style="width:85px"id="cap"/>
<script>
$(function(){
$("#cap").click(function(){
//刷新驗證碼
path=$(this).attr("src")+"?"+newDate().getTime();
$(this).attr("src",path);
? ? ?? });
?? });
</script>
十三、REST
13.1 開發(fā)風(fēng)格
是一種開發(fā)風(fēng)格,遵從此風(fēng)格開發(fā)軟件,符合REST風(fēng)格,則RESTFUL。
兩個核心要求:
每個資源都有唯一的標(biāo)識(URL)
不同的行為,使用對應(yīng)的http-method
訪問標(biāo)識資源
http://localhost:8989/xxx/users所有用戶
http://localhost:8989/xxx/users/1用戶1
http://localhost:8989/xxx/users/1/orders用戶1的所有訂單
請求方式標(biāo)識意圖
GEThttp://localhost:8989/xxx/users查詢所有用戶
POSThttp://localhost:8989/xxx/users在所有用戶中增加一個
PUThttp://localhost:8989/xxx/users在所有用戶中修改一個
DELETEhttp://localhost:8989/xxx/users/1刪除用戶1
GEThttp://localhost:8989/xxx/users/1查詢用戶1
GEThttp://localhost:8989/xxx/users/1/orders查詢用戶1的所有訂單
POSThttp://localhost:8989/xxx/users/1/orders在用戶1的所有訂單中增加一個
13.2 優(yōu)點
**輸出json:
13.3 使用
13.3.1 定義Rest風(fēng)格的 Controller
@RequestMapping(value="/users",method = RequestMethod.GET)
等價
@GetMapping("/users")
@RestController
publicclassRestController{
@GetMapping("/users")
publicList<User>queryAllUsers(){
????System.out.println("get");
????List<User>users=....
????return users;
?? }
?
@PostMapping("/users")
publicStringaddUser(@RequestBodyUseruser){
System.out.println("Post user :"+user);
return "{status:1}";
?? }
@PutMapping("/users")
publicStringupdateUser(@RequestBodyUseruser){
????System.out.println("Put user"user:"+user);
????return "{status:1}";
?? }
?
@GetMapping("/users/{id}")
publicStringqueryOneUser(@PathVariableIntegerid){//@PathVariable 接收路徑中的值
????System.out.println("Get user id:"+id);
????return "{status:1}";
?? }
?
@DeleteMapping("/users/{id}")
publicStringdeleteOneUser(@PathVariableIntegerid){//@PathVariable 接收路徑中的值
????System.out.println("delete user id:"+id);
????return"{status:1}";
?? }
}
13.3.2 Ajax請求
<script>
? ? functionputUser(){// 發(fā)送更新請求 (增加請求發(fā)送方式也是如此)
varxhr=newXMLHttpRequest();
? ? //定義 put,delete,get,post方式 即可,不用定義_method
xhr.open("put","${pageContext.request.contextPath}/rest04/users");
? ? // 設(shè)置請求頭
xhr.setRequestHeader("content-type","application/json");
// 設(shè)置請求參數(shù)
varuser={id:1,NAME:"shine",city:"bj","birth":"2020/12/12","salary":100.5};
xhr.send(JSON.stringify(user));
xhr.onreadystatechange=function(){
if(xhr.readyState==4&&xhr.status==200){
varret=xhr.responseText;
// 解析json,并輸出
console.log(JSON.parse(ret));
? ? ? ? ?? }
? ? ?? }
? ? /*$.ajax({
? ? ? ? ?? url:'${pageContext.request.contextPath}/rest04/users',
? ? ? ? ?? type:'put',
? ? ? ? ?? contentType:"application/json",//聲明請求參數(shù)類型為 json
? ? ? ? ?? data:JSON.stringify(user),// 轉(zhuǎn)換js對象成json
? ? ? ? ?? success:function(ret){
? ? ? ? ? ? ?? console.log(JSON.parse(ret));
? ? ? ? ?? }
? ? ?? });*/
?? }
?
? ? functiondelUser(){// 發(fā)送刪除請求
varxhr=newXMLHttpRequest();
//定義 put,delete,get,post方式 即可,不用定義_method
xhr.open("delete","${pageContext.request.contextPath}/rest04/users/1");
xhr.send();
xhr.onreadystatechange=function(){
if(xhr.readyState==4&&xhr.status==200){
varret=xhr.responseText;
console.log(JSON.parse(ret));
? ? ? ? ?? }
? ? ?? }
?? }
</script>
十四、跨域請求
14.1 域
域:協(xié)議+IP+端口
14.2 Ajax跨域問題
Ajax發(fā)送請求時,不允許跨域,以防用戶信息泄露。
當(dāng)Ajax跨域請求時,響應(yīng)會被瀏覽器攔截(同源策略),并報錯。即瀏覽器默認不允許ajax跨域得到響應(yīng)內(nèi)容。
互相信任的域之間如果需要ajax訪問,(比如前后端分離項目中,前端項目和后端項目之間),則需要額外的設(shè)置才可正常請求。
14.3 解決方案
允許其他域訪問
在被訪問方的Controller類上,添加注解
@CrossOrigin("http://localhost:8080")//允許此域發(fā)請求訪問
publicclassSysUserController{
? ? ....
}
攜帶對方cookie,使得session可用
在訪問方,ajax中添加屬性:withCredentials: true
$.ajax({
type:"POST",
url:"http://localhost:8989/web/sys/login",
...,
xhrFields: {
// 跨域攜帶cookie
withCredentials:true
? ? }
});
或
varxhr=newXMLHttpRequest();
// 跨域攜帶cookie
xhr.withCredentials=true;

十五、SpringMVC執(zhí)行流程

十六、Spring整合
16.1 整合思路
此時項目中有兩個工廠
DispatcherServlet 啟動的springMVC工廠==負責(zé)生產(chǎn)C及springMVC自己的系統(tǒng)組件
ContextLoaderListener 啟動的spring工廠==負責(zé)生產(chǎn)其他所有組件
springMVC的工廠會被設(shè)置為spring工廠的子工廠,可以隨意獲取spring工廠中的組件
整合過程,就是累加:代碼+依賴+配置。然后將service注入給controller即可
16.2 整合技巧
兩個工廠不能有彼此侵入,即,生產(chǎn)的組件不能有重合。
<!-- 告知SpringMVC? 哪些包中 存在 被注解的類
? ? use-default-filters=true 凡是被 @Controller @Service? @Repository注解的類,都會被掃描
? ? use-default-filters=false 默認不掃描包內(nèi)的任何類, 只掃描include-filter中指定的類
? ? 只掃描被@Controller注解的類
-->
<context:component-scanbase-package="com.zhj"use-default-filters="false">
? <context:include-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 告知Spring 唯獨不掃描@Controller注解的類 -->
<context:component-scanbase-package="com.zhj"use-default-filters="true">
? ? <context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>
</context:component-scan>