Java小白修炼手册--第五阶段-- SpringMVC框架(day03)--SpringMVC小结

目录

SpringMVC

@Controller 注解应用

@ RequestMapping 注解应用(处理请求地址映射的注解)

请求方式限制为POST类型:

@RequestMapping 处理 HTTP 的各种方法 

请求的method类型

@RequestMapping 快捷方式 

@RequestMapping 来处理多个 URI 

使用 @RequestMapping 来处理请求参数

带有 @RequestParam 的 @RequestMapping 

扫描二维码关注公众号,回复: 12856877 查看本文章

使用规范

@ResponseBody

拦截器

拦截器的作用

拦截器接口

拦截器的使用

拦截器与过滤器的区别

相同

区别

使用规范

中文乱码解决方案

SpringMVC阶段小结


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.doregister.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等。

猜你喜欢

转载自blog.csdn.net/c202003/article/details/107191540