文章目录
1、@PostConstruct 注解
2、spring 的指定init-method属性指定方法
3、实现 InitializingBean 接口,重写afterPropertiesSet()
4、实现ApplicationListener接口,重写onApplicationEvent()
5、各种接口综合使用
6、使用 定时器 quartz
6.1、quartz的定时器
6.2、Spring task任务调度 (推荐)
7、执行顺序

springmvc 容器启动后,要执行一些工作。
1、@PostConstruct 注解
@PostConstruct 是在对象构造完成,并且属性赋值完成后,调用 init-method 之前执行的。

如果你在 applicationContext.xml 中配置default-lazy-init="true" 延迟初始化,意味着容器刚启动,如果这个bean没有被调用,是不会初始化的,自然也就不会调用@PostConstruct 的方法。

注意:
1,把 @PostConstruct 放到要执行实始化的方法上面。
2,Service(如AppDicService) 必须注入到 Spring容器中才行。

package com.aop8.springmvc2.initload;
 import javax.annotation.PostConstruct;import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;import com.aop8.springmvc2.service.UserService;
@Component
 public class AppDicService{        @Autowired
     private UserService  xxxService;
     
     /**
      * Spring 启动完成后,会自动调用此方法
      */    
     @PostConstruct
     public void initAppDicCache(){
         //执行加载操作,        
         // 比如  xxxService.getList()
         System.out.println("@PostConstruct");
         xxxService.print();
     }    
 }

2、spring 的指定init-method属性指定方法
Spring在初始化bean时,在对象构造完成后,会调用 init-method 和 destroy-method属性中指定的方法。

<?xml version="1.0" encoding="UTF-8"?>
  <beans xmlns="http://www.springframework.org/schema/beans"       
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" >
         
     <-- 
     scope 的值一定要是 singleton
     inti-method 是指要执行的方法 
     -->
     <bean id="startRun" class="com.aop8.springmvc2.initload.StartRun" scope="singleton" init-method="testInit"   />
     
  </beans>

 package com.aop8.springmvc2.initload;public class StartRun {
     
     public void testInit(){
         System.out.println("开始执行 init-method() ");
     }
 }


3、实现 InitializingBean 接口,重写afterPropertiesSet()
spring很多组建的初始化都放在afterPropertiesSet 里。
我们在做一些中间件想和spring一起启动,可以放在这里启动。

package com.aop8.springmvc2.initload;
import org.springframework.beans.factory.InitializingBean;
 import org.springframework.stereotype.Component;@Component
 public class ArraignedLogService implements InitializingBean {    @Override
     public void afterPropertiesSet() throws Exception {
         System.out.println("通过实现接口 initializingBean,调用afterPropertiesSet()方法 来执行的");    }
 }



4、实现ApplicationListener接口,重写onApplicationEvent()

使用场景
在一些业务场景中,当容器初始化完成之后,需要处理一些操作,比如一些数据的加载、初始化缓存、特定任务的注册等等。这个时候我们就可以使用Spring提供的ApplicationListener来进行操作。

遇到的问题
在spring中可以通过ApplicationListener来实现相关的功能,加载完成后触发contextrefreshedevent事件(上下文件刷新事件)。

但是这个时候,会存在一个问题,在web 项目中(spring mvc),系统会存在两个容器,一个是root application context,另一个就是我们自己的 projectName-servlet context(作为 root application context 的子容器)。
这种情况下,就会造成 onApplicationEvent 方法被执行两次。

为了避免上面提到的问题,我们可以只在 root application context 初始化完成后调用逻辑代码,其他的容器的初始化完成,则不做任何处理,修改后代码。

解决方法:

if(event.getApplicationContext().getParent() == null){//root application context 没有parent
     //TODO 这里写下将要初始化的内容
 }
       
 if(event.getApplicationContext().getDisplayName().equals("Root WebApplicationContext")){}



示例:

import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
  
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.stereotype.Component;
  
 import com.xx.service.DemoService;
  
 @Component
 public class InitBeanTest implements ApplicationListener<ContextRefreshedEvent> { 
     @Resource
     DemoService demoService; 
     
     //构造函数
     public InitBeanTest() {   
         System.err.println("----> InitBeanTest: constructor: "+demoService);   
     }
  
     //实现 ApplicationListener 接口,必须重写的方法
     @Override
     public void onApplicationEvent(ContextRefreshedEvent event) {
         System.out.println("ApplicationListener:onApplicationEvent");
         
         // demoService.testMethod();
         
         if(event.getApplicationContext().getParent() == null){//root application context 没有parent
             //TODO 这里写下将要初始化的内容
         }
         
         if(event.getApplicationContext().getDisplayName().equals("Root WebApplicationContext")){
         
         }
        
     } 
 }


执行结果:

----> InitBeanTest: constructor: null
----> ApplicationListener: onApplicationEvent
----> ApplicationListener: onApplicationEvent


分析:
onApplicationEvent 出现了两次,为什么?因为bean注入了DemoService,spring容器会被刷新。

换言之onApplicationEvent会被频繁执行,需要使用它监听,需要考虑性能问题。

@Service 也可以改成使用 @Component 等 spring 注解。
如果不想使用@Service 注解,也可以在 applicationContext.xml 容器中配置bean 。

5、各种接口综合使用

import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
  
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.stereotype.Component;
  
 import com.xx.service.DemoService;
  
 @Component
 public class InitBeanTest implements InitializingBean,ApplicationListener<ContextRefreshedEvent> { 
     @Resource
     DemoService demoService;
     //构造函数
     public InitBeanTest() {   
            System.err.println("----> InitBeanTest: constructor: "+demoService);   
     }
  
     @PostConstruct
     public void postConstruct() {
         System.err.println("----> postConstruct: "+demoService);
     }
  
      //实现 InitializingBean 接口,必须重写的方法
     @Override
     public void afterPropertiesSet() throws Exception {
         System.err.println("----> InitializingBean : afterPropertiesSet: "+demoService);
     }
  
     //实现 ApplicationListener 接口,必须重写的方法
     @Override
     public void onApplicationEvent(ContextRefreshedEvent arg0) {
         System.err.println("----> ApplicationListener: onApplicationEvent");
     } 
 }


执行结果:

----> InitBeanTest: constructor: null
 ----> postConstruct: com.yiniu.kdp.service.impl.DemoServiceImpl@40fe544
 ----> InitializingBean : afterPropertiesSet: com.aa.DemoServiceImpl@40fe544
 ----> ApplicationListener: onApplicationEvent
 ----> ApplicationListener: onApplicationEvent



分析:

1、构造函数是每个类最先执行的,这个时候,bean属性还没有被注入

2、postConstruct优先于afterPropertiesSet执行,这时属性竟然也被注入了,有点意外

3、spring很多组建的初始化都放在afterPropertiesSet做。我们在做一些中间件想和spring一起启动,可以放在这里启动。

4、onApplicationEvent属于应用层的时间,最后被执行,很容易理解。注意,它出现了两次,为什么?因为bean注入了DemoService,spring容器会被刷新。

换言之onApplicationEvent会被频繁执行,需要使用它监听,需要考虑性能问题。

@Service 也可以改成使用 @Component 等 spring 注解。
如果不想使用@Service 注解,也可以在 applicationContext.xml 容器中配置bean 。

6、使用 定时器 quartz
6.1、quartz的定时器
InitLoadDataQuartzJob 类省略。

下面直接看配置:

<?xml version="1.0" encoding="UTF-8"?>
  <beans xmlns="http://www.springframework.org/schema/beans"
         xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd  
         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd  
         http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd" >
         
      <bean id="initLoadData" class="xx.xx.xx.InitLoadDataQuartzJob"/>
      
      <bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
          <property name="targetObject"  ref="initLoadDataDetail" />
          <property name="targetMethod"  value="loadMethod" />
          <property name="concurrent" value="false"  />
      </bean>
  
  
          <!-- 项目启动后任务就执行一次 -->
      <bean id="initLoadDataDetailTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
          <property name="jobDetail" ref="initLoadDataDetail"/>
          <property name="startDelay" value="500"/>
          <property name="repeatInterval" value="0"/>
          <property name="repeatCount" value="0"/>
          <!-- 
          <property name="cronExpression" value="0 0,30 8-17 * * ?"/>
           -->
          
      </bean>
     
      <bean id="startQuertz" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
          <property name="triggers">
              <list>
                  <ref bean="initLoadDataDetailTrigger"/>
              </list>
          </property>
      </bean>
      
  </beans>


6.2、Spring task任务调度 (推荐)
task是Spring自带的一个设定时间自动任务调度。

Service类:
/**
  * 定时计算
  */
 @Component("profitScheduler")
 public class ProfitScheduler {        @Autowired
     private XXXService  xxxService;    
     
     @Scheduled
     public void execute() {
         logger.info("start 执行定时任务");
         try {
                 xxxService.testMethod();
         } catch (Exception e) {
             logger.error("执行异常。{}",e);
             e.printStackTrace();
         }        
         logger.info("end 执行定时任务");
     }    
 }


1、@Component 将类注入到Spring容器中;
2、在要执行的方法上加 @Scheduled 。要执行的方法与下面的xml配置的method值是对应的;
3、方法名是不是固定的。只要@Scheduled 注解的方法名与xml配置的一致即可。

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xmlns:task="http://www.springframework.org/schema/task"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd    
         http://www.springframework.org/schema/task         http://www.springframework.org/schema/task/spring-task-4.0.xsd">    
     <!-- 
     每2分钟执行一次:
     0 0/2 * * * ?
      -->    
     
     <!-- 定时任务 -->
     <task:scheduled-tasks scheduler="scheduler" >        
         
         <task:scheduled ref="profitScheduler" method="execute" cron="0 0/2 * * * ?" />
         
     </task:scheduled-tasks>
     
     <task:scheduler id="scheduler" pool-size="5" />
 </beans>



ref="profitScheduler" 引用的是 ProfitScheduler 类中的 @Component 中的值profitScheduler 。

7、执行顺序
上面的例子,就是按照 执行顺序 列举出来的。

@PostConstruct > init-method > InitializingBean ( afterPropertiesSet ) > ServletContextAware
 ---------------------