1. Spring MVC概述
1.1 Spring MVC是什么
SpringMVC是Spring的一個模塊,是一個基于MVC設計模式的web框架。
1.2 Spring MVC執行流程。
1.3 組件分析
-
前端控制器(默認配置)Dispatcher Servlet 作用:只負責分發請求。可以很好的對其它組件進行解耦合。
-
處理器映射器(默認配置)HandlerMapping
作用:將前端的url和處理器方法建立映射關系
-
處理器適配器(默認配置)HandlerAdapter
適配并調用具體的處理器方法執行
-
處理器 controller
(需要程序員開發)
-
視圖解析器 ViewResolver
根據邏輯視圖查找映射物理視圖
簡化的MVC執行流程:
前后分離的執行流程:
2. Handler
引入依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.1 參數
Handler的參數列表,功能非常強大,主要功能有以下兩點:
-
接收請求參數
-
獲取servletAPI
@Controller
@RestController
public class Login {}
2.1.1接收請求參數
-
簡單類型的參數,Spring MVC可以直接幫我們封裝成參數列表中聲明的類型,比如String、int、double……
//http://localhost:8080/login?username=petrel&password=123 @RequestMapping("/login") public void login(String username, String password){System.out.println(username);System.out.println(password); }
-
或者也可以直接接收一個Java Bean
//http://localhost:8080/login?name=petrel&age=23 @RequestMapping("/login") public void login(Person person) {System.out.println(person.getName());System.out.println(person.getAge()); }
-
如果請求參數是一個日期,Spring MVC并不能直接封裝到Date中,需要設置一下日期格式。
//http://localhost:8080/login?name=petrel&age=23&birthday=2020-02-02 public class User {private String username;private String password;@DateTimeFormat(pattern = "yyyy-MM-dd")//前端到后端 可以把前端string類型轉換到后端date類型//@JsonFormat(pattern = "yyyy-MM-dd")//后端到前端 ? 可以把后端date類型轉換到前端stringprivate Date birthday;// getters & setters... }// ? ? SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd");// ? ? String date = sim.format(user.getBirthday()); 將Date處理成字符串
-
如果請求參數是一個數組類型,我們可以直接通過數組接收。
http://localhost:8080/delSel?ids=1&ids=2 ? 請求參數ids為方法的參數 @RequestMapping("/delSel") public String delSel(Integer[] ids) {System.out.println(Arrays.toString(ids));return "index"; }
-
如果想要用集合類型來接收數組參數呢?下面的寫法可以么?
//http://localhost:8080/delSel?ids=1&ids=2 @RequestMapping("/delSel") public String delSel(List<Integer> ids) {System.out.println(ids);return "index"; } //不可以,因為方法參數中的數據類型 必須是可實例化的,得有構造方法,List是個接口,沒辦法構造。但是可以使用ArrayList
//http://localhost:8080/delSel?ids=1&ids=2 @RequestMapping("/delSel") public String delSel(@RequestParam ArrayList<Integer> ids) {System.out.println(ids);return "index"; } //但是必須在參數前面加@RequestParam才可以拿到請求參數
2.1.2 獲取servletAPI
可以在Handler的形參中直接使用以下類型:
-
HttpServletRequest
通過request對象獲取請求信息 -
HttpServletResponse
通過response處理響應信息 -
HttpSession
通過session對象得到session中存放的對象
? @ResponseBody @RequestMapping("/login") ? // 獲取前端請求資源路徑public String login(String username, Integer password, HttpServletRequest request) {HttpSession session = request.getSession();session.setAttribute("username", username);return "index";}@ResponseBody @RequestMapping("/test") ? // 獲取前端請求資源路徑public String test(HttpServletRequest request) {HttpSession session = request.getSession();Object username = session.getAttribute("username");String uname = (String) username;return uname;}@ResponseBody @RequestMapping("/login") ? // 獲取前端請求資源路徑public String login(String username, Integer password, HttpSession session) {session.setAttribute("username", username);return "index";}@ResponseBody @RequestMapping("/test") ? // 獲取前端請求資源路徑public String test(HttpSession session) {Object username = session.getAttribute("username");String uname = (String) username;return uname;}
2.2 返回值(了解)
可以為Handler指定兩種種返回值類型:
-
void
如果返回值為
void
的時候,可以在Handler形參上定義request
和response,使用
request或
response指定響應結果response.getWriter().println();
-
String
邏輯視圖名稱
返回的字符串就是邏輯視圖。
return "index.jsp";
請求轉發與重定向
? ?@RequestMapping("/login")// @ResponseBody 注釋,否則返回json格式public String login(String username, Integer password, HttpSession session) {session.setAttribute("username", username);// ? return "forward:/test"; // 請求轉發return "redirect:/test"; // 重定向} ?@RequestMapping("/test")@ResponseBody ?// 這里需要返回jsonpublic String test(HttpSession session) {Object username = session.getAttribute("username");String uname = (String) username;return uname;}
2.3 注解
-
@RequestMapping
-
聲明在方法上:
//@RequestMapping("/login") //@RequestMapping(value = "/login") //@RequestMapping(path = "/login") //@RequestMapping(value = {"/login1", "/login2"}) @Controller public class TestController {@ResponseBody ?@RequestMapping(value = {"/login1", "/login2"}, method = RequestMethod.GET)// 只有get請求才可以 post請求進不來public String login(String username) {System.out.println(username);return username;} }
-
通過
value
屬性配置該方法的訪問路徑 -
通過method屬性指定該方法允許的訪問方式,默認情況get,post都支持
-
-
// 靜態文件目錄,resources/static/login.html 訪問方式 http://localhost:8080/login.html <form action="/login1" method="get"> ?<input type="text" name="username"><button>提交</button> ? </form>
-
聲明在類上:窄化請求
??:我們的controller中是不允許有相同的資源路徑的
假設我們的EmpController和DeptController都需要將資源路徑成定義"/findAll"必然是不合法的
我們可以將資源路徑分別定義成"emp/findAll"和"dept/findAll",這種方式叫做窄化請求
窄化請求,可以對請求URL進行分類管理,例如:
/person/add
、/person/list
……// http://localhost:5555/v1/findAll?username=petrel @RestController @RequestMapping("/v1") public class TestController { ?@RequestMapping("/findAll")public String findAll(String username) {System.out.println(username);return username;} } ? // 2合1注解 : @RestController = @Controller + @ResponseBody
-
-
@RequestParam
該注解用來標注一個請求參數:
在方法的形參前,可以加可以不加,加了就有特殊含義了
@RequestMapping(value = "/login1")public String login1(String name) {System.out.println(name);return "index";} //上述方式在請求 login1 可以不強制傳遞請求參數,那打印name結果是null ? ?@RequestMapping(value = "/login2")public String login2(@RequestParam String name) {System.out.println(name);return "index";} //上述方式在請求 login2 強制必須傳遞請求參數,那打印name結果就是請求參數傳的值 ? ? //http://localhost:8080/login3?name=tom@RequestMapping(value = "/login3")public String login3(@RequestParam("name") String username) {System.out.println(username);return "index";}//上述方式在請求 login3 的請求參數是name 指定請求參數的名字,用于處理前后端參數不一致//@RequestParam(value = "name") 和 @RequestParam(name = "name") 等同于 @RequestParam("name") ? ? ? //http://localhost:8080/login4 ? ? @RequestMapping(value = "/login4")public String login4(@RequestParam(name = "name", required = false) String username) {System.out.println(username);return "index";} // required = false,默認是true 代表著必須傳遞參數,如果設置false代表可以不傳遞參數,不傳為null ? ? @RequestMapping(value = "/login5") public String login5(@RequestParam(name = "name",defaultValue = "zs") String username) {System.out.println(username);return "index"; } //設置默認值,這樣required就不需要在寫了。
-
value
:@RequestParam(value="username")
等同于@RequestParam("username")
,對應請求參數的鍵 -
required
:參數是否必填 -
defaultValue
:設置默認值
-
-
@PathVariable
將路徑的一部分作為請求參數,RESTful的基礎。
//http://localhost:8080/findById/1 @RequestMapping("/findById/{id}") public String findById(@PathVariable Integer id) {System.out.println(id);return "index"; } ? //請求參數就不可以是?號。參數必須是目錄形式。 ? @RestController @RequestMapping("/person") public class PersonController {@GetMapping("/findById/{id}")public String findById(@PathVariable Integer id) {System.out.println(id);return null;} ?@PutMapping("/updateById/{id}")public String updateById(@PathVariable Integer id) {System.out.println(id);return null;} ?@DeleteMapping("/deleteById/{id}")public String deleteById(@PathVariable Integer id) {System.out.println(id);return null;} ? ?@GetMapping("/findByIdOrName/{id}/{name}")public String findByIdOrName(@PathVariable Integer id, @PathVariable String name) {System.out.println(id);System.out.println(name);return null;} }
RESTful
RESTful就是一個url的編寫風格。使用RESTful就不需要用?問號分割請求參數了。
一個嚴格的RESTful中是不可能存在?的。
一個URl對應一個資源,請求方式確定一個動作。
ps:有一張person表id有1 2 3 ,name有zs,lisi,wangwu。
POST請求:就是向數據庫中添加數據。/person,代表把person對象添加到表中
DELETE請求:就是向數據庫中刪除一條數據。 /person/1 ,代表把person表中id為1的 刪除
PUT請求:就是向數據庫修改一條數據。/person/1,代表把person表中id為1的 修改
GET請求:就是向數據庫表查詢數據。/person/3, 代表把person表中id為3的 查詢出來
3. 靜態資源訪問
springboot中放置靜態資源的目錄,常用有static和templates目錄,只要把靜態資源放到這幾個目錄下,就能直接訪問到
http://localhost:8080/login.html
http://localhost:8080/login1.html
static目錄下靜態資源默認是可以被訪問到的,但是templates目錄下資源默認是訪問不到的,需要做配置:
spring.web.resources.static-locations=classpath:/templates/,classpath:static/
也可以直接給所有靜態資源添加一個前綴,既可統一攔截,又可統一放開
spring.mvc.static-path-pattern=/res/**
http://localhost:8080/res/login1.html ?
http://localhost:8080/res/login.html
4. Spring MVC對JSON的支持
Spring MVC對JSON的?持?常友好,主要?到兩個注解:@RequestBody,@ResponseBody
也可以配置為其它的JSON?具,?如fastjson,?學如何配置。
為什么學習Jackson,因為它是SpringMVC默認的,方便配置使用。
@RequestBody
public class User {private Integer id;private String username;private String password;getter&& setter
}
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>
<button id="btn1">前端發送json到后端</button>
<button id="btn2">接收后端數據</button>
?
<script>
?$("#btn1").click(function () {$.ajax({type: "post",url: "http://localhost:8080/sendJson",data: '{"username":"zs","password":"123456","id":20}',contentType: 'application/json' //告訴服務器 前端發的數據是json格式})})
?$("#btn2").click(function () {$.ajax({type: "get",url: "http://localhost:8080/receiveJson",// url: "http://localhost:8080/receiveJsonData",success(res) {console.log(res);}})})
</script>
?
</body>
</html>
Handler部分
package com.whitecamellia.springboot01.controller;import com.whitecamellia.springboot01.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @author Petrel* @create 2022-08-04 6:59 上午*/
@Controller
public class JsonController {@PostMapping("/sendJson")public String jsonTest1(@RequestBody User user) {//@RequestBody 前端來的json字符串在請求體中System.out.println(user);return "index";}
}
測試:查看控制臺是否接受到前端發送過來的json數據。
@ResponseBody
JavaScript部分
<script>$("#btn2").click(function () {$.ajax({type: "get",url: "http://localhost:8080/receiveJson",// url: "http://localhost:8080/receiveJsonData",success(res) {console.log(res);}})
</script>
Handler部分
// login.html@GetMapping("/receiveJson")@ResponseBody //代表這得到的返回值要作為響應體 返回給前端,就不會走試圖解析器public List<User> jsonTest2() {User user = new User();user.setUsername("zs");user.setPassword("123456");user.setId(20);List<User> list = new ArrayList<>();list.add(user);return list;}
?@GetMapping("/receiveJsonData")@ResponseBodypublic Map<String, Object> jsonTest3() {
?User user = new User();user.setUsername("zs");user.setPassword("123456");user.setId(20);List<User> list = new ArrayList<>();list.add(user);Map<String, Object> map = new HashMap<>();map.put("code", 200);map.put("message", "數據請求成功!");map.put("data", list);return map;}
測試:查看前端頁面console頁面,是否拿到后端響應過來的數據。
注意:@ResponseBody 注解一定要放在方法上面,標注,不要放到方法返回值處標注。
@ResponseBody 注解也可以放到類上,這樣該類內部每個方法都會隱式被標注為@ResponseBody。
Fastjson
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.10</version></dependency>
5. 攔截器
登錄需求
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<div>賬號:<input type="text" id="username">
</div>
<div>密碼:<input type="password" id="password">
</div>
<div><button id="btn">登錄</button>
</div>
?
<script>
?$("#btn").click(() => {$.ajax({url: "http://localhost:8080/login",method: "post",data: {username: $("#username").val(),password: $("#password").val()},success(res) {console.log(res)if (res.code == 200) {location.href = "index.html"} else {alert("登錄失敗")}}})})
?
</script>
</body>
</html>
// index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
</head>
<body>
<h1>Home主頁</h1>
<h3 id="info">
?
</h3>
<script>
?$.ajax({url: "http://localhost:8080/findAll",method: "get",success(res) {$("#info").html(res.data)}})
</script>
</body>
?
</html>
package com.whitecamellia.uploaddemo.controller;import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;/*** @author Petrel* @create 2022-08-04 10:05 下午*/
@RestController
public class LoginController {@PostMapping("/login")public Map<String, Object> login(String username, String password, HttpSession session) {session.setAttribute("username", username);Map<String, Object> map = new HashMap();if ("admin".equals(username) && "123456".equals(password)) {map.put("code", 200);map.put("success", "登錄成功");return map;} else {map.put("code", 200);map.put("success", "登錄失敗");return map;}}
}
package com.whitecamellia.uploaddemo.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @author Petrel* @create 2022-08-04 3:52 下午*/
@RestController
public class TestController {@GetMapping("/findAll")public Map<String, Object> findAll(HttpSession session) {Object username = session.getAttribute("username");Map<String, Object> map = new HashMap<>();List<String> list = new ArrayList();if (username == null) {list.add("用戶未登錄,不能獲取數據");map.put("code", 403);map.put("message", "用戶未登錄");map.put("data", list);}list.add("用戶已登錄,可以獲取數據");map.put("code", 200);map.put("message", "用戶已登錄");map.put("data", list);return map;}
}
創建攔截器
實現HandlerInterceptor
接口
創建interceptor package
/*** 創建攔截器*/
package com.whitecamellia.uploaddemo.controller.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession();Object username = session.getAttribute("username");if (username != null) {return true; // 放行} else {Map<String, Object> map = new HashMap<>();response.setHeader("content-type", "application/json");map.put("code", 403);map.put("message", "No Access");// java對象轉成json字符串String str = JSONArray.toJSONString(map);
// ObjectMapper objectMapper = new ObjectMapper();
// String s = objectMapper.writeValueAsString(map);
// response.addHeader("charset", "utf8");PrintWriter writer = response.getWriter();writer.write(str);// json字符串 轉成 java對象// Map map1 = JSONObject.toJavaObject((JSON) o, Map.class);System.out.println("攔截器工作了");return false; // 攔截}}
}
配置攔截器
創建config package
package com.whitecamellia.uploaddemo.controller.config;import com.whitecamellia.uploaddemo.controller.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;// 將配置注入容器
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/*.html").excludePathPatterns("/login");}
}
跨域配置
package com.whitecamellia.uploaddemo.controller.config;import com.whitecamellia.uploaddemo.controller.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @author Petrel*/
@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns("/*.html").excludePathPatterns("/login");}/*** 允許跨域調用的過濾器*/@Beanpublic CorsFilter corsFilter() {CorsConfiguration config = new CorsConfiguration();//允許所有域名進行跨域調用config.addAllowedOriginPattern("*");//允許跨越發送cookieconfig.setAllowCredentials(true);//放行全部原始頭信息config.addAllowedHeader("*");//允許所有請求方法跨域調用config.addAllowedMethod("*");UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", config);return new CorsFilter(source);}}