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 訪問(wèn)
3.1 基本類型參數(shù)3.2 實(shí)體收參【重點(diǎn)】3.3 數(shù)組收參3.4 集合收參 【了解】3.5 路徑參數(shù)3.6 中文亂碼
4.1 轉(zhuǎn)發(fā)4.2 重定向4.3 跳轉(zhuǎn)細(xì)節(jié)
5.1 Request和Session5.2 JSP中取值5.3 Model5.4 ModelAndView5.5 @SessionAttributes
6.1 靜態(tài)資源問(wèn)題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 聲明驗(yàn)證碼組件12.4 Page
13.1 開(kāi)發(fā)風(fēng)格13.2 優(yōu)點(diǎn)13.3 使用13.3.1 定義Rest風(fēng)格的 Controller13.3.2 Ajax請(qǐng)求
14.1 域14.2 Ajax跨域問(wèn)題14.3 解決方案
一、SpringMVC
1.1 引言
java開(kāi)源框架乌逐,Spring Framework的一個(gè)獨(dú)立模塊竭讳。
MVC框架,在項(xiàng)目中開(kāi)辟M(fèi)VC層次架構(gòu)? ?
對(duì)控制器中的功能 包裝 簡(jiǎn)化 擴(kuò)展踐行工廠模式,功能架構(gòu)在工廠之上
1.2 MVC架構(gòu)
1.2.1 概念
名稱職責(zé)
Model模型:即業(yè)務(wù)模型浙踢,負(fù)責(zé)完成業(yè)務(wù)中的數(shù)據(jù)通信處理绢慢,對(duì)應(yīng)項(xiàng)目中的 service和dao
View視圖:渲染數(shù)據(jù),生成頁(yè)面洛波。對(duì)應(yīng)項(xiàng)目中的Jsp
Controller控制器:直接對(duì)接請(qǐng)求胰舆,控制MVC流程,調(diào)度模型奋岁,選擇視圖思瘟。對(duì)應(yīng)項(xiàng)目中的Servlet
1.2.2 好處
MVC是現(xiàn)下軟件開(kāi)發(fā)中的最流行的代碼結(jié)構(gòu)形態(tài);
人們根據(jù)負(fù)責(zé)的不同邏輯,將項(xiàng)目中的代碼分成 M V C 3個(gè)層次;
層次內(nèi)部職責(zé)單一闻伶,層次之間耦合度低;
符合低耦合 高內(nèi)聚的設(shè)計(jì)理念滨攻。也實(shí)際有利于項(xiàng)目的長(zhǎng)期維護(hù)。
二蓝翰、開(kāi)發(fā)流程
2.1 導(dǎo)入依賴
????<dependency>
????????????<groupId>org.springframework</groupId>
????????????<artifactId>spring-webmvc</artifactId>
????????????<version>5.1.6.RELEASE</version>
????</dependency>
2.2 配置核心(前端)控制器
作為一個(gè)MVC框架光绕,首先要解決的是:如何能夠收到請(qǐng)求!
所以MVC框架大都會(huì)設(shè)計(jì)一款前端控制器畜份,選型在 Servlet 或 Filter兩者之一,在框架最前沿率先工作诞帐,接收所有請(qǐng)求。
此控制器在接收到請(qǐng)求后爆雹,還會(huì)負(fù)責(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啟動(dòng)時(shí)刻:可選 -->
????<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
????<servlet-name>mvc</servlet-name>
????<url-pattern>/</url-pattern>
</servlet-mapping>
2.3 后端控制器
等價(jià)于之前定義的Servlet
@Controller//聲明這是一個(gè)控制器
@RequestMapping("/hello")//訪問(wèn)路徑 钙态,等價(jià)于url-pattern
publicclassHelloController{
? ? @RequestMapping("/test1")//訪問(wèn)路徑
? ? publicStringhello1(){
? ? ? ? System.out.println("hello world");
? ? ? ? return"index";// 跳轉(zhuǎn):/index.jsp ?
? ? }
? ? @RequestMapping("/test2")//訪問(wèn)路徑
? ? publicStringhello2(){
? ? ? ? System.out.println("hello c9");
? ? ? ? return"views/users";//? 跳轉(zhuǎn):/views/user.jsp
? ? }
}
2.4 配置文件
默認(rèn)名稱:核心控制器名-servet.xml? ? 默認(rèn)位置: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>
? ? <!-- 注冊(cè)注解開(kāi)發(fā)驅(qū)動(dòng) -->
? ? <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 訪問(wèn)
http://localhost:8989/hello/test1
http://localhost:8989/hello/test2
三慧起、接收請(qǐng)求參數(shù)
3.1 基本類型參數(shù)
請(qǐng)求參數(shù)和方法的形參 同名即可
springMVC默認(rèn)可以識(shí)別的日期字符串格式為: YYYY/MM/dd HH:mm:ss通過(guò)@DateTimeFormat可以修改默認(rèn)日志格式
// 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í)體收參【重點(diǎn)】
請(qǐng)求參數(shù)和實(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ù)組收參
簡(jiǎn)單類型的 數(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請(qǐng)求: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/*】等價(jià)
// http://localhost:8989/.../hello/10 ? {id}匹配到10
@RequestMapping("/hello/{id}")
// @PathVariable將{id}路徑匹配到值賦給id參數(shù)
// 路徑名和參數(shù)名相同則@PathVariable("id")可簡(jiǎn)寫(xiě)為 @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 中文亂碼
首先册倒,頁(yè)面中字符集統(tǒng)一
????????JSP : <%@pagepageEncoding="utf-8"%>
????????HTML :<metacharset="UTF-8">
其次蚓挤,tomcat中字符集設(shè)置,對(duì)get請(qǐng)求中,中文參數(shù)亂碼有效
????????Tomcat配置:URIEncoding=utf-8
最后灿意,設(shè)置此filter估灿,對(duì)post請(qǐng)求中,中文參數(shù)亂碼有效
<!-- 此過(guò)濾器會(huì)進(jìn)行: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";//和下一行等價(jià)
? ? ? ? return "forward:/views/users.jsp";
? ? }
? ? @RequestMapping("/test2")
? ? public String testForward2(){
? ? ? ? System.out.println("test forward2");
? ? ? ? //轉(zhuǎn)發(fā)到? /forw/test1
? ? ? ? //return "forward:test1";//相對(duì)路徑(轉(zhuǎn)發(fā)到本類中的test1)
? ? ? ? //轉(zhuǎn)發(fā)到? /forw/test1
? ? ? ? return "forward:/forw/test1"; //絕對(duì)路徑
? ? }
}
4.2 重定向
@RequestMapping("/redir")
classRedirectController{
@RequestMapping("/test1")
publicStringtestRedirect1(){
System.out.println("test redirect1");
//重定向到 /redir/test1
//return "redirect:test1"; //相對(duì)路徑(轉(zhuǎn)發(fā)到本類中的test1)
return"redirect:/redir/test1";//絕對(duì)路徑
?? }
@RequestMapping("/test2")
publicStringtestRedirect2(){
System.out.println("test redirect2");
//重定向到 /views/users.jsp
return"redirect:/view/user.jsp";
?? }
}
4.3 跳轉(zhuǎn)細(xì)節(jié)
????在增刪改之后馅袁,為了防止請(qǐng)求重復(fù)提交,重定向跳轉(zhuǎn)
????在查詢之后荒辕,可以做轉(zhuǎn)發(fā)跳轉(zhuǎn)
五司顿、傳值
????C得到數(shù)據(jù)后,跳轉(zhuǎn)到V兄纺,并向V傳遞數(shù)據(jù)。進(jìn)而V中可以渲染數(shù)據(jù)化漆,讓用戶看到含有數(shù)據(jù)的頁(yè)面
????轉(zhuǎn)發(fā)跳轉(zhuǎn):Request作用域
????重定向跳轉(zhuǎn):Session作用域
5.1 Request和Session
//形參中 即可獲得 request 和 session對(duì)象
@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中取值
建議:重點(diǎn)復(fù)習(xí) EL? JSTL
????//jsp中用EL表達(dá)式 取值即可
????<fmt:formatDatevalue="${sessionScope.user.birth}"pattern="yyyy-MM-dd"/><br/>
????${sessionScope.user.birth}<br>
? ? ${requestScope.age}
5.3 Model
//model中的數(shù)據(jù),會(huì)在V渲染之前座云,將數(shù)據(jù)復(fù)制一份給request
@RequestMapping("/test")
publicStringtestData(Modelmodel){
model.addAttribute("name","張三");
return"index";
}
?
//jsp中用EL表達(dá)式 取值即可
${requestScope.name}
5.4 ModelAndView
//modelandview 可以集中管理 跳轉(zhuǎn)和數(shù)據(jù)
@RequestMapping("/test")
publicModelAndViewtestData(){//返回值類型為ModelAndView
//新建ModelAndView對(duì)象
ModelAndViewmv=newModelAndView();
// 設(shè)置視圖名疙赠,即如何跳轉(zhuǎn)
mv.setViewName("forward:/index.jsp");
// 增加數(shù)據(jù)
mv.addObject("age",18);
returnmv;
}
?
//jsp中用EL表達(dá)式 取值即可
${requestScope.age}
5.5 @SessionAttributes
@SessionAttributes({"gender","name"})? :model中的 name和gender 會(huì)存入session中
SessionStatus 移除session
@Controller
@SessionAttributes({"gender","name"})// model中的 name和gender 會(huì)存入session中
publicclassUserController{
?
@RequestMapping("/hello")
publicStringhello(Modelm){
m.addAttribute("gender",true);// 會(huì)存入session
mv.addObject("name","zhj");// 會(huì)存入session
return"index";
?? }
@RequestMapping("/hello2")
publicStringhello(SessionStatusstatus){
// 移除通過(guò)SessionAttributes存入的session
status.setComplete();
return"index";
?? }
}
六、靜態(tài)資源
6.1 靜態(tài)資源問(wèn)題
靜態(tài)資源:html朦拖,js文件圃阳,css文件,圖片文件
靜態(tài)文件沒(méi)有url-pattern,所以默認(rèn)是訪問(wèn)不到的璧帝,之所以可以訪問(wèn)捍岳,是因?yàn)椋瑃omcat中有一個(gè)全局的servlet:org.apache.catalina.servlets.DefaultServlet睬隶,它的url-pattern是 "/",是全局默認(rèn)的Servlet.? 所以每個(gè)項(xiàng)目中不能匹配的靜態(tài)資源的請(qǐng)求锣夹,有這個(gè)Servlet來(lái)處理即可。
但苏潜,在SpringMVC中DispatcherServlet也采用了 “/” 作為url-pattern, 則項(xiàng)目中不會(huì)再使用全局的Serlvet银萍,則靜態(tài)資源不能完成訪問(wèn)。
6.2 解決方案1
DispathcerServlet采用其他的url-pattern
此時(shí)恤左,所有訪問(wèn)handler的路徑都要以 action結(jié)尾L健!
????<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依然采用 "/",但追加配置
<!--
額外的增加一個(gè)handler飞袋,且其requestMapping:? "/**" 可以匹配所有請(qǐng)求戳气,但是優(yōu)先級(jí)最低
所以如果其他所有的handler都匹配不上,請(qǐng)求會(huì)轉(zhuǎn)向 "/**" ,恰好授嘀,這個(gè)handler就是處理靜態(tài)資源的
處理方式:將請(qǐng)求轉(zhuǎn)會(huì)到tomcat中名為default的Servlet
-->
<mvc:default-servlet-handler/>
6.4 解決方案3
mapping是訪問(wèn)路徑物咳,location是靜態(tài)資源存放的路徑
將/html/** 中 /**匹配到的內(nèi)容,拼接到 /hhh/后http://..../html/a.html? 訪問(wèn) /hhh/a.html
<mvc:resourcesmapping="/html/**"location="/hhh/"/>
七、Json處理
7.1 導(dǎo)入依賴
<!-- Jackson springMVC默認(rèn)的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注解击蹲,等價(jià)于在類中的每個(gè)方法上都加了@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, 接收J(rèn)son參數(shù)
7.4.1 定義Handler
classUser{
privateIntegerid;
privateStringname;
privateBooleangender;
//set get
}
@RequestMapping("/users")
publicStringaddUser(@RequestBodyUseruser){//@RequestBody將請(qǐng)求體中的json數(shù)據(jù)轉(zhuǎn)換為java對(duì)象
????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è)置請(qǐng)求頭
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",//聲明請(qǐng)求參數(shù)類型為 json
data:JSON.stringify(user),// 轉(zhuǎn)換js對(duì)象成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時(shí)婉宰,忽略此屬性
? ? privateStringname;
?? ....
get/set
}
輸出json時(shí): {"id":xx}
7.5.4 null和empty屬性排除
Jackson 默認(rèn)會(huì)輸出null值的屬性歌豺,如果不需要,可以排除心包。
@JsonInclude(JsonInclude.Include.NON_NULL) //null值 屬性不輸出@JsonInclude(value= JsonInclude.Include.NON_EMPTY) // empty屬性不輸出( 空串类咧,長(zhǎng)度為0的集合,null值)
publicclassUser{
privateIntegerid;
@JsonInclude(JsonInclude.Include.NON_NULL)// 若"name==null" 忽略此屬性
? ? privateStringname;
@JsonInclude(value=JsonInclude.Include.NON_EMPTY)// 若hobby長(zhǎng)度為0或==null 忽略此屬性
privateList<String>hobby;
?? ....
get/set
}
如果name=null,且hobby長(zhǎng)度為0蟹腾,則輸出json時(shí):{"id":xx}
7.5.5 自定義序列化
@JsonSerialize(using = MySerializer.class) // 使用MySerializer輸出某屬性
publicclassUser{
privateIntegerid;
privateStringname;
@JsonSerialize(using=MySerializer.class)
privateDoublesalary=10000.126;//在輸出此屬性時(shí)痕惋,使用MySerializer輸出
?? ....
get/set
}
則輸出json時(shí):{"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)? 默認(rèn)會(huì)忽略所有null值,有此注解會(huì)輸出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);
// 如上對(duì)象炉爆,轉(zhuǎn)換json:
{NAME:""堕虹,city:null,"birth":"2020/12/12"芬首,"salary":"100.5元"}
?
八鲫凶、異常解析器
8.1 現(xiàn)有方案,分散處理
Controller中的每個(gè)Handler自己處理異常
此種處理方案衩辟,異常處理邏輯螟炫,分散在各個(gè)handler中,不利于集中管理
publicStringxxx(){
try{
? ? ...
}catch(Exception1e){
? ? e.printStackTrace();
return"redirect:/xx/error1";
}catch(Exception2e){
? ? e.printStackTrace();
return"redirect:/xx/error2";
?? }
}
8.2 異常解析器艺晴,統(tǒng)一處理
Controller中的每個(gè)Handler不再自己處理異常昼钻,而是直接throws所有異常。
定義一個(gè)“異常解析器” 集中捕獲處理 所有異常
此種方案封寞,在集中管理異常方面然评,更有優(yōu)勢(shì)!
publicclassMyExResolverimplementsHandlerExceptionResolver{
? ? /**
? ? * 異常解析器:主體邏輯
? ? * 執(zhí)行時(shí)刻:當(dāng)handler中拋出異常時(shí)狈究,會(huì)執(zhí)行:捕獲異常碗淌,并可以跳到錯(cuò)誤頁(yè)面
? ? */
? ? @Override
? ? publicModelAndViewresolveException(HttpServletRequestrequest,
? ? ? ? ? ? HttpServletResponseresponse,Objecthandler,Exceptionex) {
? ? ? ? ex.printStackTrace();//打印異常棧
? ? ? ? //創(chuàng)建一個(gè)ModelAndView
? ? ? ? ModelAndViewmv=newModelAndView();
? ? ? ? //識(shí)別異常
? ? ? ? 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;//中斷請(qǐng)求
*/
? ? ? ? returntrue;//放行,后續(xù)的攔截器或handler就會(huì)執(zhí)行
? ? }
? ? //在handler之后執(zhí)行:進(jìn)一步的響應(yīng)定制
? ? @Override
? ? publicvoidpostHandle(HttpServletRequestrequest,
? ? ? ? ? ? HttpServletResponseresponse,Objecthandler,
? ? ? ? ? ? ModelAndViewmodelAndView)throwsException{
? ? ? ? System.out.println("post~~");
? ? }
? ? //在頁(yè)面渲染完畢之后亿眠,執(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開(kāi)頭 -->
<mvc:mappingpath="/inter/**"/><!-- /** 任意多級(jí)任意路徑 -->
<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? 超出后會(huì)拋出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的磁盤(pán)路徑 ==> 在webapp目錄下創(chuàng)建一個(gè)目錄"upload_file",且此目錄初始不要為空纳像,否則編譯時(shí)被忽略
Stringreal_path=session.getServletContext().getRealPath("/upload_file");
System.out.println("real_path:"+real_path);
?
//將上傳的文件荆烈,存入磁盤(pán)路徑中
//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);
//獲得要下載文件的絕對(duì)路徑
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)文件憔购,寫(xiě)出給客戶端
IOUtils.copy(newFileInputStream(real_path),response.getOutputStream());
?
//上一步,已經(jīng)是響應(yīng)了,所以此handler直接是void
}
十二岔帽、驗(yàn)證碼
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 聲明驗(yàn)證碼組件
<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","驗(yàn)證碼") -->
????????<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(){
//刷新驗(yàn)證碼
path=$(this).attr("src")+"?"+newDate().getTime();
$(this).attr("src",path);
? ? ?? });
?? });
</script>
十三塌计、REST
13.1 開(kāi)發(fā)風(fēng)格
是一種開(kāi)發(fā)風(fēng)格走诞,遵從此風(fēng)格開(kāi)發(fā)軟件氓辣,符合REST風(fēng)格,則RESTFUL账蓉。
兩個(gè)核心要求:
每個(gè)資源都有唯一的標(biāo)識(shí)(URL)
不同的行為,使用對(duì)應(yīng)的http-method
訪問(wèn)標(biāo)識(shí)資源
http://localhost:8989/xxx/users所有用戶
http://localhost:8989/xxx/users/1用戶1
http://localhost:8989/xxx/users/1/orders用戶1的所有訂單
請(qǐng)求方式標(biāo)識(shí)意圖
GEThttp://localhost:8989/xxx/users查詢所有用戶
POSThttp://localhost:8989/xxx/users在所有用戶中增加一個(gè)
PUThttp://localhost:8989/xxx/users在所有用戶中修改一個(gè)
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的所有訂單中增加一個(gè)
13.2 優(yōu)點(diǎn)
**輸出json:
13.3 使用
13.3.1 定義Rest風(fēng)格的 Controller
@RequestMapping(value="/users",method = RequestMethod.GET)
等價(jià)
@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請(qǐng)求
<script>
? ? functionputUser(){// 發(fā)送更新請(qǐng)求 (增加請(qǐng)求發(fā)送方式也是如此)
varxhr=newXMLHttpRequest();
? ? //定義 put逾一,delete,get,post方式 即可铸本,不用定義_method
xhr.open("put","${pageContext.request.contextPath}/rest04/users");
? ? // 設(shè)置請(qǐng)求頭
xhr.setRequestHeader("content-type","application/json");
// 設(shè)置請(qǐng)求參數(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",//聲明請(qǐng)求參數(shù)類型為 json
? ? ? ? ?? data:JSON.stringify(user),// 轉(zhuǎn)換js對(duì)象成json
? ? ? ? ?? success:function(ret){
? ? ? ? ? ? ?? console.log(JSON.parse(ret));
? ? ? ? ?? }
? ? ?? });*/
?? }
?
? ? functiondelUser(){// 發(fā)送刪除請(qǐng)求
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>
十四舶得、跨域請(qǐng)求
14.1 域
域:協(xié)議+IP+端口
14.2 Ajax跨域問(wèn)題
Ajax發(fā)送請(qǐng)求時(shí),不允許跨域爽蝴,以防用戶信息泄露沐批。
當(dāng)Ajax跨域請(qǐng)求時(shí),響應(yīng)會(huì)被瀏覽器攔截(同源策略)蝎亚,并報(bào)錯(cuò)九孩。即瀏覽器默認(rèn)不允許ajax跨域得到響應(yīng)內(nèi)容。
互相信任的域之間如果需要ajax訪問(wèn)发框,(比如前后端分離項(xiàng)目中躺彬,前端項(xiàng)目和后端項(xiàng)目之間),則需要額外的設(shè)置才可正常請(qǐng)求。
14.3 解決方案
允許其他域訪問(wèn)
在被訪問(wèn)方的Controller類上宪拥,添加注解
@CrossOrigin("http://localhost:8080")//允許此域發(fā)請(qǐng)求訪問(wèn)
publicclassSysUserController{
? ? ....
}
攜帶對(duì)方cookie仿野,使得session可用
在訪問(wèn)方,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 整合思路
此時(shí)項(xiàng)目中有兩個(gè)工廠
DispatcherServlet 啟動(dòng)的springMVC工廠==負(fù)責(zé)生產(chǎn)C及springMVC自己的系統(tǒng)組件
ContextLoaderListener 啟動(dòng)的spring工廠==負(fù)責(zé)生產(chǎn)其他所有組件
springMVC的工廠會(huì)被設(shè)置為spring工廠的子工廠,可以隨意獲取spring工廠中的組件
整合過(guò)程犁河,就是累加:代碼+依賴+配置鳖枕。然后將service注入給controller即可
16.2 整合技巧
兩個(gè)工廠不能有彼此侵入,即桨螺,生產(chǎn)的組件不能有重合宾符。
<!-- 告知SpringMVC? 哪些包中 存在 被注解的類
? ? use-default-filters=true 凡是被 @Controller @Service? @Repository注解的類,都會(huì)被掃描
? ? use-default-filters=false 默認(rèn)不掃描包內(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 唯獨(dú)不掃描@Controller注解的類 -->
<context:component-scanbase-package="com.zhj"use-default-filters="true">
? ? <context:exclude-filtertype="annotation"expression="org.springframework.stereotype.Controller"/>
</context:component-scan>