Spring
@Configuration与@Bean
@Configuration,将一个普通类声明为配置类。
@Bean,将一个普通方法的返回值声明为Spring IOC容器管理的Bean。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.time.Duration;
import java.util.Collections;
@Configuration
public class CorsConfig {
//@Bean(name = "corsFilter"),默认使用方法名为Bean name
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(Collections.singletonList("*"));//允许的域
config.setAllowCredentials(true);//允许凭证
config.addAllowedHeader(CorsConfiguration.ALL);//允许的请求头
config.addAllowedMethod(CorsConfiguration.ALL);//允许的请求方式
config.setMaxAge(Duration.ofHours(3));//有效时间
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
@Component
| 注解 | 说明 |
|---|---|
| @Component | 将一个普通类注入Spring IOC容器托管 |
| @Repository | 作用同@Component,通常声明于DTO层 |
| @Service | 作用同@Component,通常声明于Service层 |
| @Controller | 作用同@Component,通常声明于Controller层 |
@Scope 作用域
配合@Bean或@Component使用,可以更改Bean的作用域,默认是singleton(单例模式)。
| 模式 | 说明 |
|---|---|
| singleton | 单例模式,全局有且仅有一个实例 |
| prototype | 原型模式,每次获取的Bean都是新创建的实例 |
| request | 每次HTTP请求都是新创建的实例,该Bean仅在当前HTTP Request内有效 |
| session | 每次来自新Session的HTTP请求都是新创建的实例,该Bean仅在当前HTTP Session内有效 |
| global session | 类似于标准HTTP Session作用域,不过仅适用于Portlet规范的Web应用。如果在普通Web中使用global session会被降级成session类型使用。 |
@Autowired与@Resource的区别
| 注解 | 说明 |
|---|---|
| @Autowired | Spring提供,默认通过type方式注入,type类型相同再根据name方式注入 |
| @Resource | JSR-250提供,默认通过name方式注入,name类型相同再根据type方式注入 |
@Lazy 懒加载
配合@Bean或@Component使用,懒加载Bean。
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/my")
public class MyController {
//注意:@Lazy在Bean声明和使用处都必须注解才有效
@Lazy
@Resource
private MyService myService;
@GetMapping
public String test() {
myService.test();
return "success";
}
@Lazy
@Service
public static class MyService {
public MyService() {
System.err.println("MyService加载完成");
}
public void test() {
System.err.println("MyService test被执行");
}
}
}
@Order与@Priority的区别
@Order,Spring提供,控制Bean的加载顺序,值越小优先级越高。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@Configuration
public class MyOrder {
public interface Pay {
boolean deal();
}
public class WeChat implements Pay {
public boolean deal() {
System.out.println("wechat");
return true;
}
}
public class AliPay implements Pay {
public boolean deal() {
System.out.println("alipay");
return true;
}
}
@Order(2)
@Bean
public WeChat weChat() {
return new WeChat();
}
@Order(1)
@Bean
public AliPay aliPay() {
return new AliPay();
}
// @Resource
// public List pays;//1.AliPay 2.WeChat
// @Resource
// public Pay pay;//抛异常
}
@Priority,JSR 250提供,控制Bean的加载顺序,值越小优先级越高。
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import javax.annotation.Priority;
@Configuration
public class MyOrder {
public interface Pay {
boolean deal();
}
@Priority(1)
@Component
public class WeChat implements Pay {
public boolean deal() {
System.out.println("wechat");
return true;
}
}
@Priority(2)
@Component
public class AliPay implements Pay {
public boolean deal() {
System.out.println("alipay");
return true;
}
}
// @Resource
// public List pays;//1.WeChat 2.AliPay
// @Resource
// public Pay pay;//WeChat
}
@Primary与@Qualifier的区别
@Primary,声明同type类型下,默认使用的Bean。
import com.tenbeggar.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class UserConfig {
@Bean
@Primary
public User user1() {
User user = new User();
user.setName("张飞");
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("刘备");
return user;
}
@Resource
private User user;//得到user1
}
@Qualifier,配合@Autowired或@Resource注解使用。
import com.tenbeggar.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class UserConfig {
@Bean
public User user1() {
User user = new User();
user.setName("张飞");
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("刘备");
return user;
}
@Resource
@Qualifier("user1")
private User userX;//得到user1
@Resource(name = "user2")
private User userY;//得到user2
}
@Conditional
根据实现的Condition接口判断被其注解的Bean是否托管给Spring IOC容器。
- 实现Condition接口
import org.springframework.context.annotation.Condition; import org.springframework.context.annotation.ConditionContext; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotatedTypeMetadata; //当操作系统是Windows时才进行加载 public class WindowsConditional implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { Environment environment = conditionContext.getEnvironment(); String property = environment.getProperty("os.name"); if (property.contains("Windows")) { return true; } return false; } } - 使用@Conditional注解
import com.tenbeggar.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @Configuration public class UserConfig { @Bean @Conditional({WindowsConditional.class}) public User user() { User user = new User(); user.setName("张飞"); return user; } }
@ConditionalOn扩展注解
| 注解 | 说明 |
|---|---|
| @ConditionalOnJava | 系统的java版本是否符合要求 |
| @ConditionalOnBean | 容器中存在指定的Bean |
| @ConditionalOnMissingBean | 容器不中存在指定的Bean |
| @ConditionalOnExpression | 满足SpEL表达式 |
| @ConditionalOnClass | 容器中存在指定的类 |
| @ConditionalOnMissingClass | 容器不中存在指定的类 |
| @ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
| @ConditionalOnProperty | 系统中指定的属性是否有指定值 |
| @ConditionalOnResource | 类路径下是否存在指定资源文件 |
| @ConditionalOnWebApplication | 当前是Web环境 |
| @ConditionalOnNotWebApplication | 当前不是Web环境 |
| @ConditionalOnJndi | JNDI存在指定项 |
@PostConstruct与@PreDestory
@PostConstruct,JSR-250提供,用来修饰一个非静态的void方法,且不能抛出声明式异常。被@PostConstruct修饰的方法只会在服务器加载Servlet后执行一次。
@PreDestroy,JSR-250提供,用来修饰一个非静态的void方法,且不能抛出声明式异常。被@PreDestroy修饰的方法只会在服务器卸载Servlet前执行一次。
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class User {
//执行顺序
//1. Constructor构造函数
public User() {
System.err.println("Constructor");
}
//2. PostConstruct
@PostConstruct
public void postConstruct() {
System.err.println("PostConstruct");
}
//3. init -> @Bean(initMethod = "init")
public void init() {
System.err.println("init");
}
//4. PreDestroy
@PreDestroy
public void preDestroy() {
System.err.println("PreDestroy");
}
//5. destroy -> @Bean(destroyMethod = "destroy")
public void destroy() {
System.err.println("destroy");
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
@Bean(initMethod = "init", destroyMethod = "destroy")
public User user() {
return new User();
}
}
Servlet生命周期
graph TD;
服务器加载Servlet --> Constructor;
Constructor --> PostConstruct;
PostConstruct --> init;
init --> Service;
Service --> PreDestroy;
PreDestroy --> destroy;
destroy --> 服务器卸载Servlet;
@ConfigurationProperties与@Value
这两个注解都可以加载配置文件到Spring IOC容器管理的Bean,配置文件样例application.yml
person:
name: 张飞
age: 19
sex: M
likes:
- 骑马
- 射箭
@ConfigurationProperties,将配置文件中加载到Bean。
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Data
@Component //不可缺少
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private Integer age;
private String sex;
private List likes;
}
@Value,将配置文件中某一项加载到Bean的一个属性,还支持SpEL表达式。
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
@Data
@Component //不可缺少
public class Person {
@Value("${person.name}")
private String name;
@Value("${person.age}")
private Integer age;
@Value("${person.sex}")
private String sex;
@Value("${person.likes}")
private List likes;
}
@ComponentScan
扫描指定的路径,从中找到需要装配的类并加载到Spring IOC容器。
@Import
@Import与@Configuration的区别
@Import与@Configuration的作用都是导入一个配置类,但当配置类不在@SpringBootApplication所在包及其子包下时,@Configuration就不适用了。
此时有两种方案可供选择:
- 使用@ComponentScan
- 使用@Import
@ComponentScan看似简单,但这是建立在开发者明确知道包路径的情况下。第三方jar包中的类要想也加入到Spring IOC容器中,最优雅的方式还是使用@Import。
public class User {}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
@Import({User.class})
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
ImportSelector接口
ImportSelector接口配合@Import注解可以自定义加载我们想要的Bean。
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import java.util.function.Predicate;
public class UserSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.tenbeggar.User", "com.tenbeggar.Role"}; //这个地方虽然写了Role,但它永远也不可能被加载,因为早在getExclusionFilter的时候就已经被我们排除了
}
//排除Bean的过滤器
@Override
public Predicate getExclusionFilter() {
return new Predicate() {
@Override
public boolean test(String className) {
return className.contains("Role"); //排除全限定类名中带"Role"字符串的类
}
};
}
}
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserSelector.class)
public @interface EnableUser {}
@AliasFor
定义一个注解中的两个属性互为别名
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MapperScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
}
此列中@MapperScan("com.tenbeggar")和@MapperScan(basePackages = "com.tenbeggar")作用相同
Spring Web
Controller
import org.springframework.web.bind.annotation.*;
@RestController //与@Controller的区别是,被@RestController声明的类其所有方法默认自带@ResponseBody
@RequestMapping("/user")
public class UserController {
@PostMapping //POST /user {"name":"zhangfei","age":19}
public void add(@RequestBody User user) {}
@DeleteMapping //DELETE /user?id=1
public void deleteById(@RequestParam("id") Long id) {}
@PutMapping //PUT /user?id=1&name="guanyu"
public void update(User user) {}
@GetMapping("/{id}") //GET /user/1
public User findById(@PathVariable("id") Long id) {
return null;
}
}
统一异常处理
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
//统一异常处理类
@ControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(value = Exception.class) //捕捉的异常
public String exceptionHandler(Exception e) {
return "error";
}
}
Validation
首先在pom.xml文件添加maven配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@Valid,JSR-303提供,递归的对关联对象进行校验,如果关联对象是数组或集合就遍历元素逐一校验,如果是Map就遍历value逐一校验,配合BindingResult可以获取参数验证结果。
@Validated,Spring提供,在@Vaild的基础上新增了分组功能,但缺少@Valid嵌套验证的功能。
嵌套验证
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Data
public class Student {
@NotBlank
private String name;
private Integer age;
@Valid //如果不加@Valid,Book类下的@NotBlank不会生效
@NotEmpty
private List books;
@Data
public static class Book {
@NotBlank
private String name;
}
}
分组&有序验证
- 先声明两个组
//登录验证 public interface Login {}//注册验证 public interface Logon {}//先注册后登录验证 import javax.validation.GroupSequence; @GroupSequence({Logon.class, Login.class}) //@GroupSequence,有序验证就是在分组的基础加上顺序 public interface LogonAndLogin {} - 验证的实体类
import lombok.Data; import javax.validation.constraints.NotBlank; @Data public class User { @NotBlank(groups = {Login.class, Logon.class}) private String username; @NotBlank(groups = Login.class) private String password; } - 使用分组
import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/user") public class UserController { //登录验证 @PostMapping("/login") public String login(@RequestBody @Validated(Login.class) User user, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "success"; } //注册验证 @PostMapping("/logon") public String logon(@RequestBody @Validated(Logon.class) User user, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "success"; } //先注册后登录验证 @PostMapping("/logonAndLogin") public String logonAndLogin(@RequestBody @Validated(LogonAndLogin.class) User user, BindingResult result) { if (result.hasErrors()) { return result.getFieldError().getDefaultMessage(); } return "success"; } }
常用注解
| 注解 | 说明 |
|---|---|
| @Null | 被注解的Object必须为null |
| @NotNull | 被注解的Object必须不为null |
| @NotEmpty | 被注解的String、Array、Collection、Map必须不为null,且长度必须大于0 |
| @NotBlank | 被注解的String必须不为null,且trim后长度必须大于0 |
| @AssertTrue | 被注解的Boolean必须为true |
| @AssertFalse | 被注解的Boolean必须不为false |
| @Size(min=1, max=20) | 被注解的String、Array、Collection、Map长度必须在指定范围 |
| @Length(min=1, max=20) | 被注解的String长度必须在指定范围 |
| @Past | 被注解的Date、Calender、DateTime必须是一个过去的日期 |
| @Future | 被注解的Date、Calender、DateTime必须是一个将来的日期 |
| @Pattern(regexp=”^\d{20}$”) | 被注解的String必须符合正则表达式的规则 |
| @Min(1) | 被注解的String、Number必须大于等于指定值 |
| @Max(20) | 被注解的String、Number必须小于等于指定值 |
| @DecimalMin(“0.00”) | 被注解的String、Number必须大于等于指定值,小数确定精度 |
| @DecimalMax(“20.00”) | 被注解的String、Number必须小于等于指定值,小数确定精度 |
| @Digits(integer=3, fraction=2) | 被注解的String、Number必须在指定精度范围内,integer指定整数精度,fraction指定小数精度 |
| @Range(min=1, max=20) | @Min和@Max的结合 |
| @CreditCardNumber | 验证信用卡 |
| 验证邮箱,为null也能通过验证 | |
| @URL | 验证URL路径 |
| @ScriptAssert |
序列化&反序列化
| 注解 | 说明 |
|---|---|
| @JsonProperty | 序列化时修改属性名称 |
| @JsonIgnore | 序列化时忽略该属性 |
| @JsonIgnoreProperties | 序列化和反序列化都忽略该属性 |
| @JsonFormat | 序列化Date、DateTime为指定格式的String |
| @JsonSerialize | 序列化使用自定义代码 |
| @JsonDeserialize | 反序列化使用自定义代码 |
| @JsonInclude | 属性值为null时不参与序列化 |
| @DateTimeFormat | 非jackson提供,反序列化String为指定格式的Date、DateTime |
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
@Data
@JsonIgnoreProperties({"age"})
public class User {
@JsonSerialize(using = LongToStringSerializer.class)
private Long id;
@JsonProperty("username")
private String name;
@JsonIgnore
private String password;
private Integer age;
@JsonInclude
private String sex;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime birthday;
}
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class LongToStringSerializer extends JsonSerializer {
@Override
public void serialize(Long aLong, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
String text = aLong == null ? null : String.valueOf(aLong);
if (text != null) {
jsonGenerator.writeString(text);
}
}
}
异步任务
- 使用@EnableAsync注解开启异步任务支持,它会让Spring扫描被其注解的包及其子包下的@Async方法。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; @EnableAsync //开启异步支持 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } - 使用@Async注解标记需要异步执行的方法,当在其他线程调用被@Async注解的方法时,就会开启一个新线程执行该方法。
注意:@Async也可作用在类上,表示该类的所有方法都需要异步执行。import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @Service public class AsyncService { //被@Async注解的方法需要满足以下条件: //1. 访问修饰符必须是public //2. 非静态方法,即不能被static修饰 //3. 入参随意,但返回值必须是void或Future类 //4. 不能与@PostConstruct等Bean生命周期回调函数一起使用,解决办法请参考:https://docs.spring.io/spring-framework/docs/current/reference/html/integration.html#scheduling-annotation-support-async @Async public void without() { try { Thread.sleep(TimeUnit.SECONDS.toMillis(5)); } catch (InterruptedException e) { e.printStackTrace(); } } @Async public Futurewith() { try { Thread.sleep(TimeUnit.SECONDS.toMillis(5)); } catch (InterruptedException e) { e.printStackTrace(); } //此处使用AsyncResult包装异步任务结果,AsyncResult间接继承Future,是Spring提供的一个可用于追踪异步方法执行结果的包装类。其他常用的Future类型还有Spring 4.2提供的ListenableFuture,或者JDK 8提供的CompletableFuture。 return new AsyncResult<>("ok"); } } - 调用异步任务。
import org.springframework.util.StopWatch; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import java.util.concurrent.Future; @RestController @RequestMapping("/async") public class AsyncController { @Resource private AsyncService asyncService; @GetMapping("/taskWithoutReturn") public String taskWithoutReturn() { StopWatch stopWatch = new StopWatch(); stopWatch.start(); asyncService.without(); stopWatch.stop(); return String.format("执行时长:%d 毫秒", stopWatch.getTotalTimeMillis()); } @GetMapping("/taskWithReturn") public String taskWithReturn() throws Exception { StopWatch stopWatch = new StopWatch(); stopWatch.start(); Futurewith = asyncService.with(); String s = with.get(); stopWatch.stop(); return String.format("执行时长:%d 毫秒,执行结果:%s", stopWatch.getTotalTimeMillis(), s); } }
自定义Executor(线程池)
默认情况下,Spring会自动搜索唯一类型为TaskExecutor或名称为taskExecutor的Executor实例,若实例不存在,则创建SimpleAsyncTaskExecutor来执行被@Async注解的方法。
SimpleAsyncTaskExecutor每次执行任务时,会重新启动一个新线程。并允许开发者控制并发线程数(concurrencyLimit),默认取值为-1,即不启用资源节流。
多Executor实例
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class AsyncExecutor {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);//核心线程数
executor.setMaxPoolSize(20);//最大线程数
executor.setKeepAliveSeconds(60);//空闲的非核心线程存活时间
executor.setQueueCapacity(50);//任务队列容量
//拒绝策略
//new ThreadPoolExecutor.CallerRunsPolicy(); //交由调用线程运行,如main线程
//new ThreadPoolExecutor.AbortPolicy(); //抛出异常
//new ThreadPoolExecutor.DiscardPolicy(); //丢弃异常
//new ThreadPoolExecutor.DiscardOldestPolicy(); //丢弃队列中存活时间最长的任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.setThreadNamePrefix("Task-Executor-Async-");
return executor;
}
@Bean
public TaskExecutor myExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(20);
executor.setThreadNamePrefix("My-Executor-Async-");
return executor;
}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class AsyncService {
//指定使用myExecutor
@Async("myExecutor")
public void without() {
log.info("without Thread = {}", Thread.currentThread().getName());
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//默认使用taskExecutor
@Async
public Future with() {
log.info("with Thread = {}", Thread.currentThread().getName());
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
return new AsyncResult<>("ok");
}
}
AsyncConfigurer接口
AsyncConfigurer是Spring提供的一个自定义Executor接口,它的功能更加强大。如果Spring检测到该接口实例,会优先采用。
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@EnableAsync
@Configuration
public class AsyncConfigure implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int core = Runtime.getRuntime().availableProcessors();
executor.setCorePoolSize(core);
executor.setMaxPoolSize(core * 4);
executor.setKeepAliveSeconds(60);
executor.setQueueCapacity(50);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.setThreadNamePrefix("Async-Configure-Executor-");
//等待所有任务结束后关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//注意:此处需要初始化
executor.initialize();
return null;
}
//前文介绍过,对于被@Async注解的异步方法,只能返回void或Future。对于返回Future的方法,如果异步任务报错,我们能够根据future.get()捕获该异常。但对于返回void的方法,我们只能通过下面这个异常处理器来处理。
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new DefaultAsyncExceptionHandler();
}
}
注意:使用AsyncConfigurer接口的另一个好处就是无论@EnableAsync所处的包层级多深,默认都会对整个项目进行扫描,这样我们就无需注解@EnableAsync到根包类。
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import java.lang.reflect.Method;
@Slf4j
public class DefaultAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
log.error("Exception message: {}", ex.getMessage());
log.error("Method name: {}", method.getName());
for (Object param : params) {
log.error("Parameter value: {}", param);
}
}
}
定时任务
- 使用@EnableScheduling注解开启定时任务支持。
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @EnableScheduling //开启定时支持 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } - 使用@Scheduled注解标记需要定时执行的方法。
小贴士:@Scheduled可以配合@Async一起使用,被注解的方法并行执行,即上一个任务在间隔时间内没执行完不影响下一个任务的开始。import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.time.LocalTime; import java.time.format.DateTimeFormatter; @Slf4j @Component public class SchedulingController { private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss"); //* * * * * ? * //秒 分 时 日 月 周 年 //日和周其中之一必须为? //Seconds(秒): 可使用 , - * / 有效范围为0-59 //Minutes(分): 可使用 , - * / 有效范围为0-59 //Hours(时): 可使用 , - * / 有效范围为0-23 //DayofMonth(日):可使用 , - * / ? L C W 有效范围为1-31 //Month(月): 可使用 , - * / 有效范围为1-12或JAN-DEC //DayofWeek(周): 可使用 , - * / ? L C # 有效范围为1-7或SUN-SAT。1表示星期天,2表示星期一,依次类推 //Year(年): 可使用 , - * / 有效范围为1970-2099 // * 表示所有间隔,如在分字段设置*表示每分钟都执行 // ? 忽略当前字段,如在周字段设置?表示不关心周几 // - 表示某个区间,如在时字段设置10-14表示10,11,12,13,14点执行 // , 表示多个值,如在日字段设置5,7,8表示5,7,8号执行 // / 表示递增触发,如在秒字段设置5/20表示5,25,45秒执行 // L 表示最后。如在日字段设置L表示当月的最后一天。在周字段设置L表示星期六,若在周字段设置6L表示本月最后一个星期五。 // W 表示离指定日期的最近那个工作日(周一至周五)。如在日字段设置20W表示离每月20号最近的那个工作日触发,如果20号是周六,则这周五(19号)触发,如果20号是周日,则下周一(21号)触发。如果20号是工作日(周一至周五),则当天触发。如果设置1W,表示每月1号往后最近的工作日触发。 // # 表示每月的第几个周几。如在周字段设置6#3表示每月的第三个周六,用在各种节日再合适不过了。 //每5秒执行一次 @Scheduled(cron = "0/5 * * * * ?") public void cron() { log.info("cron datetime: {}", LocalTime.now().format(formatter)); } //每5秒执行一次 @Scheduled(fixedDelay = 5 * 1000) //@Scheduled(fixedDelayString = "${clock.delay}") //application.yaml //clock: // delay: 5000 public void fixedDelay() { log.info("fixedDelay datetime: {}", LocalTime.now().format(formatter)); } //第一次延迟1秒后执行,然后每5秒执行一次 @Scheduled(initialDelay = 1000, fixedRate = 5 * 1000) public void fixedRate() { log.info("fixedRate datetime: {}", LocalTime.now().format(formatter)); } }
SchedulingConfigurer接口
SchedulingConfigurer接口与@Scheduled的作用相差不大,但若实际生产中我们想从数据库中读取指定时间来动态执行定时任务,这时@Scheduled就爱莫能助了。
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
@Slf4j
@Component
public class SchedulingConfigure implements SchedulingConfigurer {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");
//clock表数据样例
//id cron
//1 0/5 * * * * ?
@Mapper
public interface ClockMapper {
@Select("select cron from clock limit 1")
String getCron();
}
@Resource
private ClockMapper clockMapper;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.addTriggerTask(
new Runnable() {
@Override
public void run() {
log.info("triggerTask datetime: {}", LocalTime.now().format(formatter));
}
}, new Trigger() {
@Override
public Date nextExecutionTime(TriggerContext triggerContext) {
String cron = clockMapper.getCron();
return new CronTrigger(cron).nextExecutionTime(triggerContext);
}
}
);
}
}