Spring Boot核心知识点小结(上)
https://developer.aliyun.com/article/1493706?spm=a2c6h.13148508.setting.14.1e9b4f0eQyphEf
通过@ConfigurationProperties读取并与 bean 绑定
这种方式相较于上面那种更加强大,可以与bean
绑定,例如我们yml
的配置文件内容如下(注意配置名称必须全小写,否则会报一些奇怪的错误)
myobj: name: out-side-config email: out-side-config@qq.com
那么我们就可以编写一个类,代码如下所示,使用ConfigurationProperties
引入前缀为myobj
的配置内容即可,该配置就会将myobj
前缀下的所有配置和我们的类绑定
/** * 注意 yml配置文件不能有大写字母 */ @ConfigurationProperties(prefix = "myobj") public class MyObj { private String name; private String email; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "MyObj{" + "name='" + name + '\'' + ", email='" + email + '\'' + '}'; } }
@PropertySource读取指定的 properties 文件
有时候我们希望指定配置文件和类进行绑定,那么我们就可以使用PropertySource
注解,例如我们在resource
目录下有个student.properties
文件,内容为
name:xiaoming no:18
我们只需使用PropertySource执行路径以及配置文件名,再配合value即可完成属性绑定。
@Component @PropertySource("classpath:student.properties") public class Student { @Value("${name}") private String name; @Value("${no}") private String no; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNo() { return no; } public void setNo(String no) { this.no = no; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", no='" + no + '\'' + '}'; } }
Spring Boot 加载配置文件的优先级了解么?
答: 以下图为例,读取顺序优先取外层config
目录下的yml
文件,然后是resource
目录下的config
的yml
文件,最后才是resource
目录下的yml
配置文件。
如下图,以笔者为例,笔者在在不同路径下都配置了yml文件,最外层内容为
myConfig: 这个是helloworld配置的具体内容哦 myobj: name: out-side-config email: out-side-config@qq.com
然后我们通过测试单元查看结果,读取的配置取的是最外层
@SpringBootTest class DemoApplicationTests { @Resource private MyObj myObj; @Test void contextLoads() { //输出结果 MyObj{name='out-side-config', email='out-side-config@qq.com'} System.out.println(myObj); } }
常用的 Bean 映射工具有哪些?能不能给我说说你最常用的是那种?
答: 常见的是:MapStruct
、ModelMapper
、Dozer
、Orika
、JMapper
这几种吧。
最常用的还是MapStruct
,它的工作原理也很简单,我们声明一个转换接口后,它会在编译期为了我们生成转换实现类的字节码文件。
对此我们不妨距离一下它的使用方式,首先引入版本号、依赖、插件
版本号
<properties> <java.version>1.8</java.version> <org.mapstruct.version>1.5.3.Final</org.mapstruct.version> </properties>
依赖
<!--mapstruct依赖--> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>${org.mapstruct.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency>
插件
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${org.mapstruct.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin>
例如我们现在有个Doctor
希望转为DoctorDTO
类,代码如下所示
public class Doctor { private Integer id; private String name; private String srcAddr; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSrcAddr() { return srcAddr; } public void setSrcAddr(String srcAddr) { this.srcAddr = srcAddr; } @Override public String toString() { return "Doctor{" + "id=" + id + ", name='" + name + '\'' + ", srcAddr='" + srcAddr + '\'' + '}'; } }
DoctorDTO
类,可以看出地址的字段名为dstAddr
,和上面的srcAddr
有区别
public class DoctorDTO { private Integer id; private String name; private String dstAddr; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDstAddr() { return dstAddr; } public void setDstAddr(String dstAddr) { this.dstAddr = dstAddr; } @Override public String toString() { return "DoctorDTO{" + "id=" + id + ", name='" + name + '\'' + ", dstAddr='" + dstAddr + '\'' + '}'; } }
所以我们编写一个接口,如下所示,对于字段名不一样的,我们使用Mapping
手动配置映射关系
import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; @Mapper public interface DoctorMapper { DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class); /** * 会在编译期生成 * @param doctor * @return */ @Mapping(source = "srcAddr", target = "dstAddr") DoctorDTO toDTO(Doctor doctor); }
测试代码,可以看到bean转换完成
@Test public void testToDTO() { Integer doctorId = 15; String doctorName = "xiaoming"; Doctor doctor = new Doctor(); doctor.setId(doctorId); doctor.setName(doctorName); doctor.setSrcAddr("中国北京"); DoctorDTO doctorDTO = DoctorMapper.INSTANCE.toDTO(doctor); // 输出结果 DoctorDTO{id=15, name='xiaoming', dstAddr='中国北京'} System.out.println(doctorDTO); assertEquals(doctorId, doctorDTO.getId()); assertEquals(doctorName, doctorDTO.getName()); }
通过源码我们可以看到这个接口的实现类会在编译器生成
Spring Boot 如何监控系统实际运行状况?
答: 很简单,引入下面这个依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
然后键入下面的地址即可查看对应端点的信息
http://localhost:8080/actuator
具体可以参考下面这篇文章
集成Spring Boot Actuator很简单,难的是运用场景!
Spring Boot 如何做请求参数校验?能不能给我说说怎么使用。
答: 有两种校验框架,一个是Hibernate Validator
,还有一个是JSR(Java Specification Requests)
校验,后者比较常用,无需引入特殊的依赖。就例如我们现在有个Person类,希望名字不为空,性别是是数字最大值为2,而email必须为邮箱格式,那么我们就可以基于JSR
的注解进行说明。
public class Person { @NotNull(message = "姓名不可为空") @Size(max = 10, message = "姓名长度不可超过10位") private String name; @Max(value = 2, message = "性别最大值只能为2") private int sex; @Email(message = "邮箱格式不正确") private String email; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", sex='" + sex + '\'' + ", email='" + email + '\'' + '}'; } }
当他作为controller
的requestBody
的参数时,用法如下所示
@PostMapping("/test/hello") public void hello(@Valid Person person) { logger.info("hello {}", person.getName()); }
假如我们想校验路径参数时,我们只需在Controller
上方加一个注解@Validated
,然后对于路径参数加入校验注解Valid
+校验规则注解
即可即可。
@GetMapping("/test/hello2/{id}") public void hello2(@Valid @PathVariable("id") @Max(value = 5,message = "最大值为5") Integer id) { logger.info("hello {}", id); }
补充一下常见的一些校验注解:
1. @NotEmpty 被注释的字符串的不能为 null 也不能为空 2. @NotBlank 被注释的字符串非 null,并且必须包含一个非空白字符 3. @Null 被注释的元素必须为 null 4. @NotNull 被注释的元素必须不为 null 5. @AssertTrue 被注释的元素必须为 true 6. @AssertFalse 被注释的元素必须为 false 7. @Pattern(regex=,flag=)被注释的元素必须符合指定的正则表达式 8. @Email 被注释的元素必须是 Email 格式。 9. @Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值 10. @Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值 11. @DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值 12. @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 13. @Size(max=, min=)被注释的元素的大小必须在指定的范围内 14. @Digits(integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内 15. @Past被注释的元素必须是一个过去的日期 16. @Future 被注释的元素必须是一个将来的日期
如何使用 Spring Boot 实现全局异常处理?
答:通过@ControllerAdvice
将控制器声明为增强器,然后通过ExceptionHandler
对自己自己的异常进行处理。
例如我们想处理所有控制器的BindException,代码如下所示
/** * 统一异常处理、数据预处理等 */ @ControllerAdvice public class ControllerExceptionHandler { private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class); /** * 校验异常统一处理 * @param e * @return */ @ExceptionHandler(value = BindException.class) @ResponseBody public CommonResp validExceptionHandler(BindException e) { CommonResp commonResp = new CommonResp(); LOG.warn("参数校验失败:{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); commonResp.setSuccess(false); commonResp.setMessage(e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); return commonResp; } }
Spring Boot 中如何实现定时任务 ?
答: 操作步骤很简单,首先在启动类中添加@EnableScheduling
注解,然后编写一个定时任务bean
,然后在定时任务的方法上添加@Scheduled
注解
@Component @EnableAsync //@EnableAsync 和 @Async 使定时任务并行执行 public class AsyncScheduledTasks { private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTasks.class); private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); private List<Integer> index = Arrays.asList(6, 6, 2, 3); int i = 0; @Scheduled(fixedRate = 5000) @Async public void reportCurrentTimeWithFixedRate() { log.info("Current Thread : {}", Thread.currentThread().getName()); if (i == 0) { log.info("Start time is {}", dateFormat.format(new Date())); } if (i < 4) { try { TimeUnit.SECONDS.sleep(index.get(i)); log.info("Fixed Rate Task : The time is now {}", dateFormat.format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } i++; } } }