SpringBoot Application with seperate CommandLineRunner that shouldnt run when application starts - java

I have a springboot application in the form
src/main/java/example
- Application.java
- JobConfiguration.java
scheduler
- Job1.java
- Job1Runner.java
- Job2.java
- Job2Runner.java
I like to be able to run my jobs on demand locally so I create a seperate Runner class for every job (e.g JobRunner.java) but currently when I run my Application class it also runs my Job1Runner class because it extends CommandLineRunner. Is there a way I can keep my runners seperate? (so the Application class doesnt run them, and the JobRunners dont run each other etc)
Application
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
JobConfiguration
#Configuration
#EnableScheduling
public class JobConfiguration implements SchedulingConfigurer {
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
// Register jobs
}
}
Example JobRunner
#SpringBootApplication(scanBasePackages = "com.example")
public class JobXRunner implements CommandLineRunner { // Where X is my job index
#Autowired
private final JobX job;
#Override
public void run(String... args) throws Exception {
job.run();
}
public static void main(String[] args) throws Exception {
SpringApplication.run(JobXRunner.class, args);
}
}

There are more than one ways to solve this problem:
Create multiple profiles and assign them to the job runners. Activate one from command line using spring.profiles.active=jobrunner1, or spring.profiles.active=jobrunner1,jobrunner2 if you want to run more than one job. This solution lets you keep multiple CommandLineRunner implementations.
Specify which class to run, which in turn depends on how you're executing the code. If running using an IDE or a build tool like Maven or Gradle, you'll have to specify the main class name since the default resolution won't work. See this thread for a Maven-specific solution. If you're running the jar, see this thread. CommandLineRunner has a main method, so you can keep them, or just convert to regular main classes.

Related

Best way to call java interface methods after springboot application start up? [duplicate]

I want to run code after my spring-boot app starts to monitor a directory for changes.
I have tried running a new thread but the #Autowired services have not been set at that point.
I have been able to find ApplicationPreparedEvent, which fires before the #Autowired annotations are set. Ideally I would like the event to fire once the application is ready to process http requests.
Is there a better event to use, or a better way of running code after the application is live in spring-boot?
It is as simple as this:
#EventListener(ApplicationReadyEvent.class)
public void doSomethingAfterStartup() {
System.out.println("hello world, I have just started up");
}
Tested on version 1.5.1.RELEASE
Try:
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application extends SpringBootServletInitializer {
#SuppressWarnings("resource")
public static void main(final String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
context.getBean(Table.class).fillWithTestdata(); // <-- here
}
}
Have you tried ApplicationReadyEvent?
#Component
public class ApplicationStartup
implements ApplicationListener<ApplicationReadyEvent> {
/**
* This event is executed as late as conceivably possible to indicate that
* the application is ready to service requests.
*/
#Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
// here your code ...
return;
}
}
Code from: http://blog.netgloo.com/2014/11/13/run-code-at-spring-boot-startup/
This is what the documentation mentions about the startup events:
...
Application events are sent in the following order, as your application runs:
An ApplicationStartedEvent is sent at the start of a run, but before
any processing except the registration of listeners and initializers.
An ApplicationEnvironmentPreparedEvent is sent when the Environment to be used in the context is known, but before the context
is created.
An ApplicationPreparedEvent is sent just before the refresh is started, but after bean definitions have been loaded.
An ApplicationReadyEvent is sent after the refresh and any related callbacks have been processed to indicate the application is ready to
service requests.
An ApplicationFailedEvent is sent if there is an exception on startup.
...
Why not just create a bean that starts your monitor on initialization, something like:
#Component
public class Monitor {
#Autowired private SomeService service
#PostConstruct
public void init(){
// start your monitoring in here
}
}
the init method will not be called until any autowiring is done for the bean.
The "Spring Boot" way is to use a CommandLineRunner. Just add beans of that type and you are good to go. In Spring 4.1 (Boot 1.2) there is also a SmartInitializingBean which gets a callback after everything has initialized. And there is SmartLifecycle (from Spring 3).
ApplicationReadyEvent is really only useful if the task you want to perform is not a requirement for correct server operation. Starting an async task to monitor something for changes is a good example.
If, however your server is in a 'not ready' state until the task is completed then it's better to implement SmartInitializingSingleton because you'll get the callback before your REST port has been opened and your server is open for business.
Don't be tempted to use #PostConstruct for tasks that should only happen once ever. You'll get a rude surprise when you notice it being called multiple times...
You can extend a class using ApplicationRunner , override the run() method and add the code there.
import org.springframework.boot.ApplicationRunner;
#Component
public class ServerInitializer implements ApplicationRunner {
#Override
public void run(ApplicationArguments applicationArguments) throws Exception {
//code goes here
}
}
Use a SmartInitializingSingleton bean in spring > 4.1
#Bean
public SmartInitializingSingleton importProcessor() {
return () -> {
doStuff();
};
}
As alternative a CommandLineRunner bean can be implemented or annotating a bean method with #PostConstruct.
Best way to execute block of code after Spring Boot application started is using PostConstruct annotation.Or also you can use command line runner for the same.
1. Using PostConstruct annotation
#Configuration
public class InitialDataConfiguration {
#PostConstruct
public void postConstruct() {
System.out.println("Started after Spring boot application !");
}
}
2. Using command line runner bean
#Configuration
public class InitialDataConfiguration {
#Bean
CommandLineRunner runner() {
return args -> {
System.out.println("CommandLineRunner running in the UnsplashApplication class...");
};
}
}
Providing an example for Dave Syer answer, which worked like a charm:
#Component
public class CommandLineAppStartupRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(CommandLineAppStartupRunner.class);
#Override
public void run(String...args) throws Exception {
logger.info("Application started with command-line arguments: {} . \n To kill this application, press Ctrl + C.", Arrays.toString(args));
}
}
I really like the suggestion for usage of the EventListener annotation by #cahen (https://stackoverflow.com/a/44923402/9122660) since it is very clean. Unfortunately I could not get this to work in a Spring + Kotlin setup. What does work for Kotlin is adding the class as a method parameter:
#EventListener
fun doSomethingAfterStartup(event: ApplicationReadyEvent) {
System.out.println("hello world, I have just started up");
}
You have several choices:
Using CommandLineRunner or ApplicationRunner as a Bean definition:
Spring Boot executes these towards the end of the application startup process. In most circumstances, the CommandLineRunner will do the job. Following is an example of a CommandLineRunner implementation with Java 8:
#Bean
public CommandLineRunner commandLineRunner() {
return (args) -> System.out.println("Hello World");
}
Note that the args is the String array of arguments. You can also provide an implementation of this interface and define it as a Spring Component:
#Component
public class MyCommandLineRunner implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
System.out.println("Hello World");
}
}
You can use the ApplicationRunner if you need better argument management. ApplicationRunner takes an ApplicationArguments instance that has enhanced argument management options.
You can also order the CommandLineRunner and ApplicationRunner beans using Spring's #Order annotation:
#Bean
#Order(1)
public CommandLineRunner commandLineRunner() {
return (args) -> System.out.println("Hello World, Order 1");
}
#Bean
#Order(2)
public CommandLineRunner commandLineRunner() {
return (args) -> System.out.println("Hello World, Order 2");
}
Using Spring Boot's ContextRefreshedEvent:
Spring Boot publishes several events at startup. These events indicate the completion of a phase in the application startup process. You can listen to the ContextRefreshedEvent and execute custom code:
#EventListener(ContextRefreshedEvent.class)
public void execute() {
if(alreadyDone) {
return;
}
System.out.println("hello world");
}
ContextRefreshedEvent is published several times. Thus, ensure to put a check whether the code execution is already finished.
Try this one and it will run your code when the application context has fully started.
#Component
public class OnStartServer implements ApplicationListener<ContextRefreshedEvent> {
#Override
public void onApplicationEvent(ContextRefreshedEvent arg0) {
// EXECUTE YOUR CODE HERE
}
}
Spring boot provides an ApplicationRunner interface with a run() method to be invoked at application startup.
However, instead of raw String arguments passed to the callback method, we have an instance of the ApplicationArguments class.
#Component
public class AppStartupRunner implements ApplicationRunner {
#Override
public void run(ApplicationArguments args) throws Exception {
//some logic here
}
}
just implement CommandLineRunner for spring boot application.
You need to implement run method,
public classs SpringBootApplication implements CommandLineRunner{
#Override
public void run(String... arg0) throws Exception {
// write your logic here
}
}
you can use #Component
#RequiredArgsConstructor
#Component
#Slf4j
public class BeerLoader implements CommandLineRunner {
//declare
#Override
public void run(String... args) throws Exception {
//some code here
}
If you mean running peace of code once after the application started, you can use the CommandLineRunner as below:
#SpringBootApplication
public class SpringBootApplication
implements CommandLineRunner {
private static Logger LOG = LoggerFactory
.getLogger(SpringBootConsoleApplication.class);
public static void main(String[] args) {
LOG.info("STARTING THE APPLICATION");
SpringApplication.run(SpringBootConsoleApplication.class, args);
LOG.info("APPLICATION FINISHED");
}
#Override
public void run(String... args) {
// enter code you want to run after app loaded here
LOG.info("EXECUTING : command line runner");
for (int i = 0; i < args.length; ++i) {
LOG.info("args[{}]: {}", i, args[i]);
}
}
}
Otherwise, you can use the DevTools dependency, which help you to run new codes without manually restarting the application.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
don't forget to add these codes to your pom.xml to avoid version warnings:
<properties>
<java.version>17</java.version>
<spring-cloud.version>2021.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
give it a thump up if this was helpful to you!
Best way you use CommandLineRunner or ApplicationRunner
The only difference between is run() method
CommandLineRunner accepts array of string and ApplicationRunner accepts ApplicationArugument.

how to strart a backend java project using spring?

I am starting a spring boot java project. I know I should not to code in the main method and the principal class.
Is this a bad way?:
#SpringBootApplication
public class CesckproApplication implements CommandLineRunner{
#Autowired
private ProcesoPrincipal pp;
public static void main(String[] args) throws Exception {
SpringApplication.run(CanalCorSdqsApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
pp.consulta();
// mycode here....
}
}
There is an other way to execute the code in other classes without touch the main class? i saw some like:
#SpringBootApplication
public class CanalCorSdqsApplication{
#Autowired
private ProcesoPrincipal pp;
public static void main(String[] args) throws Exception {
SpringApplication.run(CanalCorSdqsApplication.class, args);
}
}
And:
#Component
public class ProcesoPrincipal implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
consulta();
}
public void consulta(){
// my code here
}
}
There is an other way to do it? and what your expertice say about? (some time many of docs and tutrorial use to code in for example MAIN method cus it is just playful not a real aplication when you dont use the main to code all).
If you really want to understand Spring-boot, well you are lucky. There are brilliant sources of information with plenty of examples to go over in https://spring.io/guides and https://www.baeldung.com/spring-boot
Don't take shortcuts to learning; Fail, build, learn and repeat.
These questions you ask:
How to start a backend project?
Is this a bad way?
Is there any other way to execute code, if not in Main?
All of the will have an answer within a couple of tutorials, but if you still do not know which one to pick, go with this one: https://www.baeldung.com/spring-boot-start

How to boot an app in spring integration test?

I need to create an integration test for my app. I used #SpringBootTest(classes = {Application.class}) annotation to boot it, but its launch takes time. So how can i run test, when my app is ready?
The problem is in kafka listener:
#SpringBootApplication
public class Application {
#Autowired
private KafkaConsumeHandler kafkaConsumeHandler;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#KafkaListener(topics = "${kafka.topics.test}", containerFactory = "kafkaListenerContainerFactory")
public void listenRegistred(KafkaMessage consumeKafka) {
kafkaConsumeHandler.handleStartProcess(consumeKafka);
}
If i try to send messages immediately in test, the listener cant catch them. So i used a little pause before sending.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {Application.class})
#DirtiesContext
public class ProcessTest {
#ClassRule
public static KafkaEmbedded embeddedKafka = new KafkaEmbedded(1, true, "testTopic");
#Test
public void sendTestRegistred() throws Exception {
Thread.sleep(5000); // Need a delay to boot an application
...
}
You need to add the class that is annotated with #SpringBootApplication.
Example:
#SpringBootApplication
public class SpringApp {}
#SpringBootTest(classes = SpringApp.class)
public class IntegrationTest {}
Also, note that the integration test will always be slower than unit test and you need to determinate what kind of test you need to test a certain functionality.
Update after updates in question:
In your case, the delay of the test is caused because of waiting on KafkaEmbded to start. So you have to find a way programmatically to determinate when Kafka is ready. This is one possibility that should work:
#Before
public void setUp() throws Exception {
// wait until the partitions are assigned
for (MessageListenerContainer messageListenerContainer :
kafkaListenerEndpointRegistry.getListenerContainers()) {
ContainerTestUtils.waitForAssignment(messageListenerContainer,
embeddedKafka.getPartitionsPerTopic());
}
Code is taken from here: https://github.com/code-not-found/spring-kafka/blob/master/spring-kafka-avro/src/test/java/com/codenotfound/kafka/SpringKafkaApplicationTest.java#L42
If this doesn't work look for how to wait on KafkaEmbedded start-up. Your problem is not caused by SpringBootTest.

Inject implementation based on command line argument

Is there a way to inject a particular interface implementation based on a command line argument in Spring Boot?
I have a data loading app and based on the command line argument I need to load a specific type of data.
Here is my main class and CommandLineRunner:
#SpringBootApplication
public class DataLoadersApplication implements CommandLineRunner {
private Type1LoadProcess type1LoadProcess;
private Type2LoadProcess type2LoadProcess;
public DataLoadersApplication(Type1LoadProcess type1LoadProcess,
Type2LoadProcess type2LoadProcess) {
this.type1LoadProcess = type1LoadProcess;
this.type2LoadProcess = type2LoadProcess;
}
public static void main(String[] args) {
SpringApplication.run(DataLoadersApplication.class, args);
}
#Override
public void run(String... args) {
if (args[0].equalsIgnoreCase("load-type1")) {
type1LoadProcess.process();
} else if (args[0].equalsIgnoreCase("load-type2")) {
type2LoadProcess.process();
}
}
}
Is there a way where I create a DataLoadeProcess interface with two implementations Type1DataLoadProcess and Type2DataLoadProcess and inject the implementaion in main class based on the commandline arg?
You can use Spring profiles to achieve your goal:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-profiles.html
Create the interface DataLoadProcess
Then the classes:
#Component
#Profile("type1")
public class Type1LoadProcess implements DataLoadProcess {
}
#Component
#Profile("type2")
public class Type2LoadProcess implements DataLoadProcess {
}
Then you can inject the interface type like:
#Autowired
DataLoadProcess dataLoadProcessor;
And now you can start your application with one of the profiles for example with a system property set:
-Dspring.profiles.active=type1
A complete example for this is
#SpringBootApplication
public class DataLoadersApplication implements CommandLineRunner {
public interface LoadProcess {
void doLoad();
}
#Component // default that exists unconditionally in any profile
static class Type1LoadProcess implements LoadProcess {
#Override public void doLoad() { System.out.println("Load1"); }
}
#Profile("type2") // this only exists in the type2 profile
#Primary // if it exists it gets picked over others
#Component
static class Type2LoadProcess implements LoadProcess {
#Override public void doLoad() { System.out.println("Load2"); }
}
// need a 3rd? #Profile("type3") #Primary #Component
#Autowired // need one of them here
private LoadProcess loadProcess;
#Override
public void run(String... args) {
loadProcess.doLoad();
}
public static void main(String[] args) {
SpringApplication.run(DataLoadersApplication.class, args);
}
}
This makes use of profiles and uses the primary bean mechanism to allow for a default implementation when no profile is specified.
You can then select which profile is used via any of the options listed at https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html e.g. by setting an environemnt variable
SPRING_PROFILES_ACTIVE=type2 java -jar myApp.jar
using a property
java -Dspring.profiles.active=type2 java -jar myApp.jar
or even a parameter
java -jar myApp.jar --spring.profiles.active=type2
when you want the type2 implementation. You can still put "type1" as active profile even though it is nowhere defined. It will still do the right thing and use the type1 code since that's the default.
I would use Spring profiles for this. Just turn your implementations into Spring Beans and then load the desired Bean based on an active profile.
When you then specify the active profile(s) as command line parameters when starting the app the respective Bean should get used.

Can I create multiple entry points to a Spring Boot app?

In Spring Boot, a main class needs to be specified, which is the entry point to the app. Typically this is a simple class with a standard main method, as follows;
#SpringBootApplication
public class MySpringApplication {
public static void main(String [] args) {
SpringApplication.run(MySpringApplication.class, args);
}
}
This class is then specified as the main entry point when the application runs.
However, I want to run my code using a different main class using config to define this, And without using a different jar!! (I know rebuilding the jar will enable me to specify an alternative main class, but this effectively gives me two apps, not one! So, how can I do this to utilise one jar with two main classes and select the one to use via the Spring application.yml file?
I found an answer - use the CommandLineRunner interface...
So now I have two classes;
public class ApplicationStartupRunner1 implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
//implement behaviour 1
}
and
public class ApplicationStartupRunner2 implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
//implement behaviour 2
}
and how to switch between them in config..
#Configuration
public class AppConfig {
#Value("${app.runner}")
private int runner;
#Bean
CommandLineRunner getCommandLineRunner() {
CommandLineRunner clRunner = null;
if (runner == 1) {
clRunner = new ApplicationStartupRunner1();
} (else if runner == 2) {
clRunner = new ApplicationStartupRunner2();
} else {
//handle this case..
}
return clRunner;
}
}
and finally in the application.properties file, use
app.runner=1
I would stick with the original pattern of having just one main class and main method, however within that method you could configure where you want to go. I.e. rather than having 2 main methods and configuring which gets called make these 2 methods just normal methods, and create one main method which uses the config to determine which of your two methods get run.
The answer of jonny.l is fine.
Another very similar, but more manual/DIY solution is to get the ApplicationContext, from which you can get all other things:
#SpringBootApplication
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(App.class);
ConfigurableEnvironment env = context.getBean(ConfigurableEnvironment.class);
String mainApp = env.getProperty("app.runner");
if (mainApp.equals("Main1")) {
Main1 main1 = context.getBean(Main1.class);
main1.run();
} else if (mainApp.equals("Main2")) {
Main2 main2 = context.getBean(Main2.class);
main2.run();
}
}
}
#Service #Lazy
public class Main1 {
public void run() {}
}
#Service #Lazy
public class Main2 {
public void run() {}
}
#Lazyis used to prevent those beans from loading unnecessarily automatically.

Categories

Resources