一、SpringMVC
1.1 什么是SpringMVC
Java开源框架,Spring Framework的一个独立模块。
MVC框架,在项目中开辟MVC层次架构
对控制器中的功能包装简化扩展践行工厂模式,功能架构在工厂之上。
1.2 MVC架构
1.2.1 概念
名称 | 职责 |
---|---|
Model | 模型:承载数据,并对用户提交请求进行计算的模块。分为两类,一类称为数据承载Bean,一类称为业务处理Bean。所谓数据承载Bean是值实体类,专门承载业务数据的,如Student、User等。而业务处理Bean则是指Service或Dao对象,专门用于处理用户提交请求的 |
View | 视图:渲染数据,生成页面。对应项目中的Jsp , html 等 作用是与用户交互 展示数据 |
Controller | 控制器:用于将用户请求转发给相应的Model进行处理,并处理Model的计算结果向用户提供相应响应 |
1.2.2 MVC工作流程:
用户通过View页面向服务端发送请求,可以是表单请求、超链接请求、AJAX请求等。
服务端Controller控制器接收到请求后对请求进行解析,找到相应的Model对用户请求进行处理。
Model处理后,将处理结果再交给Controller。
Controller接到处理结果后,根据处理结果找到要作为向客户端发回的响应View页面。页面经渲染后,再发给客户端。
1.2.3 优点
MVC是现下软件开发中的最流行的代码结构形态;
人们根据负责的不同逻辑,将项目中的代码分成 M V C 3个层次;
层次内部职责单一,层次之间耦合度低;
符合低耦合 高内聚的设计理念。也实际有利于项目的长期维护。
二、开发流程
2.1 导入依赖
<!--——————————加入springMVC依赖—————————-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
2.2 配置核心(前端)控制器(web.xml)
作为MVC框架,首先要解决的是:如何能够收到请求!
所以MVC框架大都会设计一款前端控制器,选型在 Servlet 或 Filter两者之一,在框架最前沿率先工作,接收所有请求。
此控制器在接收到请求后,还会负责springMVC的核心的调度管理,所以既是前端又是核心。
- 补充:DispatcherServlet前端控制器,是框架提供的,作用统一处理请求和响应,整个流程的控制中心,是由它来调用其他组件处理用户的请求。
web.xml文件配置:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--前端控制器-->
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 局部参数:声明配置文件位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- Servlet启动时刻:可选 -->
<!--DispatcherServlet前端控制器是springmvc中非常重要的一个组件,内置了很多初始化的工作,所以让他随着服务器的启动而启动
提前把初始化工作做好,避免第一次访问的时候速度很慢
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<!--
这里设置的是前端控制器可以处理的请求路径/表示拦截处理jsp以外的所有请求
也就是说处理以.jsp结尾的请求其他请求都要经过前端控制器,这里注意不要写成/*,/*表示匹配所有路径
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 此过滤器会进行: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>
</web-app>
2.3 后端控制器
等价于之前定义的Servlet。
@Controller // 交由spring创建bean对象
@RequestMapping("/hello") // 访问路径
public class HelloController {
@RequestMapping("/hello1")
public String hello(){
return "index"; // 跳转到index.jsp
}
@RequestMapping("/hello2")
public String hello2(){
return "pages/main"; // 跳转到pages包下的main.jsp
}
}
2.4 springmvc.xml文件配置
<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-scan base-package="com.ymk.controller"/>
<!--—— 注册注解开发驱动 ——-->
<mvc:annotation-driven/>
<!-- 视图解析器
作用:1.捕获后端控制器的返回值="index"
2.解析: 在返回值的前后 拼接 ==> "/index.jsp"
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--—— 前缀 ——-->
<property name="prefix" value="/"/>
<!--—— 后缀 ——-->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
2.5 浏览器访问
URL地址栏输入:
- http://localhost:8080/hello/hello1
- http://localhost:8080/hello/hello2
三、接受请求参数
3.1基本类型参数
请求参数和方法的形参同名即可。
- springMVC默认可以识别的日期字符串格式为: YYYY/MM/dd HH:mm:ss
- 通过@DateTimeFormat可以修改默认日志格式
@RequestMapping("/test1")
public String test1Param(Integer id, String name, Boolean gender,@DateTimeFormat(pattern = "yyyy-MM-dd") Date birthday){
// springmvc自动接收参数,并且转换参数类型
System.out.println(id);
System.out.println(name);
System.out.println(gender);
System.out.println(birthday);
return "index";
}
浏览器访问: http://localhost:8080/.../test1?id=001&name=张三&gender=true&birthday=2002-12-11
3.2实体收参
请求参数和实体类的属性需要同名。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private Boolean gender;
/**
* 设置日期格式
*/
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
}
@RequestMapping("/test2")
public String test2Param(User user){
System.out.println(user);
return "index";
}
浏览器访问:http://localhost:8080/.../test2?id=001&name=张三&gender=true&birthday=2000-11-22
3.3 数组收参
简单类型数组
<form action="http://localhost:8080/param/test3">
<input type="checkbox" name="hobby" value="fb">足球
<input type="checkbox" name="hobby" value="bb">篮球
<input type="checkbox" name="hobby" value="vb">排球
<input type="submit" value="提交">
</form>
@RequestMapping("/test3")
public String test3Param(String[] hobby){
for (String s : hobby) {
System.out.println(s);
}
return "index";
}
浏览器访问:http://localhost:8080/.../test3?hobby=football&hobby=basketball
3.4 集合收参
@Data
public class UserList {
private List<User> users;
}
@RequestMapping("/test4")
public String testParam4(UserList userList){
for(User user:userList.getUsers()){
System.out.println(user);
}
return "index";
}
浏览器访问:
post请求:http://.../test4?userList[0].id=111&users[0].name=tom&users[0].gender=false&users[0].birthday=2000-04-02&users[1].id=2&....
3.5 路径参数
@RequestMapping("/hello/{id}")
// @PathVariable将{id}路径匹配到值赋给id参数
// 路径名和参数名相同则@PathVariable("id")可简写为 @PathVariable
public String testParam5(@PathVariable("id") Integer id){
System.out.println("id:"+id);
return "index";
}
// http://localhost:8989/.../hello/10 {id}匹配到10
注意:@PathVariable将{id}路径匹配到值赋给id参数
路径名和参数名相同则@PathVariable("id")可简写为 @PathVariable
@RequestMapping("/hello/{username}")
public String testParam6(@PathVariable("username") String name){//将{username}路径匹配到的值赋给name参数
System.out.println("username:"+name);
return "index";
}
// http://localhost:8989/.../hello/tom {username}匹配到tom
3.6中文乱码
1.页面中字符集统一
- JSP : <%@page pageEncoding="utf-8" %>
- HTML : <meta charset="UTF-8">
2.tomcat中字符集设置,对get请求中,中文参数乱码有效
3.在web.xml中设置此filter,对post请求中,中文参数乱码有效
<!-- 此过滤器会进行: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>
四、跳转
4.1转发
@Controller
@RequestMapping("/forw")
public class ForwardController {
@RequestMapping("/test1")
public String test1(HttpServletRequest request){
System.out.println("test forward1");
// 这也是转发,是走视图解析器的转发
// return "index";
//请求转发传值通过request.setAttribute
request.setAttribute("name","zs");
// 这种是不走视图解析器的转发
return "forward:/page/success.jsp";
}
@RequestMapping("/test2")
public String testForward2(){
System.out.println("test forward2");
// 转发到 /forw/test1
return "forward:test1"; // 这种是相对路径(转发到本类中的test1)
// 转发到 /forw/test1
// return "forward:/forw/test1"; // 这种是绝对路径
}
}
- test1中model.addAttribute("name",name),在jsp中取值方式:${name}
- 注意:转发也可以不走视图解析器,如:forward:/page/success.jsp
4.2重定向
@Controller
@RequestMapping("/redir")
public class RedirectController {
@RequestMapping("/test1")
public String test1(HttpSession session){
System.out.println("test redirect1");
// 重定向传值通过session.setAttribute
session.setAttribute("name","ls");
// 这种是不走视图解析器的转发
return "redirect:/page/success.jsp";
}
@RequestMapping("/test2")
public String testForward2(){
System.out.println("test redirect2");
// 重定向到 /redir/test1
return "redirect:test1"; // 这种是相对路径(转发到本类中的test1)
// 重定向到 /redir/test1
// return "redirect:/redir/test1"; // 这种是绝对路径
}
}
4.3 跳转细节
- 在增删改之后,为了防止请求重复提交,重定向跳转
- 在查询之后,可以做转发跳转
五、传值
C得到数据后,跳转到V,并向V传递数据。进而V中可以渲染数据,让用户看到含有数据的页面。
- 转发跳转: Request作用域
- 重定向跳转:Session作用域
5.1 Request和Session
转发传给页面值
@RequestMapping("/test1")
public String testServlet(HttpServletRequest request, User user){
user.setName("张三");
request.setAttribute("user",user);
System.out.println("scope test1");
return "forward:/page/success.jsp";
//jsp中取值方式${user.name}
}
重定向传给页面值
@RequestMapping("test2")
public String testServlet2(HttpSession session,User user){
user.setName("李四");
session.setAttribute("user",user);
System.out.println("scope test1");
return "forward:/page/success.jsp";
//jsp中取值方式${user.name}
}
5.2 JSP中取值
//jsp中用EL表达式 取值即可
<fmt:formatDate value="${user.birth}" pattern="yyyy-MM-dd"/> <br/>
${user.birth} <br>
${age}
5.3Model
//model中的数据等价于放在了request中,会在V渲染之前,将数据复制一份给request
@RequestMapping("/test3")
public String testData(Model model){
// model中的数据等价于放在了request汇总
model.addAttribute("name", "王二1");
//转发带.jsp不走视图解析器
return "forward:/page/success.jsp";
//jsp中取值方式${name}
}
5.4 ModelAndView
//modelandview 封装了model和view
@RequestMapping("/test4")
public ModelAndView testData(){//返回值类型为ModelAndView
//新建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
// 使用model存储数据
modelAndView.addObject("age",18);
// 设置视图名,即如何跳转
modelAndView.setViewName("page/success"); //等价 return "page/success";
return modelAndView;
//jsp中取值方式${age}
}
5.5 @SessionAttributes[不用]
@SessionAttributes({"gender","name"}) :model中的 name和gender 会存入session中
SessionStatus 移除session
@Controller
@SessionAttributes({"gender","name"}) // model中的 name和gender 会存入session中
public class UserController {
@RequestMapping("/hello")
public String hello(Model m){
m.addAttribute("gender",true); // 会存入session
mv.addObject("name","zhj"); // 会存入session
return "index";
}
@RequestMapping("/hello2")
public String hello(SessionStatus status){
// 移除通过SessionAttributes存入的session
status.setComplete();
return "index";
}
}
5.6 补充
使用Map和ModelMap也可以在页面中也可以通过${user}取值。
@RequestMapping("/test5")
public String test5(Map<String,Object> map){
User user = new User();
user.setName("阿伟");
user.setBirthday(new Date());
map.put("user", user);
return "page/success";
}
@RequestMapping("/test6")
public String test6(ModelMap map){
User user = new User();
user.setName("阿伟");
user.setBirthday(new Date());
map.put("user", user);
return "page/success";
}
六、静态资源
6.1静态资源问题
静态资源:html,js文件,css文件,图片文件
静态文件没有url-pattern,所以默认是访问不到的,之所以可以访问,是因为,tomcat中有一个全局的servlet:org.apache.catalina.servlets.DefaultServlet,它的url-pattern是 "/",是全局默认的Servlet.。所以每个项目中不能匹配的静态资源的请求,有这个Servlet来处理即可。
在SpringMVC中DispatcherServlet也采用了 “/” 作为url-pattern, 则项目中不会再使用全局的Serlvet,则静态资源不能完成访问。
6.1.2 解决方案1
如果DispathcerServlet采用其他的url-pattern,web.xml中所有访问handler的路径都要以action结尾!!
<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.1.3 解决方案2【常用】
DispathcerServlet的url-pattern依然采用 "/",但追加配置
在springmvc.xml文件中配置以下内容。
<!--
额外的增加一个handler,且其requestMapping: "/**" 可以匹配所有请求,但是优先级最低
所以如果其他所有的handler都匹配不上,请求会转向 "/**" ,恰好,这个handler就是处理静态资源的
处理方式:将请求转会到tomcat中名为default的Servlet
-->
<mvc:default-servlet-handler/>
6.1.4解决方案3
mapping是访问路径,location是静态资源存放的路径
将/html/** 中 /**匹配到的内容,拼接到 /hhh/后 http://..../html/a.html 访问 /hhh/a.html
<mvc:resources mapping="/html/**" location="/hhh/"/>
七、Json处理
HttpMessageConverter 提供了两个注解:
- @RequestBody 可以获取请求体,需要在控制器的方法形参上使用,使用请求体中的参数给当前形参赋值,将请求体中的json数据转为java对象
- @ResponseBody 作用在控制器的方法上面,把响应的数据转为json两个类
7.1导入依赖
<!-- Jackson springMVC默认的Json解决方案选择是 Jackson,所以只需要导入jackson的jar,即可使用。-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
7.2 使用 @ResponseBody
@Controller
@RequestMapping("/json")
public class JsonController {
/**
* @ResponseBody 将handler返回值, 转为json(jackson), 并响应到客户端
*/
@ResponseBody
@RequestMapping("/test1")
public User test1() {
User user = new User(1, "张三", true, new Date());
return user;
}
/**
* @ResponseBody 还可以用在handler的返回值上
*/
@RequestMapping("/test2")
public @ResponseBody List<User> test2() {
User user = new User(1, "李四", true, new Date());
User user2 = new User(2, "李四", true, new Date());
User user3 = new User(3, "王二", true, new Date());
List<User> users = new ArrayList<>();
users.add(user);
users.add(user2);
users.add(user3);
return users;
}
/**
* 如果返回值已经是字符串,则不需要转json,直接将字符串响应给客户端
*/
@ResponseBody
@RequestMapping(value="/test3",produces = "text/html;charset=utf-8") //produces 防止中文乱码
public String test3(){
System.out.println("hello world");
return "你好";
}
}
7.3 使用@RestController
Controller类上加了@RestController注解,等价于在类中的每个方法上都加了@ResponseBody.
@Controller
@RestController // 等价于 @Controller + @ResponseBody
public class JsonController{
@RequestMapping("/test1")
public User test1() {
User user = new User(1, "张三", true, new Date());
return user;
}
@RequestMapping("/test2")
public List<User> test2() {
User user = new User(1, "李四", true, new Date());
User user2 = new User(2, null, true, new Date());
User user3 = new User(3, "王二", true, new Date());
List<User> users = new ArrayList<>();
users.add(user);
users.add(user2);
users.add(user3);
return users;
}
}
7.4 使用@RequestBody
@RequestBody接受参数
7.4.1 定义Handler
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private Boolean gender;
private Date birthday;
}
/**
* @RequestBody 将请求体中的json数据转换为java
*/
@RequestMapping("/test4")
public User test4(@RequestBody User user){
// 前端必须发送 json格式的字符串 后端才可以使用@RequestBody接受
return user;
}
注意:前端必须发送json格式的字符串,后端才可以使用@RequestBody接受。
7.4.2 Ajax发送json
<button id="btn">ajax请求</button>
<script>
$("#btn").click(function () {
var user = {id: 1, name: "ls"};
// 将 json对象 转为 json串
var userStr = JSON.stringify(user);
// 将 json串 转为 json对象
var userObj = JSON.parse(userStr);
$.ajax({
url: "http://localhost:8080/json/test4",
type: "post",
contentType:"application/json",
data: userStr,
success: function (res) {
console.log(res)
}
})
})
</script>
- JSON.stringify(xx):将 json对象 转为 json串
- JSON.parse(xx):将 json串 转为 json对象
7.5 Jackson常用注解
7.5.1 日期格式化
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private Boolean gender;
// 设置日期格式,需要加上时区
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
private Date birthday;
}
7.5.2 属性名修改
@JsonProperty("new_name")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
// 响应时用新的属性名username
@JsonProperty("username")
private String name;
private Boolean gender;
private Date birthday;
}
7.5.3属性忽略
@JsonIgnore
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
// 忽略gender属性
@JsonIgnore
private Boolean gender;
private Date birthday;
}
7.5.4 null和empty属性排除
Jackson 默认会输出null值的属性,如果不需要,可以排除。
- @JsonInclude(JsonInclude.Include.NON_NULL):null值 属性不输出
- @JsonInclude(value= JsonInclude.Include.NON_EMPTY):empty属性不输出( 空串,长度为0的集合,null值)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
// 若"name==null" 忽略此属性
@JsonInclude(JsonInclude.Include.NON_NULL)
private String name;
private Boolean gender;
// 若birthday长度为0或==null 忽略此属性
@JsonInclude(value= JsonInclude.Include.NON_EMPTY)
private Date birthday;
}
7.5.5 自定义序列化
@JsonSerialize(using = MySerializer.class):使用MySerializer输出某属性
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;
private String name;
// 再次输出此属性时,使用MySerializer输出
@JsonSerialize(using = MySerializer.class)
private Double salary;
private Boolean gender;
private Date birthday;
}
创建自定义序列化器。
public class MySerializer extends JsonSerializer<Double> {
// value即 Double salary的值
@Override
public void serialize(Double value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// 将Double salary的值 四舍五入
String number = BigDecimal.valueOf(value).setScale(2, BigDecimal.ROUND_HALF_UP).toString();
// 输出 四舍五入后的值
gen.writeNumber(number);
}
}
7.6 FastJson
7.6.1 导入依赖
<!-- FastJson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
7.6.2 安装FastJson
<mvc:annotation-driven>
<!-- 安装FastJson,转换器 -->
<mvc:message-converters>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<!-- 声明转换类型:json -->
<property name="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)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable{
@JSONField(serialize = false)
private Integer id;
@JSONField(name="NAME",serialzeFeatures = SerializerFeature.WriteNullStringAsEmpty)
private String name;
@JSONField(serialzeFeatures = SerializerFeature.WriteMapNullValue)
private String city;
@JSONField(format="yyyy/MM/dd")
private Date birth;
@JSONField(serializeUsing = MySerializer2.class)
private Double salary;
}
public class MySerializer2 implements ObjectSerializer {
@Override
public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException {
// salary属性值
Double value = (Double) object;
// 在salary后拼接 “元”
String text = value + "元";
// 输出拼接后的内容
serializer.write(text);
}
}
new User(1,null,null,new Date(),100.5);
// 如上对象,转换json:
{NAME:"",city:null,"birth":"2020/12/12","salary":"100.5元"}
八、异常解析器
8.1 现有方案,分散处理
Controller中的每个Handler自己处理异常
- 此种处理方案,异常处理逻辑,分散在各个handler中,不利于集中管理
public String xxx(){
try{
...
}catch(Exception1 e){
e.printStackTrace();
return "redirect:/xx/error1";
}catch(Exception2 e){
e.printStackTrace();
return "redirect:/xx/error2";
}
}
8.2 异常解析器,统一处理
Controller中的每个Handler不再自己处理异常,而是直接throws所有异常。
定义一个 “异常解析器” 集中捕获处理所有异常
- 此种方案,在集中管理异常方面,更有优势!
public class MyExResolver implements HandlerExceptionResolver{
/**
* 异常解析器:主体逻辑
* 执行时刻:当handler中抛出异常时,会执行:捕获异常,并可以跳到错误页面
*/
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
ex.printStackTrace();//打印异常栈
//创建一个ModelAndView
ModelAndView mv = new ModelAndView();
//识别异常
if (ex instanceof Exception1) {
mv.setViewName("redirect:/xxx/error1");
}else if(ex instanceof Exception2){
mv.setViewName("redirect:/xxx/error2");
}else{
mv.setViewName("redirect:/xxx/error");
}
return mv;
}
}
<!-- 声明异常解析器 -->
<bean class="com.baizhi.exception.resolver.MyExResolver"></bean>
8.3 自定义异常
package com.glls.exception;
import java.io.Serializable;
public class CustomerException extends Exception implements Serializable {
private static final long serialVersionUID = -5212079010855161498L;
public CustomerException() {
}
public CustomerException(String message) {
super(message);
}
}
8.4 自定义异常解析器1
进行页面跳转的全局异常处理的方式,开发中不常用。
package com.ymk.exception;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 进行页面跳转的全局异常处理的方式,开发中不常用
*/
public class CustomExceptionResolver implements HandlerExceptionResolver {
/**
* 异常解析器:主体逻辑
* 执行时刻:当handler中抛出异常时,会执行:捕获异常,并可以跳到错误页面
*/
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
CustomerException customerException = null;
ModelAndView modelAndView = new ModelAndView();
ex.printStackTrace();
if (ex instanceof CustomerException){
// 预料之中的异常
customerException = (CustomerException) ex;
modelAndView.addObject("error",customerException.getMessage());
} else {
// 如果系统抛出的异常,不是自定义异常,可以重新构造一个未知错误异常
modelAndView.addObject("error","对不起,服务器繁忙,请联系管理员");
}
modelAndView.setViewName("page/error");
return modelAndView;
}
}
在springmvc.xml中添加配置。
<!-- 声明异常解析器 -->
<bean id="handlerExceptionResolver" class="com.glls.exception.CustomExceptionResolver"/>
8.5 自定义异常解析器2【常用】
向前端响应json的全局异常处理方式,开发中常用。
封装一个后端给前端响应的数据结构,封装了状态码、信息等。
先创建一个枚举类用来定义提示信息。
package com.ymk.common;
import lombok.Getter;
@Getter // get方法
public enum ResultCodeEnum {
//枚举值
SUCCESS(true,"操作成功",20000),
UNKNOWN_REASON(false,"操作失败",999),
BAD_SQL_GRAMMAR(false,"sql语法错误",520),
ERROR(false,"操作失败",444);
private Boolean success;
private String message;
private Integer code;
ResultCodeEnum(Boolean success, String message, Integer code) {
this.success = success;
this.message = message;
this.code = code;
}
}
package com.ymk.common;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* 封装一个后端给前端响应的数据结构,封装了状态码、信息等
*/
@Data
public class R {
/**
* 响应的状态码
*/
private Integer code;
/**
* 响应的信息
*/
private String message;
/**
* 是否成功
*/
private Boolean success;
/**
* 封装响应的数据
*/
private Map<Object, Object> data = new HashMap<>();
public R() {
}
/**
* 返回成功的结果
* @return
*/
public static R ok() {
R r = new R();
r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
r.setCode(ResultCodeEnum.SUCCESS.getCode());
r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
return r;
}
/**
* 返回失败的结果
* @return
*/
public static R error() {
R r = new R();
r.setSuccess(ResultCodeEnum.ERROR.getSuccess());
r.setCode(ResultCodeEnum.ERROR.getCode());
r.setMessage(ResultCodeEnum.ERROR.getMessage());
return r;
}
public static R setResult(ResultCodeEnum resultCodeEnum) {
R r = new R();
r.setSuccess(resultCodeEnum.getSuccess());
r.setCode(resultCodeEnum.getCode());
r.setMessage(resultCodeEnum.getMessage());
return r;
}
/**
* 创建一个R对象去调success方法,然后把失败或成功的值传进去
*/
public R success(Boolean success) {
this.setSuccess(success);
return this;
}
/**
* 创建一个R对象去调message方法,然后把信息传进去
*/
public R message(String message) {
this.setMessage(message);
return this;
}
/**
* 创建一个R对象去调code方法,然后把状态码传进去
*/
public R code(Integer code) {
this.setCode(code);
return this;
}
/**
* 创建一个R对象去调data方法,封装一个键值
*/
public R data(Object key, Object value) {
this.data.put(key, value);
return this;
}
/**
* 创建一个R对象去调data方法,封装一个map
*/
public R data(Map<Object, Object> map) {
this.setData(map);
return this;
}
}
package com.ymk.exception;
import com.ymk.common.R;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* 向前端响应json的全局异常处理方式,开发中常用。
*/
@Component
@ControllerAdvice // 对controller 进行增强
@ResponseBody
public class CustomExceptionResolver2 {
/**
* 自定义异常
* 在@ExceptionHandler里面指定当前方法要处理的异常
*/
@ExceptionHandler(CustomerException.class) // 处理自定义异常
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public R handleEx(CustomerException customerException){
return R.error().message(customerException.getMessage());
}
/**
* 除零异常
*/
@ExceptionHandler(ArithmeticException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public R handleEx(ArithmeticException arithmeticException){
return R.error().message("除零异常");
}
/**
* 缺少请求参数异常
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public R handleHttpMessageNotReadableException(
MissingServletRequestParameterException ex) {
return R.error().message("缺少必要的请求参数");
}
/**
* 系统异常 预期以外异常
*
* 项目中,我们一般都会比较详细的去拦截一些常见异常,拦截 Exception 虽然可以一劳永逸,
* 但是不利于我们去排查或者定位问题。实际项目中,可以把拦截 Exception 异常写在 GlobalExceptionHandler
* 最下面,如果都没有找到,最后再拦截一下 Exception 异常,保证输出信息友好。
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public R handleEx(Exception Exception){
return R.error().message("请联系管理员");
}
}
九、拦截器
9.1 作用
作用:抽取handler中的冗余功能
9.2 定义拦截器
执行顺序:
- preHandle:前置拦截,请求到达 handler(controller) 之前进行拦截,方法的返回值是boolean类型,返回true就放行 , 返回false就拦截。
- postHandle:后置拦截,是controller执行后响应回来执行的方法,是在视图解析器解析视图之前执行的,所以在数据渲染之前在ModelAndView中再次渲染修改数据。
- afterCompletion:在页面渲染完之后执行,整个请求都执行结束了才执行,可以在这个方法中做一些收尾操作,如:记录日志、资源清理,还可以得到请求中出现的异常对象,对异常进行处理分析。
- 前端请求路径顺序:过滤器—>Servlet(前端控制器)—>拦截器—>Controller
package com.ymk.interceptor;
import com.ymk.pojo.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* 配置拦截器步骤:
* 1.创建拦截器类
* 2.将拦截器交给spring容器
*/
public class MyInterceptor implements HandlerInterceptor {
/**
* 前置拦截 请求到达 handler(controller) 之前进行拦截
* 方法的返回值是boolean类型,返回true就放行 , 返回false就拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截器的前置拦截--在请求到达目标controller 之前 进行拦截");
// true 表示放行 false 表示拦截,不放行
return true; // 放行 后续的拦截器或controller就会执行
// 结论:可以在这个放法中对请求进行条件判断,满足条件返回true放行,不满足返回false
}
/**
* 后置拦截,是controller执行之后响应回来执行的方法
* 是在视图解析器解析视图之前执行的,所以在数据渲染之前在ModelAndView中
* 再次渲染修改数据
* @param modelAndView:封装了要跳转的页面以及数据
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 获得将要跳转的视图名称
String viewName = modelAndView.getViewName();
System.out.println(viewName);
// controller执行后响应回来的数据可以获取到,也可以进行修改
Map<String, Object> model = modelAndView.getModel();
// 获取响应的值
User user = (User) model.get("user");
System.out.println(user);
// 修改响应的值
user.setName("ls");
System.out.println("后置拦截--");
}
/**
* 在页面渲染完之后执行,整个请求都执行结束了才执行,
* 可以在这个方法中做一些收尾操作,如:记录日志、资源清理
* 还可以得到请求中出现的异常对象,对异常进行处理分析
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("after----");
}
}
9.3 配置拦截路径
在springmvc.xml中配置拦截器。
<!--——配置拦截器——-->
<mvc:interceptors>
<mvc:interceptor>
<!--——配置拦截的路径——-->
<mvc:mapping path="/inter/test1"/>
<mvc:mapping path="/inter/test2"/>
<mvc:mapping path="/inter/test*"/> <!-- test开头 -->
<mvc:mapping path="/inter/*"/> <!-- inter/开头 -->
<mvc:mapping path="/**"/> <!-- /** 任意多级任意路径 -->
<mvc:exclude-mapping path="/inter/login"/> <!--不拦截此路径-->
<bean class="com.ymk.interceptor.MyInterceptor"></bean> <!--拦截器类-->
</mvc:interceptor>
</mvc:interceptors>
9.4登录拦截
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("判断有没有登录");
HttpSession session = request.getSession();
if (session.getAttribute("name") != null){
System.out.println("登录成功");
return true;//session有值不拦截,返回true
} else {
//没有登录重定向到首页
System.out.println("没有登录");
session.setAttribute("error","请先登录");
response.sendRedirect(request.getContextPath()+"/index.jsp");
return false;//拦截返回false
}
}
}
Controller层
@Controller
@RequestMapping("/login")
public class UserController {
@RequestMapping("/login1")
public String login(HttpSession session, Model model, String name, String password){
System.out.println("用户名和密码:"+name+":"+password);
//登录成功将name存到域里
if (name.equals("jack") && password.equals("123")){
session.setAttribute("name",name);
return "house/add";//跳转到add.jsp中
} else {
model.addAttribute("error","用户名或密码错误");
return "index";//跳转到登录界面
}
}
}
十、上传与下载
文件上传是项目开发中最常见的功能之一 ,springMVC 可以很好的支持文件上传,但是SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver。
10.1 文件上传三要素
表单的提交方式 method="POST"
表单的enctype属性是多部分表单形式 enctype=“multipart/form-data"
表单项(元素)type="file"
<form action="/upload" enctype="multipart/form-data" method="post">
用户:<input type="text" name="username"> <br />
请选择要上传的文件:<input type="file" name="file"/><br />
<input type="submit" value="upload">
</form>
10.2 enctype 属性值
- application/x-www-form-urlencoded
默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式。
- multipart/form-data
这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
- text/plain
除了把空格转换为 "+" 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。
注意:一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。
10.3文件上传的方式
使用apache提供的工具类 commons-fileupload(麻烦)
使用servlet3.0版本,通过注解使用
使用SpirngMVC的MultipartResolver
Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类,因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。
10.4 文件上传操作步骤
10.4.1 导入依赖
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!--servlet 需要这个依赖 并且 tomcat8 版本 要不会有 request 转换异常-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
10.4.2 编写表单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<!--单文件上传-->
<form action="${pageContext.request.contextPath}/upload"
enctype="multipart/form-data" method="post">
用户:<input type="text" name="username"> <br />
请选择要上传的文件:<input type="file" name="file"/><br />
<input type="submit" value="上传">
</form>
</body>
</html>
10.4.3 配置文件上传组件
springmvc.xml文件配置
<!--——文件上传配置——-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--——请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1——-->
<property name="defaultEncoding" value="utf-8"/>
<!--——上传文件大小上限,单位为字节(10485760=10M)——-->
<property name="maxUploadSize" value="10485760"/>
</bean>
10.4.4 编写FileController
package com.ymk.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@Controller
@RequestMapping("/file")
public class FileController {
// MultipartFile file:对应接受上传表单的文件项
@RequestMapping("/upload")
public String upload(MultipartFile file, String username, HttpServletRequest request) throws IOException {
// 得到文件名
String originalFilename = file.getOriginalFilename();
// 假如对文件类型有要求可以做简单的逻辑判断
// 获得文件后缀
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
// 文件的后缀是jpg 或者 png 才接受上传
if (suffix.equalsIgnoreCase(".jpg") || suffix.equalsIgnoreCase(".png")) {
String realPath = request.getServletContext().getRealPath("/");
// 得到上传文件存放的路径
File uploadDir = new File(realPath, "upload");
// 如果路径不存在就创建
if (!uploadDir.exists()){
uploadDir.mkdirs();
}
// 把上传的文件写到上传目录中去,上传之后的文件名称根据需求看是否修改
// 使用封装好的方法,底层是 io 流
file.transferTo(new File(uploadDir, System.currentTimeMillis() + suffix));
request.setAttribute("msg","文件上传成功");
// 得到上传目录下所有的文件名
String[] list = uploadDir.list();
List<String> filerNames = Arrays.asList(list);
// 把文件名放在域对象,跳转到页面展示
request.setAttribute("fileNames",filerNames);
} else {
request.setAttribute("msg", "文件格式错误");
}
return "page/result";
}
}
result.jsp文件上传上去后的展示。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>上传的文件展示</title>
</head>
<body>
${msg}<br>
<c:forEach items="${fileNames}" var="fileName">
<img style="height: 100px;width: 100px" src="http://localhost:8080/upload/${fileName}">
</c:forEach>
</body>
</html>
10.4.5 多文件上传
<form action="${pageContext.request.contextPath}/method2" method="post" enctype="multipart/form-data" >
用户名: <input type="text" name="name"> <br>
文件: <input type="file" name="upload">
文件: <input type="file" name="upload">
文件: <input type="file" name="upload"><br>
<input type="submit" value="提交">
</form>
/**
* 多文件上传
*/
@RequestMapping("/upload2")
public String upload2(MultipartFile[] files, String username, HttpServletRequest request) throws IOException {
for (MultipartFile file : files) {
// 得到文件名
String originalFilename = file.getOriginalFilename();
// 假如对文件类型有要求可以做简单的逻辑判断
// 获得文件后缀
String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
// 文件的后缀是jpg 或者 png 才接受上传
if (suffix.equalsIgnoreCase(".jpg") || suffix.equalsIgnoreCase(".png")) {
String realPath = request.getServletContext().getRealPath("/");
// 得到上传文件存放的路径
File uploadDir = new File(realPath, "upload");
// 如果路径不存在就创建
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
// 把上传的文件写到上传目录中去,上传之后的文件名称根据需求看是否修改
// 使用封装好的方法,底层是 io 流
file.transferTo(new File(uploadDir, System.currentTimeMillis() + suffix));
request.setAttribute("msg", "文件上传成功");
} else {
request.setAttribute("msg", "文件格式错误");
}
}
return "page/result";
}
}
10.5 文件下载
@RequestMapping("/download")
// fileName是指jsp页面中点击下载带过来的fileName
public void download(String fileName, HttpServletResponse response,HttpServletRequest request) throws IOException {
// 告诉浏览器以下载的方式来打开
// 设置响应头,告诉浏览器如何处理这个数据
response.setHeader("content-disposition","attachment;filename="+fileName);
System.out.println(fileName);
// 输出流,目的地指向浏览器
ServletOutputStream outputStream = response.getOutputStream();
String realPath = request.getServletContext().getRealPath("/upload");
// 得到要下载的文件对象
File file = new File(realPath, fileName);
// 把文件对象转为字节数组
byte[] bytes = FileUtils.readFileToByteArray(file);
// 指向浏览器的输出流,发送一个字节数组到客户端浏览器
outputStream.write(bytes);
outputStream.close();
}
}
十一、验证码
11.1 作用
防止暴力攻击,前端安全保障
11.2 加入依赖
<!-- 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>
11.3 声明验证码组件
写在web.xml中
<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>
11.4 Page
<img src="${pageContext.request.contextPath}/captcha" style="width:85px" id="cap"/>
<script>
$(function(){
$("#cap").click(function(){
//刷新验证码
path = $(this).attr("src")+"?"+new Date().getTime();
$(this).attr("src",path);
});
});
</script>
11.5登录实例
前端
<body>
<h1>登录页面</h1>
<form action="${pageContext.request.contextPath}/login/login1" method="post">
用户名:<input type="text" name="name"><br>
密码:<input type="password" name="password"><br>
验证码:<input type="text" name="captcha">
<img src="${pageContext.request.contextPath}/captcha" style="width:85px" id="cap"/><br>
<input type="submit" value="登录">
</form>
提示:${error}
</body>
<script src="js/jquery-3.6.0.js"></script>
<script>
$(function(){
$("#cap").click(function(){
//刷新验证码
path = $(this).attr("src")+"?"+new Date().getTime();
$(this).attr("src",path);
});
});
</script>
后端
@Controller
@RequestMapping("/login")
public class UserController {
@RequestMapping("/login1")
public String login(HttpSession session, Model model, String name, String password,String captcha){
System.out.println("用户名和密码:"+name+":"+password);
//通过session取出来验证码
String captcha1 = (String) session.getAttribute("captcha");
//判断验证码与用户输入是否相等
if (!captcha1.equals(captcha)){
//不相等给出提示
model.addAttribute("error","验证码错误");
return "index";
}
if (name.equals("jack") && password.equals("123")){//登录成功将name存到域里
session.setAttribute("name",name);
return "house/add";//跳转到add.jsp中
} else {
model.addAttribute("error","用户名或密码错误");
return "index";//跳转到登录界面
}
}
}
十二、REST
12.1 开发风格
是一种开发风格,遵从此风格开发软件,符合REST风格,则RESTFUL。
两个核心要求:
每个资源都有唯一的标识(URL)
不同的行为,使用对应的http-method
访问标识 | 资源 |
---|---|
http://localhost:8989/xxx/users | 所有用户 |
http://localhost:8989/xxx/users/1 | 用户1 |
http://localhost:8989/xxx/users/1/orders | 用户1的所有订单 |
请求方式 | 标识 | 意图 |
---|---|---|
GET | http://localhost:8989/xxx/users | 查询所有用户 |
POST | http://localhost:8989/xxx/users | 在所有用户中增加一个 |
PUT | http://localhost:8989/xxx/users | 在所有用户中修改一个 |
DELETE | http://localhost:8989/xxx/users/1 | 删除用户1 |
GET | http://localhost:8989/xxx/users/1 | 查询用户1 |
GET | http://localhost:8989/xxx/users/1/orders | 查询用户1的所有订单 |
POST | http://localhost:8989/xxx/users/1/orders | 在用户1的所有订单中增加一个 |
12.2 优点
输出json:
12.3 使用
12.3.1 定义Rest风格的 Controller
@RequestMapping(value="/users",method = RequestMethod.GET)
等价于
@GetMapping("/users")
@RestController
public class RestController {
@GetMapping("/users")
public List<User> queryAllUsers(){
System.out.println("get");
List<User> users = ....
return users;
}
@PostMapping("/users")
public String addUser(@RequestBody User user){
System.out.println("Post user :"+user);
return "{status:1}";
}
@PutMapping("/users")
public String updateUser(@RequestBody User user){
System.out.println("Put user" user:"+user);
return "{status:1}";
}
@GetMapping("/users/{id}")
public String queryOneUser(@PathVariable Integer id){//@PathVariable 接收路径中的值
System.out.println("Get user id:"+id);
return "{status:1}";
}
@DeleteMapping("/users/{id}")
public String deleteOneUser(@PathVariable Integer id){//@PathVariable 接收路径中的值
System.out.println("delete user id:"+id);
return "{status:1}";
}
}
12.3.2 Ajax请求
<script>
function putUser(){ // 发送更新请求 (增加请求发送方式也是如此)
var xhr = new XMLHttpRequest();
//定义 put,delete,get,post方式 即可,不用定义_method
xhr.open("put","${pageContext.request.contextPath}/rest04/users");
// 设置请求头
xhr.setRequestHeader("content-type","application/json");
// 设置请求参数
var user = {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){
var ret = xhr.responseText;
// 解析json,并输出
console.log(JSON.parse(ret));
}
}
/*$.ajax({
url:'${pageContext.request.contextPath}/rest04/users',
type:'put',
contentType:"application/json",//声明请求参数类型为 json
data:JSON.stringify(user),// 转换js对象成json
success:function(ret){
console.log(JSON.parse(ret));
}
});*/
}
function delUser(){ // 发送删除请求
var xhr = new XMLHttpRequest();
//定义 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){
var ret = xhr.responseText;
console.log(JSON.parse(ret));
}
}
}
</script>
十三、跨域请求
13.1 域
域:协议+IP+端口
13.2 Ajax跨域问题
Ajax发送请求时,不允许跨域,以防用户信息泄露。
当Ajax跨域请求时,响应会被浏览器拦截(同源策略),并报错。即浏览器默认不允许ajax跨域得到响应内容。
互相信任的域之间如果需要ajax访问,(比如前后端分离项目中,前端项目和后端项目之间),则需要额外的设置才可正常请求。
13.3 解决方案
方案1:
在被访问方的Controller类上,添加注解(写前端的域)
@CrossOrigin("*") //允许此域发请求访问
public class SysUserController {
....
}
方案2:
- 使用 request 和 response 设置
- 其中origin就是前端的域名
// 允许的跨域
String origin = request.getHeader("Origin");
// 允许携带Cookie
response.setHeader("Access-Control-Allow-Origin",origin);
携带对方cookie,使得session可用
在访问方,ajax中添加属性:withCredentials: true
$.ajax({
type: "POST",
url: "http://localhost:8989/web/sys/login",
...,
xhrFields: {
// 跨域携带cookie
withCredentials: true
}
});
或
var xhr = new XMLHttpRequest();
// 跨域携带cookie
xhr.withCredentials=true;
十四、SpringMVC执行流程
- DispatcherServlet : 前端控制器 不需要程序员开发 由springmvc框架提供的,它的作用是 统一处理请求和响应 整个流程的控制中心 ,由他来调度其他组件 处理用户的请求
- HandlerMapping: 处理器映射器 不需要程序员开发 有框架提供, 它的作用是根据 url ,method 等 信息 查找 handler 准确点说是 controller中的方法
- Handler : 需要程序员开发的 controller
- HandlerAdapter: 处理器适配器 不需要程序员开发 对handler 方法 进行 调用
- ViewResolver: 视图解析器 不需要程序员开发 进行视图解析
- View: 视图
- Model: 数据
十五、Spring整合
15.1 整合思路
DispatcherServlet 启动的springMVC工厂== 负责生产C及springMVC自己的系统组件
ContextLoaderListener 启动的spring工厂== 负责生产其他所有组件
springMVC的工厂会被设置为spring工厂的子工厂,可以随意获取spring工厂中的组件
整合过程,就是累加:代码+依赖+配置。然后将service注入给controller即可
15.2 整合技巧
两个工厂不能有彼此侵入,即生产的组件不能有重合。
<!-- 告知SpringMVC 哪些包中 存在 被注解的类
use-default-filters=true 凡是被 @Controller @Service @Repository注解的类,都会被扫描
use-default-filters=false 默认不扫描包内的任何类, 只扫描include-filter中指定的类
只扫描被@Controller注解的类
-->
<context:component-scan base-package="com.zhj" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 告知Spring
唯独不扫描@Controller注解的类 -->
<context:component-scan base-package="com.zhj" use-default-filters="true">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
15.3 ssm整合
15.3.1 创建maven的web工程ssm
15.3.2 添加依赖
<dependencies>
<!--—————————mysql架包—————————-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<!--————————mybatis架包————————-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--————————log4j日志架包————————-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<!--————————加入servlet依赖(servlet的jar)————————-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!--———————jsp的依赖(jsp相关的jar加进来)————————-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<!--——————jstl架包——————-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--——————注解@Test测试类架包——————-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--——————逆向工程架包——————-->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
<!--——————spring-context依赖中关联了其他核心依赖——————-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!--——————spring和mybatis整合时,需要的架包——————-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--——————————加入springMVC依赖—————————-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!--——————自动生成get、set、构造方法等——————-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<!--————————导入aop依赖————————-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!--————————导入切面aspect依赖————————-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!--——————spring事务—————-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.14.RELEASE</version>
</dependency>
<!--——————spring操作数据库——————-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.14.RELEASE</version>
</dependency>
<!--——————spring测试相关依赖——————-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.0.RELEASE</version>
<scope>test</scope>
</dependency>
<!--——————阿里的连接池——————-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!--——————jackson架包——————-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.0</version>
</dependency>
<!--——————分页插件—————-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.0</version>
</dependency>
<!--——————文件上传—————-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!--——————验证码组件—————-->
<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>
</dependencies>
<!--——把xml文件放在Java里会读取不到,通过下方配置可以读取——-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>*.xml</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<!--——————————————————————————————————-->
<!--——————————逆向工程的插件配置——————————-->
<!--——————————————————————————————————-->
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.5</version>
<!--————————指定资源文件的路径————————-->
<configuration>
<configurationFile>src\main\resources\generator.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
15.3.3 web.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 上下文参数 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- spring 配置文件 -->
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 封装了一个监听器,帮助加载 Spring 的配置文件 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--前端控制器-->
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 局部参数:声明配置文件位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- Servlet启动时刻:可选 -->
<!--DispatcherServlet前端控制器是springmvc中非常重要的一个组件,内置了很多初始化的工作,所以让他随着服务器的启动而启动
提前把初始化工作做好,避免第一次访问的时候速度很慢
-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<!--
这里设置的是前端控制器可以处理的请求路径/表示拦截处理jsp以外的所有请求
也就是说处理以.jsp结尾的请求其他请求都要经过前端控制器,这里注意不要写成/*,/*表示匹配所有路径
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 此过滤器会进行: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>
</web-app>
15.3.4 applicationContext.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--—————开启注解扫描,即让注解生效—————-->
<context:component-scan base-package="com.ymk"/>
<!--—————开启AOP自动代理配置—————-->
<aop:aspectj-autoproxy/>
<!--———————读取db.properties———————-->
<context:property-placeholder location="classpath:db.properties"/>
<!--—————1 druid数据源 实例化druid—————-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!--—————配置初始化大小、最小、最大——————-->
<property name="initialSize" value="${jdbc.initialSize}"/>
<property name="minIdle" value="${jdbc.minIdle}"/>
<property name="maxActive" value="${jdbc.maxActive}"/>
<!--——————配置获取连接等待超时的时间———————-->
<property name="maxWait" value="${jdbc.maxWait}"/>
<!--——配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒——— -->
<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/>
<!--————配置一个连接在池中最小生存的时间,单位是毫秒————-->
<property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/>
<property name="testWhileIdle" value="true"/>
<!--————这里建议配置为TRUE,防止取到的连接不可用—————-->
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="false"/>
</bean>
<!--—————2 创建Mybatis的工厂对象——————-->
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<!--————————设置数据库连接池———————— -->
<property name="dataSource" ref="dataSource"/>
<!--—————加载mybatis主配置文件 classpath 表示classes目录所在路径—————-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--————————加载映射文件———————— -->
<property name="mapperLocations" value="classpath:com/ymk/mapper/*.xml"/>
</bean>
<!--—————SqlSessionFactoryBean具体加载那个接口,adminDao,讲接口代理对象实例化出来
spring就是依赖控制反转将对象创建出来的——————-->
<!--———————3设置Mybatis的映射接口 ———————-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--——设置映射接口所在包,会将mapper下所有的接口实例化出一个对象,不用sqlSession.getMapper了——-->
<property name="basePackage" value="com.ymk.mapper"></property>
</bean>
</beans>
15.3.5 db.properties配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/empployee?useSSL=false
jdbc.username=root
jdbc.password=123456
# 配置初始化大小、最小、最大
jdbc.initialSize=5
jdbc.minIdle=3
jdbc.maxActive=20
# 配置获取连接等待超时的时间
jdbc.maxWait=0
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
jdbc.timeBetweenEvictionRunsMillis=600000
# 配置一个连接在池中最小生存的时间,单位是毫秒
jdbc.minEvictableIdleTimeMillis=300000
15.3.6 springmvc.xml配置文件
<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-scan base-package="com.ymk" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!-- 注册注解开发驱动 -->
<mvc:annotation-driven>
</mvc:annotation-driven>
<!-- 视图解析器
作用:1.捕获后端控制器的返回值="index"
2.解析: 在返回值的前后 拼接 ==> "/index.jsp"
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/"></property>
<!-- 后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
<!--
额外的增加一个handler,且其requestMapping: "/**" 可以匹配所有请求,但是优先级最低
所以如果其他所有的handler都匹配不上,请求会转向 "/**" ,恰好,这个handler就是处理静态资源的
处理方式:将请求转会到tomcat中名为default的Servlet
-->
<mvc:default-servlet-handler/>
<!-- <mvc:resources mapping="/image/**" location="/image/"/>-->
<!-- <mvc:interceptors>-->
<!-- <mvc:interceptor>-->
<!-- <mvc:mapping path="/inter/test1"/>-->
<!-- <bean class="com.qf.java2110.interceptor.MyInter1"></bean>-->
<!-- </mvc:interceptor>-->
<!--<!– <mvc:interceptor>–>-->
<!--<!– <mvc:mapping path="/**"/> <!– /** 任意多级任意路径 –>–>-->
<!--<!– <mvc:exclude-mapping path="/inter/login"/> <!–不拦截此路径–>–>-->
<!--<!– <mvc:exclude-mapping path="/json/test7"/> <!–不拦截此路径–>–>-->
<!--<!– <bean class="com.qf.java2110.interceptor.LoginInterceptor"></bean>–>-->
<!--<!– </mvc:interceptor>–>-->
<!-- </mvc:interceptors>-->
<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上传文件大小上限,单位为字节(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
</bean>
</beans>
15.3.7 mybatis-config.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--————————使用外部properties文件————————-->
<!-- <properties resource="db.properties"/>-->
<!--<settings>-->
<!--<setting name="logImpl" value="LOG4J"/>-->
<!--</settings>-->
<!--——————给类起别名,路径为包直接扫描包,不用每次配置——————-->
<typeAliases>
<package name="com.ymk.model"/>
</typeAliases>
<!--——————————分页插件配置——————————-->
<!-- <plugins>-->
<!-- <plugin interceptor="com.github.pagehelper.PageHelper">-->
<!-- <property name="dialect" value="mysql"/>-->
<!-- </plugin>-->
<!-- </plugins>-->
</configuration>
15.3.8 log4j.properties配置文件
log4j.rootLogger=DEBUG, stdout, logfile
#log4j.logger.com.ymk.mapper=debug,stdout
log4j.category.org.springframework=ERROR
log4j.category.org.apache=INFO
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#\u5E74\u6708\u65E5\u65F6\u5206\u79D2,xxx
#log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
#\u5E74\u6708\u65E5\u65F6\u5206\u79D2
#log4j.appender.stdout.layout.ConversionPattern=[%p][%d{yy-MM-dd HH:mm:ss}][%c]%m%n
log4j.appender.stdout.layout.ConversionPattern=%p-[%c]%m%n
log4j.appender.logfile=org.apache.log4j.RollingFileAppender
log4j.appender.logfile.File=${myweb.root}/WEB-INF/log/myweb.log
log4j.appender.logfile.MaxFileSize=512KB
log4j.appender.logfile.MaxBackupIndex=5
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
15.3.9 实例操作
创建实体创建类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin {
private int id;
private String user;
private String password;
}
创建AdminMapper接口和AdminMapper.xml文件
public interface AdminMapper {
Admin findAdminById(int id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ymk.mapper.AdminMapper">
<select id="findAdminById" parameterType="int" resultType="admin">
select *
from admin
where id = #{id};
</select>
</mapper>
AdminService接口和AdminServiceImpl实现类
@Service("adminService")
public class AdminServiceImpl implements AdminService {
@Autowired
private AdminMapper adminMapper;
@Override
public Admin findAdminById(int id) {
Admin admin = adminMapper.findAdminById(id);
return admin;
}
}
AdminController
@Controller
@RequestMapping("/admin")
public class AdminController {
@Autowired
private AdminService adminService;
//通过id查询用户信息
@RequestMapping("/{id}")
public String fridUserById(@PathVariable("id")Integer id, Model model){
Admin admin = adminService.findAdminById(id);
model.addAttribute("admin",admin);
System.out.println("AdminController:"+admin);
return "index";
}
}