ดำเนินการวิธีการเริ่มต้นในฤดูใบไม้ผลิ


176

มีฟีเจอร์ Spring 3 ใดบ้างที่จะดำเนินการวิธีการบางอย่างเมื่อแอปพลิเคชันเริ่มต้นเป็นครั้งแรก? ฉันรู้ว่าฉันสามารถทำเคล็ดลับในการตั้งค่าวิธีการด้วย@Scheduledคำอธิบายประกอบและจะดำเนินการหลังจากเริ่มต้น แต่ก็จะดำเนินการเป็นระยะ


1
เคล็ดลับของ @Scheduled คืออะไร นั่นคือสิ่งที่ฉันต้องการ!
chrismarx

คำตอบ:


185

ถ้าโดย "เริ่มต้นโปรแกรมประยุกต์" คุณหมายถึง "การเริ่มต้นบริบทแอพลิเคชัน" จากนั้นใช่มีหลายวิธีที่จะทำเช่นนี้ที่ง่ายที่สุด (สำหรับถั่ว singletons แล้ว) @PostConstructเป็นอธิบายวิธีการของคุณด้วย ลองดูที่ลิงค์เพื่อดูตัวเลือกอื่น ๆ แต่สรุปได้ว่า:

  • วิธีการข้อเขียนด้วย @PostConstruct
  • afterPropertiesSet()ตามที่กำหนดโดยInitializingBeanอินเตอร์เฟสการติดต่อกลับ
  • วิธี init () ที่กำหนดค่าเอง

ในทางเทคนิคแล้วสิ่งเหล่านี้คือ hooks ลงในlifecycle ของถั่วแทนที่จะเป็น lifecycle บริบท แต่ใน 99% ของทั้งสองกรณีนั้นเทียบเท่ากัน

หากคุณต้องการเชื่อมโยงกับการLifecycleเริ่มต้น / ปิดบริบทโดยเฉพาะคุณสามารถใช้อินเทอร์เฟซแทนได้ แต่นั่นอาจไม่จำเป็น


7
ฉันยังไม่เห็นการใช้งาน Lifecycle หรือ SmartLifecycle หลังจากทำการวิจัยค่อนข้างน้อย ฉันรู้ว่านี่เป็นปี แต่ skaffman ถ้าคุณมีอะไรที่คุณสามารถโพสต์ที่จะได้รับการชื่นชมมาก

4
วิธีการข้างต้นจะถูกเรียกใช้ก่อนที่จะสร้างบริบทของแอปพลิเคชันทั้งหมด (เช่น. / before / การแบ่งเขตแดนของธุรกรรมได้รับการตั้งค่า)
Hans Westerbeek

ฉันได้รับคำเตือนแปลก ๆ ที่พยายามใช้ @PostConstruct ใน java 1.8:Access restriction: The type PostConstruct is not accessible due to restriction on required library /Library/Java/JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/jre/lib/rt.jar
encrest

2
มีหลายกรณีที่สำคัญที่วงจรชีวิตของถั่วและบริบทแตกต่างกันมาก @HansWesterbeek สังเกตว่าสามารถตั้งค่า bean ก่อนที่บริบทจะขึ้นอยู่กับว่ามีความพร้อม ในสถานการณ์ของฉันถั่วขึ้นอยู่กับ JMS - มันถูกสร้างขึ้นอย่างสมบูรณ์ดังนั้น@PostConstructวิธีการของมันจึงถูกเรียก แต่ JMS โครงสร้างพื้นฐานที่ขึ้นอยู่กับทางอ้อมนั้นยังไม่ได้เชื่อมต่ออย่างเต็มรูปแบบ เมื่อเปลี่ยนไปใช้@EventListener(ApplicationReadyEvent.class)ทุกอย่างที่ทำงาน ( ApplicationReadyEventเป็น Spring Boot เฉพาะสำหรับวานิลลาสปริงดูคำตอบของ Stefan)
George Hawkins

@Skaffman: ถ้าถั่วของฉันไม่ได้ถูกเรียกโดยถั่วใด ๆ และฉันต้องการเริ่มต้นถั่วโดยไม่ต้องใช้ทุกที่
Sagar Kharab

104

ApplicationListenerนี้จะกระทำได้อย่างง่ายดายด้วยการ ฉันได้สิ่งนี้เพื่อฟังการทำงานของ Spring ContextRefreshedEvent:

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

@Component
public class StartupHousekeeper implements ApplicationListener<ContextRefreshedEvent> {

  @Override
  public void onApplicationEvent(final ContextRefreshedEvent event) {
    // do whatever you need here 
  }
}

แอปพลิเคชั่นตัวรับฟังทำงานพร้อมกันใน Spring หากคุณต้องการตรวจสอบให้แน่ใจว่าคุณใช้รหัสเพียงครั้งเดียวให้คงสถานะไว้ในองค์ประกอบ

UPDATE

เริ่มต้นด้วย Spring 4.2+ คุณสามารถใช้@EventListenerคำอธิบายประกอบเพื่อสังเกตContextRefreshedEvent(ขอบคุณ@bphilipnycสำหรับการชี้เรื่องนี้):

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

@Component
public class StartupHousekeeper {

  @EventListener(ContextRefreshedEvent.class)
  public void contextRefreshedEvent() {
    // do whatever you need here 
  }
}

1
สิ่งนี้เหมาะสำหรับฉันเช่นกัน - เหมาะสำหรับการเริ่มต้นครั้งแรกที่ไม่ใช่ถั่ว
Rory Hunter

9
หมายเหตุสำหรับผู้ที่ถูกล่อลวงให้ใช้ContextStartedEventแทนมันเป็นการยากที่จะเพิ่มผู้ฟังก่อนที่เหตุการณ์จะเริ่ม
OrangeDog

2
วิธีการเรียกที่เก็บ JPA @Autowired เป็นเหตุการณ์ ที่เก็บเป็นโมฆะ
e-info128

ไม่ทำงานสำหรับฉัน ฉันกำลังใช้สปริง mvc 3. mehod onApplicationEvent (___) นี้ไม่ได้รับการเรียกเมื่อเริ่มต้นแอปพลิเคชัน ความช่วยเหลือใด ๆ นี่คือรหัสของฉัน @Component public class AppStartListener ใช้ ApplicationListener <ContextRefreshedEvent> {โมฆะสาธารณะ onApplicationEvent (เหตุการณ์ ContextRefreshedEvent สุดท้าย) {System.out.println ("\ n \ n \ n ภายในแอปพลิเคชัน"); }}
Vishwas Tyagi

@VishwasTyagi คุณจะเริ่มภาชนะของคุณอย่างไร? คุณแน่ใจหรือว่า AppStartListener เป็นส่วนหนึ่งของการสแกนส่วนประกอบของคุณ?
Stefan Haberl

38

ใน Spring 4.2+ ตอนนี้คุณสามารถทำได้ง่ายๆ

@Component
class StartupHousekeeper {

    @EventListener(ContextRefreshedEvent.class)
    public void contextRefreshedEvent() {
        //do whatever
    }
}

มันรับประกันได้หรือไม่ว่าผู้ฟังนี้จะเรียกใช้เพียงครั้งเดียวหลังจากเริ่มต้น?
gstackoverflow

ไม่เห็นคำตอบของฉันด้านบน ให้บางรัฐอยู่ในผู้ฟังของคุณเพื่อตรวจสอบว่ามันกำลังดำเนินการเป็นครั้งแรก
Stefan Haberl

13

หากคุณใช้สปริงบูตนี่คือคำตอบที่ดีที่สุด

ฉันรู้สึกว่า@PostConstructและคำอุทานต่าง ๆ ของวงจรชีวิตอื่น ๆ เป็นวิธีรอบด้าน สิ่งเหล่านี้สามารถนำไปสู่ปัญหาโดยตรงของรันไทม์หรือทำให้เกิดข้อบกพร่องน้อยกว่าที่เห็นได้ชัดเนื่องจากเหตุการณ์รอบการทำงานของ bean / บริบทที่ไม่คาดคิด ทำไมไม่ลองใช้ bean ของคุณโดยตรงด้วย Java ธรรมดา คุณยังคงเรียกใช้ถั่วว่า 'spring way' (เช่นผ่านพร็อกซี AoP ของสปริง) และที่ดีที่สุดคือ java ธรรมดาไม่สามารถทำได้ง่ายกว่านั้น ไม่จำเป็นสำหรับผู้ฟังตามบริบทหรือตัวกำหนดเวลาแปลก ๆ

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext app = SpringApplication.run(DemoApplication.class, args);

        MyBean myBean = (MyBean)app.getBean("myBean");

        myBean.invokeMyEntryPoint();
    }
}

5
นี่เป็นความคิดที่ดีโดยทั่วไป แต่เมื่อเริ่มต้นบริบทแอปพลิเคชันสปริงของคุณจากการทดสอบการรวมระบบหลักจะไม่ทำงาน!
Jonas Geiregat

@JonasGeiregat: นอกจากนี้ยังมีสถานการณ์อื่น ๆ ที่ไม่มีmain()เลยตัวอย่างเช่นเมื่อใช้เฟรมเวิร์กแอปพลิเคชัน (เช่น JavaServer Faces)
sleske

9

สำหรับผู้ใช้ Java 1.8 ที่ได้รับคำเตือนเมื่อพยายามอ้างอิงคำอธิบายประกอบ @PostConstruct ฉันลงเอยด้วยการแทนที่คำอธิบายประกอบ @Scheduled ซึ่งคุณสามารถทำได้หากคุณมีงาน @Scheduled ที่ fixedRate หรือ fixedDelay แล้ว

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@EnableScheduling
@Component
public class ScheduledTasks {

private static final Logger LOGGER = LoggerFactory.getLogger(ScheduledTasks.class);

private static boolean needToRunStartupMethod = true;

    @Scheduled(fixedRate = 3600000)
    public void keepAlive() {
        //log "alive" every hour for sanity checks
        LOGGER.debug("alive");
        if (needToRunStartupMethod) {
            runOnceOnlyOnStartup();
            needToRunStartupMethod = false;
        }
    }

    public void runOnceOnlyOnStartup() {
        LOGGER.debug("running startup job");
    }

}


7

สิ่งที่เราทำคือการขยายorg.springframework.web.context.ContextLoaderListenerการพิมพ์บางอย่างเมื่อบริบทเริ่มต้นขึ้น

public class ContextLoaderListener extends org.springframework.web.context.ContextLoaderListener
{
    private static final Logger logger = LoggerFactory.getLogger( ContextLoaderListener.class );

    public ContextLoaderListener()
    {
        logger.info( "Starting application..." );
    }
}

กำหนดค่าคลาสย่อยจากนั้นในweb.xml:

<listener>
    <listener-class>
        com.mycomp.myapp.web.context.ContextLoaderListener
    </listener-class>
</listener>

7

ด้วย SpringBoot เราสามารถดำเนินการวิธีการในการเริ่มต้นผ่าน@EventListenerคำอธิบายประกอบ

@Component
public class LoadDataOnStartUp
{   
    @EventListener(ApplicationReadyEvent.class)
    public void loadData()
    {
        // do something
    }
}

4

ข้อควรระวังนี่จะแนะนำก็ต่อเมื่อrunOnceOnStartupวิธีการของคุณขึ้นอยู่กับบริบทสปริงที่เริ่มต้นอย่างสมบูรณ์ ตัวอย่างเช่น: คุณต้องการเรียก dao ด้วยการแบ่งเขตธุรกรรม

นอกจากนี้คุณยังสามารถใช้วิธีการตั้งเวลากับ fixedDelay ตั้งค่าสูงมาก

@Scheduled(fixedDelay = Long.MAX_VALUE)
public void runOnceOnStartup() {
    dosomething();
}

นี่เป็นข้อดีที่แอปพลิเคชันทั้งหมดเชื่อมต่อกัน (ธุรกรรม, Dao, ... )

เห็นได้ในการจัดตารางงานให้ทำงานครั้งเดียวโดยใช้เนมสเปซ Spring


ฉันไม่เห็นความได้เปรียบมากกว่าการใช้@PostConstruct?
Wim Deblauwe

@WimDeblauwe ขึ้นอยู่กับสิ่งที่คุณต้องการทำใน dosomething () เรียก dao Autowired ด้วยการแบ่งเขต Trasaction ต้องการบริบททั้งหมดที่จะเริ่มต้นขึ้นไม่ใช่แค่ถั่วนี้
Joram

5
@WimDeblauwe '@PostConstruct' วิธีการ fires เมื่อถั่วจะเริ่มต้นบริบททั้งหมดอาจไม่พร้อม (การจัดการการทำธุรกรรม fe)
Joram

นี่คือ IMO ที่สง่างามกว่าการสร้างโพสต์หรืออินเทอร์เฟซหรือกิจกรรมใด ๆ
aliopi

1

โพสต์โซลูชันอื่นที่ใช้ WebApplicationInitializer และเรียกว่ามากก่อนสปริงถั่วใด ๆ จะถูกยกตัวอย่างในกรณีที่มีคนใช้กรณีที่

เริ่มต้นสถานที่เริ่มต้นและเขตเวลาเริ่มต้นด้วยการกำหนดค่าสปริง


1
AppStartListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if(event instanceof ApplicationReadyEvent){
            System.out.print("ciao");

        }
    }
}

2
ApplicationReadyEvent อยู่ในบูตฤดูใบไม้ผลิไม่ได้อยู่ในฤดูใบไม้ผลิ 3
John Mercier

0

หากคุณต้องการกำหนดค่า bean ก่อนที่แอปพลิเคชันของคุณจะทำงานเต็มที่คุณสามารถใช้@Autowired:

@Autowired
private void configureBean(MyBean: bean) {
    bean.setConfiguration(myConfiguration);
}

0

คุณสามารถใช้@EventListenerกับส่วนประกอบของคุณซึ่งจะถูกเรียกใช้หลังจากเซิร์ฟเวอร์เริ่มทำงานและถั่วทั้งหมดเริ่มต้นได้

@EventListener
public void onApplicationEvent(ContextClosedEvent event) {

}

0

สำหรับไฟล์ที่StartupHousekeeper.javaอยู่ในแพคเกจcom.app.startup,

ทำสิ่งนี้ในStartupHousekeeper.java:

@Component
public class StartupHousekeeper {

  @EventListener(ContextRefreshedEvent.class)
  public void keepHouse() {
    System.out.println("This prints at startup.");
  }
}

และทำสิ่งนี้ในmyDispatcher-servlet.java:

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <mvc:annotation-driven />
    <context:component-scan base-package="com.app.startup" />

</beans>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.