Java面试准备之Spring框架系列四

hresh 524 0

Java面试准备之Spring框架系列四

前后端传值

@PathVariable@RequestParam

@PathVariable用于获取路径参数,@RequestParam用于获取查询参数。

@GetMapping("/klasses/{klassId}/teachers")
public List<Teacher> getKlassRelatedTeachers(
         @PathVariable("klassId") Long klassId,
         @RequestParam(value = "type", required = false) String type ) {
...
}

如果我们请求的 url 是:/klasses/{123456}/teachers?type=web

那么我们服务获取到的数据就是:klassId=123456,type=web

@RequestBody

用于读取 Request 请求(可能是 POST,PUT,DELETE,GET 请求)的 body 部分并且Content-Type 为 application/json 格式的数据,接收到数据之后会自动将数据绑定到 Java 对象上去。系统会使用HttpMessageConverter或者自定义的HttpMessageConverter将请求的 body 中的 json 字符串转换为 java 对象。

我们有一个注册的接口:

@PostMapping("/sign-up")
public ResponseEntity signUp(@RequestBody @Valid UserRegisterRequest userRegisterRequest) {
  userService.save(userRegisterRequest);
  return ResponseEntity.ok().build();
}

UserRegisterRequest对象:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRegisterRequest {
    @NotBlank
    private String userName;
    @NotBlank
    private String password;
    @FullName
    @NotBlank
    private String fullName;
}

我们发送 post 请求到这个接口,并且 body 携带 JSON 数据:

{"userName":"coder","fullName":"shuangkou","password":"123456"}

这样我们的后端就可以直接把 json 格式的数据映射到我们的 UserRegisterRequest 类上。

如果没有对应的封装类,则可以将数据绑定到 Map 对象上,如下述代码所示:

@RequestMapping("/getProductsByXx")
public List<Product> queryByXx(@RequestBody Map<String,Object> objectMap){
    String productName = null;
    if(objectMap.get("eqType") != null){
        productName = objectMap.get("eqType").toString();
    }
    Integer productId = null;
    if(objectMap.get("materialCode") != null){
        productId = Integer.parseInt(objectMap.get("materialCode").toString());
    }
    return productMapper.queryByXx(productId,productName);
}

参数校验

数据的校验的重要性就不用说了,即使在前端对数据进行校验的情况下,我们还是要对传入后端的数据再进行一遍校验,避免用户绕过浏览器直接通过一些 HTTP 工具直接向后端请求一些违法数据。

JSR(Java Specification Requests) 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候进行校验了,非常方便!

校验的时候我们实际用的是 Hibernate Validator 框架。SpringBoot 项目的 spring-boot-starter-web 依赖中已经有 hibernate-validator 包,不需要引用相关依赖。

需要注意的是: 所有的注解,推荐使用 JSR 注解,即javax.validation.constraints,而不是org.hibernate.validator.constraints

一些常用的字段验证的注解

  • @NotEmpty 被注释的字符串的不能为 null 也不能为空
  • @NotBlank 被注释的字符串非 null,并且必须包含一个非空白字符
  • @Null 被注释的元素必须为 null
  • @NotNull 被注释的元素必须不为 null
  • @AssertTrue 被注释的元素必须为 true
  • @AssertFalse 被注释的元素必须为 false
  • @Pattern(regex=,flag=)被注释的元素必须符合指定的正则表达式
  • @Email 被注释的元素必须是 Email 格式。
  • @Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
  • @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size(max=, min=)被注释的元素的大小必须在指定的范围内
  • @Digits (integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
  • @Past被注释的元素必须是一个过去的日期
  • @Future 被注释的元素必须是一个将来的日期
  • ......

验证请求体(RequestBody)

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {

    @NotNull(message = "classId 不能为空")
    private String classId;

    @Size(max = 33)
    @NotNull(message = "name 不能为空")
    private String name;

    @Pattern(regexp = "((^Man|^Woman|^UGM$))", message = "sex 值不在可选范围")
    @NotNull(message = "sex 不能为空")
    private String sex;

    @Email(message = "email 格式不正确")
    @NotNull(message = "email 不能为空")
    private String email;

}

我们在需要验证的参数上加上了@Valid注解,如果验证失败,它将抛出MethodArgumentNotValidException

@RestController
@RequestMapping("/api")
public class PersonController {

    @PostMapping("/person")
    public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
        return ResponseEntity.ok().body(person);
    }
}

验证请求参数(Path Variables 和 Request Parameters)

一定一定不要忘记在类上加上 Validated 注解了,这个参数可以告诉 Spring 去校验方法参数。

@RestController
@RequestMapping("/api")
@Validated
public class PersonController {

    @GetMapping("/person/{id}")
    public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "超过 id 的范围了") Integer id) {
        return ResponseEntity.ok().body(id);
    }
}

全局处理 Controller 层异常

介绍一下我们 Spring 项目必备的全局处理 Controller 层异常。

相关注解:

  1. @ControllerAdvice :注解定义全局异常处理类
  2. @ExceptionHandler :注解声明异常处理方法

如何使用呢?拿我们在第 5 节参数校验这块来举例子。如果方法参数不对的话就会抛出MethodArgumentNotValidException,我们来处理这个异常。

@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    /**
     * 请求参数异常处理
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
       ......
    }
}

更多关于 Spring Boot 异常处理的内容,请看我的这两篇文章:

1. SpringBoot 处理异常的几种常见姿势
2.
使用枚举简单封装一个优雅的 Spring Boot 全局异常处理!

Spring自定义注解

根据注解使用的位置,分为字段注解、方法、类注解。

字段注解

字段注解一般是用于校验字段是否满足要求,hibernate-validate依赖就提供了很多校验注解 ,如@NotNull@Range等,但是这些注解并不是能够满足所有业务场景的。

首先通过@interface 声明一个注解类,以及类上的 @Target@Retention@Constraint 注解;

创建一个验证器类,需要实现 ConstraintValidator 泛型接口。第一个泛型参数类型Check:注解,第二个泛型参数Object:校验字段类型。需要实现initializeisValid方法,isValid方法为校验逻辑,initialize方法初始化工作。

方法、类注解

在开发过程中遇到过这样的需求,如只有有权限的用户的才能访问这个类中的方法或某个具体的方法、查找数据的时候先不从数据库查找,先从guava cache中查找,在从redis查找,最后查找mysql(多级缓存)。

首先通过@interface 声明一个注解类,以及类上的 @Target@Retention注解;

在拦截器中获取该注解配置的参数,然后在 preHandle() 方法中对参数值进行判断,如果满足则通过。

推荐阅读:Spring自定义注解从入门到精通

参考文献

https://github.com/Snailclimb/JavaGuide/blob/master/docs/system-design/framework/spring/spring-annotations.md

### @ConfigurationProperties

在编写项目代码时,我们要求更灵活的配置,更好的模块化整合。在 Spring Boot 项目中,为满足以上要求,我们将大量的参数配置在 application.properties 或 application.yml 文件中,通过 @ConfigurationProperties 注解,我们可以方便的获取这些参数值。

在 application.yml文件中创建这些参数:

data:
  env: config-eureka-dev
  user:
    username: eureka-client-user
    password: 1291029102111

如果使用 @Value 注解,则实体类这样设计:

@Data
@Component
public class GitConfig {

    @Value("{data.env}")
    private String env;

    @Value("{data.user.username}")
    private String username;

    @Value("${data.user.password}")
    private String password;
}

使用 @Value 注入方式比较笨重,尤其是从配置中心(Nacos或者SpringCloud-Config)动态获取最新配置信息时,@Value 无法及时获取到最新值,因此我们采用 @ConfigurationProperties 注解。

实现原理

@ConfigurationProperties注解(将配置文件中的配置,以属性的形式自动注入到实体中)可以注入在application.properties配置文件中的属性,和@Bean 或者 @Component 能生成spring bean 的注解结合起来使用;该类在加载过程中会调用AbstractAutowireCapableBeanFactory中的applyBeanPostProcessorsBeforeInitialization接口进行一些前置处理。

@ConfigurationProperties 注解其对应的bean的后置处理器为ConfigurationPropertiesBindingPostProcessor,它实现了Spring容器的postProcessBeforeInitialization方法,会在 bean 初始化之前被调用注解处理器会读取@ConfigurationProperties 注解的对象获取配置文件中的prefix,和注解对象的类成员变量然后递归将配置属性赋值给类成员变量。

@Value获取值和@ConfigurationProperties获取值比较:

Java面试准备之Spring框架系列四

@ConfigurationProperties 的基本用法非常简单:我们为每个要捕获的外部属性提供一个带有字段的类。请注意以下几点:

  • 前缀定义了哪些外部属性将绑定到类的字段上
  • 根据 Spring Boot 宽松的绑定规则,类的属性名称必须与外部属性的名称匹配
  • 我们可以简单地用一个值初始化一个字段来定义一个默认值
  • 类本身可以是包私有的
  • 类的字段必须有公共 setter 方法
  • 特别说明的一个注属性 ignoreUnknownFields = false 这个超好用,自动检查配置文件中的属性是否存在,不存在则在启动时就报错

实体类设计如下:

@Component
@Data
@ConfigurationProperties(prefix = "data")
public class GitAutoRefreshConfig {

    public static class UserInfo {
        private String username;

        private String password;

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        @Override
        public String toString() {
            return "UserInfo{" +
                    "username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }

    private String env;

    private UserInfo user;
}

更多内容推荐阅读:@ConfigurationProperties 注解使用姿势,这一篇就够了

SpringBoot——@ConfigurationProperties注解

发表评论 取消回复
表情 图片 链接 代码

分享