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
线程安全传递变量值。
五 应用配置
配置加载顺序的优先级如下:
- 命令行参数
- SPRING_APPLICATION_JSON环境变量,json字符串
- ServletConfig初始化参数
- ServletContext初始化参数
- JNDI(java:comp/env)
- Java 系统属性
- 操作系统变量
- RandomValuePropertiesSource随机值
- jar包外部的application-{profile}.properties/yaml
- jar包内部的application-{profile}.properties/yaml
- jar包外部的application.properties/yaml
- 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)指定具体的控制器
- 使用
@ExceptionHandler
可以处理指定控制器的异常,如果在控制器中使用,则只处理所在控制器的异常 - 使用
@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
可以定制请求体与响应体,实现原理就是与上面一致,实现了JsonViewRequestBodyAdvice
和JsonViewResponseBodyAdvice
类。
在控制器的参数对象中,添加内部接口,并在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.接口执行顺序
- HandlerInterceptor preHandler 拦截器预处理
- RequestBodyAdvice beforeBodyRead 参数解析前
- HandlerMethodArgumentResolve 请求参数解析
- RequestBodyAdvice afterBodyRead 参数解析后
- @Valid 参数校验
- AOP before 切面前处理
- AOP around 切面环绕处理
- 业务方法
- AOP afterReturing 切面后处理
- ResponseBodyAdvice beforeBodyWrite
- HttpMessageConverter 转为JSON
- HandlerInterceptor postHandler 拦截器后处理
- 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/**
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
· Manus的开源复刻OpenManus初探