SpringMVC参数解析ArgumentResolver(三)

测试

准备测试环境

SimpleController.java

@RestController
public class SimpleController {

    @ModelAttribute("nickname")
    public String init(Model model) {
        System.out.println("Controller: SimpleController init 执行");
        model.addAttribute("postCode", "225431");
        return "Rick";
    }

    @GetMapping("request")
    public Map<String, Object> request(@Validated User user,
                                   BindingResult bindingResult, //必须紧跟@Valid/@Validated对象后面
                                   @RequestParam(required = false) @Validated @Min(18) Integer age,
                                   @RequestParam Map<String, Object> requestParamMap,
                                   @RequestParam("names") List<String> namesList,
                                   String[] names,
                                   @ModelAttribute("postCode") String postCode,
                                   @ModelAttribute("nickname") String nickname,
                                   Map<String, Object> modelMap,
                                   Model model,
                                   HttpServletRequest request) {
        System.out.println("Controller: request 执行");

        Map<String, Object> resultMap = new HashMap<>();

        resultMap.put("requestParamMap", requestParamMap);
        resultMap.put("namesList", namesList);
        resultMap.put("names", names);

        resultMap.put("user", user);

        resultMap.put("age", age);
        resultMap.put("postCode", postCode);
        resultMap.put("nickname", nickname);
        resultMap.put("bindingResult", bindingResult.getAllErrors());

        modelMap.put("hobby", "swimming");
        model.addAttribute("sex", "男");
        request.setAttribute("home", "TaiXing");
        return resultMap;
    }
}
  • 请求
http://localhost:8080/request?age=1&names=Jim,Tom&name=Rick
  • 返回
{
    "names": ["Jim", "Tom"],
    "bindingResult": [],
    "namesList": ["Jim", "Tom"],
    "nickname": "Rick",
    "postCode": "225431",
    "requestParamMap": {
        "age": "1",
        "names": "Jim,Tom",
        "name": "Rick"
    },
    "user": {
        "age": 1,
        "name": "Rick"
    },
    "age": 1
}

分析

  • @ModelAttribute 写在方法上,返回值就是model的value,init在每次请求都会执行。init中有2个model属性 nickname postCode ,值分别是“Rick”“225432”
  • 对象 User 前面加了@Valid/@Validated 解析器 ModelAttributeMethodProcessor 会进行字段的验证。如果要收集异常,那么后面紧跟BindingResult。如果没有紧跟,那么如果验证失败将会抛出异常 BindException
    源代码查看ModelAttributeMethodProcessor => validateIfApplicable(binder, parameter)
    => Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
  • age 使用 RequestParamMethodArgumentResolver 解析,解析后对字段类型由String转换成Integer,该解析器不支持验证,所以虽然加了“@Validated @Min(18)”但是并不会验证
  • requestParamMap 将所有的请求参数值放到requestParamMap中,如果参数是数组,只放第一个
  • namesList 和 names 是“简单类型” 由RequestParamMethodArgumentResolver 解析,解析后对字段类型由String转换成对应的类型
  • postCodenickname 参数使用注解 @ModelAttribute 表示从model中获取值,这里@ModelAttribute的属性name不要省略
  • model 使用解析器 ModelMethodProcessormodelMap 使用解析器 MapMethodProcessormodelMapmodel 是同一个引用。 视图解析器比如ThymeleafView会将model中的值放入request scope中
  • request 使用 ServletRequestMethodArgumentResolver 进行解析

自定义参数解析器

系统提供了很多默认的参数处理器

0 = {RequestParamMethodArgumentResolver@8604} 
1 = {RequestParamMapMethodArgumentResolver@8605} 
2 = {PathVariableMethodArgumentResolver@8606} 
3 = {PathVariableMapMethodArgumentResolver@8607} 
4 = {MatrixVariableMethodArgumentResolver@8608} 
5 = {MatrixVariableMapMethodArgumentResolver@8609} 
6 = {ServletModelAttributeMethodProcessor@8610} 
7 = {RequestResponseBodyMethodProcessor@8611} 
8 = {RequestPartMethodArgumentResolver@8612} 
9 = {RequestHeaderMethodArgumentResolver@8613} 
10 = {RequestHeaderMapMethodArgumentResolver@8614} 
11 = {ServletCookieValueMethodArgumentResolver@8615} 
12 = {ExpressionValueMethodArgumentResolver@8616} 
13 = {SessionAttributeMethodArgumentResolver@8617} 
14 = {RequestAttributeMethodArgumentResolver@8618} 
15 = {ServletRequestMethodArgumentResolver@8619} 
16 = {ServletResponseMethodArgumentResolver@8620} 
17 = {HttpEntityMethodProcessor@8621} 
18 = {RedirectAttributesMethodArgumentResolver@8622} 
19 = {ModelMethodProcessor@8623} 
20 = {MapMethodProcessor@8624} 
21 = {ErrorsMethodArgumentResolver@8625} 
22 = {SessionStatusMethodArgumentResolver@8626} 
23 = {UriComponentsBuilderMethodArgumentResolver@8627} 
24 = {PrincipalMethodArgumentResolver@8629} 
25 = {RequestParamMethodArgumentResolver@8630} 
26 = {ServletModelAttributeMethodProcessor@8631} 

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new HandlerMethodArgumentResolver() {

            @Override
            public boolean supportsParameter(MethodParameter parameter) {
                return User.class == parameter.getParameterType();
            }

            @Override
            public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
                User user = new User();
                user.setName("Green");
                user.setAge(92);
                return user;
            }
        });
    }
}

这个参数解析器会在 ServletModelAttributeMethodProcessor 之前执行,非简单对象会先使用自定义的解析器解析。自定义的参数解析器的应用场景,我们可以根据token解析出用户信息放入参数中

GitHub:https://github.com/jkxyx205/spring-boot-learn/tree/master/spring-mvc-argument-resolver

SpringMVC添加拦截器Interceptor(二)

如何添加Interceptor

实现接口 WebMvcConfigurer 并重写方法 addInterceptors

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HandlerInterceptor() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
                System.out.println("Interceptor: HandlerInterceptor preHandle 执行目标方法是:" + handler);
                return true;
            }

            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
                System.out.println("Interceptor: HandlerInterceptor postHandle 执行,视图名称是:" + modelAndView);
            }

            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                System.out.println("Interceptor: HandlerInterceptor afterCompletion 执行,查看异常:" + ex);
            }
        });
    }
}
  • preHandle 在目标方法执行之前调用,可以对目标方法进行拦截,阻止继续执行
  • postHandle 在目标方法执行之后,视图渲染之前执行,可以对 ModelAndView 进行获取或操作
  • afterCompletion 在视图渲染之后执行,可以获取异常
Interceptor: HandlerInterceptor preHandle 执行目标方法是:com.rick.spring.mvc.InterceptorTestApplication#index()
Interceptor: HandlerInterceptor postHandle 执行,视图名称是:ModelAndView [view="index"; model={}]
Interceptor: HandlerInterceptor afterCompletion 执行,查看异常:null

github地址:https://github.com/jkxyx205/spring-boot-learn/tree/master/spring-mvc-inteceptor

SpringMVC添加过滤器Filter(一)

实现方式

第一种方式

继承自 OncePerRequestFilter 并标记 @Component 组件

@Component
public class MyFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Filter: MyFilter 执行之前");
        chain.doFilter(request, response);
        System.out.println("Filter: MyFilter 执行之后");
    }

}

第二种方式

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean filterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
                System.out.println("Filter: filterRegistration 执行 执行之前");
                chain.doFilter(request, response);
                System.out.println("Filter: filterRegistration 执行 执行之后");
            }
        });
        registration.addUrlPatterns("/*");
        registration.addInitParameter("paramName", "paramValue");
        registration.setName("filterRegistration");
        registration.setOrder(Integer.MIN_VALUE);
        return registration;
    }
}

这种方式可以对 Filter 做更详细的设置,如拦截的url、初始化参数、拦截器order

用途

拦截请求,可以对请求做「权限验证」,比如验证码校验。

github地址: https://github.com/jkxyx205/spring-boot-learn/tree/master/spring-mvc-filter

sharp-common集成验证Service验证

对Service中的方法的参数进行校验,该如何做呢?

Spring validator 方法级别的校验

只需要加入类注解 @Validated

@Service
@Validated
public class UserService {

    public void save(@Valid User user,
                     @Min(value = 22, message = "不能小于22岁") Integer age) {
        System.out.println("userService save...");
    }

    public void save2(@NotBlank String title) {
        System.out.println("userService save3...");
    }
}

spring 在此基础上进行了扩展,添加了 MethodValidationPostProcessor 拦截器,可以实现对方法参数的校验。

sharp-common集成验证Service验证

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
@Configuration
@RequiredArgsConstructor
@Import({ServiceMethodValidationInterceptor.class})
public class AppConfig {
    private final Validator validator;
    @Bean
    public ValidatorHelper validatorHelper() {
        return new ValidatorHelper(validator);
    }
}
@Service
public class UserService {

    @Validated
    public void save(@Valid User user,
                     @Min(value = 22, message = "不能小于22岁") Integer age) {
        System.out.println("userService save...");
    }

    @Validated
    public void save2(@NotBlank String title) {
        System.out.println("userService save3...");
    }
}

扫描service目录下,类名是Service结尾的类。只要在方法上加注解 @Validated

参考链接:

SpringBoot注解@SpringBootApplication

@SpringBootApplication

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication

@SpringBootApplication 注解是由 @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan 三个注解合并而成的。下面分析这三个注解。

@SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

@SpringBootConfiguration 就是 @Configuration

@ComponentScan

@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

@ComponentScan 扫描Spring组件,比如注解Configuration Controller Service 等。如果没有指定扫描包,表示当前类的路径下。
SpringBoot排除 AutoConfigurationExcludeFilter 过滤器中的自动配置类。

AutoConfigurationExcludeFilter.java

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
     return this.isConfiguration(metadataReader) && this.isAutoConfiguration(metadataReader);
 }

由match方法可知,如果是Configuration,同时是AutoConfiguration才会扫描。Configuration类就是在类上加入@Configuration 的类。AutoConfiguration类就是在 spring.factories 配置的类。

/spring-boot-autoconfigure/META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
...

这些都属于自动配置类,不需要 @ComponentScan 去扫描。

EnableAutoConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration

EnableAutoConfiguration 作用:

  1. 当我们的 classpath 中存在某些 jar 或者类时,默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件,帮助我们自动配置 bean;
  2. 当我们定义自己的配置时,自动配置的 bean 将不再加载。(具体是通过 一系列的 @ConditionalOnXxx 来实现的)

EnableAutoConfiguration 是由注解 @AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class}) 组成。

  • AutoConfigurationPackage 注解的作用是将添加该注解的类所在的package 作为自动配置package进行管理。
  • @Import(AutoConfigurationImportSelector.class)
  1. 利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
  2. 调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
  3. 利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader)得到所有的组件
  4. 从META-INF/spring.factories位置来加载一个文件。
    默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
    spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factorie

@EnableXxx 注解是用在 @Configuration 标记的类上,用来自动配置和加载一些 @Configuration 类或者@bean的。 所以,@EnableXxx 一定是与 @Configuration 一起使用的。
@EnableXxx 一般会与 @Import 一起使用,通过 @Import 来达到自动配置的目的。
比如:EnableAsync EnableCaching

参考文档: