SpringMVC
大家好呀,我是小笙,今天我来分享一下我的SpringMVC的学习笔记!
Spring Web MVC
概述
SpringMVC 是 WEB 层框架 [SpringMVC 接管了 Web 层组件, 比如控制器, 视图, 视图解析, 返回给用户的数据格式, 同时支持 MVC 的开发模式/开发架构]
SpringMVC 采用低耦合的组件设计方式,具有更好扩展和灵活性
支持 REST 统一格式的 URL 请求
SpringMVC 是基于 Spring 框架进行开发的,核心组件如下
SpringMVC 执行流程
基本注解
@RequestMapping
概念:可以用来修饰类和方法(用来指定访问的 url,修饰类就类似同个类下访问方法的共同路径,修饰方法就是访问该方法的特有路径)
举例加深理解
@Controller
@RequestMapping(value = "/user") // 修饰类
public class UserServlet {
// 访问路径: http://ip:port/工程路径/user/login
@RequestMapping(value = "/login") // 修饰方法
public String login(){
System.out.println("login ok....");
return "login_ok";
}
// 访问路径: http://ip:port/工程路径/user/bug
@RequestMapping(value = "/bug") // 修饰方法
public String bug(){
System.out.println("bug ok....");
return "bug_ok";
}
}
注解源码分析
主要讲解一下 path、method、params
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
String name() default "";
@AliasFor("path")
String[] value() default {};
// 上文的路径指定,以下为路径匹配通配符(注意路径不能重复)
// ?:匹配文件中的一个字符
// *:匹配文件名中的任意字符
// **:匹配多层路径
@AliasFor("value")
String[] path() default {};
// 指定范围的方法: GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE; (默认支持 GET,POST)
// 注意:我们也可以通过注解就指定是什么请求形式,如下
// @GetMappping、@PostMapping、@PutMapping、@DeleteMapping等等
RequestMethod[] method() default {};
// 表示请求必须包含名为"xx"的请求参数,但是值不做限定,格式如:params="xxx" 或者 params = {"xxx","yyy"}
// params="xxx=100" 表示必须给一个 xxx 参数,并且值必须为100,不然报 400 错误
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
@RequestHeader
概述:获取请求头信息比如:host、accept-encoding等等
@Controller
public class UserServlet {
@GetMapping(value = "/test")
// 不需要请求携带数据
public String test(@RequestHeader("host") String host){
System.out.println("host:" + host);
return "ok";
}
}
@PathVariable
概念:路径变量,就是在传 url 的时候把值也顺便传进来了
通过例子对比一下就清楚了
// 不使用该注解
// 请求url: http://localhost:8080/test?bookId=100
@RequestMapping(value = "/test")
public String book(int bookId){
System.out.println("书本id:" + bookId);
return "ok";
}
// 使用该注解 @PathVariable
// 请求url: http://localhost:8080/test/100
@RequestMapping(value = "/test/{bookId}") // 通过 url 传入值
public String book(@PathVariable int bookId){
System.out.println("书本id:" + bookId);
return "ok";
}
@RequestParam
概述:可以用来指定某形参的接收参数名
@Controller
public class UserServlet {
/**
* @RequestParam 指定接收的参数名
* 不添加注解 url: http://localhost:8080/web工程路径/test?param="steven"
* 添加注解 url:http://localhost:8080/web工程路径/test?par="steven"
* required 指的是该形参是否是必须的
*/
@GetMapping(value = "/test")
public String test(@RequestParam(value = "par",required = false) String param){
System.out.println(param);
return "ok";
}
}
@ModeAttribute
概述:前置处理,调用其他 Handler 的时候,都会先调用这个方法
@ModelAttribute
public void beforeHandler(){
System.out.println("前置处理注解");
}
@DateTimeFormat
概述:控制日期格式,标注在字段上
@DateTimeFormat(pattren="yyyy-MM-dd")
private Date birthday;
JSR 303 提供的基本验证注解
<!-- 需要引入的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
1.进行数据转换/格式化,将错误的信息放入到 BindingResult 中
2.进行数据校验(判空以及判 null),将错误的信息放入到 BindingResult 中
注意:检验的时候需要在控制层对应方法形参前添加注解 @Valid
@Responsebody
概述:表示目标方法返回的数据格式为 json 格式
@Controller
class TestJsonController{
@RequestMapping(value = "/json")
@Responsebody
public Dog TestJson(){
// 返回的为 json 格式的对象
return Dog对象;
}
}
@RequestBody
概述:前端以 json 格式的数据请求后端的时候,将他封装成对象的 javaBean 对象中
@Controller
class TestJsonController{
@RequestMapping(value = "/json2")
@Responsebody
public User TestJson(@RequestBody User user){
return Dog对象;
}
}
注意:@RequestBody、@Responsebody 底层数据转换流程图
接收请求过程
请求方式 Rest
概述
概述:常用请求方式(GET、POST、PUT、DELETE) 分别对应 crud 的操作
但是由于浏览器发送的请求只支持 POST、GET 请求,所以我们需要添加 Spring 的 HiddenHttpMethodFilter 过滤器进行转换
注意:只能把浏览器发送的 POST 的请求转换成对应Spring 的 PUT、DELETE 请求
<!--
配置 HiddenHttpMethodFilter 过滤器
作用:把浏览器发送的 POST 的请求转换成对应 Spring 的 PUT、DELETE 请求
注意:是在 web.xml 配置过滤器
-->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
加入两个常规配置
1.能支持 SpringMVC 高级功能,比如 JSR303 校验,映射动态请求
2.将 SpringMVC 不能处理的请求交给 Tomcat,比如请求 css,js等
注意:在 spring.xml 配置信息,引入 mlns:mvc="http://www.springframework.org/schema/mvc"
-->
<mvc:annotation-driven/>
<mvc:default-servlet-handler/>
HiddenHttpMethodFilter 源码解析
// 如果想要发送 delete 或者 put 请求,则需要发送 post 请求并且携带 _method = delete 类似参数
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List<String> ALLOWED_METHODS;
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";
// 把浏览器发送的 POST 的请求转换成对应 Spring 的 PUT、DELETE 请求
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
// 类加载初始化方法含有的列表:PUT、DELETE、PATCH
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
}
接收 Java 对象
概述:就是将接收到的数据,赋值到对象的属性值上(注意对象的属性需要有 get 和 set 方法)并封装成对象(底层就是反射和注解)
例子如下
@Controller
public class UserServlet {
@GetMapping(value = "/test")
public String test(Obj obj){
System.out.println(obj);
return "ok";
}
}
// @Data 是 lombok 类中用来代替 get、set、toString方法的注解
@Data
class Obj {
private Integer id;
private String name;
// 如果属性是对象的话,请求的参数可以通过 obj2.id 的形式来传参(级联操作)
private Obj2 obj2;
}
@Data
class Obj2 {
private Integer id;
private String name;
}
注意:前端请求参数赋值到接收的对象的属性上时候,SpringMVC 机制里还实现了将该对象放入到 request 请求域中,可以将这些数据带给需要显示参数的页面(属性名为类名首字母小写)
存放数据域的操作
存放数据到 request 域
方式 1:通过 HttpServletRequest 放入 request 域
需要导入类 servlet-api.jar 包
@RequestMapping(value = "/test")
public String test(Master master, HttpServletRequest request, HttpServletResponse response) {
// 可以手动放入 request 域中
request.setAttribute("address", "杭州");
// 从本质看:请求响应的方法 return "xx", 是返回了一个字符串,其实本质是返回了一个 ModelAndView 对象,只是默认被封装起来了
return "ok";
}
方式2:通过请求的方法参数 Map<String,Object> 放入 request 域
@RequestMapping(value = "/test")
public String test(Master master, Map<String,Object> map) {
// 原理分析:springmvc 底层会遍历 map ,然后将 map 中的数据放入到 request
map.put("address", "杭州");
// 如果已经存放在 request 域中的数据将会被覆盖
map.put("master", null);
return "ok";
}
方式 3: 通过返回 ModelAndView 对象实现 request 域数据
@GetMapping(value = "/test")
public ModelAndView test(Monster master){
// 创建 ModelAndView 对象
ModelAndView modelAndView = new ModelAndView();
// 将需要数据放入到该对象,并将该数据放入到 request 域中
modelAndView.addObject("address","杭州");
// 指定跳转的页面名为 ok 的页面
modelAndView.setViewName("ok");
// 返回结果
return modelAndView;
}
存放数据到 session 域
方式 1:通过 HttpSession 放入 session 域
@RequestMapping(value = "/test")
public String test(Master master, HttpSession session) {
session.setAttribute("address","杭州");
return "ok";
}
自定义视图
工作流程概述
- SpringMVC 调用目标方法, 返回自定义 View 在 IOC 容器中的 id
- SpringMVC 调用 BeanNameViewResolver 解析视图(从 IOC 容器中获取返回 id 值对应的 bean, 即自定义的 View 的对象)
- SpringMVC 调用自定义视图的 renderMergedOutputModel 方法渲染视图
注意:如果在 SpringMVC 调用目标方法, 返回自定义 View 在 IOC 容器中的 id 不存在, 则仍然按照默认的视图处理器机制处理
代码实现
// 自定义视图
@Component(value="view")
public class MyView extends AbstractView {
@Override
protected void renderMergedOutputModel(Map<String, Object> map, HttpServletRequest req, HttpServletResponse resp) throws Exception {
System.out.println("进入到自己的视图");
// 确定到哪个页面去,默认的视图解析机制就无效
req.
("/WEB-INF/pages/my_view.jsp").forward(req, resp);
}
}
// handler 控制层
@RequestMapping(value = "/test")
public String test(Master master, HttpSession session) {
return "view";
}
配置视图解析器
<!--
配置可以解析自定义的视图的解析器 BeanNameViewResolver
默认视图解析器:InternalResourceViewResolver
-->
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<!--
设置优先级,默认优先级很低值 Integer.MAX_VALUE
视图解析优先级高,order 值越小,优先级越高
-->
<property name="order" value="99"></property>
</bean>
源码分析
BeanNameViewResolver 的源码分析
public class BeanNameViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered {
// 优先级设置
private int order = 2147483647;
public BeanNameViewResolver() {
}
public void setOrder(int order) {
this.order = order;
}
public int getOrder() {
return this.order;
}
// 视图解析器
@Nullable
public View resolveViewName(String viewName, Locale locale) throws BeansException {
ApplicationContext context = this.obtainApplicationContext();
// ioc 容器中是否存在该 beanName 对象名
if (!context.containsBean(viewName)) {
return null;
// 查看类型是否匹配,因为 AbstractView 实现了 View 接口,所以就是查看是否继承了 AbstractView 类
} else if (!context.isTypeMatch(viewName, View.class)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Found bean named '" + viewName + "' but it does not implement View");
}
return null;
} else {
// 获取到 View 对象
return (View)context.getBean(viewName, View.class);
}
}
}
InternalResourceViewResolver 默认视图源码分析
注意:默认视图解析器(优先级高)一旦执行,则不再执行自定义视图解析器
public class InternalResourceViewResolver extends UrlBasedViewResolver {
private static final boolean jstlPresent = ClassUtils.isPresent("javax.servlet.jsp.jstl.core.Config", InternalResourceViewResolver.class.getClassLoader());
@Nullable
private Boolean alwaysInclude;
public InternalResourceViewResolver() {
Class<?> viewClass = this.requiredViewClass();
if (InternalResourceView.class == viewClass && jstlPresent) {
viewClass = JstlView.class;
}
this.setViewClass(viewClass);
}
// 配置文件中有配置该信息
public InternalResourceViewResolver(String prefix, String suffix) {
this();
// 获取到前缀和后缀 拼接 url
// <property name="prefix" value="/pages/"/>
// <property name="suffix" value=".jsp"/>
this.setPrefix(prefix);
this.setSuffix(suffix);
}
public void setAlwaysInclude(boolean alwaysInclude) {
this.alwaysInclude = alwaysInclude;
}
protected Class<?> requiredViewClass() {
return InternalResourceView.class;
}
protected AbstractUrlBasedView instantiateView() {
return (AbstractUrlBasedView)(this.getViewClass() == InternalResourceView.class ? new InternalResourceView() : (this.getViewClass() == JstlView.class ? new JstlView() : super.instantiateView()));
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
// 通过前缀 + viewName + 后缀,拼接出 url 并创建出 view 对象
InternalResourceView view = (InternalResourceView)super.buildView(viewName);
if (this.alwaysInclude != null) {
view.setAlwaysInclude(this.alwaysInclude);
}
view.setPreventDispatchLoop(true);
return view;
}
}
请求转发、重定向
注意事项:
-
默认处理器返回的方式是请求转发,然后用视图解析器来进行处理(如上述)
-
可以在目标方法指定重定向或转发的 url 地,可以请求转发到 /WEBN-INF 目录中的资源
// handler 控制层 @RequestMapping(value = "/test") public String test() { // 指定转发的 url 地址 return " v :url"; }
-
可以使用重定向,但是注意不能重定向到 /WEBN-INF 目录中的资源
// handler 控制层 @RequestMapping(value = "/test") public String test() { return "redirect:url"; }
文件的上传和下载
文件的下载
@RequestMapping("/download")
public ResponseEntity<byte[]> downloadFile(HttpSession session) throws IOException {
// 1.获取资源的流
InputStream resourceAsStream = session.getServletContext().getResourceAsStream("/imgs/1.png");
// 2.将资源流数据保存到二进制数组里
byte[] bytes = new byte[resourceAsStream.available()];
resourceAsStream.read(bytes);
// 3.组装 ResponseEntity 数据进行返回
// 3.1参数(@Nullable T body, @Nullable MultiValueMap<String, String> headers, HttpStatus status)
final HttpStatus CG = HttpStatus.OK;
MultiValueMap<String, String> headers = new HttpHeaders();
// 3.2以附件的形式进行下载
headers.add("Content-Disposition","attachment;filename=2.png");
return new ResponseEntity<byte[]>(bytes,headers,CG);
}
文件的上传
引入依赖
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.2</version>
</dependency>
配置 spring xml 文件
<!--配置文件上传需要的bean-->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver"
id="multipartResolver"/>
代码实现
/**
* 文件上传
*/
@RequestMapping("/upload")
public void uploadFile(@RequestParam("file") MultipartFile file,
@RequestParam("introduce") String introduce){
System.out.println("文件介绍: " + introduce);
try {
// 传入文件名
String originalFilename = file.getOriginalFilename();
// 文件资源路径(根目录 + 文件夹 + 文件名)
String filePath = this.getClass().getResource("").getPath() + "/uploadFile/" + originalFilename;
// 创建文件夹
File saveFile = new File(filePath);
// 文件是否存在
if(!saveFile.exists()){
// 创建文件
saveFile.mkdirs();
}
// 文件保存到资源路径
file.transferTo(saveFile);
System.out.println("文件接收成功");
} catch (IOException e) {
System.out.println("文件上传失败" + e.getMessage());
}finally{
}
}
拦截器
概述 :对请求进行拦截处理(类似 javaWeb 的过滤器)
条件 :自定义拦截器必须实现 HandlerInterceptor 接口
方法
- preHandle() 目标方法处理请求之前调用
- postHandle() 目标方法处理完请求之后调用
- afterCompletion() 主要进行一些资源清理的操作
注意事项
- 如果 preHandle() 方法返回 false,则不再执行 postHandle() 方法 以及 afterCompletion() 方法
示意图
代码实现
拦截器的相关配置是放在 spring.xml 文件中
<!-- 配置自定义拦截器 -->
<!-- 第一种配置方式 -->
<mvc:interceptors>
<!--
ref 指定对哪个拦截器进行配置(注意首字母小写)
默认是拦截所有目标方法
-->
<ref bean="myInterceptor01"/>
</mvc:interceptors>
<!-- 第二种配置方式 -->
<mvc:interceptors>
<!--
mvc:mapping path="/path" 指定要拦截的路径
ref 指定对哪个拦截器进行配置
-->
<mvc:interceptor>
<mvc:mapping path="/path"/>
<ref bean="myInterceptor01"/>
</mvc:interceptor>
</mvc:interceptors>
<!-- 第三种配置方式 -->
<mvc:interceptors>
<!--
mvc:mapping path="/p*" 通配符方式 表示拦截 /p 开头的路径
mvc:exclude-mapping path="/path" /path 不拦截
ref bean="myInterceptor01" 指定对哪个拦截器配置
-->
<mvc:interceptor>
<mvc:mapping path="/h*"/>
<mvc:exclude-mapping path="/hello"/>
<ref bean="myInterceptor01"/>
</mvc:interceptor>
</mvc:interceptors>
拦截器的实现
@Component
public class MyInterceptor01 implements HandlerInterceptor {
/**
* 目标方法处理请求之前调用
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println();
return false;
}
/**
* 目标方法处理完请求之后调用
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
/**
* 视图渲染后被执行,可以做资源清理工作
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
多个拦截器流程示意图
注意:配置前后就表示拦截器的先后顺序
异常处理
概述:SpringMVC 通过 HandlerExceptionResolver 处理程序异常,包括:Handler 映射、数据绑定以及目标方法执行时发生的异常
注解:@ExceptionHandler({Exception.class、…等等局部异常类})
匹配机制:首先去寻找本类是否有被上述注解修饰的方法,如果有,则被该方法捕获,反之则找到 @ControllerAdvice 修饰的类的该注解修饰的方法,可以用作全局异常处理器
格式:
// 局部异常
@ExceptionHandler({Exception.class})
public String localException(Exception e){
// 显示异常信息
System.out.println("异常信息" + e.getMessage();
return "xxx";
}
// 全局异常
@ControllerAdvice
class GlobalExcepiton{
@ExceptionHandler({Exception.class})
public String localException(Exception e){
// 显示异常信息
System.out.println("异常信息" + e.getMessage();
return "xxx";
}
}
优先级:局部异常捕获的优先级高于全局异常捕获
注意:自定义异常查看 JavaSE 基础