SpringBoot记录

〇 tips

运行检验

注册CommandLineRunner的Bean可以在容器启动后执行任务。

CommandLineRunner是一个函数式接口,输入的参数为main方法中的参数。

@Bean
CommandLineRunner config()return args -> System.out.println(args);
}

与CommandLineRunner类似的,ApplicationRunner接口,区别是这个接口使用org.springframework.boot.DefaultApplicationArguments的参数。

spring启动初始化

如果有需要在spring启动后进行初始化操作的工作,可以实现ApplicationListener接口。

@Component
public class Init implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        //执行初始化操作
    }
}

一 生命周期

1.1 Bean的生命周期

1.初始化与销毁

使用@PostConstruct@PreDestroy可以定制Bean在容器中的初始化和销毁行为。

@PostConstruct
public void init() {
    //初始化之后
}

@PreDestroy
public void end() {
    //销毁之前
}

也可以使用Java配置进行配置。

@Bean(initMethod = "init", destroyMethod="end")
public class A {
}

2.延迟加载

使用@Lazy注解,Bean将会在被调用时执行初始化。

3.依赖顺序

设置@DependsOn将表明依赖顺序,springboot的初始化顺序也会自动调整。

二 应用环境

2.1 场景

通过@Profile注解可以指定当前的运行环境,只有在profile激活(active)的情况下才会创建该类。

2.2 属性配置

@PropertySource允许读取配置文件,并通过@Value的形式将值注入到属性中。

@Configuration
//多个配置文件可以使用@PropertySources注解,采用套娃的方式获取配置
//单个配置文件泽不需要
//配置文件支持xml、properties文件,但不支持yaml文件
@PropertySources({@PropertySource("classpath:test1.properties"),@PropertySource("classpath:test2.properties")})
public class Test {
    Environment env;

    //只有一个构造函数,所以不用加@Autowired注解
    public Test(Environment env) {
        this.env = env;
    }

    //通过@Value获取配置的值
    @Value("${loginUser}")
    private String user;
}

三 Bean异步通信

3.1 事件数据

事件数据传递通过继承ApplicationEvent来实现。

public class MessageEvent entends ApplicationEvent {}

3.2 发布者Publisher

@Autowired
ApplicationEventPublisher publisher;

public void publish() {
    publisher.publish(new MessageEvent("yeah"));
}

3.3 监听者Listener

监听者可以通过实现ApplicationListener接口来监听事件。

@Component
public class MessageListener implements AppliactionListener<MessageEvent> {

    @Override
    public void eventListener(MessageEvent event) {
    }
}

也可以通过注解@EventListener来监听。

四 切面

添加额外行为(如日志记录)到现有的Bean上,不修改原来的代码。

对请求方法添加日志,设置请求参数和响应值。

可以添加一个自定义注解来进行切点拦截。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @inteface Logging {
    String value() default "";
}

4.1 添加注解

在要拦截的方法前增加注解

@Logging("添加人员")
public void addUser(User user) {}

4.2 编写切面代码

//定义切面
@Aspect
@Component
public class LoggingAspect {

    //将注解了@Logging的Bean作为条件
    //@PointCut("@annotation(io.swagger.annotations.ApiOperation)")
    @PointCut("@annotation(Logging)")
    public void annotationPointCut() {}

    //@Before,将annotationPointCut()方法中符合的Bean执行before的行为
    //接口执行前执行此方法
    @Before("annotationPointCut()")
    public void before(JoinPoint joinPoint) {
        //joinPoint代表被拦截的方法,可以获得方法的签名信息
        MethodSignature sign = (MethodSignature) jointPoint.getSifnature();
        String value = sign.getMethod().getAnnotation(Logging.class).value();
    }

    //@AfterReturning,可以获取被拦截方法的返回值
    //接口执行后,执行此方法
    @AfterReturning(pointcut = "annotationPointCut()", returing = "result")
    public void after(JoinPoint joinPoint, String result) {
        //joinPoint代表被拦截的方法,可以获得方法的签名信息
        MethodSignature sign = (MethodSignature) jointPoint.getSifnature();
        String value = sign.getMethod().getAnnotation(Logging.class).value();
    }
    
    //使用@Around注解,对被拦截方法前后进行操作
    @Around("annotationPointCut()")
    public Object around(ProceedingJoinPoint proceedJoinPoint) throws Throwable{
        //获取被拦截的参数
        Object[] args = proceedJoinPoint.getArgs();
        
        //执行接口方法,返回接口相应
        return proceedJoinPoint.proceed(args);
    }
}

Before()After()方法之间传递参数可以使用ThreadLocal线程安全传递变量值。

五 应用配置

  配置加载顺序的优先级如下:

  1. 命令行参数
  2. SPRING_APPLICATION_JSON环境变量,json字符串
  3. ServletConfig初始化参数
  4. ServletContext初始化参数
  5. JNDI(java:comp/env)
  6. Java 系统属性
  7. 操作系统变量
  8. RandomValuePropertiesSource随机值
  9. jar包外部的application-{profile}.properties/yaml
  10. jar包内部的application-{profile}.properties/yaml
  11. jar包外部的application.properties/yaml
  12. jar包内部的application.properties/yaml

5.1 通过入口main方法配置

SpringApplication app = new SpringApplication(App.class);
//关闭banner
app.setBannerMode(Banner.Mode.OFF);
//添加自定义的监听器
app.addListeners(new MyListener());
app.run(args);

5.2 通过builder配置

  SpringApplicationBuilder是一个建造者模式,可以用于定制应用启动。

new SpringApplicationBuilder()
    .bannerMode(Banner.Mode.OFF)
    .listeners(new MyListener())
    .sources(App.class)
    .build(args)
    .run();

5.3 通过配置文件进行配置

spring.main.banner-mode=off

六 多线程

  Spring提供了异步多线程任务的功能,在SpringBootApplication上添加@EnableAsync就能支持异步多线程任务。

  在需要为多线程的方法上添加@Async注解,表示这是一个多线程方法,打印线程名将看见多个实例。

  如果@Async注解在类上,表示这个类的所有方法都是异步的。

spring:
  task:
    execution:
      pool:
        core-size: 8 # 线程池核心线程数默认为8
        max-size: 16 # 线程池最大线程数为16
      thread-name-prefix: mytask- # 设置多线程的线程名前缀

七 定时计划

7.1 注解配置

Spring提供了计划任务的功能,在SpringBootApplication上添加@EnableScheduling就能支持任务计划。

在类或者方法上添加@Scheduled注解表示这是一个计划任务。

fixedRate每隔固定时间执行一次任务;
fixedDelay在上次任务完成后指定时间执行任务;
cron时间表达式,6个参数分别代表:秒、分、时、日、月、星期

7.2 动态配置

除了通过@Scheduled注解配置cron表达式来计划定时任务,也可以通过动态配置来修改。

1.计划任务池

<!-- pom文件引入定时任务依赖 -->
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
</dependency>
@Autowried
private ThreadPoolTaskScheduler taskScheduler;

private void addTask() {
    //检查cron表达式是否合法
    String cron = "";
    boolean valid = CronExpression.isValidExpression(cron);
    //添加到计划任务池中
    taskScheduler.schedule(new Runnable(), new CronTrigger(cron));
}

2.定时任务工厂

<!-- pom依赖 -->
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
</dependency>
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz-jobs</artifactId>
</dependency>
//从数据库获取定时任务配置,所以需要配置数据库连接
@Autowired
private SchedulerFactoryBean schedulerFactoryBean;

//继承QuartzJobBean,说明定时任务要执行的工作
public class JobBean extends QuartzJobBean {}

private void addTask() {
    //创建任务
    JobDetail jobDetail = JobBuilder.newJob(JobBean.class);
    
    //创建时间计划
    String cron = "";
    CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cron);
    CronTrigger trigger = TriggerBuilder.newTrigger()
        .withSchedule(scheduleBuilder)
        .build();
    
    //加入定时任务中
    schedulerFactoryBean.getScheduler().scheduleJob(jobDetail, trigger);
}

八 端点

  端点为springboot应用中自动添加的请求路径,用于监控应用行为。

8.1 常用端点

  常用端点默认为使用get请求方式,默认请求前缀为/actuator

1.actuator

  显示所有地址已暴露的端点访问信息

2.health

  显示应用的整体健康情况,实现HealthIndicator接口可以自定义健康情况

3.info

  显示应用信息,在配置文件中可以添加配置信息

  使用info.*可以设置想要展示的信息

4.shutdown

  关闭springboot应用,post请求,默认不开启。

management.endpoints.web.exposure.include=shutdown,health,info # *代表所有
management.endpoint.shutdown.enabled=true

5.env

  可以获取应用所有的Environment信息,包括profile、系统环境变量、应用properites等。

  默认不开启。

6.beans

  显示当前应用所有bean,默认不开启。

7.conditions

  显示应用的自动配置报告,包括匹配与不匹配的自动配置类以及条件配置类。默认不开启。

8.httptrace

  显示HTTP请求的跟踪信息,需要实现HttpTraceRepository接口。默认不开启。

9.configprops

  显示所有注解为@ConfigurationProperties的Bean。

10.threaddump

  显示Java虚拟机线程信息。

11.loggers

  显示应用中所有logger。

12.mappings

  显示应用中所有的请求映射。

13.metrics

  显示当前应用的指标信息,包括内存使用情况等。

8.2 修改默认前缀

management.endpoints.web.base-path=/prefix # 将actuator前缀修改为prefix
management.endpoints.web.path-mapping.端点名=自定义路径 #修改单个端点名,修改后不用加actuator前缀

8.3 自定义端点

  在Bean前增加@Endpoint@WebEndpoint@EndpointWebExtension注解之一,就可以讲Bean通过Http暴露为端点。

  三种请求方式的注解为@ReadOperation(get请求)、@WriteOperation(post请求)、@DeleteOperation(delete请求),@Selector注解可用于接受参数。

  实现MeterBinder接口可以实现自定义的指标数据。

九 Web MVC

9.1 请求参数

外部的请求数据通过HttpMessageConverter 转为Java对象,也可以反方向操作。

通过继承AbstractHttpMessageConverter抽象类可以自定义如何解析参数。

在控制器的方法中添加注解,可以获取请求中的参数。

获取请求头:

  • @RequestHeader:注解在HttpHeaders、Map<String, String>、MultiValueMap<String, String>获取请求头信息,在注解设置值可以获取指定请求头的值
  • @CookieValue:设置值可以获取cookie中的指定值
  • @RequestBody:数据来源于请求体
  • @DateTimeFormat:参数转为Date类型
  • @NumberFormat:参数转为BigDecimal类型
  • BindingResult:@Valid的参数校验结果
  • RequestEntity:获取请求头
  • WebRequest:通用Web请求接口
  • NativeWebRequest:继承WebRequest,通用请求返回接口
  • ServletRequest:Servlet请求
  • ServletResponse:Servlet返回
  • HttpSession:Http会话
  • Locale:本地信息
  • TimeZone:时区信息
  • ZoneId:时区id
  • DemoService:新建对象,非注入Bean
  • MultipartFile:接收文件,@RequestPart("file") MultipartFile/@RequestParam("file") Multipart/@RequestParam("file")Part也可以接收,@RequestParam Map<String, MultipartFile>接收多个文件
//实现Validator接口可以实现自定义的参数校验
public class CustomValidator implements Validator {
    
    @Override
    public boolean supports(Class<?> clazz) {
        //允许指定特定的类
        return Bean.class.isAssignableFrom(clazz);
    }
    
    @Override
    public void validate(Object target, Errors errors) {
        //具体的校验逻辑
        Bean bean = (Bean) target;
        if (bean.getName().getSize() < 1) {
            errors.rejectValue("name", "bean.name", "大小不能小于1");
        }
    }
}
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.util.StringUtils;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

/**
 * 自定义注解验证
 * 时间顺序验证,注解在类上,前一个时间要不晚于后一个时间
 * 使用 @Constraint 注解说明如何验证
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateTimeSequenceValidate.DateTimeValidator.class)
@Documented
public @interface DateTimeSequenceValidate {

    /**
     * 默认错误消息
     * 可以使用{error.message}使用配置中值
     * 如果使用EL表达式,请确保EL表达式版本是 2.2或以上
     */
    String message() default "";

    /**
     * 前一个时间的属性名
     */
    String before();

    /**
     * 后一个时间的属性名
     */
    String after();

    /**
     * 时间格式
     */
    String pattern();

    Class< ? >[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    class DateTimeValidator implements ConstraintValidator<DateTimeSequenceValidate, Object> {

        private String beforeProperty;

        private String afterProperty;

        private String pattern;

        /**
         * 初始化,读取注解中的值
         */
        @Override
        public void initialize(DateTimeSequenceValidate constraintAnnotation) {
            this.beforeProperty = constraintAnnotation.before();
            this.afterProperty = constraintAnnotation.after();
            this.pattern = constraintAnnotation.pattern();
        }

        @Override
        public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) {
            //使用fastjson读取对象
            JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(o));

            DateTimeFormatter formatter = DateTimeFormatter.ofPattern(this.pattern);
            //验证开始时间
            String before = jsonObject.getString(this.beforeProperty);
            LocalDateTime startTime = null;
            if (!StringUtils.isEmpty(before)) {
                try {
                    //含时间的日期格式
                    if (this.pattern.contains("H")) {
                        startTime = LocalDateTime.parse(before, formatter);
                    } else {
                        //仅为日期
                        startTime = LocalDate.parse(before, formatter).atTime(0, 0, 0);
                    }
                } catch (DateTimeParseException e) {
                    //时间格式错误,去掉默认的验证,不显示注解上的错误信息
                    constraintValidatorContext.disableDefaultConstraintViolation();
                    //添加自定义的错误信息
                    constraintValidatorContext.buildConstraintViolationWithTemplate("开始时间格式错误")
                            .addConstraintViolation();
                    return false;
                }
            }

            //验证结束时间
            String after = jsonObject.getString(this.afterProperty);
            LocalDateTime endTime = null;
            if (!StringUtils.isEmpty(after)) {
                try {
                    //含时间的日期格式
                    if (this.pattern.contains("H")) {
                        endTime = LocalDateTime.parse(after, formatter);
                    } else {
                        //仅为日期
                        endTime = LocalDate.parse(after, formatter).atTime(0, 0, 0);
                    }
                } catch (DateTimeParseException e) {
                    //时间格式错误,去掉默认的验证,不显示注解上的错误信息
                    constraintValidatorContext.disableDefaultConstraintViolation();
                    //添加自定义的错误信息
                    constraintValidatorContext.buildConstraintViolationWithTemplate("结束时间格式错误")
                            .addConstraintViolation();
                    return false;
                }
            }


            return startTime == null || endTime == null || startTime.isBefore(endTime) || startTime.isEqual(endTime);
        }
    }
}

9.2 响应参数

通过实现HandlerMethodArgumentResolver接口对控制器方法参数处理,实现HandlerMethodReturnValueHandler接口对方法返回值进行处理。

使用ResponseEntity可以定制整个返回

return ResponseEntity.ok()
    .header("my-header", "hello") //设置响应头
    .body(new Bean()); //设置响应体

9.3 控制器

1.ControllerAdvice

@ControllerAdvice将处理控制器的通用功能。

@ControllerAdvice(annotations=RestController.class)通过注解限制

@ControllerAdvice("com.demo")通过包名限制

@ControllerAdvice(assignableTypes=DemoController.class)指定具体的控制器

  1. 使用@ExceptionHandler可以处理指定控制器的异常,如果在控制器中使用,则只处理所在控制器的异常
  2. 使用@InitBinder可以在请求进入控制前初始化数据绑定,如果在控制器中使用,则 只绑定所在控制器的数据
//自定义数据绑定的属性编辑器
public class CustomEditor extends PropertyEditorSupport{
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        Bean bean = new Bean(text);
        setValue(bean);
    }
}
//使用@InitBinder实现数据绑定
@InitBinder
public void registerEditor(WebDataBinder binder) {
    binder.registerCustomEditor(Bean.class, new CustomEditor());
    //添加自定义校验
    binder.setValidator(new CustomValidator());
}

2.RestControllerAdvice

@RestControllerAdvice允许对RESTful风格的请求体和响应体进行定制。

请求体处理如下,处理 POST 请求体,GET 请求不会进入处理。

import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;

import java.io.IOException;
import java.lang.reflect.Type;

@RestControllerAdvice
public class CustomRequestAdvice implements RequestBodyAdvice {
    /**
     * 判断请求是否需要处理,在请求体解析为Bean对象前后都会通过这个方法判断是否需要生效
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return false;
    }

    /**
     * 在请求体解析为Bean对象前处理
     */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        return null;
    }

    /**
     * 在请求体解析为Bean对象后处理
     */
    @Override
    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return null;
    }

    /**
     * 当请求体为空时,如何解析为Bean对象
     */
    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return null;
    }
}

响应体处理如下

import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@RestControllerAdvice
public class CustomResponseAdvice implements ResponseBodyAdvice {
    /**
     * 判断响应是否需要处理
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return false;
    }

    /**
     * 在响应写入到response前对响应进行处理
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return body;
    }
}

3.JsonView

@JsonView可以定制请求体与响应体,实现原理就是与上面一致,实现了JsonViewRequestBodyAdviceJsonViewResponseBodyAdvice类。

在控制器的参数对象中,添加内部接口,并在getter方法上添加注解,就可以实现自定义的响应类。

//请求类设置要返回的属性
public class Person {
    public interface TestView {}
    
    private String id;
    
    @JsonView(TestView.class)
    public String getId() {
        return this.id;
    }
}
@PostMapping
@ResponseBody
@JsonView(Person.TestView.class)
public Person get(@RequestBody Person p) {
    //JsonView只支持一个参数的请求
}

4.JSON

@JsonIgnore忽略属性,注解在Bean中,无法请求到这个属性,也无法返回。

@JsonFormat定制时间格式,spring.jackson.date-format: yyyy-MM-dd配置文件中设置表示全局的设置。

@JsonProperty设置json中值对应对象中的属性。

5.接口执行顺序

  1. HandlerInterceptor preHandler 拦截器预处理
  2. RequestBodyAdvice beforeBodyRead 参数解析前
  3. HandlerMethodArgumentResolve 请求参数解析
  4. RequestBodyAdvice afterBodyRead 参数解析后
  5. @Valid 参数校验
  6. AOP before 切面前处理
  7. AOP around 切面环绕处理
  8. 业务方法
  9. AOP afterReturing 切面后处理
  10. ResponseBodyAdvice beforeBodyWrite
  11. HttpMessageConverter 转为JSON
  12. HandlerInterceptor postHandler 拦截器后处理
  13. HandlerInterceptor afterCompletion 拦截器渲染页面后

9.4 拦截器

拦截器,用于决定是否拦截当前请求。

public class CustomInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //在handler执行前执行,返回true继续向下执行,返回false则直接返回
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        //handler后处理器
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //在整个请求完成后执行
    }
}
@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(customInterceptor());
    }

    @Bean
    public CustomInterceptor customInterceptor() {
        return new CustomInterceptor();
    }
}

9.5 路径匹配

通过覆写WebMvcConfigurer接口的configurePathMatch接口可以自定义路径匹配。

在Spring MVC中,有以下默认设置:

  • 后缀匹配默认false,即路径/abc/abc.*不一致
  • 斜线匹配默认true,即路径/abc/abc/一致

9.6 静态目录

在Spring Boot中,默认以下目录可放置静态文件

  • classpath:/META-INF/resources
  • classpath:/resources/
  • classpath:/static/
  • classpath:/public/

可以在配置中设置spring-resources.static-locations来覆盖默认目录。

Spring Boot还对webjar做了映射,classpath:/META-INF/resources/webjars/映射成/webjars/**

posted @   abcoder  阅读(139)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探
点击右上角即可分享
微信分享提示