Springboot 8种初始化方式
Springboot 8种初始化方式
综合所有提到的初始化方式,以下是它们的大致执行顺序:
- @PostConstruct
- InitializingBean.afterPropertiesSet
- @Bean 的 initMethod
- SmartInitializingSingleton.afterSingletonsInstantiated
- @EventListener (如 ContextRefreshedEvent)
- ApplicationRunner.run
- CommandLineRunner.run
- ApplicationListener (如 ApplicationReadyEvent)
1. @PostConstruct注解
该注解是Java jdk提供的注解,而不是Spring框架提供的, JavaEE5引入了@PostConstruct和@PreDestroy两个作用于 Servlet 生命周期的注解,实现Bean初始化之前和销毁之前的自定义操作。
该注解的方法在整个Bean初始化中的执行顺序,从先往后依次:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的初始化方法)
写法有两种:
@PostConstruct
public void method(){}
public @PostConstruct void method(){}
如果想在生成对象时完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。
@Component
public class A {
@Autowired
private B b;
public A() {
System.out.println("执行A的构造方法,此时b还未被注入: b = " + b);
}
@PostConstruct
private void init() {
System.out.println("@PostConstruct将在依赖注入完成后被自动调用: b = " + b);
}
}
@Component
public class B {
public B(){
System.out.println("执行B的构造方法");
}
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:application-context.xml"})
public class MessageTest {
@Resource
A a;
@Test
public void testAB(){
}
}
2. InitializingBean
InitializingBean 接口为 bean 提供了初始化方法的方式,它只包括 afterPropertiesSet 方法,凡是继承该接口的类,在初始化bean的时候会执行该方法。
- spring为bean提供了两种初始化bean的方式,实现 InitializingBean 接口的 afterPropertiesSet 方法,或者在配置文件中通过过 init-method 指定,两种方式可以同时使用
- 实现 InitializingBean 接口是直接调用 afterPropertiesSet 方法,比通过反射调用 init-method 指定的方法效率相对来说要高点。但是 init-method 方式消除了对 spring 的依赖
- 如果调用 afterPropertiesSet 方法时出错,则不调用 init-method 指定的方法。
public class InitBean implements InitializingBean {
public void afterPropertiesSet() throws Exception {
System.out.println("启动时自动执行 afterPropertiesSet...");
}
public void init(){
System.out.println("init method...");
}
}
这方式在 spring 中是怎么实现的?通过查看 spring 的加载 bean 的源码类(AbstractAutowireCapableBeanFactory)可看出其中奥妙。
AbstractAutowireCapableBeanFactory 类中的 invokeInitMethods 讲解的非常清楚,源码如下:
protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
throws Throwable {
//判断该bean是否实现了实现了InitializingBean接口,如果实现了InitializingBean接口,则只掉调用bean的afterPropertiesSet方法
boolean isInitializingBean = (bean instanceof InitializingBean);
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
//直接调用afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet();
return null;
}
},getAccessControlContext());
} catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
//直接调用afterPropertiesSet
((InitializingBean) bean).afterPropertiesSet();
}
}
if (mbd != null) {
String initMethodName = mbd.getInitMethodName();
//判断是否指定了init-method方法,如果指定了init-method方法,则再调用制定的init-method
if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
!mbd.isExternallyManagedInitMethod(initMethodName)) {
//进一步查看该方法的源码,可以发现init-method方法中指定的方法是通过反射实现
invokeCustomInitMethod(beanName, bean, mbd);
}
}
}
3. @Bean 的 initMethod
@Component
public class MyInitializingBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("implements InitializingBean......");
}
public void testInit(){
System.out.println("init-method......");
}
/**
* 相当于在xml配置文件中加入
* <bean id="myInitializingBean" class="类路径" init-method="testInit"></bean>
* @return
*/
@Bean(initMethod = "testInit")
public MyInitializingBean test() {
return new MyInitializingBean();
}
}
结果:在 spring 初始化 bean 的时候,如果该 bean 是实现了 InitializingBean 接口,并且同时在配置文件中指定了 init-method,系统则是先调用 afterPropertiesSet 方法,然后再调用 init-method 中指定的方法。
原理在上面 InitializingBean 小节源码处。
4. SpringBoot 中的 SmartInitializingSingleton 接口的使用
SmartInitializingSingleton 接口的 afterSingletonsInstantiated() 方法类似 bean 实例化执行后的回调函数。非抽象、非懒加载的单利都 getBean 完成后,才会调用 afterSingletonsInstantiated
方法
afterSingletonsInstantiated 会在spring 容器基本启动完成后执行。此时所有的单列bean都已初始化完成。实现了SmartInitializingSingleton 接口的类可以在 afterSingletonsInstantiated 中做一些回调操作。
使用场景
在spring容器 单列被加载完成后,处理一些自定义的逻辑。
例如:
- EventListenerMethodProcessor 实现 SmartInitializingSingleton 接口的 afterSingletonsInstantiated 方法,主要用于筛选出被 @EventListener 注解的方法,在后续的使用中通过发布-订阅模式,实现事件的监听。
- XxlJobSpringExecutor 实现 SmartInitializingSingleton 接口的 afterSingletonsInstantiated 方法,主要用于筛选出被 @XxlJob 注解标注的方法,用于后续任务的启动和停止。
5. @EventListener
定义事件类型,User对象就省略了
public class UserEvent extends ApplicationEvent {
/**
* 实现父类方法
* @param source 数据源
*/
public UserEvent(Object source) {
super(source);
}
}
两种发送事件的方式:
@Service("eventUserService")
public class UserService implements ApplicationContextAware, ApplicationEventPublisherAware {
private ApplicationContext applicationContext;
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public String addUser(User user) {
// 保存用户
user.setId(1L);
user.setName("name-1");
// 发生事件(发邮件、发短信、、、)
applicationContext.publishEvent(new UserEvent(user));
// 两种发生方式一致
applicationEventPublisher.publishEvent(new UserEvent(user));
return "ok";
}
}
@EvnetListener监听实现
@Component
public class UserListener {
@EventListener
public void getUserEvent(UserEvent userEvent) {
System.out.println("getUserEvent-接受到事件:" + userEvent);
}
@EventListener
public void getUserEvent2(UserEvent userEvent) {
System.out.println("getUserEvent2-接受到事件:" + userEvent);
}
}
测试
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = KevinToolApplication.class )
public class AnnotationEventListenerTest {
@Autowired
private UserService userService;
@Test
public void annotationEventTest() {
userService.addUser(new User());
}
}
6. ApplicationRunner、7. CommandLineRunner
有时针对一些特殊的业务场景,需要在系统启动时执行某些任务,如:配置文件的加载、数据库的初始化等等操作。SpringBoot 提供了两种解决方案:一种是使用 CommandLineRunner,另一种是使用 ApplicationRunner。
项目在启动时会遍历所有的 ApplicationRunner 的实现类并调用其中的 run 方法,如果在系统中有多个 ApplicationRunner的实现类,可以使用 @Order 注解对这些实现类的调用顺序进行排序(数字越小越先执行); run方法的参数是系统启动时传入的参数,即入口类中main方法的参数(在调用SpringApplication.run方法时传入到 SpringBoot项目的上下文环境中)。
@Component
@Slf4j
@Order(1)
public class MyApplicationRunner implements ApplicationRunner {
// ApplicationArguments, 需要区分选项参数和非选项参数;
// 选项参数, 通过ApplicationArguments的getOptionNames()方法获取所有选项名称即参数的KEY, 然后通过 getOptionValues()方法根据参数KEY, 获取实际值(它会返回一个列表字符串), 一般为: --user-name=ROCKY --age=30
// 非选项参数, 通过ApplicationArguments的getNonOptionArgs()方法获取一个参数值数组;
@Override
public void run(ApplicationArguments args) throws Exception {
// TO DO SOMETHING...
}
}
@Component
@Slf4j
@Order(1)
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// TO DO SOMETHING...
}
}
ApplicationRunner与CommandLineRunner的主要区别体现在run方法的参数上,CommandLineRunner 中的 run 方法的参数是参数数组;ApplicationRunner 中的 run 方法的参数是 ApplicationArguments 对象。
执行顺序 系统支持创建多个 CommandLineRunner 或 ApplicationRunner 的实现类,可以使用 @Order 注解或实现 Ordered 接口,来设定各个实现类的执行顺序。
选项参数、非选项参数、系统参数 选项参数:可以理解为Spring Boot 提供的参数格式,以
–-
开头,使用=
分割键值对,如:java -jar XXX.jar --name=ROCKY --age=30 --spring.profiles.active=dev
非选项参数:不是以-–
开头,也没有设置值的单一参数KEY只有值,如:java -jar XXX.jar --name=ROCKY --age=30 --spring.profiles.active=dev 陕西 西安 雁塔区
,其中陕西 西安 雁塔区
就是非选项参数; 系统参数:-Dxxxx
是设置 Java 运行上下文的参数语法,用于配置一些环境变量,如:java -jar XXX.jar -Dserver.port=8081 --name=ROCKY --age=30 --spring.profiles.active=dev 陕西 西安 雁塔区
,其中-Dserver.port
就是系统参数。
8. Spring中ApplicationListener的使用
ApplicationListener是Spring事件机制的一部分,与抽象类ApplicationEvent类配合来完成ApplicationContext的事件机制。
如果容器中存在ApplicationListener的Bean,当ApplicationContext调用publishEvent方法时,对应的Bean会被触发。这一过程是典型的观察者模式的实现
自定义事件及监听
public class NotifyEvent extends ApplicationEvent {
private String email;
private String content;
public NotifyEvent(Object source) {
super(source);
}
public NotifyEvent(Object source, String email, String content) {
super(source);
this.email = email;
this.content = content;
}
// 省略getter/setter方法
}
定义监听器NotifyListener:
@Component
public class NotifyListener implements ApplicationListener<NotifyEvent> {
// 监听器通过@Component注解进行实例化,并在onApplicationEvent中打印相关信息。
@Override
public void onApplicationEvent(NotifyEvent event) {
System.out.println("邮件地址:" + event.getEmail());
System.out.println("邮件内容:" + event.getContent());
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class ListenerTest {
@Autowired
private WebApplicationContext webApplicationContext;
@Test
public void testListener() {
NotifyEvent event = new NotifyEvent("object", "abc@qq.com", "This is the content");
webApplicationContext.publishEvent(event);
}
}
应用:Spring 中内置事件 ContextRefreshedEvent ,当 ApplicationContext 被初始化或刷新时,会触发 ContextRefreshedEvent 事件
参考文章: https://blog.csdn.net/wo541075754/article/details/96287667