SpringMVC小结
1. Spring简化IOC容器的获取
1.1 原理
- 在spring学习中,我们要获取IOC容器通常都是
new ClasspathXmlApplicationContext(applicationContest.xml)来获取,这种方式的弊端是每次通过IOC获取Bean时都要new一下,applicationContest.xml文件要加载多次。 - 在web项目中,我们可以利用监听器监听web应用的启动,当web应用启动时,就自动加载Spring配置文件applicationContext.xml,创建容器,并把容器存入到Servlet中的最大作用域servletContext中。然后就可以在任一位置获取servletContext中存储的ioc容器。进而获得Bean。
- 上述这一过程Spring帮我们简化了,我们直接调用相应API就行。
- spring 提供了一个监听器ContextLoaderListener,该监听器内部加载配置文件**,创建ioc容器**,并存储到ServletContext域中,并提供一个客户端工具WebApplicationContextUtils供使用者来获取容器。
1.2 如何利用spring的监听器来获取IOC
- 导入spring-web坐标(pom.xml)
- 在web.xml里配置ContextLoaderListener监听器。
- 使用WebApplicationContextUtils获取IOC容器。
示例:
- 导入Spring集成web的坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
- 在web.xml里配置ContextLoaderListener监听器
<!-- spring的配置文件 指定为classpath下的applicationContext.xml-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--Spring的监听器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
- 通过WebApplicationContextUtils工具来获得IOC容器。
ApplicationContext applicationContext =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
Object obj = applicationContext.getBean("id");
2. SpringMVC的概述
- SpringMVC是目前主流的实现MVC设计模式的企业级开发框架,它是spring框架的一个子模块,无需整合,开发起来更加便捷。
- MVC设计模式:将应用程序分为Controller,Model,View三层,Controller接收客户端请求,调用Model生成业务数据,传递给View。
- SpringMVC就是对这套流程的封装,屏蔽了很多底层代码,开放出接口,让开发者可以更加轻松便捷的完成基于MVC模式的web开发。
3. springmvc容器的创建及与spring容器的关系
web.xml(只有关于spring和springmvc的配置,其他的先省略)
<!-- spring的配置文件 指定为classpath下的applicationContext.xml-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- spring的监听-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- spring mvc核心:分发servlet -->
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- spring mvc的配置文件 为classpath下的springMVC.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--表示容器在启动时立即加载servlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<!-- 处理所有的url,即所有的请求都要经过DispatcherServlet-->
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- Tomcat启动时会依次加载web.xml配置中的Listener,Filter和Servlet,当加载到Listener时,会根据ContextLoaderListener监听器来创建spring ioc容器,当加载到Servlet时,会依据上面的已经创建的spring ioc 容器在DispatcherServlet里创建springMVC容器。spring容器是父容器,springMVC是子容器。
- spring中的bean指的是被实例的,组装的及被spring容器管理的java对象。
- spring和springMVC都是管理bean对象的容器。springMVC是负责管理controller对象(bean)的容器,Spring是负责管理service和Dao对象的容器。因此我们在springMVC配置文件里配置的扫描路径是controller的路径,而spring的配置文件里配的是service和dao的路径
- spring容器和springMVC容器是父子容器的关系。spring容器是父容器,springMVC是子容器。在子容器里可以访问父容器里的对象(bean),但在父容器里不可以访问子容器的对象(bean)。通俗的说就是在controller里可以访问service,dao对象,但在service,dao里不能访问controller对象。
- 注意:在web.xml里也可以不配置ContextLoaderListener,这样的话会将所有的bean全都放在DispatcherServlet创建的springMVC容器里。
4. SpringMVC入门
- 通过一个小案例来快速了解一下:客户端发起请求,服务端接收请求,执行逻辑并进行视图跳转。
4.1 开发步骤
- 导入SpringMVC的坐标
- 配置SpringMVC核心控制器DispathcerServlet
- 创建Controller类和视图页面
- 使用注解配置Controller类中业务方法的映射地址
- 配置SpringMVC核心文件spring-mvc.xml
- 客户端发起请求测试
4.2 代码实现
-
在pom.xml里导入Spring和SpringMVC的坐标,导入Servlet和jsp的坐标
<!--Spring坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version> </dependency> <!--SpringMVC坐标--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.5.RELEASE</version> </dependency> <!--Servlet坐标--> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <!--Jsp坐标--> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> </dependency> -
在web.xml里配置SpringMVC的核心控制器
//通过DispatcherServlet创建springMVC容器,并且加载spring-mvc.xml配置文件 <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:spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <!-- 处理所有的url,即所有的请求都要经过DispatcherServlet--> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> -
创建Controller类
public class QuickController { public String quickMethod(){ System.out.println("quickMethod running....."); return "index"; } }视图界面(/WEB-INF/jsp/index.jsp)
<html> <body> <h2>Hello SpringMVC!</h2> </body> </html> -
使用注解配置Controller类中业务方法的映射地址
@Controller//将QuickController类对象加入SpringMVC容器中 @RequestMapping("")//用于建立请求url和处理请求方法之间的对应关系 public class QuickController { @RequestMapping("/quick") public String quickMethod(){ System.out.println("quickMethod running....."); return "index"; } } -
配置SpringMVC核心文件spring-mvc.xml
<!--配置注解扫描 ,让其扫描controller下的注解--> <context:component-scan base-package="com.itheima"/> <!-- 配置视图解析器,对Controller类中返回的字符串进行解析,在其前面加上/WEB-INF/jsp/,后面加上.jsp --> <!--这样才能使DispatchServlet找到index.jsp的具体位置,而不是一个代表文件名字的字符串 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> -
客户端发起请求测试
http://localhost:8080/itheima_springmvc1/quick
页面显示:
4.3 流程分析
- 当浏览器访问测试地址时,Tomcat会接收客户的请求,然后将请求传给前端控制器(DispatchServlet),前端控制器会根据url的内容找到对应的Controller处理器类,(这里注意url里的quick和requestMapping("/quick")里的quick要对应) 然后执行这个Controller类QuickController,在控制台输出quickMethod running.....,然后返回一个字符串(代表要进行页面跳转),这个字符串会被返回到DispatchServlet前端控制器,前端控制器并不知道代表"index"什么意思,会把它交给视图解析器(就是在spring-mvc.xml里配置的),经过视图解析器解析后将结果返回,然后前端控制器再将结果index.jsp页面输出到浏览器。可以看出,这其中最重要的就是前端控制器DispatchServlet
- 上面是对SpringMVC流程的简单分析,实际上要比这个复杂一点,下面我们会深入分析,同时只要理解了这个流程就行,实际开发中要我们写代码的地方很少,springmvc框架自动帮我们做了,但这个过程要了解!
5. SpringMVC的执行流程及分析
5.1 执行流程图
5.2 SpringMVC的核心组件的说明
- DispatcherServlet:前端控制器,是整个springmvc流程控制的核心,控制其他的组件的执行,进行统一的调度,降低组件之间的耦合性,相当于总指挥。
- HandlerMapping:处理器映射器,负责完成客户端请求与controller类的映射,根据请求找到具体的处理器(即controller类),通常通过注解方式查找,例如requestMapping("/xxx")。
- HandlerExecutionChain:处理器执行链包括两部分内容:Handler和HandlerInterceptor(处理器拦截器,是一个接口,如果要完成一些拦截处理,可以实现该接口,系统有一个默认的,要额外拦截时才实现接口)。
- HandlerAdaptor:处理器适配器,Handler要执行业务操作之前,需要进行一系列的操作,包括表单数据的验证,数据类型的转换,将表单数据封装到javaBean等,这些操作都是由HandlerAdaptor来完成,开发者只需要将注意力集中到业务逻辑的处理上,DispatcherServlet通过HandlerAdaptor执行不同的Handler。
- Handler:处理器,也就是controller类,是我们开发中要具体编写的业务控制器,由DispatcherServlet把用户请求转发给Handler。Handler对具体的用户请求进行处理。处理完后返回ModelAndView
- ViewResolver:视图解析器,负责将传进的ModelAndView解析,生成View视图,ViewResolver首先根据逻辑视图解析成物理视图名,即具体的页面地址,再生成View视图对象,将其返回给前端控制器。
- 视图View:它的作用是渲染数据模型,将模型里的数据按照某种MIME类型呈现给客户端,常见的MIME类型有tetx/html,image/jpeg等。
MIME 类型:www.w3school.com.cn/media/media…
5.3 执行流程文字描述
- 用户发送请求到前端控制器DispatcherServlet
- 前端控制器根据请求调用HandlerMapping处理器映射器
- 处理器映射器找具体的处理器Handler 生成处理器对象及处理器拦截器,然后将它们作为一个处理器执行链返回前端控制器。
- 前端控制器根据处理器执行链中的处理器 首先访问HandlerAdapter处理器适配器,
- HandlerAdapter处理器适配器经过适配调用具体的处理器(controller)
- controller执行完逻辑代码返回ModelAndView。
- HandlerAdapter处理器适配器将ModelAndView传给前端控制器。
- 前端控制器将ModelAndView传给ViewReslover视图解析器进行解析
- ViewReslover视图解析器解析后返回具体 的View给前端控制器
- 前端控制器根据返回的view进行渲染视图(将模型数据填充到视图中)。
- 前端控制器将结果返回给客户端浏览器。
6. SpringMVC的相关注解
@RequestMapping
- SpringMVC通过@RequestMapping注解将url请求与业务方法controller进行映射,在Handler的类定义处以及方法处都可以添加@RequestMapping注解,在类定义处添加,相当于客户端多了一层访问路径。
- 相关参数:
- value:指定url请求的实际地址,是@RequestMapping的默认值。例如
@RequestMapping("/index")等价于@@RequestMapping(value="/index"),因此value可以省略。 - method:指定请求的method类型,GET,POST,PUT,DELETE等。例如
@RequestMapping(value="/index",method=ReauestMethod.GET)表示这个注解修饰的方法只能接收get请求的url。浏览器请求大多都是get,表单请求是post,其他请求可以通过postman来实现演示。 - params:指定请求中必须包含某些参数,否则无法调用方法。
@RequestMapping(value="/index",method=ReauestMethod.GET,params={"name","id=10"})表示请求中必须包含name和id两个参数,同时id的值必须是10
- value:指定url请求的实际地址,是@RequestMapping的默认值。例如
@Controller
- @Controller在类定义处添加,表示将该类交给springMVC容器来管理(需要在springmvc.xml配置文件里开启自动扫描controller包),同时使其成为一个控制器,可以接收客户端请求。
@RestController
- @RestController注解是@Controller和@ResponseBody的合集,表明这是个控制器Bean,并且将返回值直接返回到http响应体中。即作用也是@Controller和@ResponseBody两个注解 的合集。
- @RestController返回的对象数据直接以json或xml形式写入http响应体中。
@RequestParam
- 当请求参数的名称与Controller方法的参数名称不一致,即无法自动进行映射时,需要使用这个注解来完成手动映射。
请求参数为name,接收的形参为username,这时需要这个注解来进行映射。这个工作是由HandlerAdapter处理器映射器来完成的。
public void save16(@RequestParam(value="name") String username)
此外还有一些参数可供选择:
@RequestParam(value="name",required=false,defaultValue="hello")
value:与请求参数的名称对应,将请求参数的值赋给它所修饰的形参
required:默认不写时为true,表示请求中的请求参数不能为空。可以设置为false,表示请求参数为空。
defaultValue:当required为false时,即请求参数为空时,可以给请求参数一个默认值hello。
@Autowired*
@PathVariable
url:http://localhost:8080/quick17/zhangsan
@RequestMapping("/quick17/{name}")
@ResponseBody
public void save17(@PathVariable(value="name") String username) throws IOException {
System.out.println(username);
}
- @PathVariable注解主要是用来完成@RequestMapping("/quick17/{name}")中的占位符{name}与形参username的映射。在restful风格的url中常用。
- 注意:@RequestMapping("/quick17/{name}")中的name要和@PathVariable(value="name")中的name匹配。后面的username形式可以变
- 参考10.10 获得Restful风格的参数
@PathParam*
@ResponseBody
- 这个注解用在Controller类下的方法上,表示告知SpringMVC框架这个方法不进行视图跳转,直接进行数据响应。ResponseBody即响应体,方法返回的字符串不是进行页面跳转而是直接在http响应体中返回。
@RequestBody
- 作用在形参列表上,用于将前台发送来的固定格式的数据(json或者xml)封装为对应的javaBean对象(封装时用到的一个对象是SpringMVC默认配置的HttpMessageConverter进行解析,然后封装在形参)
- @RequestBody注解常用来处理content-type为application/json类型(即content-type不是默认的application/x-www-form-urlcoded编码的内容,比如说:application/json或者是application/xml等)。同时使用@RequestBody时请求方式要选择post方式,get方式返回为null
@RequestHeader
-
可以获取http请求消息的请求头信息。
-
@RequestHeader注解的属性如下:
value:请求头的名称
required:是否必须携带此请求头
-
详见10.12 获取请求头中的消息。
@CookieValue
-
获取请求头中Cookie 的值
-
@CookieValue注解的属性如下:
value:指定cookie的名称
required:是否必须携带此cookie
-
详见10.12 获取请求头中的消息。
@PostMapping
@PostMapping("users")等价于@RequestMapping(value="/users",method=RequestMethod.POST)
@GetMapping
@GetMapping("users")等价于@RequestMapping(value="/users",method=RequestMethod.GET)
7. spring-mvc.xml配置文件的分析
7.1注解扫描
<context:component-scan base-package=“com.itheima.controller"/>
表示将controller包下面的所有标记@Controller的类装载进springMVC容器。
7.2 视图解析器
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
表示将handler返回的ModelAndView进行解析。例如返回字符串"index"后,会在前边加上/WEB-INF/views/ 后面加上.jsp。
7.3 注解驱动
<mvc:annotation-driven/>
在springmvc.xml里配置后,会自动加载RequestMappingHandlerMapping(处理器映射器)和RequestMappingHandlerAdapter(处理器适配器)。但不配置也会加载默认的处理器映射器和处理器适配器。
还有一个作用是配置后底层就会集成jackson进行对象或集合的json格式字符串的转换。
7.4 开通静态资源访问
<!--开通静态资源的访问-->
<mvc:default-servlet-handler />
这个标签的意思是当请求的url经过前端控制器来到HandlerMapping后,如果找不到Controller对应的RequestMapping,就交由默认的Tomcat的servelt来处理寻找默认资源。
7.5 文件上传
<!--配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
//设置编码和上传文件的大小
<property name="defaultEncoding" value="UTF-8"/>
<property name="maxUploadSize" value="500000"/>
</bean>
详见11.2 单文件上传
7.6 拦截器
<mvc:interceptors>
<mvc:interceptor>
<!--对哪些资源进行拦截操作-->
<!--/admin/** 拦截的是/admin/下的所有-->
<!--/** 包括路径及其子路径,即拦截所有的资源-->
<mvc:mapping path="/**"/>
<!--bean配置的就是拦截器-->
<bean id="loginInterceptor" class="com.southwind.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
详见12. springmvc拦截器
8. web.xml配置文件的分析(补充)
8.1 中文过滤器
<!--中文过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
8.2 spring的配置文件和监听
<!-- spring的配置文件 指定为classpath下的applicationContext.xml-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- spring的监听-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
- 主要的作用是通过spring配置的监听器自动加载配置文件,帮我们创建一个IOC容器。
- 详见1.2 如何利用spring的监听器来获取IOC
8.3 SpringMVC的配置文件
<!-- spring mvc核心:分发servlet -->
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- spring mvc的配置文件 为classpath下的springMVC.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--表示容器在启动时立即加载servlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<!-- 处理所有的url,即所有的请求都要经过DispatcherServlet-->
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 主要作用是在根据spring-mvc.xml配置文件在DispatcherServlet里创建一个springMVC容器,然后完成初始化。
- 可以在springmvc.xml里开通静态资源的访问,这样在访问静态资源时,前端控制器会放行相应的url。
9.springMVC的数据响应
9.1 springMVC返回String和ModelAndView的区别
- 这两种方法来自不同的Spring版本。ModelAndView方法是Spring2.0之前从控制器返回模型和视图的主要方法。现在可以结合Model参数和String返回值,但是旧的方法任然有效。原理上他们是一样的,根据习惯不同,选择哪个都行。
9.2 直接返回字符串String-页面跳转
- 转发:转发的地址栏路径不变,是一次请求,可以使用request对象共享数据。只能访问当前服务器下的资源。
- 重定向:地址栏路径发生变化,是两次请求,不能使用request对象来共享数据。可以访问其他服务器的资源。
9.3 通过ModelAndView对象返回-页面跳转
形式一:在Controller中方法返回ModelAndView对象,并设置视图名称:
@RequestMapping("/quick1")
public ModelAndView save1(){
//Model:模型,作用是封装数据
//View:视图,作用是展示数据
//创建一个ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
//设置模型数据
modelAndView.addobject("username","itcast");
//设置视图名称
modelAndView.setViewName("success");
return modelAndView;
}
返回的modelAndView会被视图解析器解析,将setViewName("success")中的逻辑视图success转换为物理视图xx/xx/success.jsp,在这个页面可以用EL标签取出模型数据中的值,${"username"}=itcast
补充:EL表达式:
EL表达式的作用是替换和简化jsp页面中java代码的编写。jsp默认支持el表达式,如果要忽略可以在jsp的page指令中<%@ page isELIgnored="false" %>设置为true
形式二:在Controller中方法形参上直接声明ModelAndView,无需在方法中自己创建,在方法中直接使用该对象设置视图,同样可以跳转页面。
@RequestMapping("/qiuck2")
public ModelAndView save2(ModelAndView modelAndView){
modelAndView.addObject("username","itheima");
modelAndView.setViewName("success");
return modelAndView;
}
当SpringMVC调用相应的方法时,会根据方法中对象参数的类型自动创建相应的对象,然后调用方法。这个过程是SpringMVC自动完成的。
形式三:使用Model和字符串结合的方式
@RequestMapping("/qiuck3")
public String save3(Model model){
model.addAttribute("username","itheima");
return "success";
}
注意:Model和ModelAndView的区别是 Model只是用来传数据的,ModelAndView可以传数据并进行业务寻址,即返回到指定的静态文件(jsp)。
同样在success.jsp可以通过EL 标签${"username"}来获取值itheima。
形式四:在Controller方法的形参上可以直接使用原生的HttpServletRequest对象,只需声明即可。
@RequestMapping("/qiuck4")
public String save4(HttpServletRequest request){
request.setAttribute("username","hello");
return "success";
}
效果同上。
9.4 直接返回字符串-回写数据
形式一:将需要回写的字符串直接返回给客户端浏览器,需要加一个@ResponseBody注解告诉SpringMVC框架,方法返回的字符串不进行页面跳转而是直接在http响应体中返回,即直接显示在浏览器页面上。
@RequestMapping(value="/quick5")
@ResponseBody //告知SpringMVC框架 不进行视图跳转 直接进行数据响应
public String save5() throws IOException{
return "hello itheima";
}
会在浏览器中看到hello itheima字符串
形式二:通过SpringMVC框架注入的response对象,使用response.getWriter().print()方法回写数据。
@RequestMapping("/quick6")
pubilc void save6(HttpServletResponse response) throws IOException{
response.getWriter().print("hello itheima");
}
同样会在浏览器看到hello itheima
9.5 返回对象或集合或json格式字符串-回写数据
json的快速入门:mp.weixin.qq.com/s/RAqRKZJqs…
形式一:直接回写json格式字符串
@RequestMapping("/quick7")
@ResponseBody
public String save7() throws IOException{
return "{\"username\":\"zhangsan\",\"age\":18}"
}
形式二:使用json转换工具返回json格式字符串
手动拼接json格式字符串的方式很麻烦,开发中往往要将复杂的java对象转换成json格式的字符串,我们可以使用json转换工具jackson进行转换,通过jackson转换json格式字符串,回写数据。
@RequestMapping("/quick8")
@ResponseBody
public String save8() throws IOEXception{
User user=new User();
user.setUsername("lisi");
user.setAge(30);
//使用json的转换工具将对象转换成json格式字符串再返回
ObjectMapper objectMapper=new ObjectMapper();
String json=objectMapper.writeValueAsString(user);
return json;
}
注意:使用json转换工具要在pom.xml里导入三个包的坐标:jsonson-core,jackson-databind,jackson-annotations
形式三:通过SpringMVC配置来返回json格式字符串
在SpringMVC.xml里配置
//配置处理器映射器,springmvc内部帮我们实现了处理器映射器,但如果要进行json的转换就要手动重新配置这个处理器映射器
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</list>
</property>
</bean>
java代码:
@RequestMapping("/quick9")
@ResponseBody
//期望SpringMVC自动将User转换成json格式的字符串
public User save10() throws IOException {
User user = new User();
user.setUsername("lisi2");
user.setAge(32);
return user;
}
通过在springMVC.xml里配置代码,springmvc会帮助我们自动进行对象转json字符串,无需其他操作。
形式四:通过SpringMVC简化配置来返回json字符串
在springmvc.xml里配置注解驱动
<mvc:annotation-driven/>
java代码
@RequestMapping("/quick10")
@ResponseBody
//期望SpringMVC自动将User转换成json格式的字符串
public User save10() throws IOException {
User user = new User();
user.setUsername("lisi2");
user.setAge(32);
return user;
}
形式三中的配置比较繁琐,可以简化为形式四。使用<mvc:annotation-driven/> ,底层就会继承jackson进行对象或集合的json格式字符串的转换。
总结
四种形式都是一样的作用,但逐步优化,使用形式四的最便捷,也最常用
10. SpringMVC的请求获取
10.1 请求参数的类型
-
客户端请求参数的格式是:name=value&name=value…..
-
类型:
基本数据类型参数
pojo类型参数
数组类型参数
集合类型参数
10.2 获得基本类型的参数
- Controller中的业务方法的参数名称要和请求参数的name一致,参数值会进行自动映射匹配,并且能够自动做类型转换。
- 自动做类型转换指的是 从String类型转其他类型。例如String转int
Get请求url : http://localhost:8080/quick10?username=zhangsan&age=12
@RequestMapping("/quick11")
@ResponseBody
public void save11(String username,int age) throws IOException{
System.out.println(username);
System.out.println(age);
}
注意:请求url中的类型名字要和方法中的参数名字一致。这样SpringMVC框架就会自动帮我们进行映射,username=zhangsan中的zhangsan就会自动赋给参数username,age=12中的12自动赋值给参数age,并进行类型转换,转换为方法中定义的参数的类型。
10.3 获得pojo/javaBean类型的参数
-
Controller中的业务方法的pojo参数的属性名与请求参数的name一致,参数值会自动映射匹配。
pojo类(pojo:简单的Java对象,就是Javabean)
import lombok.Data; @Data//生成Getter,Setter,toString public class User{ private String username; private int age; }Controller:
@RequestMapping("/quick12") @ResponseBody public void save12(User user) throws IOException{ System.out.println(user); }请求url:http://localhost:8080/quick12?username=zhangsan&age=12
因为请求参数中的username和age正好和Controller方法参数中的user的属性名相对应,所以会自动进行参数映射,将传入的username和age的值自动赋值给user中的username属性和age属性。这个过程SpringMVC自动完成。
10.4 获得数组类型的参数
- Controller中的业务方法数组名称与请求参数的name一致,参数值会进行自动映射匹配。
请求url:http://localhost:8080/quick13?strs=111&strs=222&strs=333
@RequestMapping("/quick13")
@ResponseBody
public void quick13(String[] strs) throws IOException{
System.out.println(Arrays.toString(strs));
}
当传入的数组数组名和参数对应时,SpringMVC会自动进行封装,将请求参数封装进数组。然后调用Arrays.tostring()方法输出
10.5 获得集合类型的参数
-
获得集合类型的参数时要将集合参数包装到一个pojo中才可以。
先定义一个提交表单form.jsp:
<from action="${pageContext.request.contextPath}/quick14" method="post"> <%--表明是第一个User对象的username,age --%> <input type="text" name="userList[0].username"><br/> <input type="text" name="userList[0].age"><br/> <input type="text" name="userList[1].username"><br/> <input type="text" name="userList[1].age"><br/> <input type="submit" value="提交"><br/> </from>这里的userList[0].username即表示集合userList中的第一个user对象的username属性。
定义一个user类,有username,age属性。
定义一个类VO
import lombok.Data; @Data//生成Getter,Setter,toString public class VO { private List<User> userList; }Controller类
@RequestMapping("/quick14") @ResponseBody public void save14(VO vo) throws IOException { System.out.println(vo); }请求url:http://localhost:8080/form.jsp
然后输入表单信息后会跳转到Controller方法,将userList集合数据传给vo,然后输出:
VO{userList=[User{username='zhangsan',age=18},User{username='lisi',age=20}]}
10.6 获得集合类型的参数2
- 当使用Ajax提交时,可以指定contentType为json格式,那么在方法参数位置使用@RequestBody可以直接接收集合数据而无需使用pojo进行包装。
定义Ajax代码ajax.jsp
<html>
<head>
<script src="${pageContext.request.contextPath}/js/jquery-3.3.1.js"></script>
<script>
var userList = new Array();
userList.push({username:"zhangsan",age:18});
userList.push({username:"lisi",age:28});
$.ajax({
type:"POST",
url:"${pageContext.request.contextPath}/user/quick15",
data:JSON.stringify(userList),
contentType:"application/json;charset=utf-8"
});
</script>
</head>
<body></body>
</html>
Controller:
@RequestMapping("/quick15")
@ResponseBody
public void save15(@RequestBody List<User> userList) throws IOException {
System.out.println(userList);
}
分析:请求Url:http://localhost/ajax.jsp ,这个url会提交三次请求向服务器,第一次访问ajax,jsp,第二次访问/js/jquery-3.3.1.js,第三次访问要跳转的quick15。在访问quick15时,@RequestBody这个注解会将传入的json格式的数据转为javaBean,然后userList获取后输出。
结果:[User{username='zhangsan',age=18}, User{username='lisi',age=28}]
@RequestBody 的作用:作用在形参列表上,用于将前台发送来的固定格式的数据(json或者xml)封装为对应的javaBean对象user,封装时用到的一个对象是SpringMVC默认配置的HttpMessageConverter进行解析,然后封装在形参userList ,然后输出userList即得json转为javaBean的字符串。
@RequestBody注解常用来处理content-type不是默认的application/x-www-form-urlcoded编码的内容,比如说:application/json或者是application/xml等。一般情况下来说常用其来处理application/json类型。同时使用@RequestBody时要选择post方式,get方式返回为null
10.7 上面出现的问题
-
当我们访问ajax.jsp页面时,通过谷歌抓包工具发现,没有加载到jQuery文件,原因是SpringMVC的前端控制器DispatcherServlet的url-pattern配置的是 / ,因此所有的url请求都会经过前端控制器DispatcherServlet,然后交由HandlerMapping处理,但对jsp页面的访问并不适应于 专门处理用户请求对某个Controller访问的HandlerMapping,因此我们不应该让DispatcherServlet来管理访问静态资源的请求url,要放行静态资源
-
放行静态资源的方式:在Spring-mvc.xml里配置要放行的静态资源的位置
<mvc:resource mapping="/js/**" location="/js/"/>或者使用如下标签:
<mvc:default-servlet-handler/>这个标签的意思是当请求的url经过前端控制器来到HandlerMapping后,如果找不到Controller对应的RequestMapping,就交由默认的Tomcat的servelt来处理寻找默认资源。
10.8 配置全局乱码过滤器
- 当我们进行请求时,如果参数中有中文可能会出现数据乱码。我们可以在web.xml配置文件里设置一个过滤器来进行编码的过滤。
<!--中文过滤器-->
<filter>
<filter-name>CharacterEncodingFilter</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>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
10.9 获得的参数与形参不匹配
参数绑定注解@RequestParam
- 之前我们获得基本类型,pojo类型,数组类型的参数时都是要请求参数和Controller参数方法一一对应,然后SpringMVC才能帮我们完成映射。
- 如果请求的参数名称与Controller的业务方法参数名称不一致时,就要通过@RequestParam注解来进行参数的绑定,完成http请求参数与Controller方法中形参的映射。
<form action="${pageContext.request.contextPath}/quick16" method="post">
<input type="text" name="name"><br>
<input type="submit" value="提交"><br>
</form>
@RequestMapping(value="/quick16")
@ResponseBody
public void save16(@RequestParam(value="name") String username) throws IOException {
System.out.println(username);
}
-
上面的代码将请求参数name与Controller方法的形参username进行映射,同时有必要的话会进行数据类型转换。这些工作都是由HandlerAdapter完成的。
-
此外注解@RequestParam还有一些其他参数可以使用。
@RequestParam(value="name",required=false,defaultValue="hello")value:与请求参数的名称对应,将请求参数的值赋给它所修饰的形参
required:默认不写时为true,表示请求中的请求参数不能为空。可以设置为false,表示请求参数为空。
defaultValue:当required为false时,即请求参数为空时,可以给请求参数一个默认值hello。
10.10 获得Restful风格的参数
-
什么是Restful:Restful是一种软件架构风格,设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互的软件,基于这个风格,设计的软件可以更加简洁,更有层次,更易于实现缓存机制等。
-
Restful风格的请求是使用"url+请求方式"表示一次请求目的,http协议里面有四个表示操作方法的动词。
GET:用于获取资源
POST:用于新建资源
PUT:用于更新资源
DELETE:用于删除资源
例如:
/user/1 GET:得到id=1的user
/user/1 DELETE: 删除 id = 1 的 user
/user/1 PUT: 更新 id = 1 的 user
/user POST: 新增 user
-
传统类型与Restful风格的url:
传统类型:http://localhost:8080/user?name=zhangsan&id=10
Restful:http://localhost:8080/user/zhangsan/10
这里要注意在浏览器中访问url时,大部分操作都是GET类型的。
-
在SpringMVC中可以使用占位符进行参数绑定/quick17/zhangsan可以写成/quick17/{name},占位符{name}对应的值就是zhangsan, 在业务方法中可以通过使用@PathVariable注解进行占位符的匹配获取
url:http://localhost:8080/quick17/zhangsan
@RequestMapping("/quick17/{name}")
@ResponseBody
public void save17(@PathVariable(value="name") String username) throws IOException {
System.out.println(username);
}
- 注意这里value="/quick17/{name}中的name和@PathVariable(value="name")中的name要匹配,后面的username形式可以变。
- 通过 @PathVariable 注解完成请求参数与形参的映射。
10.11 自定义数据类型转换器
- 数据类型转换器是指将客户端http请求中的参数转换为业务方法中定义的形参。SpringMVC默认提供了一些常用的类型转换,例如String转int,String转double,表单数据的封装等。这些是由HandlerAdapter处理器适配器完成的。
- 如果我们需要的数据类型HandlerAdapter处理器适配器不能转换,这时我们需要自定义一个数据类型装换器,来达到目的。
自定义一个将"2021-4-16"的String类型的数据转换成Date类型的类型转换器。
步骤:
-
定义转换器类实现Converter接口
-
在spring-mvc.xml配置文件中声明转换器,在
<annotation-driven>中引用转换器 -
Controller包中的方法测试
实现:
- 定义转换器类DateConverter,实现Converter接口
public class DateConverter implements Converter<String,Date>{
public Date convert(String dateStr){
//将字符串转换为Date对象
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date=null;
try{
date=format.prase(dateStr);
}catch(ParseException e){
e.printStackTrace();
}
return date;
}
}
- 在Springmvc.xml配置文件里声明转换器,在
<annotation-driven>中引用转换器
//声明转换器
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="xxx.xxx.DateConverter"></bean>
//这里是自定义的转换器类DateConverter所在的全类名
</list>
</property>
</bean>
//配置注解驱动,注意要将上面bean里的id加入下面的conversion-service里
<mvc:annotation-driven conversion-service="conversionService">
- Controller包中的方法测试
@RequestMapping("/quick18")
@ResponseBody
public void save18(Date date) throws IOException{
System.out.println(date);
}
请求url:http://localhost:8080/quick18&date=2021-04-16
10.12 获得请求头中的信息
- 在浏览器地址栏输入url后,浏览器会解析url,首先会根据请求方式的不同(GET,POST等)生成不同的http请求消息。
- 补充一下HTTP的相关知识:
GET请求消息格式:
请求行:GET /url HTTP/1.1
请求头:客户端浏览器告诉服务器的一些信息。例如日期,客户端支持的语言,数据类型,压缩格式等。格式:请求头名称:请求头值。
请求空行:告诉服务器请求头已经结束。
POST请求消息格式:
请求行:POST /url HTTP/1.1
请求头:客户端浏览器告诉服务器的一些信息
请求空行:告诉服务器请求头已经结束。
请求体:封装post请求消息的请求参数的。
二者的区别:
**GET:**请求参数在请求行,在url后。请求的url长度有限制,且不太安全。
**POST:**请求参数在请求体中,请求的url长度没有限制,相对安全。
- 使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)。
@RequestMapping("/quick19")
@ResponseBody
public void save19(@RequestHeader(value="User-Agent",required=false) String user_agent) throws IOException{
System.out.println(user_agent);
}
@RequestHeader的属性:
value:请求头的名称
required:是否必须携带此请求头
- 使用@RequestHeader注解也可以获取Cookie的值,但有一个更简便的获取Cookie的注解:@CookieValue
@RequestMapping("/quick20")
@ResponseBody
public void save20(@CookieValue(value = "JSESSIONID") String jsessionId) throws IOException {
System.out.println(jsessionId);
}
@CookieValue注解的属性如下:
value:指定cookie的名称
required:是否必须携带此cookie
10.13 获取Servlet的相关api
SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的对象如下:
HttpServletRequest
HttpServletResponse
HttpSession
@RequestMapping(value="/quick21")
@ResponseBody
public void save21(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException {
System.out.println(request);
System.out.println(response);
System.out.println(session);
}
11. SpringMVC文件上传
11.1 客户端表单的三要素
- 表单项:type="file"
- 表单的提交方式:post
- 表单的enctype属性是多部分表单形式:enctype="multipart/form-data"。如果不设置这个属性,只能将文件名传给服务器。
- 例如:
<form action="${pageContext.request.contextPath}/user/quick22" method="post" enctype="multipart/form-data">
名称<input type="text" name="username"><br/>
文件<input type="file" name="uploadFile"><br/>
<input type="submit" value="提交">
</form>
-
注意:文件上传的底层是使用Apache fileupload组件完成上传,SpringMVC对这种方式进行了封装。
-
当form表单修改为多部分表单时,request.getParameter()将失效
11.2 单文件上传
步骤:
- 在pom.xml里导入fileupload和io坐标
- 在sping-mvc.xml里配置文件上传解析器
- 编写表单
- 编写Handler
实现:
- pom.xml里导坐标
<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.3</version>
</dependency>
- 在sping-mvc.xml里配置文件上传解析器
<!--配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
//设置编码和上传文件的大小
<property name="defaultEncoding" value="UTF-8"/>
<property name="maxUploadSize" value="500000"/>
</bean>
- 编写表单
<form action="${pageContext.request.contextPath}/user/quick22" method="post" enctype="multipart/form-data">
名称<input type="text" name="username"><br/>
文件<input type="file" name="uploadFile"><br/>
<input type="submit" value="提交">
</form>
- 编写Handler
@RequestMapping(value="/quick22")
@ResponseBody
public void save22(String username, MultipartFile uploadFile) throws IOException {
System.out.println(username);
//获得上传文件的名称
String originalFilename=uploadFile.getOriginalFilename();
//将上传的文件转存入c盘下的upload,文件名字就是上传的文件名
uploadFile.transferTo(new File("c:\\upload\\"+originalFilename));
}
- 注意:Handler里的 MultipartFile uploadFile参数要和表单里的name属性名字一致。
11.3 多文件上传
- 多文件上传,只需将页面修改为多个文件上传项,将方法参数MultipartFile类型修改为MultipartFile[]即可。
实现:
- 在pom.xml里导坐标
- 在springmvc.xml里配置文件上传解析器
- 编写表单
<form action="${pageContext.request.contextPath}/user/quick23" method="post" enctype="multipart/form-data">
名称<input type="text" name="username"><br/>
文件1<input type="file" name="uploadFile"><br/>
文件2<input type="file" name="uploadFile"><br/>
<input type="submit" value="提交">
</form>
- 编写Handler
@RequestMapping("/quick23")
@ResponseBody
public void save23(String username, MultipartFile[] uploadFile) throws IOException {
System.out.println(username);
for (MultipartFile multipartFile : uploadFile) {
String originalFilename = multipartFile.getOriginalFilename();
multipartFile.transferTo(new File("C:\\upload\\"+originalFilename));
}
}
12. SpringMVC的拦截器
12.1 什么是springMVC的拦截器
- SpringMVC的拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
- 将拦截器按照一定的顺序联结成一条链,这条链称为拦截器链(InterceptorChain)。在访问被拦截的方法和字段时,拦截器就会按照其之前定义的顺序被调用。
- 拦截器是AOP思想的具体应用。
12.2 过滤器Filter和拦截器的区别
| 区别 | 过滤器Filter | 拦截器 |
|---|---|---|
| 使用范围 | 是servlet规范中的一部分,任何java web工程都可以使用 | 是springmvc框架自己的,只有使用 了springmvc框架的工程才能用。 |
| 拦截范围 | 在url-pattern中配置了/*后,可以对所有要访问的资源进行拦截 | 只会拦截访问的控制器方法,如果访问的是jsp,html,css,image或者js是不会拦截的 |
- WEB-INF目录下的资源浏览器不能直接访问,需要访问controller方法,由方法通过视图解析器的解析才能访问web-inf下的资源
- 这里要与之前的前端控制器有所区分,在前端控制器配置那里,我们在web.xml里配置了拦截所有的客户端请求,统一交由前端控制器dispatcherServlet来处理,如果访问的是对应的controller方法,则交由controller方法处理,如果是静态资源,则交由tomcat默认的servlet来处理。
- 通过上面的图片可知webapp下面包括css,img等资源和web-inf目录。除了wen-inf下面的资源,其他的都可以在浏览器输入特定的地址来访问。web-inf则必须由controller经视图解析器解析后来访问。
12.3 快速入门:自定义一个验证用户登录的拦截器
实现思路:
- 有一个登陆页面,需要写一个controller访问页面。
- 登陆页面有一提交表单的动作。需要在controller中处理。判断用户名密码是否正确。如果正确, 向session中写入用户信息。返回登陆成功
- 拦截用户请求,判断用户是否登陆。如果用户已经登陆。放行, 如果用户未登陆,跳转到登陆页面
代码
- 编写一个登录界面login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>登录页面</h1>
<hr>
<form action="/user/login">
用户名:<input type="text" name="username"> <br>
密码: <input type="password" name="pwd"> <br>
<input type="submit" value="提交">
</form>
</body>
</html>
- 编写一个登录成功的界面success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>登录成功界面</h1>
<hr>
${user}
<a href="/user/logout">注销</a>
</body>
</html>
- 在默认的index.jsp上编写开始页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<h1>首页</h1>
<hr>
<a href="user/jumpLogin">登录</a>
<a href="user/jumpSuccess">成功页面</a>
</body>
</html>
- 编写controller处理请求
package com.southwind.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
/**
* Created with Intellij IDEA
* Description:
* user: CoderChen
* Date: 2021-05-05
* Time: 12:24
*/
@Controller
@RequestMapping("/user")
public class userController {
// 跳转到登录界面
@RequestMapping("/jumpLogin")
public String jumpLogin() {
return "login";
}
// 跳转到成功界面
@RequestMapping("/jumpSuccess")
public String jumpSuccess() {
return "success";
}
// 登录提交
@RequestMapping("/login")
public String login(HttpSession session, String username, String pwd) {
System.out.println("接收前端===" + username);
session.setAttribute("user", username);
return "success";
}
// 退出登录
@RequestMapping("/logout")
public String logout(HttpSession session) {
// session过期
//这里补充一下session的作用域是一个用户打开网站的那一刻直到浏览器关闭,这期间无论在该网站访问多少资源
//都属于同一个session
session.invalidate();
return "login";
}
}
- 配置springmvc.xml中的视图解析器和注解扫描
**注意:**到目前我们可以访问登录这个界面。(如图)
**存在的问题:**即使没有登录成功也可以从首页进入登录成功界面。实际开发中不能这样做。因此就需要进行拦截器的设置,来拦截未登录就成功进入登录成功界面的行为。
- 编写用户登录拦截请求
package com.southwind.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* Created with Intellij IDEA
* Description:
* user: CoderChen
* Date: 2021-05-05
* Time: 19:59
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("uri: " + request.getRequestURI());
//如果是登录页面就放行
if (request.getRequestURI().contains("login")) {
return true;
}
//如果用户已经登录也放行
HttpSession session = request.getSession();
if (session.getAttribute("user") != null) {
return true;
}
//用户没有登录就跳转到登录界面
request.getRequestDispatcher("/login.jsp").forward(request,response);
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.xml里配置注册拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="loginInterceptor" class="com.southwind.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
如图:
- 根据springmvc.xml里关于拦截器的配置,对所有请求都进行了拦截。然后根据条件判断是否放行。
12.4 拦截器相应api和配置的说明:
自定义拦截器类的说明:
拦截器中方法的说明
| 方法名 | 说明 |
|---|---|
| preHandle() | 方法将在请求处理之前进行调用,如果返回true就执行下一个拦截器,如果为false就不执行下一个拦截器。 |
| postHandle() | 在请求处理方法执行后才被调用,前提是preHandle()方法返回true。它会在DispatcherServlet进行视图返回渲染之前调用,所以我们可以在这个方法中对controller处理之后的ModelAndView对象进行操作。 |
| afterCompletion() | 在整个请求结束后执行,做清理工作,前提是preHandle()返回值为true |
- 如果有多个拦截器,则在xml里配置在前的先执行,配置在后的后执行。
- 拦截器中方法执行顺序是:preHandle()—>目标资源—->postHandle()——>afterCompletion()
springMVC.xml里拦截器配置的说明:
<mvc:interceptors>
<mvc:interceptor>
<!--对哪些资源进行拦截操作-->
<!--/admin/** 拦截的是/admin/下的所有-->
<!--/** 包括路径及其子路径,即拦截所有的资源-->
<mvc:mapping path="/**"/>
<!--bean配置的就是拦截器-->
<bean id="loginInterceptor" class="com.southwind.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
12.5 拦截器的执行过程说明:
- 当请求从客户端传过来时,首先会经过dispatcherServlet前端控制器处理,然后将请求交由handlerMapping处理器映射器来找具体的handler,handlerMapping处理器映射器在处理过程中会根据handler和拦截器生成一个处理器执行链返回给前端控制器,前端控制器根据这个链来依次执行操作,将它们交由处理器适配器HandlerAdaptor,执行后边的操作。
- 这个过程中拦截器的定义点是HandlerMapping处理器映射器。拦截器有三个方法,其中preHandle()方法 在业务处理器处理请求之前被调用,SpringMVC 中的Interceptor是链式调用的,在一个应用中或者说是在一个请求中可以同时存在多个Interceptor。每个Interceptor 的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor中的preHandle 方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值Boolean 类型的,当它返回为false 时,表示请求结束,后续的Interceptor 和Controller都不会再执行;当返回值为true 时就会继续调用下一个Interceptor 的preHandle 方法,如果已经是最后一个Interceptor 的时候就会是调用当前请求的Controller 方法。
- postHandle():这个方法在当前请求进行处理之后,也就是Controller方法调用之后执行,但是它会在DispatcherServlet 进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理之后的ModelAndView 对象进行操作。postHandle 方法被调用的方向跟preHandle 是相反的,也就是说先声明的Interceptor 的postHandle 方法反而会后执行。
- afterCompletion():该方法也是需要当前对应的Interceptor 的preHandle 方法的返回值为true 时才会执行。顾名思义,该方法将在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作的。afterCompletion方法被调用的方向和perHandle也是相反的,先声明的Interceptor的afterCompletion方法后执行。
- 参考文章:springMVC框架--springMVC拦截器原理(五)_愚人节第二天的博客-CSDN博客_spring拦截器实现原理