SpringBoot中常见的坑
SpringBoot中常见的坑
配置数据总是出错?搞清楚加载顺序吧
SpringBoot的配置文件
- SpringBoot 使用一个全局的配置文件,且配置文件名是固定的。配置文件的作用是用于修改SpringBoot自动配置的默认值
- 可以使用application.properties格式,也可以使用application.yml格式
- 由于YAML格式紧凑且可读性高,所以,SpringBoot支持并推荐使用YAML格式的配置文件
- 如果两种配置文件同时存在的时候,默认优先使用.properties配置文件(不要这么做)
SpringBoot配置文件优先级加载顺序
高优先级配置会覆盖低优先级
多配置文件互补
SpringBoot 多环境配置
- 多环境下使用spring.profile.active可以指定配置文件
- 使用占位符${spring.profiles.active},在启动命令中指定配置文件
使用指定配置文件的方式(如下指定的就是application-dev.yml)文件。
spring:
profiles:
active: dev
也可以使用占位符${spring.profiles.active}中指定
java -jar spring-escape.jar --spring.profiles.active=dev
定时任务不定时了,这到底是怎么了?
@EnableScheduling 允许定时任务执行
- fixDelay:任务结束与任务开始之间固定间隔时间
- fixedRate:两次任务开始的间隔
- initialDelay:初始化延迟
- cron:cron表达式方式
配置定时任务线程池
- 配置文件中指定定时任务线程数
spring.task.scheduling.pool.size
- 自定义定时任务的线程池,编写ScheduleConfig
第一种方式:
第二种方式:自定义定时任务线程池
@Configuration
public class ScheduleConfig {
@Bean
public TaskScheduler taskScheduler(){
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(5);
return taskScheduler;
}
}
异步任务你有没有处理好?(自定义线程池、超时、异常处理)
SpringBoot中编写异步任务
两个注解、一个原则
@EnableAsync
@Async
有没有返回值
对于异步任务的思考
默认情况下,异步任务的线程池配置是在TaskExecutionProperties
类中进行默认配置。自定义修改策略,简单的可以直接在application.yml中进行配置。复杂的可以自定义异步任务线程池
简单的配置方式
复杂的可以通过自定义线程池,可以配置自定义线程池和自定义拒绝策略
@Slf4j
@Configuration
public class AsyncTaskConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("imooc-qinyi-task-");
executor.setCorePoolSize(2);
executor.setMaxPoolSize(8);
executor.setKeepAliveSeconds(5);
executor.setQueueCapacity(100);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//设置线程池关闭的时候是否需要等待任务全部执行完成
executor.setWaitForTasksToCompleteOnShutdown(true);
//设置线程池中任务的等待时间
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
/**
* 异步任务处理方式
* @return
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// 发送报警邮件,短信,等
log.error("Async Task Has Some Error: {}, {}, {}",
ex.getMessage(),
method.getDeclaringClass().getName() + "." + method.getName(),
Arrays.toString(params));
}
};
}
}
对于异步任务有返回值的场景,如果获取结果超时,可以在Future.get()
方法中设置超时时间。
log.info("Async Process 02 Return :{}",future.get(1, TimeUnit.SECONDS));
SpringBoot默认使用Jackson,你能用好它麽?
对第二点补充 ObjectMapper初始化也是比较耗时的
@JsonIgnore: 忽略属性
@JsonIgnoreProperties: 定义在类上
定义测试类,Coupon对象和CouponStatus枚举类,使用@JsonInclude将属性为null的值进行忽略,使用JsonIgnoreProperties忽略多个想要忽略的属性,使用@JsonFormat进行日期格式化,使用@JsonProperty将userId映射为User。定义ObjectMapper配置类。
- ObjectMapperConfig.java
@Configuration
public class ObjectMapperConfig {
@Bean
@Primary
public ObjectMapper objectMapper(){
ObjectMapper mapper = new ObjectMapper();
//忽略json字符串中不识别的字段
mapper.configure(
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
false);
return mapper;
}
}
- Coupon.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({"couponCode","status"})
public class Coupon {
@JsonIgnore
private int id;
@JsonProperty("user")
private Long userId;
private String couponCode;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss")
private Date assignTime;
private CouponStatus status;
private CouponTemplate template;
@Data
@NoArgsConstructor
@AllArgsConstructor
private static class CouponTemplate{
private String name;
private String logo;
}
public static Coupon fake(){
return new Coupon(1,100L,"123456",new Date(),CouponStatus.USABLE,
new CouponTemplate("CouponTemplate","imooc"));
}
}
- CouponStatus.java
@Getter
@AllArgsConstructor
public enum CouponStatus {
USABLE("可用的",1),
USED("使用过",2);
private String desc;
private Integer code;
}
编写测试类
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestObjectMapper {
@Autowired
private ObjectMapper mapper;
@Test
public void testUseJacksonAnnotation() throws Exception{
Coupon coupon = Coupon.fake();
coupon.setTemplate(null);
log.info("ObjectMapper set Coupon : {}",
mapper.writeValueAsString(coupon));
}
}
运行测试类,输出结果为ObjectMapper set Coupon : {"user":100,"assignTime":"13:30:37"}
针对于@JsonSerialize序列化,编写序列化类,并使用注解进行绑定
- CouponSerialize.java。将Coupon对象中的属性进行反序列化,将内部类进行压平处理。
public class CouponSerialize extends JsonSerializer<Coupon> {
@Override
public void serialize(Coupon coupon, JsonGenerator generator,
SerializerProvider serializers) throws IOException {
// 开始序列化
generator.writeStartObject();
generator.writeStringField("id", String.valueOf(coupon.getId()));
generator.writeStringField("userId", coupon.getUserId().toString());
generator.writeStringField("couponCode", coupon.getCouponCode());
generator.writeStringField("assignTime",
new SimpleDateFormat("HH:mm:ss").format(coupon.getAssignTime()));
generator.writeStringField("status", coupon.getStatus().getDesc());
generator.writeStringField("name", coupon.getTemplate().getName());
generator.writeStringField("logo", coupon.getTemplate().getLogo());
// 结束序列化
generator.writeEndObject();
}
}
在Coupon类上加入@JsonSerialize(using = CouponSerialize.class)
编写测试类
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestObjectMapper {
@Autowired
private ObjectMapper mapper;
@Test
public void testUseJacksonAnnotation() throws Exception{
// Coupon coupon = Coupon.fake();
// coupon.setTemplate(null);
// log.info("ObjectMapper set Coupon : {}",
// mapper.writeValueAsString(coupon));
String jsonCoupon = " {\"id\":\"1\",\"userId\":\"100\",\"couponCode\":\"123456\",\"assignTime\":\"21:45:50\",\"status\":\"USABLE\"}";
mapper.setDateFormat(new SimpleDateFormat("HH:mm:ss"));
Coupon coupon1 = mapper.readValue(jsonCoupon, Coupon.class);
log.info("{}",coupon1);
}
}
其中,通过对反序列化的对象进行打印输出,验证了反序列化成功
测试用例运行结果为Coupon(id=0, userId=100, couponCode=123456, assignTime=Thu Jan 01 21:45:50 CST 1970, status=USABLE, template=null)