以下内容是我在学习 Spring MVC 框架时候做的笔记总结。如有错误请指正,谢谢。
Spring 为展现层提供的基于 MVC 设计理念的优秀的Web 框架,通过把Model,View,Controller分离,将web层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,减少出错,方便组内开发人员之间的配合。
(1)用户发送请求至前端控制器DispatcherServlet;
(2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
(4)DispatcherServlet 调用 HandlerAdapter处理器适配器;
(5)HandlerAdapter 经过适配调用具体处理器(Handler,也叫后端控制器);
(6)Handler执行完成返回ModelAndView;
(7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
(9)ViewResolver解析后返回具体View;
(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中);
(11)DispatcherServlet响应用户。
<!--Servlet - JSP -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!--Spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
在 web.xml 中配置 DispatcherServlet(前端控制器)
<!-- 配置DispatcherServlet -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置DispatcherServlet 的一个初始化参数:配置SpringMVC配置文件的位置和名称-->
<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>springDispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
在springmvc.xm中配置自动扫描包以及视图解析器
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="com.atguigu.springmvc">
</context:component-scan>
<!-- 配置视图解析器(也可以是html等,支持不同视图类型) -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
编写处理请求的处理器HelloWorld.java,并标识为处理器。
使用 @RequestMapping 注解来映射请求的 URL ,返回值会通过视图解析器解析为实际的物理视图, 对于 InternalResourceViewResolver 视图解析器, 会做如下的解析:通过 prefix + returnVal + 后缀 这样的方式得到实际的物理视图,然会做转发操作。
@Controller
public class HelloWorld {
@RequestMapping("/helloworld")
public String hello() {
System.out.println("hello world");
return "success";
}
}
编写视图
index.jsp
<body>
<a href="helloworld">Hello World</a>
</body>
success.jsp
<body>
<h1>Sucess Page</h1>
</body>
Spring MVC 使用 @RequestMapping 注解为控制器指定可以处理哪些 URL 请求,在控制器的类定义及方法定义处都可标注@RequestMapping。DispatcherServlet 截获请求后,就通过控制器上 @RequestMapping 提供的映射信息确定请求所对应的处理方法。
//类定义处:提供初步的请求映射信息
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
//方法处:提供进一步的细分映射信息
@RequestMapping("/testRequestMapping")
public String testRequestMapping() {
System.out.println("testRequestMapping");
return SUCCESS;
}
}
@RequestMapping 的 value、method、params 及 heads分别表示请求 URL、请求方法、请求参数及请求头的映射条件,他们之间是与的关系,联合使用多个条件可让请求映射更加精确化。
@RequestMapping(value="testMethod",method=RequestMethod.POST)
@RequestMapping(value="testParamsAndHeaders",
params= {"username","age!=10"},
headers= {"content=\"text/*"})
@RequestMapping 还支持 Ant 风格的 URL:
在处理方法入参处使用 @RequestParam 可以把请求参数传递给请求方法。
– value:参数名
– required:是否必须。默认为 true, 表示请求参数中必须包含对应的参数,若不存在,将抛出异常。
@RequestMapping(value="/testRequestParam")
public String testRequestParam(@RequestParam(value="username") String un,
@RequestParam(value="age",required=false,defaultValue="0") int age) {
System.out.println("testRequestParam,username="+un+",age="+age);
return SUCCESS;
}
请求头包含了若干个属性,服务器可据此获知客户端的信息,通过 @RequestHeader 即可将请求头中的属性值绑定到处理方法的入参中。
@RequestMapping(value="/testRequestHeader")
public String testRequestHeader(@RequestHeader(value="Accept-Language") String al) {
System.out.println("testRequestHeader,Accept-Language:"+al);
return SUCCESS;
}
Spring MVC 提供了以下几种途径输出模型数据:
控制器处理方法的返回值如果为 ModelAndView,则其既包含视图信息,也包含模型数据信息。SpringMVC 会把 ModelAndView 的 model 中数据放入到 request 域对象中。
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
String viewName = SUCCESS;
ModelAndView modelAndView = new ModelAndView(viewName);
//添加数据模型到ModelAndView中
modelAndView.addObject("time", new Date());
return modelAndView;
}
index.jsp
<a href="springmvc/testModelAndView">Test ModelAndVies</a>
Spring MVC 在内部使用了一个org.springframework.ui.Model 接口存储模型数据使得目标方法可以添加 Map 类型。举例如下:
@RequestMapping(value = "/getshopbyid",method = RequestMethod.GET)
@ResponseBody
private Map<String,Object> getShopById(HttpServletRequest request){
Map<String,Object> modelMap = new HashMap<String,Object>();
long shopId = HttpServletRequestUtil.getLong(request, "shopId");
if(shopId > -1){
try {
Shop shop = shopService.getByShopId(shopId);
List<Area> areaList = areaService.getAreaList();
modelMap.put("shop",shop);
modelMap.put("areaList",areaList);
modelMap.put("success",true);
}catch (Exception e){
modelMap.put("success",false);
modelMap.put("errMsg",e.toString());
}
}else {
modelMap.put("success",false);
modelMap.put("errMsg","empty shopId");
}
return modelMap;
}
若希望在多个请求之间共用某个模型属性数据,则可以在控制器类上标注一个 @SessionAttributes, Spring MVC将在模型中对应的属性暂存到 HttpSession 中。该注解只能放在类的上面,而不能修饰放方法。
prefix 前缀 + 字符串 + suffix 后缀
请求处理
视图
视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。而视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 会在内部将它们装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图。借助视图解析器(ViewResolver)得到最终的视图对象(View),最终的视图可以是 JSP ,也可能是Excel、JFreeChart 等各种表现形式的视图。
<!-- 配置视图解析器(也可以是html等,支持不同视图类型) -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
一般情况下,控制器方法返回字符串类型的值会被当成逻辑视图名处理。如果返回的字符串中带 forward: 或 redirect: 前缀时,Spring MVC 会对他们进行特殊处理:将 forward: 和redirect: 当成指示符,其后的字符串作为 URL 来处理。
当想把某个控制器的返回转变为JSON数据集的时候只需要在方法上标注@ResponseBody注解即可,而 Sping MVC 是通过 HttpMessageConverter 接口实现。在进入控制器方法前,当遇到标注的 @ResponseBody后,处理器会记录这个方法的响应类型为 JSON 数据集。当执行完执行器后,处理器会启动解析器(ResultResolver)去解析这个结果,它会去轮询注册给 Spring MVC 的 HttpMessageConverter 接口的实现类。因为 MappingJackson2HttpMessageConverter 这个实现类已经被 Spring MVC所注册,加上 Spring MVC将控制器的结果类型标明为 JSON,所以就匹配上了于是通过它在处理器内部把结果转换为了 JSON,并且后续的模型和视图(ModelAndView)就会返回 null,这样视图解析器和视图渲染将不再被执行。(来自《深入浅出Spring Boot 2.x》)
Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。下图为实现该接口的两类:
而CommonsMultipartResolver与StandardServletMultipartResolver不同的是前者还需要导入commons-fileupload、commons-io等相关依赖。并且Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring的文件上传功能,需现在上下文中配置 MultipartResolver。
springmvc.xml
<!-- 文件上传解析器 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="20971520"></property>
<property name="maxInMemorySize" value="20971520"></property>
</bean>
jsp前端页面
<form action="testFileUpload" method="POST" enctype="multipart/form-data">
File:<input type="file" name="file"/>
Desc:<input type="text" name="desc"/>
<input type="submit" value="Submit"/>
</form>
Controller层
@RequestMapping("/testFileUpload")
public String testFileUpload(@RequestParam("file") MultipartFile file,
@RequestParam("desc") String desc) throws IOException {
System.out.println("OriginalFilename"+file.getOriginalFilename());
System.out.println("InputStream"+file.getInputStream());
System.out.println("desc:"+desc);
return "success";
}
html前端页面
<li>
<div class="item-content">
<div class="item-inner">
<div class="item-title label">上传文件</div>
<div class="item-input">
<input type="file" id="file" >
</div>
</div>
</div>
</li>
Controller层在方法参数中使用MulitpartHttpServletRequest类型或者HttpServletRequest也可以(后面强转即可)
当请求来到 DispatcherServet 时,它会根据 HandlerMapping 的机制找到处理器这样就会返回一个 HandlerExecutionChain 对象,这个对象包含处理器和拦截器。这里的拦截器会对处理器进行拦截,这样通过拦截器就可以增强处理器的功能,而所有的拦截器都需要实现 HandIerInterceptor 接口。
public class FirstInterceptor implements HandlerInterceptor{
/**
* 该方法在目标方法之前被调用.
* 若返回值为 true, 则继续调用后续的拦截器和目标方法.
* 若返回值为 false, 则不会再调用后续的拦截器和目标方法.
*
* 可以考虑做权限. 日志, 事务等.
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("[FirstInterceptor] preHandle");
return true;
}
/**
* 调用目标方法之后, 但渲染视图之前.
* 可以对请求域中的属性或视图做出修改.
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("[FirstInterceptor] postHandle");
}
/**
* 渲染视图之后被调用. 释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("[FirstInterceptor] afterCompletion");
}
}
在springmvc.xml中配置拦截器作用路径
<!-- 配置拦截器作用或不作用的路径 -->
<mvc:interceptor>
<mvc:mapping path="/emps"/>
<bean class="com.atguigu.springmvc.interceptors.FirstInterceptor"></bean>
</mvc:interceptor>
下图为多个拦截器的调用方法顺序(先正序后反序,因为源码中获取拦截器的时候第一个for循环用的是i=0,i++而接下来是i=length,i–):
默认情况下,SpringMVC 根据 Accept-Language 参数判断客户端的本地化类型。
REST:即 Representational State Transfer。(资源)表现层状态转化。是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
示例:/order/1
而浏览器 form 表单只支持 GET与 POST 请求,而DELETE、PUT 等 method 并不支持,Spring3.0 添加了一个过滤器,可以将这些请求转换为标准的 http 方法,使得支持 GET、POST、PUT 与DELETE 请求。
在web.xml文件中配置org.springframework.web.filter.HiddenHttpMethodFilter
<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>/*
控制层:
@RequestMapping(value = "/testRest/{id}", method = RequestMethod.GET)
public String testRest(@PathVariable Integer id) {
System.out.println("testRest GET: " + id);
return SUCCESS;
}
@RequestMapping(value = "/testRest/{id}", method = RequestMethod.DELETE)
public String testRestDelete(@PathVariable Integer id) {
System.out.println("testRest Delete: " + id);
return SUCCESS;
}
@RequestMapping(value = "/testRest/{id}", method = RequestMethod.PUT)
public String testRestPut(@PathVariable Integer id) {
System.out.println("testRest Put: " + id);
return SUCCESS;
}
@RequestMapping(value = "/testRest", method = RequestMethod.POST)
public String testRest() {
System.out.println("testRest POST");
return SUCCESS;
}