目录
Spring MVC是Spring Framework提供的Web组件,全称是Spring Web MVC,是目前主流的实现MVC设计模式的框架,提供前端路由映射、视图解析等功能。
++MVC是一种软件架构思想,把软件按照模型,视图,控制器来划分++。
View
:视图层,指工程中的html,jsp等页面,作用是和用户进行交互,展示数据。
Controler
:控制层,指工程中的Servlet,作用是接收请求和响应浏览器,调用业务逻辑。
Model
:模型层,指工程中的JavaBean,进行数据交互,用来处理数据。
JavaBean分成两类:
一实体类Bean:专门用来存储业务数据的实体类。
一业务处理Bean: 指Servlet或Dao对象,专门用来处理业务逻辑和数据访问。
服务端三层架构:
表现层 业务逻辑层 数据访问层
Spring MVC Spring MyBatis
MVC核心组件
DispatcherServlet
:前置控制器,负责调度其他组件的执行,可以降低不同组件之间的耦合性。
Handler
:处理器,完成具体的业务逻辑,相当于Servlet。
HandlerMapping
:DispatcherServlet是通过 HandlerMapping把请求映射到不同的Handler。
HandlerInterceptor
:处理器拦截器,如果我们需要进行一些拦截处理,可以通过实现该接口完成。
HandlerExecutionChain
:处理器执行链。
HandlerAdapter
:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作包括数据类型转换、把表单数据封装等,这些一系列的操作都是由HandlerAdapter完成,DispatcherServlet通过HandlerAdapter执行不同的Handler。
ModelAndView
:封装了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet。
ViewResolver
:视图解析器,DispatcherServlet通过它把逻辑视图解析为物理视图,最终把渲染的结果响应给客户端
++mvc响应流程++:
客户端请求被DispatcherServlet接收,根据HandlerMapping映射到Handler,生成Handler和HandlerInterceptor,Handler和HandlerInterceptor以HandlerExecutionChain的形式一并返回给DispatcherServlet,DispatcherServlet通过HandlerAdapter调用Handler的方法完成业务逻辑处理,返回一个ModelAndView对象给DispatcherServlet,DispatcherServlet把获取的ModelAndView对象传给ViewResolver视图解析器,把逻辑视图解析成物理视图。ViewResolver返回一个View,把模型填充到视图中,DispatcherServlet把渲染后的视图响应给客户端。
@RequestMapping注解
底层是@Maping
注解,作用在类、接口和方法上,主要属性有name、value、method和params,其他属性如下。
java
String name() default "";
//path,和value一样
@AliasFor("path")
String[] value() default {};
//value,一个字符串数组
@AliasFor("value")
String[] path() default {};
RequestMethod[] method() default {};
String[] params() default {};
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
++value
属性必须配置++ ,当有多个时能够匹配多个请求。支持ant风格路径,?
表示匹配任意单个字符(不为空、?和斜线),*
表示匹配任意的0或多个字符(不为?和斜线),**
表示匹配任意一层或多层目录或路径(0层也匹配),必须为/**/test
,左右不添任何字符。
java
@RequestMapping("/a?c/test")
public String test(){
return "test";
}
//?,<a th:href="@{/abc/test}"></a>
//*,<a th:href="@{/ac/test}"></a>
//**,<a th:href="@{/test}"></a>
++Spring MVC支持路径中的占位符++ ,原始方式:/test?id=1
,Rest风格:/test/1
。
java
@RequestMapping("/test/{id}")
public String test(@PathVariable("id")Integer Id){
System.out.println("id="+ID);
return "test";
}
//<a th:href="@{/test/1}"></a>
好处是可以在路径中传入参数。
method
属性可以通过请求方式匹配请求,比如get(参数放请求头、无请求体、速度快)、post(安全、参数放请求体,传数据量大),没有配置则匹配任意方式请求,不支持的请求方式浏览器报405。
java
@RequestMapping(value={"/user","/admin"},
method={RequestMethod.get,RequestMethod.post})
++派生注解++ :GetMapping
、@PostMapping
、@DeleteMapping
、@PutMapping
对应不同的请求方式。
而目前浏览器只支持get、post两种请求方式,其他请求方式默认通过get请求,设置hidden隐藏域完成。这一点在spring boot原理解析有讲。
html
<form action="/user" method="post">
<input name="_method" type="hidden" value="delete"/>
<input type="submit" value="delete提交"/>
</form>
SpringBoot中手动开启。
yaml
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
设置自定义的methodFilter,编写webconfig配置类,创建filter对象,调用HiddenHttpMethodFilter的setMethodParam方法。
params
属性同样也是一个数组,参数设置为多个时请求参数必须全部满足才能匹配并执行方法,不满足则报400错误。
java
@RequestMapping(value={"/user","/admin"},
params={"username=admin","pass"})
//<a th:href="@{/admin(username='admin',pass=123456)}"></a>
//<a th:href="@{/admin?username=admin&pass=123456}"></a>
也可以不用params属性获取参数,只要方法中的形参与请求中的参数名保持一致,便能获取参数值。
其他方式获取请求参数:
(1)ServletAPI获取请求参数:
java
@RequestMapping("/test")
public String test(HttpServletRequest r){
String username = r.getParameter("username");
String password = r.getParameter("pass");
System.out.println(username+":"+password);
return "test";
}
//<a th:href="@{/admin?username=admin&pass=123456}"></a>
(2)springMVC获取多个请求参数
hobby为一个参数数组或集合,若请求参数中出现多个同名的参数,可以在方法中设置形参为字符串类型或字符串数组接收参数,若为字符串则参数值用逗号拼接。
java
@RequestMapping("/test")
public String test(String username,String password,String hobby){
System.out.println(username+";"+password+";"+hobby);
return "test";
}
(3) 通过@RequestParam
注解
@RequestParam
将建立请求参数与控制器方法中的形参之间的映射关系,含有属性value(请求参数),布尔属性required,默认true,必须传入该请求参数。默认值defaultValue属性,为该参数设置一个默认值。
java
@RequestMapping("/test")
public String test(
@RequestParam("user_name")String username,
String password,
String[] hobby){
System.out.println(username+";"+password+";"+Arrays.toString(hobby));
return "test";
}
(4)通过实体类对象接收请求参数
这种方式要求实体类的对象属性与请求参数对应并一致,名字必须相同。
java
@RequestMapping("/test")
public String test(User user){
System.out.println(user);
return "test";
}
Headers
属性同样也是一个数组,设置header请求头参数信息,比如Accept、Host、User-Agent等,不符合Header参数则浏览器报404错误。
java
@RequestMapping(value={"/user","/admin"},
header={"Host=localhost:8080"})
@RequestHeader
注解建立请求头与方法中形参之间的映射,他与@RequestParam
一样具有相同的属性。
@CookieValue
注解将Cookie数据与控制器方法的形参建立映射关系,属性与上述一样,用法也一样。
CharacterEncodingFilter处理请求参数乱码问题:
xml
<!--web.xml中配置过滤器-->
<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>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
域对象共享数据
域对象:request、session、applicationcontext
(1)通过Servlet在request共享数据
java
@RequestMapping("/test")
public String test(HttpServletRequest r){
//name=value
r.setAttribute("testScope","helloworld");
return "test";
}
//获取<a th:text="${testScope}"/>
(2)通过ModelAndView向request共享数据
java
@RequestMapping("/test")
public ModelAndView test(){
ModelAndView mav = new ModelAndView();
mav.addObject("testScope","helloworld");
mav.setViewName("test");
return mav;
}
//获取<a th:text="${testScope}"/>
(3)通过Model向request共享数据
java
@RequestMapping("/test")
public String test(Model model){
model.addAttribute("testScope","helloworld")
return "test";
}
//获取<a th:text="${testScope}"/>
(4)通过Map向request共享数据
java
@RequestMapping("/test")
public String test(Map<String,Object> map){
map.put("testScope","helloworld")
return "test";
}
//获取<a th:text="${testScope}"/>
(5)通过ModelMap向request共享数据
java
@RequestMapping("/test")
public String test(ModelMap modelMap){
modelMap.addAttribute("testScope","helloworld")
return "test";
}
不管使用何种方式共享数据,返回页面,最终将通过DispatcherServlet封装在一个ModelAndView
中,并通过视图解析器解析。
向session中共享数据:
java
@RequestMapping("/test")
public String test(HttpSession httpSession){
//name=value
httpSession.setAttribute("testScope","helloworld");
return "test";
}
//获取<a th:text="${session.testScope}"/>
向application中共享数据:
java
@RequestMapping("/test")
public String test(HttpSession httpSession){
//request也可以获取application
ServletContext application = httpSession.getServletContext();
application.setAttribute("testScope","helloworld");
return "test";
}
//获取<a th:text="${application.testScope}"/>
视图
springMVC的视图是View接口,用于渲染数据,把model中的数据放入view中展示。默认视图种类有重定向Redirect和转发Forward。当控制器中的视图名称无任何前缀,此时视图会被配置的视图解析器解析,最终通过转发出去,比如ThymeleafResolver。
java
@RequestMapping("/test")
public String test(){
return "test";
}
转发视图InternalResourceViewResolver:
java
@RequestMapping("/test")
public String test(){
return "forward:/test2";
}
@RequestMapping("/test2")
public String test(){
return "test";
}
重定向视图RedirectViewResolver:
此时视图名不会被配置的视图解析器解析,而是创建一个重定向视图解析器,去掉redirect:重定向到另一个请求。
java
@RequestMapping("/test")
public String test(){
return "redirect:/test2";
}
++使用视图控制器view-controller++
在类路径下SpringMVC.xml配置文件中使用view-controller标签,配置请求路径与视图名称的对应关系。
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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.example.mvc.controller"/>
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<property name="order" value="1" />
<property name="characterEncoding" value="UTF-8" />
<property name="templateEngine">
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/templates/" />
<property name="suffix" value=".html" />
<property name="templateMode" value="HTML5" />
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
<mvc:view-controller path="/" view-name="index"></mvc:view-controller>
</beans>
如果既使用了视图控制器,又使用了控制器方法,需要开启注解。
xml
<mvc:annotation-driven></mvc:annotation-driven>
开启对静态资源的访问:
xml
<mvc:default-servlet-handler/>
RESTful
REST: Representational State Transfer表现层资源状态转移。
特点:
1)不同的请求方式对应不同的控制器处理方法,get,post,delete,put
2)请求参数融入URL中成为一个完整URL,/user/1,而不是用问号开头
3)虽然请求方式不同,但请求地址的路径大致相同,比如以同样的开头/user,/user/{id}
由于浏览器普通只支持get和post请求,所以这里只介绍通过表单隐藏域方式实现put和delete请求处理。
上述条件:请求方式为post(Ajax除外);请求参数中有_method,用于替换请求方式,type为hidden隐藏。
(1)delete删除请求
java
@DeleteMapping("/user/{id}")
public String deleteUserById(@PathVariable("id")Integer Id){
System.out.println("删除用户id="+ID+"的用户");
return "redirect:/user";
}
删除用户:
html
<a th:href="@{/user/}+${user.id}">删除</a>
或
<a th:href="@{'/user/'+${user.id} }">删除</a>
(1)put修改请求
put请求处理
java
@PutMapping("/user/{id}")
public String updateUserById(@PathVariable("id")Integer Id){
System.out.println("修改用户id="+ID+"的用户");
return "redirect:/user";
}
修改用户表单:
html
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT"/>
<input name="id" type="hidden" th:value="${user.id}"/>
用户名:<input type="text" value="username"/>
密码:<input type="password" value="password"/>
<input type="submit" value="修改"/>
</form>
请求与响应
HttpMessageConverter
http消息转换器,将请求报文信息转换为java对象,或将java对象转换为响应报文。它提供了两个注解和两个类型:@RequestBody
、@ResponseBody
、RequestEntity
、ResponseEntity
。
请求
1)@RequestBody
获取请求体,需要在控制器方法中设置++形参++,并用该注解标识。
java
@RequestMapping("/test")
public String test(@RequestBody String requestBody){
System.out.println("RequestBody="+requestBody);
return "test";
}
2)RequestEntity
封装请求报文的java类型,在控制器方法中的++参数前设置++,可以获取请求头、请求体等信息。
java
@RequestMapping("/test")
public String test(RequestEntity<String> requestEntity){
System.out.println("请求头:"+requestEntity.getHeaders());
System.out.println("请求体:"+requestEntity.getBody());
return "test";
}
响应
使用HttpServletResponse实现响应数据:
java
@RequestMapping("/test")
public String test(HttpServletResponse response) throws IOException{
//将helloworld作为响应体返回
response.getWriter().print("helloworld");
}
3)@ResponseBody
在控制器++方法上标识++,将该方法的返回值作为响应报文的响应体返回给浏览器。
java
@RequestMapping("/test")
@ResponseBody
public String test(RequestEntity<String> requestEntity){
String test = "abc";
return test;
}
++@ResponseBody处理json对象:++
springboot自动配置了默认的jackson依赖,只需要在核心配置文件中进行配置
yaml
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
如何使用springMVC则需要导入依赖,在springmvc的核心配置文件中配置注解驱动,在处理器方法上使用@ResponseBody
注解标识,并将Java对象作为方法返回值返回,就会自动转换为json格式的字符串。
@RestController注解
@RestController
是一个复合注解,标识在类上,相当于为类添加@Controller注解,为类中的所有方法添加@ResponseBody注解。
4)ResponseEntity
将该类型的数据作为控制器方法的返回值,该返回值就是响应报文,即可以自定义响应报文。
java
MultiValueMap<String,String> headers = new HttpHeaders();
headers.add("Content-Disposition","attchment;filename=xx.jpg");
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes,headers,statuscode) ;
++使用ResponseEntity文件下载++:使用ServletContext获取下载文件真实路径并创建输入流对象,创建字节数组,输入流读入字节数组,创建响应头对象HttpHeaders和状态码HttpStatus,设置下载方式和文件名,最后根据响应头、状态码、读入流创建ResponseEntity对象并返回。
++文件上传++ :控制器方法接收MultipartFile
类型的文件数据,先通过session获得ServletContext对象,查看文件名是否在对应路径中存在,若存在使用UUID作为文件名拼接文件名后缀上传,若文件不存在创建文件上传路径,MultipartFile
上传对象调用tranferTo方法上传即可。
html
<form th:action="@{/fileupload}" method="post" enctype="multipart/formdata">
文件:<input type="file" value="photo"/>
<input type="submit" value="上传"/>
</form>
拦截器配置
SpringMVC中拦截器用于拦截控制器方法,拦截器必须实现HandlerInterceptor
接口(ctrl±o)重写preHandle
方法或者继承HandlerInterceptorAdapter
类。拦截器在配置文件或配置类中配置,config配置类须实现WebMvcConfigurer
的配置接口,重写addInterceptor
方法,注册拦截器。
java
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取session
HttpSession httpSession = request.getSession();
if(httpSession.getAttribute("loginUser")==null){
response.sendRedirect("/f/toLogin");
return false;//拦截
}//放行
return true;
}
}
拦截器配置:
xml
<mvc:interception>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login"/>
<ref bean="LoginInterceptor" />
</mvc:interception>
三个抽象方法:
preHandle
执行控制器方法前执行该方法,postHandle
执行控制器方法后执行该方法,afterComplation
渲染视图后执行该方法。
++多个拦截器执行顺序:++与配置的先后顺序或@Order注解的优先级顺序有关,执行流程具体如下。
先顺序执行所有拦截器的preHandle方法
- 如果当前拦截器prehandler返回为true,则执行下一个拦截器的preHandle。
- 如果当前拦截器返回为false,直接倒序执行所有已经成功执行(返回true)的拦截器的afterCompletion。
如果任何一个拦截器返回false,直接跳出不执行目标方法,postHandle都不会执行,倒序执行所有已经成功执行(返回true)的拦截器的afterCompletion方法。当所有拦截器preHandle都返回True,执行目标方法,倒序执行所有拦截器的postHandle方法。前面的步骤有任何异常都会直接倒序触发afterCompletion。页面成功渲染完成以后,也会倒序触发 afterCompletion。
异常处理
对于控制器方法执行所出现的异常,SpingMVC提供了HandlerExceptionResolver
接口,其实现类有默认异常处理解析器DefaultHandlerExceptionResolver
和自定义的SimpleMappingExceptionResolver
简单映射异常处理解析器。
基于配置的异常处理
Spring MVC配置文件:
- properties的键表示处理器方法执行过程中出现的异常
- properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
- exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享
xml
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<!--设置异常映射,算术运算异常:例如1/0,跳转到error.html-->
<props>
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--设置在请求域中共享的异常属性信息,th:text="${e}"-->
<property name="exceptionAttribute" value="e"/>
</bean>
基于注解的异常处理
@ControllerAdvice
:将当前类标识为异常处理的组件,对控制器Controller的增强,可对 controller中被 @RequestMapping注解的方法加一些逻辑处理。
@ExceptionHandler
:用于设置所标识的方法处理的异常,@ExceptionHandler加在ControllerAdvice中,处理全局异常。@ExceptionHandler的value值可以是数组,可以添加许多可能出现的异常,在该方法中当出现算数运算异常或空指针异常就会跳转到设置的异常页面,并且展示异常信息。
java
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class})
public String exception(Exception e, Model model){
model.addAttribute("e",e);
return "error";
}
}
//<p th:text="${e}"/>
配置类与注解配置
在配置中,使用@Configuration
注解标识配置类,使用 @EnableWebMvc
注解来启用MVC配置,使用@ComponentScan
开启组件扫描。
java
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
//拦截器配置
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/","/login.html","/f/toLogin",
"/user/login","/img/*","/layui/**");
}
//文件上传解析器
@Bean
public MultipartResolver multipartResolver(){
CommonsMultipartResolver cmr = new CommonsMultipartResolver();
return cmr;
}
//异常处理
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers){
SimpleMappingExceptionResolver eResolver = new SimpleMappingExceptionResolver();
Properties prop = new Properties();
prop.setProperty("java.lang.ArithmeticException","errpr");
eResolver.setExceptionMapping(prop);
eResolver.setExceptionAttribute("e");
resolvers.add(eResolver);
}
}
其他组件配置如请求媒体内容协商器配置ContentNegotiationConfigurer
、信息转换器配置configureMessageConverters
、视图控制器addViewControllers
、configureViewResolvers
视图解析器、addResourceHandlers
静态资源、configureDefaultServletHandling
请求处理,可以参考以下文章:webconfig配置 Spring MVC 配置详解 WebMvcConfigurer
MVC执行流程
DispatcherServlet.doService()->DispatcherServlet.doDispatch()
继承FrameworkServlet.service()-->super.service()->doGet()...
>processRequest()->抽象doService()
1)前端控制器DispatcherServlet捕获请求,解析请求URL得到URI,判断URI对应映射,若没有相应的映射处理则交给default-servlet-handler处理,访问静态资源,没有找到同样返回404。
2)若找到了对应的映射,则寻找到合适的Handler、HandlerAdapter、相应拦截器链。
3)执行拦截器preHandler,进入一个异常处理流程,preHandle执行成功将执行Handle控制器方法。
4)Handler执行完后返回一个ModelAndView对象。
5)倒序执行拦截器postHandle和afterCompeletion方法。
6)根据ModelAndView对象,选择合适的ViewResolver进行视图解析渲染视图,如有异常,异常处理解析器HandlerExceptionResolver会处理。