一、简介
在Spring应用程序中启动时,有时需要在所有Bean都已加载,初始化并准备好之后执行某些自定义代码。这时,Spring提供了一个可用的方式,即使用@PostConstruct注解。这个注解用于标记一个方法,这个方法将在Bean初始化完成后被执行。而且,它是所有注解中最后一个执行的。
总结一下 @PostConstruct 的使用和特点:
- 只有一个非静态方法能使用此注解;
- 被注解的方法不得有任何参数;
- 被注解的方法返回值必须为void;
- 被注解方法不得抛出已检查异常;
- 此方法只会被执行一次;
二、@PostConstruct作用
@PostConstruct注解用于指定一个方法在对象创建后由容器自动执行,用于完成一些初始化操作。具体作用如下
1.完成依赖注入后,执行一些需要依赖注入的操作。在构造方法执行完毕后,自动调用被@PostConstruct注解修饰的方法,可以保证对象的依赖已经被注入,可以安全地执行一些需要依赖的操作。
2.初始化一些资源。@PostConstruct注解修饰的方法可以用于初始化一些需要在对象创建后立即准备好的资源,例如数据库连接、网络连接等。
3.执行一些必要的校验操作。在对象创建后,可以使用@PostConstruct注解修饰的方法进行一些必要的校验操作,例如检查配置是否正确、检查依赖是否满足等
总之,@PostConstruct注解的作用是在对象创建后执行一些需要在对象注入完毕后进行的操作,可以用于完成初始化总之资源准备、校验等操作。
三、执行顺序
在Spring框架中,每个bean都有一个完整的生命周期,包括实例化、属性赋值、调用Bean初始化和销毁bean等阶段。@PostConstruct注解的执行时间是在初始化阶段之后。
具体而言,一个bean的生命周期的主要阶段如下:
- 实例化(Instantiation)
- 填充属性(Populate Properties)
- BeanNameAware设置(BeanNameAware set)
- BeanFactoryAware设置(BeanFactoryAware set)
- 前置初始化(Post Process Before Initialization)
- 初始化(Initialization)
- 后置初始化(Post Process After Initialization)
- 销毁(Destroy)
最后一个阶段是销毁,这不是我们关心的。我们的attention是初始化方法,其中@PostConstruct注解会在Post Process After Initialization方法后调用。
3.1 执行顺序详解
往往我们在项目启动时需要加载某个方法的时候,可以使用@Component和@PostConstruct组合将一个方法完成初始化操作,@PostConstruct 注解的方法会将在依赖注入完成之后被自动调用。
通过上图,可以看出该注解在整个Bean初始化中执行的顺序:@Constructor(构造方法)-> @Autowired(依赖注入)-> @PostConstruct(注解的方法)
注意: 使用此注解时会影响服务启动时间。服务启动时会扫描WEB-INF/classes的所有文件和WEB-INF/lib下的所有jar包
四、使用案例
@PostConstruct 可以在Servlet初始化之前加载一些缓存数据,如:预热数据字典,读取properties配置文件,那案例就模拟这两个场景:
4.1 数据预热
使用Redis进行的数据预热,需要项目启动以后,触发第一次调用才能生成缓存,而利用 @PostConstruct 注解能让预热数据在Bean初始化阶段完成,比Redis更早。
@Component+@PostConstruct完成预热代码如下:
@Slf4j
@Configuration
public class BeanConfiguration {
@Autowired
private BusinessService businessService;
// 模拟预热的数据
private static String mysql_data;
@PostConstruct
public void construct(){
log.info("〓〓〓〓〓〓〓〓〓〓 Autowired 加载完成!!");
mysql_data = businessService.demo5();
log.info("〓〓〓〓〓〓〓〓〓〓 mysql_data = " + mysql_data);
}
}
BusinessService 演示
@Slf4j
@Service
public class BusinessServiceImpl implements BusinessService {
/**
* 模拟从数据库查询数据的操作
*/
public String demo5() {
log.info("〓〓〓〓〓〓〓〓〓〓 demo5:执行!!");
return "mysql data";
}
}
执行结果如下所示:
4.2 加载配置文件
@Value 注解修饰的常量不能是静态的,否则会 null,因为 static 的加载在 @Value 之前。如果不是 static 的,就要每次使用都要去加载一次 .properties 文件,有悖我们设置常量类的初衷。
现在,@PostConstruct注解可以帮我们完成预期,因为@PostConstruct的加载是在static之后的,不会出现null的情况,演示一下:
@Value获取数据,再通过@PostConstruct向static常量赋值
@Slf4j
@Component
public class GlobalConstent {
@Value("${server.port}")
private String port;
// 模拟静态常量
public static String server_port;
@PostConstruct
public void construct(){
log.info("〓〓〓〓〓〓〓〓〓〓 Before PostConstruct:" + server_port);
server_port = port;
log.info("〓〓〓〓〓〓〓〓〓〓 After PostConstruct:" + server_port);
}
}
使用过程很友好,直接采用“类名·”的方式访问
@Slf4j
@RestController
@RequestMapping("/construct")
public class PostConstructController {
@RequestMapping("/demo")
public String demo() {
log.info("〓〓〓〓〓〓〓〓〓〓 server_port:" + GlobalConstent.server_port);
return "success";
}
}
结果展示:加载过程都在项目成功启动之前
五、总结
- @PostConstruct注解时会影响服务启动时间,服务启动时会扫描WEB-INF/classes的所有文件和WEB-INF/lib下的所有jar包;
- @PostConstruct注解在整个Bean初始化中执行的顺序:@Constructor(构造方法)-> @Autowired(依赖注入)-> @PostConstruct(注解的方法);
- @PostConstruct 可以在Servlet初始化之前加载一些缓存数据,如:预热数据字典,读取properties配置文件;