Thực hiện phương pháp khởi động vào mùa xuân


176

Có tính năng Spring 3 nào để thực thi một số phương thức khi ứng dụng khởi động lần đầu tiên không? Tôi biết rằng tôi có thể thực hiện thủ thuật thiết lập một phương thức với @Scheduledchú thích và nó thực thi ngay sau khi khởi động, nhưng sau đó nó sẽ thực hiện định kỳ.


1
Bí quyết với @Schediated là gì? đó chính xác là những gì tôi muốn!
chrismarx

Câu trả lời:


185

Nếu bằng "khởi động ứng dụng", bạn có nghĩa là "khởi động bối cảnh ứng dụng", thì có, có nhiều cách để làm điều này , cách đơn giản nhất (đối với đậu singletons, dù sao) là chú thích phương thức của bạn @PostConstruct. Hãy nhìn vào liên kết để xem các tùy chọn khác, nhưng tóm lại chúng là:

  • Các phương thức chú thích với @PostConstruct
  • afterPropertiesSet()như được định nghĩa bởi InitializingBeangiao diện gọi lại
  • Một phương thức init () được cấu hình tùy chỉnh

Về mặt kỹ thuật, đây là những cái móc vào vòng đời của đậu , chứ không phải là vòng đời bối cảnh, nhưng trong 99% trường hợp, cả hai đều tương đương nhau.

Nếu bạn cần nối cụ thể vào việc khởi động / tắt ngữ cảnh, thì bạn có thể thực hiện Lifecyclegiao diện thay thế, nhưng điều đó có lẽ không cần thiết.


7
Tôi vẫn chưa thấy triển khai Vòng đời hoặc SmartLifecycle sau khá nhiều nghiên cứu. Tôi biết đây là một năm tuổi, nhưng skaffman nếu bạn có bất cứ điều gì bạn có thể đăng sẽ được đánh giá cao.

4
Các phương thức trên được gọi trước khi toàn bộ bối cảnh ứng dụng đã được tạo (ví dụ: / trước / phân định giao dịch đã được thiết lập).
Hans Westerbeek

Tôi nhận được một cảnh báo lạ khi cố gắng sử dụng @PostConstruct trong 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
bắt giữ

2
Có những trường hợp quan trọng trong đó vòng đời của beanbối cảnh rất khác nhau. Như @HansWesterbeek lưu ý rằng một bean có thể được thiết lập trước khi bối cảnh mà nó phụ thuộc hoàn toàn sẵn sàng. Trong tình huống của tôi, một hạt đậu phụ thuộc vào JMS - nó được xây dựng hoàn chỉnh, do đó @PostConstructphương thức của nó được gọi, nhưng cấu trúc hạ tầng của JMS mà nó gián tiếp phụ thuộc vào vẫn chưa được kết nối đầy đủ (và mọi thứ đều thất bại trong mùa xuân). Khi chuyển sang @EventListener(ApplicationReadyEvent.class)mọi thứ đã hoạt động ( ApplicationReadyEventlà Spring Boot dành riêng cho vanilla Spring, hãy xem câu trả lời của Stefan).
George Hawkins

@Skaffman: điều gì xảy ra nếu hạt đậu của tôi không được giới thiệu bởi bất kỳ loại đậu nào và tôi muốn khởi tạo hạt đậu mà không được sử dụng ở bất cứ đâu
Sagar Kharab

104

Điều này dễ dàng được thực hiện với một ApplicationListener. Tôi đã làm điều này để nghe Spring's 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 
  }
}

Trình nghe ứng dụng chạy đồng bộ vào mùa xuân. Nếu bạn muốn đảm bảo mã của bạn chỉ được thực thi một lần, chỉ cần giữ một số trạng thái trong thành phần của bạn.

CẬP NHẬT

Bắt đầu với Spring 4.2+, bạn cũng có thể sử dụng @EventListenerchú thích để quan sát ContextRefreshedEvent(cảm ơn @bphilipnyc vì đã chỉ ra điều này):

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
Điều này cũng làm việc cho tôi - hoàn hảo cho việc khởi tạo không phải là đậu một lần.
Rory Hunter

9
NB cho những người bị cám dỗ sử dụng ContextStartedEventthay vào đó, khó khăn hơn để thêm người nghe trước khi sự kiện diễn ra.
OrangeDog

2
Làm thế nào để gọi một kho lưu trữ JPA @Autowired vào sự kiện? kho lưu trữ là null.
điện tử

Không làm việc cho tôi. Tôi đang sử dụng spring mvc 3. mehod onApplicationEvent (___) này không được gọi khi ứng dụng khởi động. Bất kỳ giúp đỡ. Đây là mã của tôi @Component lớp công khai AppStartListener triển khai ApplicationListener <ContextRefreshedEvent> {public void onApplicationEvent (sự kiện ContextRefreshedEvent cuối cùng) {System.out.println ("\ n \ n \ nInside) }}
Vishwas Tyagi

@VishwasTyagi Làm thế nào để bạn bắt đầu container của bạn? Bạn có chắc chắn, AppStartListener của bạn là một phần của quá trình quét thành phần của bạn?
Stefan Haberl

38

Trong Spring 4.2+ bây giờ bạn có thể chỉ cần làm:

@Component
class StartupHousekeeper {

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

Có đảm bảo rằng người nghe này chỉ gọi một lần sau khi khởi động?
gstackoverflow

Không, xem câu trả lời của tôi ở trên. Giữ một số trạng thái trong trình nghe của bạn để kiểm tra nếu nó đang thực thi lần đầu tiên
Stefan Haberl

13

Nếu bạn đang sử dụng spring-boot, đây là câu trả lời tốt nhất.

Tôi cảm thấy điều đó @PostConstructvà các cách xen kẽ vòng đời khác nhau là những cách xoay quanh. Những điều này có thể dẫn trực tiếp đến các vấn đề thời gian chạy hoặc gây ra ít lỗi hơn rõ ràng do các sự kiện vòng đời / bối cảnh không mong muốn. Tại sao không trực tiếp gọi bean của bạn bằng cách sử dụng Java đơn giản? Bạn vẫn gọi bean theo 'con đường mùa xuân' (ví dụ: thông qua proxy AoP mùa xuân). Và tốt nhất, đó là java đơn giản, không thể đơn giản hơn thế. Không cần người nghe bối cảnh hoặc lịch trình kỳ lạ.

@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
Đây là một ý tưởng tốt nói chung nhưng khi bắt đầu bối cảnh ứng dụng mùa xuân của bạn từ một bài kiểm tra tích hợp, chính là không bao giờ chạy!
Jonas Geiregat

@JonasGeiregat: Ngoài ra, có những kịch bản khác không có gì main()cả, ví dụ như khi sử dụng khung ứng dụng (ví dụ: JavaServer Faces).
sleske

9

Đối với người dùng Java 1.8 đang nhận được cảnh báo khi cố gắng tham chiếu chú thích @PostConstruct, tôi đã kết thúc thay vì cõng chú thích @Schediated mà bạn có thể làm nếu bạn đã có một công việc @Schediated với fixedRate hoặc 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

Những gì chúng tôi đã làm là mở rộng org.springframework.web.context.ContextLoaderListenerđể in một cái gì đó khi bối cảnh bắt đầu.

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

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

Cấu hình lớp con sau đó trong web.xml:

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

7

Với SpringBoot, chúng ta có thể thực hiện một phương thức khi khởi động thông qua @EventListenerchú thích

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

4

Chú ý, điều này chỉ được khuyên nếu runOnceOnStartupphương pháp của bạn phụ thuộc vào bối cảnh mùa xuân được khởi tạo hoàn toàn. Ví dụ: bạn muốn gọi một dao với phân định giao dịch

Bạn cũng có thể sử dụng một phương thức được lên lịch với fixedDelay được đặt rất cao

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

Điều này có lợi thế là toàn bộ ứng dụng được nối dây (Giao dịch, Dao, ...)

được thấy trong Lập lịch tác vụ để chạy một lần, sử dụng không gian tên tác vụ Spring


Tôi không thấy lợi thế nào khi sử dụng @PostConstruct?
Wim Deblauwe

@WimDeblauwe phụ thuộc vào những gì bạn muốn làm trong dos somebody () gọi một dao Tự động với phân định Trasaction cần toàn bộ bối cảnh để bắt đầu, không chỉ là hạt đậu này
Joram

5
Phương thức @WimDeblauwe '@PostConstruct' kích hoạt khi bean được khởi tạo, toàn bộ bối cảnh có thể chưa sẵn sàng (quản lý giao dịch)
Joram

Đây là imo thanh lịch hơn so với xây dựng bài hoặc bất kỳ giao diện hoặc sự kiện nào
aliopi


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

        }
    }
}

2
ApplicationReadyEvent đang khởi động vào mùa xuân chứ không phải vào mùa xuân 3
John Mercier

0

Nếu bạn muốn cấu hình một bean trước khi ứng dụng của bạn chạy hoàn toàn, bạn có thể sử dụng @Autowired:

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

0

Bạn có thể sử dụng @EventListenertrên thành phần của mình, thành phần này sẽ được gọi sau khi máy chủ được khởi động và tất cả các bean được khởi tạo.

@EventListener
public void onApplicationEvent(ContextClosedEvent event) {

}

0

Đối với một tập tin StartupHousekeeper.javanằm trong gói com.app.startup,

Làm điều này trong StartupHousekeeper.java:

@Component
public class StartupHousekeeper {

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

Và làm điều này trong myDispatcher-servlet.java:

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

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

</beans>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.