目录:
- SpringMVC
- 什么是MVC模型
- SpringMVC
- 案例
- SpringMVC执行流程
- SpringMVC封装参数
- 简单数据类型
- 简单对象
- 关联对象
- 简单数据类型集合
- Map集合
- 参数类型转换器
- 编码过滤器
- Servlet原生对象
- SpringMVC处理响应
- 视图解析器
- 返回值为void
- 返回值为ModelAndView
- 向request域设置数据
- 向session域设置数据
- 向context域设置数据
- 请求转发&重定向
- SpringMVC注解
- @Controlle r和 @RequestMapping
- @RequestParam
- @RequestHeader、@CookieValue
- @SessionAttributes
- @ModelAttribute
- RESTful风格支持
- @PathVariable
- @PostMapping、@GetMapping、@PutMapping、@DeleteMapping
- HiddenHttpMethodFilter
- @ResponseBody
- @RestController
- 静态资源映射
- @RequestBody
- SpringMVC上传
- 原生方式上传
- SpringMVC方式上传
- 上传多文件
- 异步上传
- 跨服务器上传
- 查询可下载的文件
- 下载文件
- SpringMVC异常处理
- 单个控制器异常处理
- 全局异常处理
- 自定义异常处理器
- SpringMVC拦截器
- 拦截器的使用
- 全局拦截器
- 拦截器链与执行顺序
- 过滤敏感词
- SpringMVC跨域请求
- 同源策略
- 跨域请求
- 接收跨域请求
SpringMVC
什么是MVC模型
MVC全称Model View Controller,是一种设计创建Web应用程序的模式。这三个单词分别代表Web应用程序的三个部分:
Model(模型):指数据模型。用于存储数据以及处理用户请求的业务逻辑。在Web应用中,JavaBean对象,业务模型等都属于Model。
View(视图):用于展示模型中的数据的,一般为jsp或html文件。
Controller(控制器):是应用程序中处理用户交互的部分。接受视图提出的请求,将数据交给模型处理,并将处理后的结果交给视图显示。
SpringMVC
SpringMVC是一个基于MVC模式的轻量级Web框架,是Spring框架的一个模块,和Spring可以直接整合使用,我们使用的版本是Spring6,所以JDK需要17以上。SpringMVC代替了Servlet技术,它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。
案例
-
使用maven创建web项目,补齐包结构。
-
引入相关依赖
<dependencies><!-- Spring核心依赖 --><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.11</version></dependency><!-- SpringMVC --><dependency><groupId>org.springframework</groupId><artifactId>spring-web</artifactId><version>6.0.11</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>6.0.11</version></dependency><!-- Servlet --><dependency><groupId>jakarta.servlet</groupId><artifactId>jakarta.servlet-api</artifactId><version>6.0.0</version><scope>provided</scope></dependency><!-- JSP --><dependency><groupId>jakarta.servlet.jsp</groupId><artifactId>jakarta.servlet.jsp-api</artifactId><version>3.0.0</version><scope>provided</scope></dependency>
</dependencies>
- 在web.xml中配置前端控制器DispatcherServlet。
<web-app><display-name>Archetype Created Web Application</display-name><!--SpringMVC前端控制器,本质是一个Servlet,接收所有请求,在容器启动时就会加载--><servlet><servlet-name>dispatcherServlet</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><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>dispatcherServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping>
</web-app>
4.编写SpringMVC核心配置文件springmvc.xml,该文件和Spring配置文件写法一样。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:mvc="http://www.springframework.org/schema/mvc"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- 扫描包 --><context:component-scan base-package="com.jjy"/><!-- 开启SpringMVC注解的支持 --><mvc:annotation-driven/></beans>
5.编写控制器
@Controller
public class MyController1 {// 该方法的访问路径是/c1/hello1@RequestMapping("/c1/hello1")public void helloMVC(){System.out.println("hello SpringMVC!");}
}
- 配置tomcat10启动项目,访问 http://localhost:8080/c1/hello1
SpringMVC执行流程
SpringMVC的组件
- DispatcherServlet:前端控制器,接受所有请求,调用其他组件。
- HandlerMapping:处理器映射器,根据配置找到方法的执行链。
- HandlerAdapter:处理器适配器,根据方法类型找到对应的处理器。
- ViewResolver:视图解析器,找到指定视图。
组件的工作流程
- 客户端将请求发送给前端控制器。
- 前端控制器将请求发送给处理器映射器,处理器映射器根据路径找到方法的执行链,返回给前端控制器。
- 前端控制器将方法的执行链发送给处理器适配器,处理器适配器根据方法类型找到对应的处理器。
- 处理器执行方法,将结果返回给前端控制器。
- 前端控制器将结果发送给视图解析器,视图解析器找到视图文件位置。
- 视图渲染数据并将结果显示到客户端。
SpringMVC封装参数
简单数据类型
在Servlet中我们通过request.getParameter(name)
获取请求参数。该方式存在两个问题:
- 请求参数较多时会出现代码冗余。
- 与容器紧耦合。
而SpringMVC支持参数注入的方式用于获取请求数据,即将请求参数直接封装到方法的参数当中。用法如下:
- 编写控制器方法
// 封装为简单数据类型类型参数
@RequestMapping("/c1/param1")
public void simpleParam(String username,int age){System.out.println(username);System.out.println(age);
}
- 访问该方法时,请求参数名和方法参数名相同,即可完成自动封装。
http://localhost:8080/c1/param1?username=bz&age=10
简单对象
SpringMVC支持将参数直接封装为对象,写法如下:
- 编写实体类
public class Student {private int id;private String name;private String sex;// 省略getter/setter/tostring
}
- 编写控制器方法
// 封装为简单对象类型参数
@RequestMapping("/c1/param2")
public void objParam(Student student){System.out.println(student);
}
- 访问该方法时,请求参数名和对象参数的属性名相同,即可完成自动封装。
http://localhost:8080/c1/param2?id=1&name=bz&sex=female
关联对象
SpringMVC还可以将请求参数封装为关联对象,即对象的属性也是一个对象。写法如下:
- 编写实体类
public class Student {private int id;private String name;private String sex;private Address address; // 地址对象// 省略getter/setter/tostring
}public class Address {private String info; //地址信息private String postcode; //邮编// 省略getter/setter/tostring
}
- 编写控制器方法
// 获取关联对象类型参数
@RequestMapping("/c1/param3")
public void objParam2(Student student){ System.out.println(student);
}
- 访问该方法时,请求参数名和方法参数的属性名相同,即可完成自动封装。
http://localhost:8080/c1/param3?id=1&name=bz&sex=female&address.info=beijing&address.postcode=030000
我们也可以使用表单发送带有参数的请求:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>表单提交</title></head><body><form action="/c1/param3" method="post">id:<input name="id">姓名:<input name="name">性别:<input name="sex">住址:<input name="address.info">邮编:<input name="address.postcode"><input type="submit"></form></body>
</html>
简单数据类型集合
1.编写控制器方法
// 封装为简单数据类型集合,参数前必须添加@RequestParam注解
@RequestMapping("/c1/param4")
public void listParam(@RequestParam List<String> users){ System.out.println(users);
}
- 该方式也可以封装为简单数据类型数组:
@RequestMapping("/c1/param5")
public void listParam2(@RequestParam String[] users){ System.out.println(users[0]); System.out.println(users[1]);
}
- 请求的参数写法
http://localhost:8080/c1/param4?users=bj&users=sxt
Map集合
同样,SpringMVC要想把参数封装到Map集合中,需要封装到有Map属性的对象中。
- 编写实体类
public class AddressMap {private Map<String,Address> address;// 省略getter/setter/tostring
}
- 编写控制器方法
// 对象中包含map属性
@RequestMapping("/c1/param7")
public void mapParam3(AddressMap addressMap){System.out.println(addressMap.getAddress());
}
- 请求的参数写法
参数的写法为:map属性名[键].值中的属性,例如:address[one].info
请求的路径如下:
http://localhost:8080/c1/param7?address[one].info=bj&address[one].postcode=100010&address[two].info=sh&address[two].postcode=100011
编码后为:
http://localhost:8080/c1/param7?address%5Bone%5D.info=bj&address%5Bone%5D.postcode=100010&address%5Btwo%5D.info=sh&address%5Btwo%5D.postcode=100011
参数类型转换器
前端传来的参数全部为字符串类型,SpringMVC使用自带的转换器将字符串参数转为需要的类型。如:
// 获取简单类型参数
@RequestMapping("/c1/param1")
public void simpleParam(String username,int age){ System.out.println(username); System.out.println(age);
}
但在某些情况下,无法将字符串转为需要的类型,如:
@RequestMapping("/c1/param8")
public void dateParam(Date birthday){ System.out.println(birthday);
}
由于日期数据有很多种格式,SpringMVC默认只能转换2050/1/1
这样的日期格式。但假如前端传来的参数格式为2025-01-01
时,SpringMVC就无法解析参数。此时需要自定义参数类型转换器。
- 定义类型转换器类,实现Converter接口
// 类型转换器必须实现Converter接口,两个泛型代表转换前的类型,转换后的类型
public class DateConverter implements Converter<String, Date> {/*** 转换方法* @param source 转换前的数据* @return 转换后的数据*/@Overridepublic Date convert(String source) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");Date date = null;try {date = sdf.parse(source);} catch (ParseException e) {e.printStackTrace();}return date;}
}
- 0注册类型转换器对象
<!-- 配置转换器工厂 -->
<bean id="dateConverter" class="org.springframework.context.support.ConversionServiceFactoryBean"><!-- 转换器集合 --><property name="converters"><set><!-- 自定义转换器 --><bean class="com.jjy.converter.DateConverter"></bean></set></property>
</bean><!-- 使用转换器工厂 -->
<mvc:annotation-driven conversion-service="dateConverter"></mvc:annotation-driven>
- 此时再访问http://localhost:8080/c1/param9?birthday=2025-01-01时,SpringMVC即可将请求参数封装为Date类型的参数。
编码过滤器
在传递参数时,tomcat10以上不会出现中文乱码,tomcat8以上能处理get请求的中文乱码,但不能处理post请求的中文乱码。
编写jsp表单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>编码过滤器</title></head><body><form action="/cn/code" method="post">姓名:<input name="username"><input type="submit"></form></body>
</html>
编写控制器方法
@RequestMapping("/cn/code")
public void code(String username){System.out.println(username);
}
SpringMVC提供了处理中文乱码的过滤器,在web.xml中配置该过滤器即可解决中文乱码问题:
<!--SpringMVC中提供的字符编码过滤器,放在所有过滤器的最上方-->
<filter><filter-name>encFilter</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>encFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>
Servlet原生对象
SpringMVC的方法中已经封装好了Servlet原生对象,在方法参数中定义HttpServletRequest
、HttpServletResponse
、HttpSession
等类型的参数即可直接在方法中使用。
// 使用Servlet原生对象
@RequestMapping("/c1/param9")
public void servletParam(HttpServletRequest request, HttpServletResponse response, HttpSession session){ // 原生对象获取参数 System.out.println(request.getParameter("name")); System.out.println(response.getCharacterEncoding()); System.out.println(session.getId());
}
访问该方法即可:http://localhost:8080/c1/param9?name=bjsxt
一般情况下,在SpringMVC中都有对Servlet原生对象的方法的替代,推荐使用SpringMVC的方式代替Servlet原生对象。
SpringMVC处理响应
视图解析器
SpringMVC默认情况下会在控制器执行完成后跳转到视图页面,视图解析器能找到相应的视图,之前的404异常就是由于没有配置视图解析器导致找不到视图。
在SpringMVC中提供了13个视图解析器,用于支持不同的视图技术。InternalResourceViewResolver是SpringMVC的默认视图解析器,用来解析JSP视图。
<!-- 视图解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!-- 视图前缀 --><property name="prefix" value="/" /><!-- 视图后缀 --><property name="suffix" value=".jsp" />
</bean>
返回值为void
我们可以通过控制器方法的返回值设置跳转的视图,控制器方法支持的返回值类型为void
、String
、ModelAndView
,我们以此进行介绍。
当控制器方法的返回值为void时,会跳转到名字是 前缀+方法路径名+后缀 的jsp页面
- 编写控制器方法
// 路径是helloMVC,方法执行完后会跳转到/helloMVC.jsp
@RequestMapping("/helloMVC")
public void helloMVC(){System.out.println("hello SpringMVC!");
}
- 编写helloMVC.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>MVC</title></head><body><h1>欢迎来到SpringMVC</h1></body>
</html>6
返回值为ModelAndView
ModelAndView
是SpringMVC提供的对象,该对象可以向request域设置数据并指定跳转的页面。该对象中包含Model对象和View对象。
Model:向request域中设置数据。 View:指定跳转的页面。
编写控制器方法
// 返回值为ModelAndView
@RequestMapping("/c2/hello2")
public ModelAndView useMAV(){System.out.println("返回值类型为ModelAndView");// 1.创建ModelAndView对象ModelAndView modelAndView = new ModelAndView();// 2.获取Model对象,本质是一个MapMap<String, Object> model = modelAndView.getModel();// 3.使用Model对象向request域设置数据model.put("name","jjy");// 4.使用View对象设置跳转的路径为/baizhan.jspmodelAndView.setViewName("jjy");return modelAndView;
}
- 编写jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<html><head><title>中北</title></head><body><h1>你好!${requestScope.name}</h1></body>
</html>
- 修改web.xml命名空间,让jsp页面默认支持el表达式
<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/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"version="3.1">
</web-app>
向request域设置数据
当控制器返回值为ModelAndView时我们可以向request域设置数据,我们还有以下方法可以向request域设置数据:
使用原生的HttpServletRequest
@RequestMapping("/c2/hello3")
public String setRequestModel(HttpServletRequest request){request.setAttribute("name","中北");return "baizhan";
}
使用Model、ModelMap
SpringMVC提供了Model接口和ModelMap类,控制器方法添加这两个类型的参数,使用该参数设置数据,该数据就会存到request域中。
@RequestMapping("/c2/hello4")
public String setRequestModel2(Model model, ModelMap modelMap){// model.addAttribute("name","中北2");modelMap.addAttribute("name","中北3");return "baizhan";
}
使用Map集合
Model接口底层就是一个Map集合,我们可以给控制器方法设置Map类型的参数,向Map中添加键值对,数据也会存到request域中。
@RequestMapping("/c2/hello5")
public String setRequestModel3(Map map){map.put("name","中北4");return "baizhan";
}
向session域设置数据
Session作用域表示在当前会话中有效。我们可以在控制器方法的参数中使用HttpSession对象来向session域设置数据。
编写控制器方法
@RequestMapping("/c2/hello6")
public String setSeesionModel(HttpSession session){session.setAttribute("address","北京");return "baizhan";
}
编写jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<html><head><title>中北</title></head><body><h1>你好!${requestScope.name}</h1><h1>地址是!${sessionScope.address}</h1></body>
</html>
除了使用Servlet原生对象,我们也可以使用SpringMVC提供的注解向session域设置数据,后面我们会讲到。
向context域设置数据
context作用域表示在整个应用范围都有效。在SpringMVC中对context作用域传值,可以使用ServletContext对象来实现。但是该对象不能直接注入到方法参数中,需要通过HttpSession对象获取。
- 编写控制器方法
@RequestMapping("/c2/hello7")
public String setContextModel(HttpSession session){ServletContext servletContext = session.getServletContext();servletContext.setAttribute("age",10);return "baizhan";
}
2.编写jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<html><head><title>中北</title></head><body><h1>你好!${requestScope.name}</h1><h1>地址是!${sessionScope.address}</h1><h1>年纪是!${applicationScope.age}</h1></body>
</html>
请求转发&重定向
之前的案例,我们发现request域中的值可以传到jsp页面中,也就是通过视图解析器跳转到视图的底层是请求转发。
如果我们跳转时不想使用视图解析器,可以使用原生HttpServletRequest进行请求转发或HttpServletResponse进行重定向:
@RequestMapping("/c2/hello8")
public void myForward1(HttpServletRequest request, HttpServletResponse response) throws Exception{request.setAttribute("name","中北");// 请求转发// request.getRequestDispatcher("/c2/hello9").forward(request,response);// 原生重定向response.sendRedirect("/c2/hello9");
}@RequestMapping("/c2/hello9")
public void myForward2(HttpServletRequest request){System.out.println("hello");System.out.println(request.getAttribute("name"));
}
SpringMVC还提供了一种更简单的请求转发和重定向的写法:
@RequestMapping("/c2/hello10")
public String myForward3(HttpServletRequest request){request.setAttribute("name","中北");// 请求转发return "forward:/c2/hello9";// 重定向// return "redirect:/c2/hello9";
}
SpringMVC注解
@Controlle r和 @RequestMapping
@Controller
作用:标记控制器,将控制器交给Spring容器管理。
位置:类上方
@RequestMapping
作用:给控制器方法设置请求路径
位置:方法或类上方。用于类上,表示类中的所有控制器方法都是以该地址作为父路径。
属性:
- value/path:请求路径
- method:指定请求方式
- params:规定必须发送的请求参数
- headers:规定请求必须包含的请求头
@Controller
@RequestMapping("/c3")
public class MyController3 {/*** 访问路径是 /c3/a1* 支持post和get请求* 请求必须带有username参数* 请求必须带有User-agen请求头**/@RequestMapping(path = "/a1",method = {RequestMethod.POST, RequestMethod.GET},params = {"username"},headers = {"User-agent"})public String annotation1(String username) {System.out.println(username);return "baizhan";}
}
@RequestParam
作用:在控制器方法中封装请求参数
位置:方法参数前
属性:
- value/name:指定请求参数名称
- defaultValue: 为参数设置默认值
- required:设置是否是必须要传入的参数
/*定义请求的参数名为name,默认值为sxt,不是必须的参数*/
@RequestMapping("/a2")
public String annotation2(@RequestParam(name = "name",defaultValue = "sxt",required = false) String username) {System.out.println(username);return "baizhan";
}
请求URL的写法:http://localhost:8080/c3/a2?name=bz
@RequestHeader、@CookieValue
@RequestHeader
作用:将请求头数据封装到控制器方法参数中
位置:方法参数前
@CookieValue
作用:将Cookie数据封装到控制器方法参数中
位置:方法参数前
/*获取User-Agent请求头获取JSESSIONID的Cookie值*/
@RequestMapping("/annotation3")
public String annotation3(@RequestHeader("User-Agent") String userAgent, @CookieValue("JSESSIONID") String jSessionId){System.out.println(userAgent);System.out.println(jSessionId);return "baizhan";
}
@SessionAttributes
作用:将Model模型中的数据存到session域中
位置:类上方
@Controller
@RequestMapping("/c4")
// 将模型中的name数据保存到session中
@SessionAttributes("name")
public class MyController4 {@RequestMapping("/t1")public String t1(Model model){// model中保存name数据model.addAttribute("name","中北1111");return "baizhan";}@RequestMapping("/t2")public String t2(HttpSession session){// 从session中获取name数据Object name = session.getAttribute("name");System.out.println(name);return "baizhan";}
}
@ModelAttribute
作用1:设置指定方法在控制器其他方法前执行
位置:
方法上方
@Controller
@RequestMapping("/c5")
public class MyController5 {@ModelAttributepublic void before(){System.out.println("前置方法");}@RequestMapping("/t1")public Stringt1(){System.out.println("t1");return "zb";}
}
作用2: 从Model模型中获取数据给参数赋值
位置:方法参数前
@Controller
@RequestMapping("/c6")
public class MyController6 {//前置方法向Model中设置数据@ModelAttributepublic void before(Model model){model.addAttribute("name","中北");}//该参数不是从请求中获取,而是从Model中获取@RequestMapping("/t1")public String t1(@ModelAttribute("name") String name){System.out.println(name);return "baizhan";}
}
RESTful风格支持
RESTful风格是一种URL路径的设计风格。在RESTful风格的URL路径中,网络上的任意数据都可以看成一个资源,它可以是一段文本、一张图片,也可以是一个Java对象。而每个资源都会占据一个网络路径,无论对该资源进行增删改查,访问的路径是一致的。
传统URL:
- 查找id为1的学生:
http://localhost:8080/student/findById?id=30
- 删除id为1的学生:
http://localhost:8080/student/deleteById?id=30
RESTful风格URL:
- 查找id为30的学生:
http://localhost:8080/student/30
- 删除id为30的学生:
http://localhost:8080/student/30
那么如何区分对该资源是哪一种操作?通过请求方式不同,判断进行的是什么操作。
之前我们学过两种请求方式,GET请求和POST请求,而访问RESTful风格的URL一共有四种请求方式:
- GET请求:查询操作
- POST请求:新增操作
- DELETE请求:删除操作
- PUT请求:修改操作
RESTful风格URL:
- 查找id为30的学生:
http://localhost:8080/student/30 GET方式请求
- 删除id为30的学生:
http://localhost:8080/student/30 DELETE方式请求
RESTful风格的优点:
结构清晰、符合标准、易于理解、扩展方便。
@PathVariable
作用:在RESTful风格的URL中获取占位符的值
位置:方法参数前
属性:
value:获取哪个占位符的值作为参数值,如果占位符和参数名相同,可以省略该属性。
@Controller
@RequestMapping("/student")
// 模拟学生的增删改查控制器
public class MyController6 {// 路径中的/{id}表示占位符,最后会封装到方法的参数中使用// 删除学生@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)public String deleteStudent(@PathVariable("id") int id){System.out.println("删除id为"+id+"的学生");return "baizhan";}// 如果占位符和参数名相同,可以省略@PathVariable中的value属性// 根据id查询学生@RequestMapping(value = "/{id}",method = RequestMethod.GET)public String findStudentById(@PathVariable int id){System.out.println("查找id为"+id+"的学生");return "baizhan";}// 新增学生@RequestMapping(value = "/{id}",method = RequestMethod.POST)public String addStudent(@PathVariable int id, Student student){System.out.println("新增id为"+id+"的学生");System.out.println(student);return "baizhan";}// 修改学生@RequestMapping(value = "/{id}",method = RequestMethod.PUT)public String updateStudent(@PathVariable int id, Student student){System.out.println("修改id为"+id+"的学生");System.out.println(student);return "baizhan";}
}
访问方式:
- 新增学生:POST localhost:8080/student/100?name=百战&sex=女
- 修改学生:PUT localhost:8080/student/100?name=百战1&sex=女1
- 删除学生:DELETE localhost:8080/student/100
- 查询学生:GET localhost:8080/student/100
@PostMapping、@GetMapping、@PutMapping、@DeleteMapping
作用:简化设置请求方式的@RequestMapping写法
位置:方法上方。
@Controller
@RequestMapping("/student")
public class StudentController {// 删除学生@DeleteMapping("/{id}")public String deleteStudent(@PathVariable("id") int id){System.out.println("删除id为"+id+"的学生");return "baizhan";}// 根据id查询学生@GetMapping("/{id}")public String findStudentById(@PathVariable int id){System.out.println(id);System.out.println("根据id查询学生");return "baizhan";}// 新增学生@PostMapping("/{id}")public String addStudent(@PathVariable int id, Student student){System.out.println(id);System.out.println(student);System.out.println("新增学生");return "baizhan";}// 修改学生@PutMapping("/{id}")public String updateStudent(@PathVariable int id, Student student){System.out.println(id);System.out.println(student);System.out.println("修改学生");return "baizhan";}
}
HiddenHttpMethodFilter
由于浏览器的表单只支持GET与POST请求,不支持DELETE、PUT请求。所以SpringMVC提供了一个过滤器,可以将浏览器的POST请求改为指定的请求方式,发送给控制器的方法。
用法如下:
- 在web.xml中配置过滤器
<!-- 请求方式过滤器 -->
<filter><filter-name>httpMethodFilter</filter-name><filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping><filter-name>httpMethodFilter</filter-name><url-pattern>/*</url-pattern>
</filter-mapping>
- 编写控制器方法
@Controller
@RequestMapping("/c7")
public class MyController7 {@DeleteMapping("/delete")public String testDelete(){System.out.println("删除方法");return "baizhan";}@PutMapping("/put")public String testPut(){System.out.println("修改方法");return "baizhan";}
}
- 在jsp中编写表单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>DELETE、PUT提交</title></head><body><!-- 删除 --><%-- 提交DELETE、PUT请求,表单必须提交方式为post --%><%-- 表单中有一个隐藏域,name值为_method,value值为提交方式 --%><form action="/c7/delete" method="post"><input type="hidden" name="_method" value="DELETE"><input type="submit" value="删除"></form><hr/><!-- 修改 --><form action="/c7/put" method="post"><input type="hidden" name="_method" value="PUT"><input type="submit" value="修改"></form></body>
</html>
@ResponseBody
作用:方法返回的对象转换为JSON格式,并将JSON数据直接写入到输出流中,使用此注解后不会再经过视图解析器。使用该注解可以处理Ajax请求。
位置:方法上方或方法返回值前
- 编写jsp页面,发送ajax请求
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>ajax请求</title><script src="/js/jquery-2.1.1.min.js"></script><script>$(function () {$("#btn").click(function () {var name = $("#name").val();var sex = $("#sex").val();$.get("/c8/addStudent",{"name":name,"sex":sex},function (data){console.log(data);});});});</script></head><body>姓名:<input id="name"/><br/>性别:<input id="sex"/><br/><input type="button" value="提交" id="btn"/></body>
</html>
- 由于jsp页面中引入jQuery的js文件,而SpringMVC会拦截所有资源,造成jquery.js失效,需要在SpringMVC核心配置文件中放行静态资源。
<!-- 放行静态资源 -->
<mvc:default-servlet-handler />
- 编写结果实体类,该实体类会封装一个请求的结果
// 请求的结果对象
public class Result { private boolean flag; // 请求是否成功 private String message; // 请求提示信息 // 省略getter/setter/构造方法
}
- 编写控制器
@Controller
@RequestMapping("/c8")
public class MyController8 {@GetMapping("/addStudent")@ResponseBodypublic Result addStudent(String name,String sex){System.out.println(name+":"+sex);Result result = new Result(true, "添加学生成功");return result;}
}
- SpringMVC会将Result对象转为JSON格式写入输出流,而SpringMVC默认使用的JSON转换器是jackson,需要在pom中添加jackson依赖。
<!-- jackson -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.9.0</version>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.0</version>
</dependency>
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.9.0</version>
</dependency>
@RestController
如果一个控制器类下的所有控制器方法都返回JSON格式数据且不进行跳转,可以使用@RestController代替@Controller,此时每个方法上的@ResponseBody都可以省略。
@RestController
@RequestMapping("/c8")
public class MyController8 { @GetMapping("/addStudent") public Result addStudent(String name, String sex) { // 输出接受到的参数,模拟添加学生 System.out.println(name+":"+sex); // 返回结果 Result result = new Result(true, "添加学生成功!"); return result; }
}
静态资源映射
当在DispatcherServlet的<url-pattern
>中配置拦截 “/” 时,除了jsp文件不会拦截以外,其他所有的请求都会经过前端控制器进行匹配。此时静态资源例如css、js、jpg等就会被前端控制器拦截,导致不能访问,出现404问题。想要正常映射静态资源共有三种方案:
配置静态资源筛查器
在SpringMVC的配置文件中配置<mvc:default-servlet-handler />
后,会在Spring容器中创建一个资源检查器,它对进入DispatcherServlet的URL进行筛查,如果不是静态资源,才由DispatcherServlet处理。
修改SpringMVC核心配置文件:
<mvc:default-servlet-handler/>
配置静态资源映射器
SpringMVC模块提供了静态资源映射器组件,通过<mvc:resources>
标签配置静态资源映射器,配置后的路径不会由DispatcherServlet处理。
修改SpringMVC核心配置文件:
<!--配置静态资源映射器-->
<!-- mapping:配置请求的URL location:资源路径-->
<mvc:resources mapping="/img/" location="/img/"/>
<mvc:resources mapping="/js/" location="/js/"/>
配置默认Servlet处理静态资源
在web.xml可以配置默认Servlet处理静态资源,该Servlet由tomcat提供,它会直接访问静态资源不进行其他操作。这样就避免了使用DispatcherServlet对静态资源的拦截:
修改web.xml:
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.jpg</url-pattern>
</servlet-mapping>
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern>
</servlet-mapping>
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.js</url-pattern>
</servlet-mapping>
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.png</url-pattern>
</servlet-mapping>
@RequestBody
作用:将请求中JSON格式的参数转为JAVA对象
位置:写在方法参数前
- AJAX请求发送JSON格式的参数
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>ajax请求</title><script src="/js/jquery-2.1.1.min.js"></script><script>$(function (){$("#btn").click(function (){var name=$("#name").val();var sex=$("#sex").val();var param = JSON.stringify({"name":name,"sex":sex});$.ajax({url:"/c8/addStudent2",contentType:"application/json",type:"post",data:param,success:function (data){console.log(data);}})})})</script></head><br>姓名:<input id="name"><br />性别:<input id="sex"><br /><input type="button" value="提交" id="btn"></body>
</html>
- 编写控制器
@PostMapping("/addStudent2")
@ResponseBody
public Result addStudent2(@RequestBody Student student) {System.out.println(student);// 返回添加结果Result result = new Result(true, "添加学生成功!");return result;
}
SpringMVC上传
:
原生方式上传
上传是Web工程中很常见的功能,SpringMVC框架简化了文件上传的代码,我们首先使用JAVAEE原生方式上传文件:
- 编写上传表单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>上传</title></head><body><h3>文件上传</h3><%-- 上传表单的提交方式必须是post --%><%-- enctype属性为multipart/form-data,意思是不对表单数据进行编码 --%><form action="/fileUpload" method="post" enctype="multipart/form-data"><%-- 文件上传控件,类型是file,必须要有name属性--%>选择文件:<input type="file" name="upload"/><input type="submit" value="上传"/></form></body>
</html>
- 接收请求体数据:
@RequestMapping("/fileUpload")
public String upload(HttpServletRequest request) throws Exception {// 获取输入流ServletInputStream is = request.getInputStream();// 从输入流获取请求体数据int i = 0;while ((i=is.read())!=-1){System.out.print((char)i);}return "baizhan";
}
接下来需要分析请求体中的文件项,并将数据写入磁盘,此时需要借助文件上传工具
- 引入文件上传依赖:
<!--文件上传-->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-fileupload2-jakarta</artifactId><version>2.0.0-M1</version>
</dependency>
- 编写控制器接收上传请求,控制器进行三步操作:
-
创建文件夹,存放上传文件。
-
分析请求体,找到上传文件数据。
-
将文件数据写入文件夹。
@RequestMapping("/fileUpload")
public String upload(HttpServletRequest request) throws Exception {// 创建文件夹,存放上传文件// 1.设置上传文件夹路径String realPath = "E:\\springmvc\\mvc_demo1\\upload";// 2.判断该目录是否存在,如果不存在,创建该目录File file = new File(realPath);if (!file.exists()){file.mkdirs();}// 分析请求体,找到上传文件数据// 1.创建磁盘文件工厂DiskFileItemFactory factory = DiskFileItemFactory.builder().get();// 2.创建上传数据分析器对象JakartaServletDiskFileUpload upload = new JakartaServletDiskFileUpload(factory);// 3.利用分析器对象解析请求体,返回所有数据项List<DiskFileItem> fileItems = upload.parseRequest(request);// 4.遍历所有数据,找到文件项(非表单项)for (FileItem fileItem : fileItems) {if (!fileItem.isFormField()){// 将文件数据写入文件夹// 1.获取文件名String name = fileItem.getName();// 2.将文件写入磁盘fileItem.write(Paths.get(realPath,name));// 3.删除内存中的临时文件fileItem.delete();}}return "baizhan";
}
SpringMVC方式上传
SpringMVC使用框架提供的文件解析器对象,可以直接将请求体中的文件数据转为MultipartFile对象,从而省略原生上传中分析请求体的步骤。
- 在SpringMVC核心配置文件配置文件解析器
<!-- 文件解析器对象 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"></bean>
- 在web.xml中进行上传配置
<!--SpringMVC前端控制器,本质是一个Servlet,接收所有请求,在容器启动时就会加载-->
<servlet><servlet-name>dispatcherServlet</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><multipart-config><!-- 支持一次上传文件的总容量。单位:字节 100M = 100*1024*1024--><max-file-size>104857600</max-file-size><!-- 支持一次请求上传文件的总容量--><max-request-size>418018841</max-request-size><!-- 文件最小容量 --><file-size-threshold>0</file-size-threshold></multipart-config>
</servlet>
- 创建JSP表单
<form action="/fileUpload2" method="post" enctype="multipart/form-data"><input type="file" name="file"/><input type="submit" value="上传"/>
</form>
- 编写控制器接收上传请求
// MultipartFile参数名必须和JSP文件空间的name属性一致
@RequestMapping("/fileUpload2")
public String upload2(MultipartFile file,HttpServletRequest request) throws IOException {// 创建文件夹,存放上传文件String realPath = "E:\\springmvc\\mvc_demo1\\upload";File dir = new File(realPath);if (!dir.exists()){dir.mkdirs();}// 将上传的数据写到文件夹的文件中// 1.拿到上传的文件名String filename = file.getOriginalFilename();filename = UUID.randomUUID()+"_"+filename;// 2.创建空文件File newFile = new File(dir,filename);// 3.将数据写入空文件中file.transferTo(newFile);return "baizhan";
}
上传多文件
SpringMVC支持一次性上传多个文件,写法如下:
- 创建JSP表单
<form action="/fileUpload3" method="post" enctype="multipart/form-data">用户名:<input name="username"/>文件1:<input type="file" name="files"/>文件2:<input type="file" name="files"/><input type="submit" value="上传"/>
</form>
- 编写控制器接收上传请求
// 处理多文件上传,参数类型为MultipartFile数组,参数名和JSP文件控件的name属性一致
@RequestMapping("/fileUpload3")
public String upload3(MultipartFile files[],String username,HttpServletRequest request) throws Exception {System.out.println(username);//1.设置上传文件保存的文件夹String realPath = "E:\\springmvc\\mvc_demo1\\upload";File dir = new File(realPath);if (!dir.exists()){dir.mkdirs();}//2.遍历数组,将上传文件保存到文件夹for(MultipartFile file:files){String filename = file.getOriginalFilename();filename = UUID.randomUUID()+"_"+filename;File newFile = new File(dir, filename);file.transferTo(newFile);}return "baizhan";
}
异步上传
之前的上传方案,在上传成功后都会跳转页面。而在实际开发中,很多情况下上传后不进行跳转,而是进行页面的局部刷新,比如:上传头像成功后将头像显示在网页中。这时候就需要使用异步文件上传。
- 编写JSP页面,引入jQuery和jQuery表单上传工具jquery.form.js
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head><title>上传</title><script src="/js/jquery-2.1.1.min.js"></script><script src="/js/jquery.form.js"></script>
</head>
<body>
<h3>文件上传</h3>
<form id="ajaxForm" enctype="multipart/form-data" ><input type="file" name="file"/><%-- 按钮类型不能是submit,否则会刷新页面 --%><input type="button" value="上传头像" id="btn"/>
</form>
<%-- 上传头像后展示的位置 --%>
<img src="/" width="100" id="img">
<script>$(function () {$("#btn").click(function () {// 异步提交表单$("#ajaxForm").ajaxSubmit({url:"/fileUpload4",type:"post",success:function (data) {$("#img").attr("src",data);}})})})
</script>
</body>
</html>
- 编写控制器接收异步上传请求
@RequestMapping("/fileUpload4")
@ResponseBody
public String upload4(MultipartFile file,HttpServletRequest request) throws Exception {// 创建文件夹,存放上传文件String realPath = request.getServletContext().getRealPath("/upload");File dir = new File(realPath);if (!dir.exists()){dir.mkdirs();}// 将上传的数据写到文件夹的文件中String filename = file.getOriginalFilename();filename = UUID.randomUUID()+"_"+filename;File newFile = new File(dir, filename);file.transferTo(newFile);// 返回文件的路径return "/upload/"+filename;
}
跨服务器上传
由于文件占据磁盘空间较大,在实际开发中往往会将文件上传到其他服务器中,此时需要使用跨服务器上传文件。
-
解压tomcat作为图片服务器,在tomcat的webapps下创建upload目录作为文件上传目录。
-
修改tomcat的conf/web.xml文件,支持跨服上传。
<servlet> <init-param> <param-name>readonly</param-name> <param-value>false</param-value> </init-param>
</servlet>
- 修改tomcat的conf/server.xml文件,修改tomcat端口,修改完开启tomcat服务器
<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
- 编写JSP上传表单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>上传</title><script src="/js/jquery-2.1.1.min.js"></script><script src="/js/jquery.form.js"></script></head><body><h3>文件上传</h3><form id="ajaxForm" enctype="multipart/form-data" ><input type="file" name="file"/><input type="button" value="上传头像" id="btn"/></form><img src="/" width="100" id="img"><script>$(function () {$("#btn").click(function () {$("#ajaxForm").ajaxSubmit({url:"/fileUpload5",type:"post",success:function (data) {$("#img").attr("src",data);}})})})</script></body>
</html>
- 添加跨服上传依赖
<!-- 跨服上传 -->
<dependency><groupId>com.sun.jersey</groupId><artifactId>jersey-core</artifactId><version>1.18.1</version>
</dependency>
<dependency><groupId>com.sun.jersey</groupId><artifactId>jersey-client</artifactId><version>1.18.1</version>
</dependency>
- 创建控制器方法,该方法在接受到上传请求后将文件保存到其他服务器上。
@RequestMapping("/fileUpload5")
@ResponseBody
public String upload4(MultipartFile file) throws Exception {// 设置跨服上传的服务器路径String path = "http://localhost:8081/upload/";// 获取上传的文件名String filename = file.getOriginalFilename();filename = UUID.randomUUID()+"_"+filename;// 跨服上传:// 1.创建客户端对象Client client = Client.create();// 2.使用客户端对象连接图片服务器WebResource resource = client.resource(path + filename);//3.传输数据resource.put(file.getBytes());return path+filename;
}
查询可下载的文件
将文件上传到服务器后,有时我们需要让用户下载上传的文件,下载分为两步,第一步查询可以下载的文件,第二步选中文件进行下载。首先我们编写查询所有可下载文件的功能:
- 编写控制器方法,查询所有可下载的文件,并跳转到下载页面
// 查询可下载的文件
@RequestMapping("/showFiles")
public String showFileDown(Model model){// 1.获取下载文件的路径集合。注:跨服务器上传中,网络路径无法获取文件列表。String path = "E:\\springmvc\\mvc_demo1\\upload";File file = new File(path);String[] files = file.list();// 2.将路径放入Model中,跳转到JSP页面model.addAttribute("files",files);return "download";
}
- 添加JSTL依赖
<!-- JSTL -->
<dependency><groupId>jakarta.servlet.jsp.jstl</groupId><artifactId>jakarta.servlet.jsp.jstl-api</artifactId><version>3.0.0</version>
</dependency>
<dependency><groupId>org.glassfish.web</groupId><artifactId>jakarta.servlet.jsp.jstl</artifactId><version>2.0.0</version>
</dependency>
- 编写下载页面
<%@ 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><h3>文件下载</h3><%-- 遍历文件集合 --%><c:forEach items="${files}" var="file"><a href="/download?fileName=${file}">${file}</a><br/></c:forEach></body>
</html>
下载文件
// 文件下载
@RequestMapping("/download")
public void fileDown(HttpServletResponse response,String fileName) throws IOException {// 设置响应头response.setHeader("Content-Disposition","attachment;filename="+fileName);// 获取文件路径String path = "E:\\springmvc\\mvc_demo1\\upload";File file = new File(path,fileName);// 获取字节输出流ServletOutputStream os = response.getOutputStream();// 使用输出流写出文件os.write(FileUtils.readFileToByteArray(file));os.flush();os.close();
}
SpringMVC异常处理
单个控制器异常处理
在开发过程中, Dao、Service、Controller层代码出现都可能抛出异常。如果哪里产生异常就在哪里处理,则会降低开发效率。所以一般情况下我们会让异常向上抛出,最终到达DispatcherServlet中,此时SpringMVC提供了异常处理器进行异常处理,这样可以提高开发效率。
- 处理单个Controller的异常:
@Controller
@RequestMapping("/c9")
public class MyController9 {@RequestMapping("/t1")public String t1(){String str = null;str.length();return "index";}@RequestMapping("/t2")public String t2(){int i = 1/0;return "index";}@RequestMapping("/t3")public String t3(){int[] arr = new int[1];arr[2] = 10;return "index";}/*** 异常处理方法* @param ex 异常对象* @param model 模型对象* @return*/// 添加@ExceptionHandler,表示该方法是处理异常的方法,属性为处理的异常类@ExceptionHandler({java.lang.NullPointerException.class,java.lang.ArithmeticException.class})public String exceptionHandler1(Exception ex, Model model){// 向模型中添加异常对象model.addAttribute("msg",ex);// 跳转到异常页面return "error";}// 方法1不能处理的交给方法2处理,不同的处理策略@ExceptionHandler(java.lang.Exception.class)public String exceptionHandler2(Exception ex, Model model){// 向模型中添加异常对象model.addAttribute("msg",ex);// 跳转到异常页面return "error2";}
}
- 异常页面error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>出错了!</title></head><body><h3>ERROR 发生异常!${msg}</h3></body>
</html>
异常页面error2.jsp<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>出错了!</title></head><body><h3>ERROR2 发生严重异常!${msg}</h3></body>
</html>
全局异常处理
在控制器中定义异常处理方法只能处理该控制器类的异常,要想处理所有控制器的异常,需要定义全局异常处理类。
- 编写另一个有异常的控制器类
@Controller
@RequestMapping("/c10")
public class MyController10 {@RequestMapping("/t1")public String t1(){String str = null;str.length();return "index";}
}
- 编写全局异常处理器类
// 全局异常处理器类,需要添加@ControllerAdvice
@ControllerAdvice
public class GlobalExceptionHandler {/*** 异常处理方法** @param ex 异常对象* @param model 模型对象* @return*/// 添加@ExceptionHandler,表示该方法是处理异常的方法,属性为处理的异常类@ExceptionHandler({java.lang.NullPointerException.class, java.lang.ArithmeticException.class})public String exceptionHandle1(Exception ex, Model model) {// 向模型中添加异常对象model.addAttribute("msg", ex);// 跳转到异常页面return "error";}// 方法一不能处理的异常交给方法二处理@ExceptionHandler(java.lang.Exception.class)public String exceptionHandle2(Exception ex, Model model) {model.addAttribute("msg", ex);return "error2";}
}
自定义异常处理器
以上方式都是使用的SpringMVC自带的异常处理器进行异常处理,我们还可以自定义异常处理器处理异常:
// 自定义异常处理器实现HandlerExceptionResolver接口,并放入Spring容器中
@Component
public class MyExceptionHandler implements HandlerExceptionResolver {@Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {ModelAndView modelAndView = new ModelAndView();if (ex instanceof NullPointerException){modelAndView.setViewName("error");}else{modelAndView.setViewName("error2");}modelAndView.addObject("msg",ex);return modelAndView;}
}
SpringMVC拦截器
SpringMVC的拦截器(Interceptor)也是AOP思想的一种实现方式。它与Servlet的过滤器(Filter)功能类似,主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。
拦截器和过滤器的区别
- 拦截器是SpringMVC组件,而过滤器是Servlet组件。
- 拦截器不依赖Web容器,过滤器依赖Web容器。
- 拦截器只能对控制器请求起作用,而过滤器则可以对所有的请求起作用。
- 拦截器可以直接获取IOC容器中的对象,而过滤器就不太方便获取。
拦截器的使用
接下来我们使用SpringMVC拦截器
- 创建被拦截的方法
@RequestMapping("/m1")
public String m1(){System.out.println("控制器方法");return "result";
}
- 创建拦截器类,该类实现HandlerInterceptor接口,需要重写三个方法:
- preHandle:请求到达Controller前执行的方法,返回值为true通过拦截器,返回值为false被拦截器拦截。
- postHandle:跳转到JSP前执行的方法
- afterCompletion:跳转到JSP后执行的方法
// 拦截器类
public class MyInterceptor implements HandlerInterceptor {// 请求到达Controller前执行@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {System.out.println("请求到达Controller前");// 如果return false则无法到达Controllerreturn true;}// 跳转到JSP前执行,此时可以向Request域添加数据@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {System.out.println("跳转到JSP前");request.setAttribute("name","zb");}// 跳转到JSP后执行,此时已经不能向Request域添加数据@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {System.out.println("跳转到JSP后");request.setAttribute("age",10);}
}
- 编写JSP页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>结果</title></head><body><h3>name:${requestScope.name}</h3><h3>age:${requestScope.age}</h3></body>
</html>
- 在SpringMVC核心配置文件中配置拦截器
<!-- 配置拦截器-->
<mvc:interceptors> <mvc:interceptor> <!-- 配置拦截器的作用路径 /**表示拦截所有请求--> <mvc:mapping path="/**"/> <!-- 拦截器对象 --> <bean class="com.jjy.interceptor.MyInterceptor"/> </mvc:interceptor>
</mvc:interceptors>
全局拦截器
全局拦截器可以拦截所有控制器处理的URL,作用等于/**,配置方式如下:
<!-- 配置拦截器 -->
<mvc:interceptors> <!-- 全局拦截器 --> <bean class="com.jjy.interceptor.MyInterceptor"></bean>
</mvc:interceptors>
拦截器链与执行顺序
如果一个URL能够被多个拦截器所拦截,全局拦截器最先执行,其他拦截器根据配置文件中配置的从上到下执行,接下来我们再配置一个拦截器:
- 编写拦截器类
public class MyInterceptor2 implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {System.out.println("拦截器2:请求到达Controller前");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {System.out.println("拦截器2:跳转到JSP前");}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {System.out.println("拦截器2:跳转到JSP后");}
}
- 配置拦截器链
<!-- 配置拦截器 -->
<mvc:interceptors><!-- 拦截器1 --><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.jjy.interceptor.MyInterceptor"/></mvc:interceptor><!-- 拦截器2 --><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.jjy.interceptor.MyInterceptor2"/></mvc:interceptor>
</mvc:interceptors>
访问控制器方法后输出如下:
结论:
preHandle()顺序执行,postHandle()、afterComletion()逆序执行。
只要有一个preHandle()拦截,后面的preHandle(),postHandle()都不会执行。
只要相应的preHandle()放行,afterComletion()就会执行。
过滤敏感词
接下来我们编写一个拦截器案例,需求如下:
在系统中,我们需要将所有响应中的一些敏感词替换为***,此时可以使用拦截器达到要求:
- 编写控制器方法
@RequestMapping("/t3")
public String t3(Model model){model.addAttribute("name","大笨蛋");return "result";
}
- 编写敏感词拦截器
// 敏感词拦截器
public class SensitiveWordInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {// 敏感词列表String[] sensitiveWords = {"坏人","暴力","笨蛋"};// 获取Model中所有数据Map<String, Object> model = modelAndView.getModel();Set<Map.Entry<String, Object>> entries = model.entrySet();// 遍历Model,将Model值和敏感词列表遍历比对,如果Model值包含敏感词,则替换for (Map.Entry<String, Object> entry : entries) {String key = entry.getKey();String value = entry.getValue().toString();for (String sensitiveWord : sensitiveWords) {if (value.contains(sensitiveWord)){String newStr = value.replace(sensitiveWord, "***");model.put(key,newStr);}}}}
}
- 配置拦截器
<!-- 配置拦截器-->
<mvc:interceptors><!-- 敏感词拦截器 --><mvc:interceptor><mvc:mapping path="/**"/><bean class="com.jjy.interceptor.SensitiveWordInterceptor"></bean></mvc:interceptor>
</mvc:interceptors>
SpringMVC跨域请求
同源策略
同源策略是浏览器的一个安全功能。同源,指的是两个URL的协议,域名,端口相同。浏览器出于安全方面的考虑,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。
哪些不受同源策略限制:
- 页面中的
<a>
跳转、表单提交不会受到同源策略限制的。 - 静态资源引入也不会受到同源策略限制。如嵌入到页面中的
<script src="">,<img src="">,<link href="">
等。
最容易受到同源策略影响的就是Ajax请求。
跨域请求
当请求URL的协议、域名、端口三者中任意一个与当前页面URL不同时即为跨域。浏览器执行JavaScript脚本时,会检查当前请求是否同源,如果不是同源资源,就不会被执行。
当前页面URL | 被请求URL | 是否跨域 | 原因 |
---|---|---|---|
http://www.baidu.com/ | http://www.baidu.com/index.html | 否 | |
http://www.baidu.com/ | https://www.baidu.com/index.html | 跨域 | 协议不同 |
http://www.zb.com/ | http://www.baidu.com/ | 跨域 | 主域名不同 |
http://www.baidu.com/ | http://zb.baidu.com/ | 跨域 | 子域名不同 |
http://www.baidu.com:8080/ | http://www.baidu.com:7001/ | 跨域 | 端口号不同 |
比如:
- 编写控制器方法
@RequestMapping("/t1")
@ResponseBody
public String t1(){System.out.println("测试跨域请求");return "success";
}
- 编写JSP页面,发送异步请求
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head><title>跨域请求</title><script src="/js/jquery-2.1.1.min.js"></script><script>$(function (){$("#btn").click(function () {// $.get("http://localhost:8080/c12/t1",function (data) {// console.log(data);// })$.get("http://127.0.0.1:8080/c12/t1",function (data) {console.log(data);})})})</script></head><body><button id="btn">异步请求</button></body>
</html>
结果:
当浏览器通过http://localhost:8080/cross.jsp访问JSP页面时
- http://localhost:8080/c12/t1 能发送异步请求;
- http://127.0.0.1:8080/c12/t1 由于同源无法发送异步请求,报以下异常:
接收跨域请求
SpringMVC提供了注解@CrossOrigin解决跨域问题。用法如下:
@RequestMapping("/t1")
@ResponseBody
// 如果请求从http://localhost:8080发出,则允许跨域访问
@CrossOrigin("http://localhost:8080")
public String t1(){System.out.println("测试跨域请求");return "success";
}
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力