Spring是一个控制反转和依赖管理的容器。作为JavaWeb开发者,基本没有人不熟悉Spring技术栈。虽然在依赖注入领域,JavaWeb领域还有很多其他优秀的框架,比如谷歌开源的依赖管理框架guice,比如Jerseyweb框架。但是Spring已经是JavaWeb领域中应用最为广泛的Java框架。本文将重点讲解如何在Spring容器启动时实现我们想要实现的逻辑。我们经常会遇到一些必须在Spring启动时完成的初始化操作,比如创建定时任务、创建连接池等。如果没有Spring容器,则不依赖Spring的实现,返回Java类本身的实现,我们可以在静态代码块和类构造函数中实现相应的逻辑。Java类的初始化顺序是静态变量>静态代码块>全局变量>初始化块>构造函数。例如,Log4j的初始化,就是在LogManager的静态代码块中现实的:static{Hierarchyh=newHierarchy(newRootLogger((Level)Level.DEBUG));repositorySelector=newDefaultRepositorySelector(h);Stringoverride=OptionConverter.getSystemProperty(DEFAULT_INIT,null_OVERR);if(override==null||"false".equalsIgnoreCase(override)){StringconfigurationOptionStr=OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY,null);StringconfiguratorClassName=OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY,null);URLurl=null;if(configurationOptionStr==null){url=Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);if(url==null){url=Loader.getResource(DEFAULT_CONFIGURATION_FILE);}}else{try{url=newURL(configurationOptionStr);}catch(MalformedURLExceptionex){url=Loader.getResource(configurationOptionStr);}}if(url!=null){LogLog.debug("UsingURL["+url+"]forautomaticlog4jconfiguration.");try{OptionConverter.selectAndConfigure(url,configuratorClassName,LogManager.getLoggerRepository());}catch(NoClassDefFoundError){LogLog.warn("Errorduringdefaultinitialization",e);}}else{LogLog.debug("Couldnotfindresource:["+configurationOptionStr+"].");}}else{LogLog.debug("Defaultinitializationofoverriddenby"+DEFAULT_INIT_OVERRIDE_KEY+"property.");}}比如在构造函数中实现相应的逻辑:@ComponentpublicclassCustomBean{@AutowiredprivateEnvironmentenv;publicCustomBean(){env.getActiveProfiles();}}这里测试大家,上面的代码能不能正常运行——不行,构造函数中的env会出现NullPointException。这是因为在Spring中会先初始化bean,即先调用类的构造函数,然后再注入成员变量所依赖的bean(@Autowired和@Resource注解修饰的成员变量).注意@Value注入等注解的配置也是在构造函数之后。PostConstruct在Spring中,我们可以在Bean初始化后使用@PostConstruct来实现相应的初始化逻辑。@PostConstruct修饰的方法会在Bean初始化后执行。这个时候Bean的依赖也已经注入了,所以在方法中可以调用注入依赖的bean。@ComponentpublicclassCustomBean{@AutowiredprivateEnvironmentenv;@PostContrucepublicvoidinit(){env.getActiveProfiles();}}对应@PostConstruct,如果想在Bean注销时完成一些清理工作,比如关闭线程池,可以使用@PreDestroy注解:@@ComponentpublicclassCustomBean{@AutowiredprivateExecutorServiceexecutor=Executors.newFixedThreadPool(1)@PreDestroypublicvoiddestroy(){env.getActiveProfiles();}}InitializingBean实现Spring的InitializingBean接口也可以实现上述Bean初始化完成后执行相应逻辑的功能,实现InitializingBean界面。afterPropertiesSet方法中实现专辑:@ComponentpublicclassCustomBeanimplementsInitializingBean{privatestaticfinalLoggerLOG=Logger.getLogger(InitializingBeanExampleBean.class);@AutowiredprivateEnvironmentenvironment;@OverridepublicvoidafterPropertiesSet()throwsException{LOG.info(environment.getDefaultProfiles());}}ApplicationListener我们彭堡是时候实现我们想要的初始化逻辑了。此时我们可以使用Spring的初始化事件。Spring有一套完整的事件机制。Spring启动的时候,Spring容器本身预置了很多事件。在整个Spring初始化过程中,相应的节点都会触发相应的事件。我们可以通过监听这些事件逻辑来实现我们的初始化。Spring的事件实现是这样的:ApplicationEvent,一个事件对象,由ApplicationContext发布,不同的实现类代表不同的事件类型。ApplicationListener,监听对象,任何实现了该接口的Bean都会收到相应的事件通知。实现了ApplicationListener接口之后,还需要实现onApplicationEvent()方法,这个方法会在容器初始化完所有的bean之后执行。与SpringContext生命周期相关的几个事件如下:ApplicationStartingEvent:该事件在SpringBoot应用程序开始运行时以及在任何处理之前(监听器和初始化程序注册除外)发送。ContextRefreshedEvent:当ApplicationContext初始化或刷新时发布此事件。使用ConfigurableApplicationContext接口中的refresh()方法也可以发生这种情况。ContextStartedEvent:当使用ConfigurableApplicationContext接口中的start()方法启动ApplicationContext时会触发此事件。您可以查询您的数据库,或者您可以在收到此事件后重新启动任何已停止的应用程序。ApplicationReadyEvent:此事件在调用任何应用程序/命令行运行程序后发送。ContextClosedEvent:当使用ConfigurableApplicationContext接口中的close()方法关闭ApplicationContext时会触发此事件。一个封闭的上下文已经到了生命周期的尽头;它不能被刷新或重新启动。ContextStoppedEvent:Spring最后完成的事件。因此,如果我们想在Spring启动的时候实现一些相应的逻辑,我们可以在Spring启动的时候找到满足我们需求的事件,通过监听相应的事件来完成我们的逻辑:log.info("SubjectContextRefreshedEvent");}}Spring的事件机制除了实现了ApplicationListener接口来监听相应的事件之外,还实现了@EventListener注解来监听相应的事件:@Component@Slf4jpublicclassStartupApplicationListenerExample{@EventListenerpublicvoidonApplicationEvent(ContextRefreshedEventevent){log.info("SubjectContextRefreshedEvent");}}SpringEvent是一个完整的进程内事件发布和订阅机制。除了监听Spring内置的事件,我们还可以使用SpringEvent来实现自定义事件的发布和订阅。功能。构造函数注入在学习Spring的注入机制时,我们都知道Spring可以通过构造函数、Setters、反射成员变量进行注入。上面我们通过成员变量上的@Autoware注解注入依赖bean,但是注入的bean不能在bean的构造函数中使用(因为bean还没有注入)。其实我们也使用了Spring的构造函数注入方式,这也是Spring推荐的注入机制(我们在使用IDEA的时候,如果没有关闭相应的代码Warning机制,我们会发现成员变量上的@Autoware是黄色的,这是idea不推荐的代码)。Spring推荐构造函数注入:@Component@Slf4jpublicclassConstructorBean{privatefinalEnvironmentenvironment;@AutowiredpublicLogicInConstructorExampleBean(Environmentenvironment){this.environment=environment;log.info(Arrays.asList(environment.getDefaultProfiles()));}}如果我们的CommandLineRunner项目使用SpringBoot,所以可以使用SpringBoot提供的CommandLineRunner接口来实现初始化逻辑。SpringBoot初始化完成后会调用实现CommandLineRunner接口的run方法:@Component@Slf4jpublicclassCommandLineAppStartupRunnerimplementsCommandLineRunner{@Overridepublicvoidrun(String...args)throwsException{log.info("Incrementcounter");}}而且,多个CommandLineRunner实现可以通过@Order控制它们的执行顺序。SmartLifecycle还有一种更高级的方式来实现我们的逻辑。这可能是高级Spring开发的基本技能。SmartLifecycle不仅可以在初始化后执行一个逻辑,也可以在关闭前执行一个逻辑,还可以控制多个SmartLifecycle的执行顺序。正如类名所示,这是一个智能的生命周期管理接口。start():bean初始化后,会执行这个方法。stop():容器关闭后,spring容器如果发现当前对象实现了SmartLifecycle,就会调用stop(Runnable),如果只实现了Lifecycle,就会调用stop()。isRunning:当前状态,用于判断你的组件是否正在运行。getPhase:控制多个SmartLifecycles的回调顺序。返回值越小,start()方法越早执行,stop()方法越晚执行。isAutoStartup():在执行start方法之前检查该方法的返回值。如果返回false,则不会执行start方法。stop(Runnable):容器关闭后,spring容器如果发现当前对象实现了SmartLifecycle就会调用stop(Runnable),如果只实现了Lifecycle则调用stop()。@ComponentpublicclassSmartLifecycleExampleimplementsSmartLifecycle{privatebooleanisRunning=false;@Overridepublicvoidstart(){System.out.println("start");isRunning=true;}@OverridepublicintgetPhase(){//默认0return0;}@OverridepublicbooleanisAutoStartup(){//默认isfalsetruereturn;}@OverridepublicbooleanisRunning(){//默认返回falsereturnisRunning;}@Overridepublicvoidstop(Runnablecallback){System.out.println("stop(Runnable)");callback.run();isRunning=false;}@Overridepublicvoidstop(){System.out.println("stop");isRunning=false;}}本文转载自微信公众号“代码字节”,可通过以下二维码关注。转载本文请联系码哥字节公众号。
