用来加载 URL 中?之后的参数
@GetMapping("/notice")public boolean isNeedToNoticeUser(@RequestParam("email") String email) {return userNoticeService.isNeedToNoticeUser(email);}
用来加载 POST/PUT 请求的复杂请求体
@PostMapping("/status")public Response jobStatus(@RequestBody @Validated SenderJobDTO jobDTO) {ConsumerJobVO vo = senderJobService.jobStatus(jobDTO);return Response.ok().data(vo);}
用来加载 URL 路径中的参数
@GetMapping("/user/{id}")@ResponseBody()public User findUserById(@PathVariable("id") String id){return userRepo.findById(id);}
API 的参数通过;分割。比如:这个请求/books/reviews;isbn=1234;topN=5
; 就可以如下面这样,使用@MatrixVariable
来加载 URL 中用;分割的参数
@GetMapping("/books/reviews")@ResponseBody()public List<BookReview> getBookReviews(@MatrixVariable String isbn, @MatrixVariable Integer topN) {return bookReviewsLogic.getTopNReviewsByIsbn(isbn, topN);}
用来加载请求头中的数据
@GetMapping("/user")@ResponseBody()public List<User> getUserList(@RequestHeader("Authorization") String authToken) {return userRepo.findAll();}
服务端读取 Cookie 数据
@GetMapping("/user")@ResponseBody()public List<User> getUserList(@CookieValue(name = "SessionId") String sessionId) {return userRepo.findAll();}
常用的有两种方式:@Value 和@ConfigurationProperties
Feature | @ConfigurationProperties | @Value |
---|---|---|
Relaxed binding | Yes | Limited (see note below) |
Meta-data support | Yes | No |
SpEL evaluation | No | Yes |
首先需要在配置类上增加@Configuration
带默认值的
@Value("${kafka.producer.retry:1}")private int retry;
使用 Spring Expression Language (SpEL)
@Value("#{'${actuator.send.business.type:eims,item}'.split(',')}")List<String> specifyType;
该方式可以将相应的配置封装在具体类内,对代码组织和封装友好,推进使用这种方式
@ConfigurationProperties(prefix = "my.server")public class MyServerProperties {private String name="test";@NotNullprivate String bootstrapServers;private Host host;// getters/setters ...// 嵌入式结构public static class Host {private String ip;private int port;// getters/setters ...}}
my.server:name: testbootstrap_servers: xxxxx:9092host:ip: xxxport: 8081
扩展:
为了让 spring 容器可以识别该配置类,有多种方式,这里我推荐在启动类上添加注解@ConfigurationPropertiesScan({"top.trumandu.config"})
配置参数
Spring(relaxed binding )使用一些宽松的绑定属性规则。因此,以下变体都将绑定到 hostName 属性上:
mail.hostName=localhostmail.hostname=localhostmail.host_name=localhostmail.host-name=localhostmail.HOST_NAME=localhost
为了让 ide 可以识别我们自定义的配置文件,可以添加 spring-boot-configuration-processor,这样就可以自动生成additional-spring-configuration-metadata.json
, 进而进行提示。
classpath root
> classpath/config
> current directory
> current directory/config/
> current directory/../config
默认使用 Logback,通常我们只用如下配置即可快速使用.
logging:level:root: "warn"org.springframework.web: "debug"org.hibernate: "error"
当无法满足一些日志场景时,Logback 扩展可以通过在src/main/resources
创建logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?><configuration><!--<jmxConfigurator/> --><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{ISO8601} %-5level [%thread] %logger{0}: %msg%n</pattern></encoder></appender><appender name="FILE"class="ch.qos.logback.core.rolling.RollingFileAppender"><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>logs/info.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>7</maxHistory></rollingPolicy><encoder><pattern>%d{ISO8601} %-5level [%thread] %logger{0}: %msg%n</pattern></encoder></appender><appender name="ERROR_FILE"class="ch.qos.logback.core.rolling.RollingFileAppender"><rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>logs/error.%d{yyyy-MM-dd}.log</fileNamePattern><maxHistory>7</maxHistory></rollingPolicy><encoder><pattern>%d{ISO8601} %-5level [%thread] %logger{0}: %msg%n</pattern></encoder><filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>error</level></filter></appender><logger name="top.trumandu" level="info" /><logger name="org.apache" level="error" /><logger name="org.I0Itec.zkclient" level="error" /><root level="info"><appender-ref ref="STDOUT" /><appender-ref ref="FILE" /><appender-ref ref="ERROR_FILE" /></root></configuration>
可以实现ApplicationRunner
和CommandLineRunner
接口
@Componentpublic class MyCommandLineRunner implements CommandLineRunner {@Overridepublic void run(String... args) {// Do something...}}
优先级可以通过@Ordered
实现
首先启动定时注解配置
@EnableScheduling
通过以下代码可以快速实现定时 job
@Componentpublic class ManualGcJob {// 每两秒运行一次@Scheduled(cron = "0/2 * * * * ?")public void scheduleJvmGc(){System.out.println("manual gc");}}
不仅通过 corn 表达式,也可以通过 fixedDelay 和 fixedRate 配置
支持配置文件配置参数
@Scheduled(fixedDelayString = "${category.fixed.delay:300000}", initialDelayString = "${category.fixed.delay:300000}")public void updateCategory() {System.out.println("update category");}
程序硬编码指定
@Scheduled(fixedDelay = 24 * 60 * 60 * 1000, initialDelay = 24 * 60 * 60 * 1000)public void scheduleJvmGc() {System.out.println("manual gc");}
fixedDelay 和 fixedRate 区别:fixedDelay 需要等上一个任务完成后,再延迟相应时间才运行。
spring boot scheduling 相关配置如下:
spring:task:scheduling:thread-name-prefix: "scheduling-"pool:size: 2
springboot 默认定时调度线程池只有一个,强烈建议使用的时候更新该 pool.size 大小
如果使用 RestTemplate,强烈建议配置超时时间,编码等配置
@Configurationpublic class RestTemplateConfig {/*** 第三方请求要求的默认编码*/private final Charset thirdRequest = StandardCharsets.UTF_8;@Beanpublic RestTemplate restTemplate(ClientHttpRequestFactory factory) {RestTemplate restTemplate = new RestTemplate(factory);// 处理请求中文乱码问题List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();for (HttpMessageConverter<?> messageConverter : messageConverters) {if (messageConverter instanceof StringHttpMessageConverter) {((StringHttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest);}if (messageConverter instanceof MappingJackson2HttpMessageConverter) {((MappingJackson2HttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest);}if (messageConverter instanceof AllEncompassingFormHttpMessageConverter) {((AllEncompassingFormHttpMessageConverter) messageConverter).setCharset(thirdRequest);}}return restTemplate;}@Beanpublic ClientHttpRequestFactory simpleClientHttpRequestFactory() {SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();factory.setConnectTimeout(15000);factory.setReadTimeout(5000);return factory;}}
在 spring 中可以使用@Async
来声明方法异步执行,前提需要启用异步配置@EnableAsync
使用该功能需要注意三个问题:
//无效代码案例@Servicepublic class PurchaseService {public void purchase(){sendEmail();}@Asyncpublic void sendEmail(){// Asynchronous code}}//正确代码@Servicepublic class EmailService {@Asyncpublic void sendEmail(){// Asynchronous code}}@Servicepublic class PurchaseService {public void purchase(){emailService.sendEmail();}@Autowiredprivate EmailService emailService;}
//错误案例@Servicepublic class EmailService {@Asyncpublic void sendEmail() throws Exception{throw new Exception("Oops, cannot send email!");}}@Servicepublic class PurchaseService {@Autowiredprivate EmailService emailService;public void purchase(){try{emailService.sendEmail();}catch (Exception e){System.out.println("Caught exception: " + e.getMessage());}}}//正确案例@Servicepublic class EmailService {@Asyncpublic Future<String> sendEmail() throws Exception{throw new Exception("Oops, cannot send email!");}}@Servicepublic class PurchaseService {@Autowiredprivate EmailService emailService;public void purchase(){try{Future<String> future = emailService.sendEmail();String result = future.get();System.out.println("Result: " + result);}catch (Exception e){System.out.println("Caught exception: " + e.getMessage());}}}
spring:task:execution:thread-name-prefix: email-sync-taskpool:core-size: 10max-size: 20queue-capacity: 10keep-alive: 10s
或者
@Beanpublic ThreadPoolTaskExecutor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(2);executor.setMaxPoolSize(2);executor.setQueueCapacity(500);executor.setThreadNamePrefix("MyAsyncThread-");executor.setRejectedExecutionHandler((r, executor1) -> log.warn("Task rejected, thread pool is full and queue is also full"));executor.initialize();return executor;}
@Valid
和 @Validated
都是 Spring 框架中用于启用数据验证的注解
@Valid
:只能用于单一的 Bean Validation 校验,无法进行分组校验。@Validated
:不仅支持基本校验,还支持通过分组来进行不同场景下的定制化校验。
public class UserDto {@NotNull(message = "Name is required")private String name;@Email(message = "Invalid email address")private String email;}// 在 Controller 中使用 @Valid@PostMapping("/user")public ResponseEntity<String> createUser(@Valid @RequestBody UserDto user, BindingResult result) {if (result.hasErrors()) {return ResponseEntity.badRequest().body(result.getFieldErrors().toString());}return ResponseEntity.ok("User created successfully");}
public class UserDto {@NotNull(message = "Name is required", groups = Create.class)private String name;@NotNull(message = "Email is required", groups = {Create.class, Update.class})@Email(message = "Invalid email format", groups = {Create.class, Update.class})private String email;@NotNull(message = "Password is required for new users", groups = Create.class)@Size(min = 8, message = "Password must be at least 8 characters", groups = Create.class)private String password;public interface Create {}public interface Update {}}// 在 Controller 中使用 @Validated 来指定分组@PostMapping("/createUser")public ResponseEntity<String> createUser(@Validated(UserDto.Create.class) @RequestBody UserDto user, BindingResult result) {if (result.hasErrors()) {return ResponseEntity.badRequest().body(result.getFieldErrors().toString());}return ResponseEntity.ok("User created successfully");}@PutMapping("/updateUser")public ResponseEntity<String> updateUser(@Validated(UserDto.Update.class) @RequestBody UserDto user, BindingResult result) {if (result.hasErrors()) {return ResponseEntity.badRequest().body(result.getFieldErrors().toString());}return ResponseEntity.ok("User updated successfully");}
Create 场景:用户在创建时,name 和 password 都必须被验证,而 email 需要验证格式。 Update 场景:在更新时,只验证 email 和 name,且不需要验证 password。
通常我们获取bean使用@Autowired
和构造方法注入,除此以外,我们还有一种场景,按照条件获取bean。
1.根据接口注入所有实现接口的bean
方法1,appContext.getBeansOfType(EventHandler.class)
中获取
@Componentpublic class EventHandlerFactory implements InitializingBean, ApplicationContextAware {private static final Map<String, EventHandler> HANDLER_MAP = new HashMap<>(16);private ApplicationContext appContext;/*** 根据提交类型获取对应的处理器** @param type 提交类型* @return 提交类型对应的处理器*/public EventHandler getHandler(String type) {return HANDLER_MAP.get(type);}@Overridepublic void afterPropertiesSet() {// 将 Spring 容器中所有的 EventHandler 注册到 HANDLER_MAPappContext.getBeansOfType(EventHandler.class).values().forEach(handler -> HANDLER_MAP.put(handler.getType(), handler));}@Overridepublic void setApplicationContext(@NonNull ApplicationContext applicationContext) {appContext = applicationContext;}}
方法2,使用@Autowired
和构造方法注入
@Servicepublic class EmailService {private final List<IHandler> handlers;public EmailService(List<IHandler> handlers) {this.handlers = handlers;}public void sendEmail() {//省略其他处理逻辑for (IHandler handler : handlers) {boolean flag = handler.execute(request);if (!flag) {break;}}}}
context.getBeansWithAnnotation(StageConfig.class);
获取指定注解的bean@Configurationpublic class PipelineConfiguration<T extends BasePipelineContext> {/*** key:StageConfig注解中的name* value:实现了Stage接口的实例Bean*/private final Map<String, Stage<T>> stageMap = new ConcurrentHashMap<>();@Autowiredprivate ApplicationContext context;/*** 在构造方法后执行,确保所有依赖注入完,初始化pipeline的Map*/@PostConstructprivate void initStageMap() {// 拿到带有@StageConfig注解的所有beanMap<String, Object> beansWithAnnotation = context.getBeansWithAnnotation(StageConfig.class);// 遍历带有StageConfig注解的所有实例beanfor (Object bean : beansWithAnnotation.values()) {if (bean instanceof Stage) {// 拿到注解StageConfig annotation = bean.getClass().getAnnotation(StageConfig.class);// 放入MapstageMap.put(annotation.name(), (Stage<T>) bean);}}}}