了解这些,你就可以在Spring启动时为所欲为了

Spring 是一个控制反转依赖管理的容器,作为 Java Web 的开发人员,基本没有不熟悉 Spring 技术栈的,尽管在依赖注入领域,Java Web 领域不乏其他优秀的框架,如 google 开源的依赖管理框架 guice,如 Jersey web 框架等。但 Spring 已经是 Java Web 领域使用最多,应用最广泛的 Java 框架。

创新互联建站-专业网站定制、快速模板网站建设、高性价比新华网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式新华网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖新华地区。费用合理售后完善,十年实体公司更值得信赖。

此文将专注讲解如何在 Spring 容器启动时实现我们自己想要实现的逻辑。我们时常会遇到在 Spring 启动的时候必须完成一些初始化的操作,如创建定时任务,创建连接池等。

如果没有 Spring 容器,不依赖于 Spring 的实现,回归 Java 类实现本身,我们可以在静态代码块,在类构造函数中实现相应的逻辑,Java 类的初始化顺序依次是静态变量 > 静态代码块 > 全局变量 > 初始化代码块 > 构造器。

比如,Log4j 的初始化,就是在 LogManager 的静态代码块中实现的:

 
 
 
 
  1. static { 
  2.     Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); 
  3.     repositorySelector = new DefaultRepositorySelector(h); 
  4.  
  5.     String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,null); 
  6.  
  7.     if(override == null || "false".equalsIgnoreCase(override)) { 
  8.           String configurationOptionStr = OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY, null); 
  9.           String configuratorClassName = OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY, null); 
  10.  
  11.           URL url = null; 
  12.  
  13.           if(configurationOptionStr == null) { 
  14.             url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE); 
  15.             if(url == null) { 
  16.               url = Loader.getResource(DEFAULT_CONFIGURATION_FILE); 
  17.             } 
  18.           } else { 
  19.             try { 
  20.               url = new URL(configurationOptionStr); 
  21.             } catch (MalformedURLException ex) { 
  22.               url = Loader.getResource(configurationOptionStr); 
  23.             } 
  24.           } 
  25.  
  26.           if(url != null) { 
  27.             LogLog.debug("Using URL ["+url+"] for automatic log4j configuration."); 
  28.             try { 
  29.                 OptionConverter.selectAndConfigure(url, configuratorClassName,LogManager.getLoggerRepository()); 
  30.             } catch (NoClassDefFoundError e) { 
  31.                 LogLog.warn("Error during default initialization", e); 
  32.             } 
  33.           } else { 
  34.               LogLog.debug("Could not find resource: ["+configurationOptionStr+"]."); 
  35.           } 
  36.     } else { 
  37.             LogLog.debug("Default initialization of overridden by " +  DEFAULT_INIT_OVERRIDE_KEY + "property."); 
  38.     } 

比如在构造函数中实现相应的逻辑:

 
 
 
 
  1. @Component 
  2. public class CustomBean { 
  3.  
  4.     @Autowired 
  5.     private Environment env; 
  6.  
  7.     public CustomBean() { 
  8.         env.getActiveProfiles(); 
  9.     } 

这里考验一下各位,上面的代码是否可以正常运行。—— 不行,构造函数中的env将会发生NullPointException异常。这是因为在 Spring 中将先初始化 Bean,也就是会先调用类的构造函数,然后才注入成员变量依赖的 Bean(@Autowired和@Resource注解修饰的成员变量),注意@Value等注解的配置的注入也是在构造函数之后。

PostConstruct

在 Spring 中,我们可以使用@PostConstruct在 Bean 初始化之后实现相应的初始化逻辑,@PostConstruct修饰的方法将在 Bean 初始化完成之后执行,此时 Bean 的依赖也已经注入完成,因此可以在方法中调用注入的依赖 Bean。

 
 
 
 
  1. @Component 
  2. public class CustomBean { 
  3.  
  4.     @Autowired 
  5.     private Environment env; 
  6.  
  7.     @PostConstruce 
  8.     public void init() { 
  9.         env.getActiveProfiles(); 
  10.     } 

与@PostConstruct相对应的,如果想在 Bean 注销时完成一些清扫工作,如关闭线程池等,可以使用@PreDestroy注解:

 
 
 
 
  1. @Component 
  2. public class CustomBean { 
  3.  
  4.     @Autowired 
  5.     private ExecutorService executor = Executors.newFixedThreadPool(1) 
  6.  
  7.     @PreDestroy 
  8.     public void destroy() { 
  9.         env.getActiveProfiles(); 
  10.     } 

InitializingBean

实现 Spring 的InitializingBean接口同样可以实现以上在 Bean 初始化完成之后执行相应逻辑的功能,实现InitializingBean接口,在afterPropertiesSet方法中实现逻辑:

 
 
 
 
  1. @Component 
  2. public class CustomBean implements InitializingBean { 
  3.  
  4.     private static final Logger LOG 
  5.       = Logger.getLogger(InitializingBeanExampleBean.class); 
  6.  
  7.     @Autowired 
  8.     private Environment environment; 
  9.  
  10.     @Override 
  11.     public void afterPropertiesSet() throws Exception { 
  12.         LOG.info(environment.getDefaultProfiles()); 
  13.     } 

ApplicationListener

我们可以在 Spring 容器初始化的时候实现我们想要的初始化逻辑。这时我们就可以使用到 Spring 的初始化事件。Spring 有一套完整的事件机制,在 Spring 启动的时候,Spring 容器本身预设了很多事件,在 Spring 初始化的整个过程中在相应的节点触发相应的事件,我们可以通过监听这些事件来实现我们的初始化逻辑。Spring 的事件实现如下:

  • ApplicationEvent,事件对象,由 ApplicationContext 发布,不同的实现类代表不同的事件类型。
  • ApplicationListener,监听对象,任何实现了此接口的 Bean 都会收到相应的事件通知。实现了 ApplicationListener 接口之后,需要实现方法 onApplicationEvent(),在容器将所有的 Bean 都初始化完成之后,就会执行该方法。

与 Spring Context 生命周期相关的几个事件有以下几个:

  • ApplicationStartingEvent: 这个事件在 Spring Boot 应用运行开始时,且进行任何处理之前发送(除了监听器和初始化器注册之外)。
  • ContextRefreshedEvent: ApplicationContext 被初始化或刷新时,该事件被发布。这也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法来发生。
  • ContextStartedEvent: 当使用 ConfigurableApplicationContext 接口中的 start() 方法启动 ApplicationContext 时,该事件被触发。你可以查询你的数据库,或者你可以在接受到这个事件后重启任何停止的应用程序。
  • ApplicationReadyEvent: 这个事件在任何 application/ command-line runners 调用之后发送。
  • ContextClosedEvent: 当使用 ConfigurableApplicationContext 接口中的 close() 方法关闭 ApplicationContext 时,该事件被触发。一个已关闭的上下文到达生命周期末端;它不能被刷新或重启。
  • ContextStoppedEvent: Spring 最后完成的事件。

因此,如果我们想在 Spring 启动的时候实现一些相应的逻辑,可以找到 Spring 启动过程中符合我们需要的事件,通过监听相应的事件来完成我们的逻辑:

 
 
 
 
  1. @Component 
  2. @Slf4j 
  3. public class StartupApplicationListenerExample implements ApplicationListener<ContextRefreshedEvent> { 
  4.  
  5.     @Override 
  6.     public void onApplicationEvent(ContextRefreshedEvent event) { 
  7.         log.info("Subject ContextRefreshedEvent"); 
  8.     } 

除了通过实现ApplicationListener接口来监听相应的事件,Spring 的事件机制也实现了通过@EventListener注解来监听相对应事件:

 
 
 
 
  1. @Component 
  2. @Slf4j 
  3. public class StartupApplicationListenerExample { 
  4.  
  5.     @EventListener 
  6.     public void onApplicationEvent(ContextRefreshedEvent event) { 
  7.         log.info("Subject ContextRefreshedEvent"); 
  8.     } 

Spring Event 是一套完善的进程内事件发布订阅机制,我们除了用来监听 Spring 内置的事件,也可以使用 Spring Event 实现自定义的事件发布订阅功能。

Constructor 注入

在学习 Spring 的注入机制的时候,我们都知道 Spring 可以通过构造函数、Setter 和反射成员变量注入等方式。上面我们在成员变量上通过@Autoware注解注入依赖 Bean,但是在 Bean 的构造函数函数中却无法使用到注入的 Bean(因为 Bean 还未注入),其实我们也是使用 Spring 的构造函数注入方式, 这也是 Spring 推荐的注入机制(在我们使用 IDEA 的时候,如果没有关闭相应的代码 Warning 机制,会发现在成员变量上的@Autoware是黄色的,也就是 idea 不建议的代码)。Spring 更推荐构造函数注入的方式:

 
 
 
 
  1. @Component 
  2. @Slf4j 
  3. public class ConstructorBean { 
  4.  
  5.     private final Environment environment; 
  6.  
  7.     @Autowired 
  8.     public LogicInConstructorExampleBean(Environment environment) { 
  9.         this.environment = environment; 
  10.         log.info(Arrays.asList(environment.getDefaultProfiles())); 
  11.     } 

CommandLineRunner

如果我们的项目使用的是 Spring Boot,那么可以使用 Spring Boot 提供的 CommandLineRunner 接口来实现初始化逻辑,Spring Boot 将在启动初始化完成之后调用实现了CommandLineRunner的接口的run方法:

 
 
 
 
  1. @Component 
  2. @Slf4j 
  3. public class CommandLineAppStartupRunner implements CommandLineRunner { 
  4.  
  5.     @Override 
  6.     public void run(String...args) throws Exception { 
  7.         log.info("Increment counter"); 
  8.     } 

并且,多个CommandLineRunner实现,可以通过@Order来控制它们的执行顺序。

SmartLifecycle

还有一种更高级的方法来实现我们的逻辑。这可以 Spring 高级开发必备技能哦。SmartLifecycle 不仅仅能在初始化后执行一个逻辑,还能再关闭前执行一个逻辑,并且也可以控制多个 SmartLifecycle 的执行顺序,就像这个类名表示的一样,这是一个智能的生命周期管理接口。

  • start():bean 初始化完毕后,该方法会被执行。
  • stop():容器关闭后,spring 容器发现当前对象实现了 SmartLifecycle,就调用 stop(Runnable), 如果只是实现了 Lifecycle,就调用 stop()。
  • isRunning:当前状态,用来判你的断组件是否在运行。
  • getPhase:控制多个 SmartLifecycle 的回调顺序的,返回值越小越靠前执行 start() 方法,越靠后执行 stop() 方法。
  • isAutoStartup():start 方法被执行前先看此方法返回值,返回 false 就不执行 start 方法了。
  • stop(Runnable):容器关闭后,spring 容器发现当前对象实现了 SmartLifecycle,就调用 stop(Runnable), 如果只是实现了 Lifecycle,就调用 stop()。
 
 
 
 
  1. @Component 
  2. public class SmartLifecycleExample implements SmartLifecycle { 
  3.  
  4.     private boolean isRunning = false; 
  5.  
  6.     @Override 
  7.     public void start() { 
  8.         System.out.println("start"); 
  9.         isRunning = true; 
  10.     } 
  11.  
  12.     @Override 
  13.     public int getPhase() { 
  14.         // 默认为 0 
  15.         return 0; 
  16.     } 
  17.  
  18.     @Override 
  19.     public boolean isAutoStartup() { 
  20.         // 默认为 false 
  21.         return true; 
  22.     } 
  23.  
  24.     @Override 
  25.     public boolean isRunning() { 
  26.         // 默认返回 false 
  27.         return isRunning; 
  28.     } 
  29.  
  30.     @Override 
  31.     public void stop(Runnable callback) { 
  32.         System.out.println("stop(Runnable)"); 
  33.         callback.run(); 
  34.         isRunning = false; 
  35.     } 
  36.  
  37.     @Override 
  38.     public void stop() { 
  39.         System.out.println("stop"); 
  40.  
  41.         isRunning = false; 
  42.     } 
  43.  

本文转载自微信公众号「码哥字节」,可以通过以下二维码关注。转载本文请联系码哥字节公众号。

本文标题:了解这些,你就可以在Spring启动时为所欲为了
文章转载:http://www.mswzjz.com/qtweb/news38/186138.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联