目录
@ RequestMapping 注解应用(处理请求地址映射的注解)
带有 @RequestParam 的 @RequestMapping
SpringMVC
@Controller 注解应用
推荐使用@Controller注解声明Controller组件,这样可以使得Controller定义更加灵活,可以不用实现Controller接口,请求处理的方法也可以灵活定义
@Controller
public class HelloController{
public String execute() throws Exception {
return "hello";
}
}
@ RequestMapping 注解应用(处理请求地址映射的注解)
在控制器中,在处理请求的方法之前添加@RequestMapping
注解,可以配置请求路径与处理请求的方法的映射关系!
RequestMapping是一个用来处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
在@RequestMapping
注解的源代码中有:
/**
* The primary mapping expressed by this annotation.
* <p>This is an alias for {@link #path}. For example,
* {@code @RequestMapping("/foo")} is equivalent to
* {@code @RequestMapping(path="/foo")}.
* <p><b>Supported at the type level as well as at the method level!</b>
* When used at the type level, all method-level mappings inherit
* this primary mapping, narrowing it for a specific handler method.
* <p><strong>NOTE</strong>: A handler method that is not mapped to any path
* explicitly is effectively mapped to an empty path.
*/
@AliasFor("path")
String[] value() default {};
由于value
是默认的属性,所以,平时所使用到的@RequestMapping("reg.do")
其实配置的就是这个value
属性的值!
它的数据类型是String[]
,所以,在配置时,可以同时配置多个路径!例如:
// 【显示注册页面】
@RequestMapping({"reg.do", "register.do"})
public String reg() {
System.out.println("UserController.reg()");
return "register";
}
则通过reg.do
和register.do
均可使得reg()
方法被执行,而方法的代码是不变的,最终的效果就是这2个路径都可以打开同一个页面!
在源代码中,还有:
/**
* The path mapping URIs (e.g. {@code "/profile"}).
* <p>Ant-style path patterns are also supported (e.g. {@code "/profile/**"}).
* At the method level, relative paths (e.g. {@code "edit"}) are supported
* within the primary mapping expressed at the type level.
* Path mapping URIs may contain placeholders (e.g. <code>"/${profile_path}"</code>).
* <p><b>Supported at the type level as well as at the method level!</b>
* When used at the type level, all method-level mappings inherit
* this primary mapping, narrowing it for a specific handler method.
* <p><strong>NOTE</strong>: A handler method that is not mapped to any path
* explicitly is effectively mapped to an empty path.
* @since 4.2
*/
@AliasFor("value")
String[] path() default {};
结合以上2段源代码,可以看到value
属性与path
属性是完全相同的!如果需要显式的指定属性名称时,使用path
可以更好的表现语义,该属性名称是从4.2版本加入的!
在源代码还有:
/**
* The HTTP request methods to map to, narrowing the primary mapping:
* GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
* <p><b>Supported at the type level as well as at the method level!</b>
* When used at the type level, all method-level mappings inherit
* this HTTP method restriction (i.e. the type-level restriction
* gets checked before the handler method is even resolved).
*/
RequestMethod[] method() default {};
以上源代码说明在使用@RequestMapping
时,可以配置名为method
的属性,该属性的值的类型是RequestMethod[]
类型,默认值是无。
该属性的作用是“配置所映射的HTTP请求方式”,如果没有配置该属性,表示“可以通过任何请求方式访问该路径”!如果显式的配置了该属性,则只有配置值对应的请求方式才是允许的,而没有被配置值的请求方式将不被允许!
请求方式限制为POST类型:
@RequestMapping(value="handle_reg.do", method=RequestMethod.POST)
如果仍尝试使用GET或其它不被允许的请求方式,将会出现405错误:
HTTP Status 405 – Method Not Allowed
并且,还伴随具体的提示信息:
Request method 'GET' not supported
@RequestMapping 处理 HTTP 的各种方法
请求的method类型
Spring MVC 的 @RequestMapping 注解能够处理 HTTP 请求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。
所有的请求默认都会是 HTTP GET 类型的。
为了能降一个请求映射到一个特定的 HTTP 方法,你需要在 @RequestMapping 中使用 method 来声明 HTTP 请求所使用的方法类型,如下所示:
@RestController
@RequestMapping("/home")
public class IndexController {
@RequestMapping(method = RequestMethod.GET)
String get() {
return "Hello from get";
}
@RequestMapping(method = RequestMethod.DELETE)
String delete() {
return "Hello from delete";
}
@RequestMapping(method = RequestMethod.POST)
String post() {
return "Hello from post";
}
@RequestMapping(method = RequestMethod.PUT)
String put() {
return "Hello from put";
}
@RequestMapping(method = RequestMethod.PATCH)
String patch() {
return "Hello from patch";
}
}
method: 指定请求的method类型, 分为GET、POST、PUT、DELETE等;
由于method
属性的值类型是RequestMethod[]
,所以,可以设置为允许多种请求方式,例如:
@RequestMapping(path="login.do", method={RequestMethod.GET, RequestMethod.POST})
@RequestMapping 快捷方式
Spring 4.3 引入了方法级注解的变体,也被叫做 @RequestMapping 的组合注解。组合注解可以更好的表达被注解方法的语义。它们所扮演的角色就是针对 @RequestMapping 的封装,而且成了定义端点的标准方法。
例如,@GetMapping 是一个组合注解,它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一个快捷方式。
方法级别的注解变体有如下几个:
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- @PatchMapping
如果需要将请求方式限制为固定的某1种,还可以使用简化后的注解!例如使用@PostMapping
注解,就可以将请求方式限制为POST
类型,在使用时,只需要配置请求路径即可,例如:
/ @RequestMapping(value="handle_reg.do", method=RequestMethod.POST)
@PostMapping("handle_reg.do")
在@PostMapping
注解的源代码中,关于该注解的声明是:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.POST)
public @interface PostMapping {
}
在该注解的声明之前添加了@RequestMapping(method = RequestMethod.POST)
就表示当前@PostMapping
注解具有@RequestMapping
的作用特点且已经将请求方式限制为POST
了!
除此以外,还有@GetMapping
、@PutMapping
、@DeleteMapping
、@PatchMapping
,均对应某1种请求方式!
@RequestMapping 来处理多个 URI
你可以将多个请求映射到一个方法上去,只需要添加一个带有请求路径值列表的 @RequestMapping 注解就行了。
@RestController
@RequestMapping("/home")
public class IndexController {
@RequestMapping(value = {
"",
"/page",
"page*",
"view/*,**/msg"
})
String indexMultipleMapping() {
return "Hello from index multiple mapping.";
}
}
如你在这段代码中所看到的,@RequestMapping 支持统配符以及ANT风格的路径。前面这段代码中,如下的这些 URL 都会由 indexMultipleMapping() 来处理:
localhost:8080/home
localhost:8080/home/
localhost:8080/home/page
localhost:8080/home/pageabc
localhost:8080/home/view/
localhost:8080/home/view/view
使用 @RequestMapping 来处理请求参数
带有 @RequestParam 的 @RequestMapping
@RequestParam 注解配合 @RequestMapping 一起使用,可以将请求的参数同处理方法的参数绑定在一起。
@RequestParam 注解使用的时候可以有一个值,也可以没有值。这个值指定了需要被映射到处理方法参数的请求参数, 代码如下所示:
@RequestMapping(value = "/id")
String getIdByValue(@RequestParam("id") String personId) {
System.out.println("ID is " + personId);
return "Get ID from query string of URL with value element";
}
@RequestMapping(value = "/personId")
String getId(@RequestParam String personId) {
System.out.println("ID is " + personId);
return "Get ID from query string of URL without value element";
}
使用规范
如果需要将请求方式限制为某1种,则应该使用以上这些简化的注解,如果需要同时允许多种不同的请求方式,应该使用@RequestMapping
!
关于@RequestMapping
注解,除了添加在处理请求的方法之前,还可以添加在控制器类的声明之前!例如:
@Controller
@RequestMapping("user")
public class UserController {
}
当控制器类的声明之前配置了@RequestMapping("user")
后,当前类中映射的所有请求路径中都需要添加user
这个层级,例如,在没有添加该配置之前时,访问路径是:
http://localhost:8080/springmvc02/reg.do
http://localhost:8080/springmvc02/login.do
添加了配置之后,访问路径是:
http://localhost:8080/springmvc02/user/reg.do
http://localhost:8080/springmvc02/user/login.do
通常,推荐为每一个控制器类的声明之前都添加该注解的配置!
可以看到 ,SpringMVC框架在处理配置的路径时,会把类之前的@RequestMapping
配置值与方法之前的配置值拼接起来作为完整的访问路径,但是,开发人员并不需要考虑配置值的左右两侧的/
符号,例如以下配置是等效的:
在控制器类之前的配置 | 在方法之前的配置 |
---|---|
user | reg.do |
user | /reg.do |
user/ | reg.do |
user/ | /reg.do |
/user | reg.do |
/user | /reg.do |
/user/ | reg.do |
/user/ | /reg.do |
在实际使用时,推荐使用第1种即可!如果使用其它的配置风格也是可以的,但是,在同一个项目中,应该只使用1种风格的配置!
@ResponseBody
@ResponseBody注解的作用是将controller的方法返回的对象通过适当的转换器转换为指定的格式之后,写入到response对象的body区,通常用来返回JSON数据或者是XML数据,需要注意的呢,在使用此注解之后不会再走试图处理器,而是直接将数据写入到输入流中,他的效果等同于通过response对象输出指定格式的数据。
@RequestMapping(path = "/hello", method = RequestMethod.POST)
@ResponseBody
public String helloWorld() {
return "Hello SpringMVC"
}
拦截器
拦截器的作用
拦截器(Interceptor
)在SpringMVC框架中可以作用于若干个不同的请求,且经过拦截器的处理后,可以选择对这些请求进行阻止(不允许继续向后续的流程中执行),或选择放行!
注意:拦截器的作用并不一定是”拦“下来就不允许执行了,可能某些拦截器的做法就是”拦“下来后全部放行!
拦截器接口
当需要要使用拦截器时,首先,需要自定义类,实现HandlerInterceptor
接口,并在重写的方法中添加输出语句,以便于观察方法的执行时间点及执行的先后顺序:
拦截器必须实现HandlerInterceptor接口,这个接口有以下3个方法
- preHandle(.)
- 处理器执行前被调用。方法返回true表示会继续调用其他拦截器和处理器;返回false表示中断流程,不会执行后续拦截器和处理器
- postHandle(.)
- 处理器执行后、视图处理前调用。此时可以通过modelAndView对象对模型数据进行处理或对视图进行处理
- afterCompletion(.)
- 整个请求处理完毕后调用, 如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理。只有preHandle返回true时才会执行afterCompletion方法
package cn.tedu.spring;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("LoginInterceptor.preHandle()");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("LoginInterceptor.postHandle()");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("LoginInterceptor.afterCompletion()");
}
}
需要对拦截器进行配置,关于该配置的写法要求:
-
相关的配置需要写在某个类中,这个类需要实现
WebMvcConfigurer
接口,且在类的声明之前必须添加@Configuration
和@EnableWebMvc
这2个注解,且这个类的对象必须是初始化类的getServletConfigClasses()
方法的返回值; -
在以上类中重写
addInterceptors()
方法,以配置拦截器。
例如使用原本存在的SpringMvcConfig
为作配置类:
package cn.tedu.spring;
@EnableWebMvc
@Configuration
@ComponentScan("cn.tedu.spring")
public class SpringMvcConfig implements WebMvcConfigurer {
private String characterEncoding = "utf-8";
@Bean
public ViewResolver viewResolver() {
// 省略
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
HandlerInterceptor interceptor = new LoginInterceptor();
// 注意:表示拦截器处理的路径时,各路径必须使用 / 作为第1个字符
registry.addInterceptor(interceptor).addPathPatterns("/index.do");
}
}
在一个项目中,允许同时存在若干个拦截器,配置的先后顺序决定了执行顺序,如果某个请求会经过多个拦截器,只有这些拦截器全部都放行,才可以继续向后执行,只要其中任何一个拦截器的处理结果是阻止运行,该请求就不可以向后执行了!
通过运行效果可以观察到:
- 当拦截器中的
preHandle()
方法返回true
时,会行执行拦截器中的preHandle()
方法,再执行需要请求的控制器中的方法,再执行拦截器中的postHandle()
和afterCompletion()
方法; - 当拦截器中的
preHandle()
方法返回false
时,只会执行拦截器中的preHandle()
方法,且客户端的浏览器窗口将显示一片空白。 - 只有拦截器中的
preHandle()
方法才是真正意义上的”拦截“方法!
回到LoginInterceptor
类中,重写preHandle()
方法以判断阻止或放行:
Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("LoginInterceptor.preHandle()");
// 如果已经登录,则放行,如果未登录,则阻止且重定向到登录界面
HttpSession session = request.getSession();
if (session.getAttribute("username") == null) {
String contextPath = request.getContextPath();
response.sendRedirect(contextPath + "/user/login.do");
return false;
}
return true;
}
拦截器的使用
关于拦截器的配置,首先,每个拦截器都可以配置若干个拦截的路径!关于配置拦截路径时调用的addPathPatterns()
方法,其源代码是:
/**
* Add URL patterns to which the registered interceptor should apply to.
*/
public InterceptorRegistration addPathPatterns(String... patterns) {
return addPathPatterns(Arrays.asList(patterns));
}
/**
* List-based variant of {@link #addPathPatterns(String...)}.
* @since 5.0.3
*/
public InterceptorRegistration addPathPatterns(List<String> patterns) {
this.includePatterns.addAll(patterns);
return this;
}
实际使用时,可以写成例如:
registry.addInterceptor(interceptor).addPathPatterns("/index.do", "/user/password.do");
或者写成:
List<String> pathPatterns = new ArrayList<>();
pathPatterns.add("/index.do");
pathPatterns.add("/user/password.do");
registry.addInterceptor(interceptor).addPathPatterns(pathPatterns);
在配置路径时,还可以使用星号(*
)作为通配符,例如,可以将/blog/*
配置到拦截路径中,则/blog/delete.do
、/blog/edit.do
等路径都可以被匹配!
但是,需要注意的是:1个星号(*
)只能表示某层级下的资源,不可以匹配到若干个层级!例如配置为/blog/*
时,就无法匹配到/blog/2020/list.do
!如果一定匹配若干个层级,必须使用2个连续的星号(**
),例如配置为/blog/**
,则可以匹配到/blog/list.do
、/blog/2020/list.do
、/blog/2020/07/list.do
……
在注册拦截器之后,还可以调用excludePathPatterns()
方法添加”排除“的路径,被”排除“的路径将不会被拦截器处理!所以,也可以理解为”例外“或”白名单“,例如:
List<String> pathPatterns = new ArrayList<>();
pathPatterns.add("/user/reg.do");
pathPatterns.add("/user/handle_reg.do");
pathPatterns.add("/user/login.do");
pathPatterns.add("/user/handle_login.do");
registry.addInterceptor(interceptor)
.addPathPatterns("/user/**")
.excludePathPatterns(pathPatterns);
注意:在调用方法时,必须先调用addPathPatterns()
方法然后再调用excludePathPatterns()
方法!
拦截器与过滤器的区别
相同
拦截器与过滤器都是可以作用于若干个不同的请求的,在处理请求之前将执行拦截器或过滤器中的代码,并且,都能够实现放行或阻止的效果,并且,都有”链“的概念,在同一个项目中允许存在若干个拦截器或过滤器,同一个请求需要经历多个拦截器或过滤器,只有这些拦截器或过滤器全部放行,才能向后执行!
区别
-
过滤器Filter是Java EE中的组件,则任何Java EE项目都可以使用过滤器,而拦截器Interceptor是SpringMVC框架中的组件,只有使用了SpringMVC框架的Java EE项目才可以使用拦截器,并且,只有被SpringMVC框架处理的请求才可能被拦截器处理,例如将SpringMVC框架处理的路径设置为
*.do
时,直接访问HTML页面、图片等资源将不会被拦截器处理; -
过滤器Filter是执行在所有Servlet组件之前的,而拦截器Interceptor的第1次执行是在
DispatcherServlet
之后,且在Controller组件之前的(当然,使用过滤器时,也许是通过Servlet来处理请求的,使用拦截器时,是通过Controller来处理请求的,所以,这2者都是在处理请求之前执行,所以,一般情况下,差异并不明显); -
过滤器Filter只能配置过滤路径(黑名单),而拦截器Interceptor既可以配置拦截路径(黑名单),又可以配置排除路径(例外,白名单),后者的配置更加灵活!
使用规范
通过分析以上区别,可以发现:通过过滤器Filter实现的效果,改为使用拦截器Interceptor基本上都可以实现!同时,拦截器还具备”配置更加灵活“的特点,所以,在绝大部分情况下,应该优先使用拦截器!
当然,过滤器也具有拦截器无法取代的特点,就是”执行时间点“非常早,它是执行在所有Servlet组件之前的,所以,如果某个需要被”拦“下来执行的任务是非常早期就要执行,则必须使用过滤器!
例如,SpringMVC框架默认使用的编码是ISO-8859-1
,是不支持中文的,所以,使用POST
提交的请求参数中,只要存在非ASCII码字符,就会出现乱码,如果需要自定义编码,需要在项目的初始化类中重写getServletFilters()
方法,并在该方法中返回SpringMVC框架自带的字符编码过滤器,且设置编码,例如:
中文乱码解决方案
在表单提交时,如果遇到中文字符会出现乱码现象,Spring提供了-个CharacterEncodingFilter过滤器,可用于解决乱码问题。
CharacterEncodingFilter使用时需要注意以下问题
- 表单数据以POST方式提交
- 在web.xml中配置CharacterEncodingFilter过滤器
- 页面编码和过滤器指定编码要保持一致
@Override
protected Filter[] getServletFilters() {
return new Filter[] { new CharacterEncodingFilter("UTF-8") };
}
SpringMVC阶段小结
-
【理解】SpringMVC框架的作用:解决V-C交互的问题;
-
【理解】SpringMVC框架的核心执行流程图;
-
【掌握】通过SpringMVC框架接收并处理客户端的请求:
-
SpringMVC项目的搭建:添加
spring-webmvc
依赖,配置不使用web.xml,添加Tomcat环境,创建SpringMVC的配置类,创建初始化项目的类并加载SpringMVC的配置类、设置SpringMVC框架所处理的请求路径; -
创建控制器类:自定义名称,必须放在组件扫描的包或其子孙包中,必须添加
@Controller
注解; -
创建处理请求的方法:使用
@RequestMapping
系列注解配置请求路径,使用public
访问权限,暂定使用String
作为返回值类型,方法名称可以自定义,方法的参数列表可以按需设计。
-
-
【掌握】通过SpringMVC框架接收客户端提交的请求参数:
-
将请求参数逐一声明为处理请求的方法的参数;
-
将多个请求参数封装到自定义对象中,并将自定义的数据类型声明为处理请求的方法的参数。
-
-
【掌握】使用SpringMVC封装转发的数据到视图组件,并且在视图组件显示转发的数据;
-
【理解】转发与重定向的区别;
-
【掌握】Session的使用原则与使用方式;
-
【掌握】拦截器的使用与配置;
-
【理解】拦截器与过滤器的区别;
-
【掌握】解决POST请求中文乱码的问题;
-
【掌握】阅读简单的注解源代码,例如
@RequestMapping
、@RequestParam
等。