Fork me on Gitee

SpringBoot中常见的坑

SpringBoot中常见的坑

配置数据总是出错?搞清楚加载顺序吧

SpringBoot的配置文件

  • SpringBoot 使用一个全局的配置文件,且配置文件名是固定的。配置文件的作用是用于修改SpringBoot自动配置的默认值
  • 可以使用application.properties格式,也可以使用application.yml格式
  • 由于YAML格式紧凑且可读性高,所以,SpringBoot支持并推荐使用YAML格式的配置文件
  • 如果两种配置文件同时存在的时候,默认优先使用.properties配置文件(不要这么做

image-20230709185401493

SpringBoot配置文件优先级加载顺序

image-20230709190238010

高优先级配置会覆盖低优先级

多配置文件互补

SpringBoot 多环境配置

  • 多环境下使用spring.profile.active可以指定配置文件
  • 使用占位符${spring.profiles.active},在启动命令中指定配置文件

image-20230709190759132

使用指定配置文件的方式(如下指定的就是application-dev.yml)文件。

spring:
  profiles:
    active: dev

也可以使用占位符${spring.profiles.active}中指定

java -jar spring-escape.jar --spring.profiles.active=dev

定时任务不定时了,这到底是怎么了?

image-20230709192139420

@EnableScheduling 允许定时任务执行

  1. fixDelay:任务结束与任务开始之间固定间隔时间
  2. fixedRate:两次任务开始的间隔
  3. initialDelay:初始化延迟
  4. cron:cron表达式方式

配置定时任务线程池

  • 配置文件中指定定时任务线程数 spring.task.scheduling.pool.size
  • 自定义定时任务的线程池,编写ScheduleConfig

第一种方式:

image-20230709195826845

第二种方式:自定义定时任务线程池

@Configuration
public class ScheduleConfig {
    @Bean
    public TaskScheduler taskScheduler(){
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(5);
        return taskScheduler;
    }
}

异步任务你有没有处理好?(自定义线程池、超时、异常处理)

image-20230709200504576

SpringBoot中编写异步任务

两个注解、一个原则

@EnableAsync

@Async

有没有返回值

对于异步任务的思考

默认情况下,异步任务的线程池配置是在TaskExecutionProperties类中进行默认配置。自定义修改策略,简单的可以直接在application.yml中进行配置。复杂的可以自定义异步任务线程池

简单的配置方式

image-20230709210151930

复杂的可以通过自定义线程池,可以配置自定义线程池和自定义拒绝策略

@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,你能用好它麽?

image-20230709211001888

对第二点补充 ObjectMapper初始化也是比较耗时的

image-20230709211543996

@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)

image-20230709215123502

posted @ 2023-07-09 21:57  shine-rainbow  阅读(70)  评论(0编辑  收藏  举报