how to run two different spring boot instances? - java

I have a need to run two separate spring boot instances, the first instance is responsible for some code-generation, while the second is serves as an API.
Here is the process:
1. Run the generation spring boot app
2. After the generation is shutdown, run the API spring boot app
// Api.java
#Log4j2
#EnableEurekaClient
#SpringBootApplication
public class Api {
public static void main(String[] args) {
SpringApplication.run(Api.class, args);
}
}
// Generator.java
#Log4j2
#EnableEurekaClient
#SpringBootApplication
public class Generator implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Generator.class, args);
Api.run(args); // run the API instance
}
#Override
public void run(String... args) throws Exception {
// do generation here
}
}
Exact error:
Error creating bean with name 'nested exception is javax.management.InstanceAlreadyExistsException: org.springframework.boot:type=Admin,name=SpringApplication
The error I'm currently getting is that there is already a spring boot instance, and hence can not call Api.run(args). I'd like to be able to run both of these one after another. If there is a better way I'm all ears. At the end of the day it needs to be a single runnable jar. My main class is configured to point to the Generation, and theoretically it should generate the code then run the Api.

I use something like this to run 2 services - User Service and Project Service:
SpringApplicationBuilder uws = new SpringApplicationBuilder(UserWebApplication.class)
.properties("server.port=8081",
"server.contextPath=/UserService");
uws.run();
SpringApplicationBuilder pws = new SpringApplicationBuilder(ProjectWebApplication.class)
.properties("server.port=8082",
"server.contextPath=/ProjectService");
pws.run();
They use different ports and context paths.

Related

Moving to the Spring Boot. Migration of logic from the old main class

I am very new to Spring Boot and development, so, I am stuck with a problem. I have an old project that needs to be migrated to Spring Boot. The original main method has super cool multi-threaded logic. In my understanding public static void main(String[] args) was entry point to the program, now after creating Spring Boot project #springbootapplication is entry point. How to access the original logic of a method? Should it somehow be transform? I spent hours looking for a suitable solution but no luck. Could you point me? Any help is appreciated :)
You have to use #SpringBootApplication, but also need to modify the main method something like:
#SpringBootApplication
public class YourMainApplicationClass {
public static void main(String[] args) {
SpringApplication.run(YourMainApplicationClass.class, args);
}
}
This will start your application.
Then move your original code of your main method to a new class, which has annotation #Component. Implement CommandLineRunner, and override the run method. So something like:
#Component
public class YourOldMainClass implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
//Your code here
}
}
When your application starts, spring will load 'near everything' with annotation into its container, so your #Component annotated class should be also loaded. The CommandLineRunner with overrode run method will auto call your method at startup.
Also, don't forget to include necessary spring boot jars next to your project, or to your build automation tool - like Maven - if you use it.

Should I test the main() method of Spring Boot Application and how?

When I create Spring Boot Application it generates 2 classes:
#SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
And the second on is test:
#RunWith(SpringRunner.class)
#SpringBootTest
public class AppTest {
#Test
public void contextLoads() {
}
}
Like you can notice contextLoads test is empty. How should I provide correct test for contextLoad? It should stay empty maybe? Is it correct? Or I should put something inside?
UPDATE:
Why this test should stay? If I have more tests in application I'll know if spring context is loaded or nope. Isn't it just excessive.
I readed answers like this but it didn't gave me a clear answer.
Actually, the main() method of the Spring Boot Application is not covered by the unit/integration tests as the Spring Boot tests don't invoke the main() method to start the Spring Boot application.
Now I consider that all answers of this post seem overkill.
They want to add a test of the main() method to make a metric tool happy (Sonar).
Loading a Spring context and loading the application takes time.
Don't add it in each developer build just to win about 0.1% of coverage in your application.
I added an answer about that.
Beyond your simple example and the other post that you refer to, in absolute terms it may make sense to create a test for the main() method if you included some logic in. It is the case for example as you pass specific arguments to the application and that you handle them in the main() meethod.
Leave it empty. If an exception occurs while loading the application context the test will fail. This test is just saying "Will my application load without throwing an exception"
Update for Second Question
The reason you want to keep this test is that when something fails you want to find the root cause. If you application context is unable to load, you can assume all other tests that are failing are due to this. If the above test passes, you can assume that the failing tests are unrelated to loading the context.
When you build a Spring boot application using Spring Initializer. It auto creates Application and its Test Class
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#SpringBootTest
class ApplicationTest {
#Test
void contextLoads() {
}
}
Note the use of #SpringBootTest annotation on test class which tells Spring Boot to look for a main configuration class (one with #SpringBootApplication, for instance) and use that to start a Spring application context. Empty contextLoads() is a test to verify if the application is able to load Spring context successfully or not.
You do not need to provide any test cases for empty method as such. Though you can do something like this to verify your controller or service bean context:-
#SpringBootTest
public class ApplicationContextTest {
#Autowired
private MyController myController;
#Autowired
private MyService myService;
#Test
public void contextLoads() throws Exception {
assertThat(myController).isNotNull();
assertThat(myService).isNotNull();
}
}

How can I have 2 or more spring applications in one code base?

I have a springboot web application, and I want to build a spring console application for utility purposes, so I have two clases with a main static method to start, the command line app is like this:
#SpringBootApplication
public class Maintf implements CommandLineRunner {
private final AService aserviceInstance;
#Autowired
public Maintf(AService service) {
this.aserviceInstance = service;
}
public static void main(String[] args) throws Exception {
SpringApplication m = (new SpringApplicationBuilder(Maintf.class)).web(false).build();
m.run();
}
#Override
public void run(String... strings) throws Exception {
for (Account a : aserviceInstance.getAccounts() ) {
System.out.println(a.getId());
}
}
}
But when i run it tries to instantiate everything that Im not using (Controllers, configurations , and others services) even the web application container (tomcat) and fails, I just want to execute some service in the same code base. The service Im trying to inject does not have any dependencies on other components.
How can i prevent spring to instantiate like a web app?
Since you do not want to the entire Application to get started up, you could just let the current SpringBootApplication to scan the necessary packages (in this the required services package)
Forexample:
Application1
com.app.app1
com.app.app1.services
-->service1
-->service2
SpringBootApplication1
Application2
com.app.app2
com.app.app2.services
-->service1
-->service2
SpringBootApplication2
currentApplication
com.app.currentApp
com.app.currentApp.services
-->service1
-->service2
#SpringBootApplication(scanBasePackages= {"com.app.app1.services", "com.app.app2.services"})
CurrentSpringBootApplication
By this way you could just let the Springboot to scan the necessary packages and let to autowire in your app.

How to run spring boot application both as a web application as well as a command line application?

Currently, I am trying to use the CommandLineRunner along with the ConfigurableApplicationContext to run a spring boot application both as a web application by default and as a standalone command line application on demand (via command line parameters of some sort). I am struggling figuring out how to solely run this as a console application when program arguments are supplied. Please any suggestions would help.
Main class - SpringApplication
CommandLineRunner
I had the same requirement. This is how I was able to achieve it:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplicationBuilder app = new SpringApplicationBuilder(Application.class);
if (args.length == 0) { // This can be any condition you want
app.web(WebApplicationType.SERVLET);
} else {
app.web(WebApplicationType.NONE);
}
app.run(args);
}
}
And this is a console application runner.
#Component
#ConditionalOnNotWebApplication
public class ConsoleApplication implements CommandLineRunner {
#Override
public void run(String... args) {
System.out.println("************************** CONSOLE APP *********************************");
}
}
When you build your bootJar, you can run application as Web app using
java -jar app.jar and as a command line app using java -jar app.jar anything #based on the condition you specified.
Hope this helps.
EDIT:
A better way to achieve this is changing Application.java as following and keeping ConsoleApplication.java as shown above.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
and then running your bootJar with java -jar -Dspring.main.web-application-type=NONE app.jar will run the application as console application. And not passing any spring.main.web-application-type will run as a web application.
The CommandLineRunner interface provides a useful way of picking up command line arguments once the application has started, but it won’t help change the nature of the application. As you’ve probably discovered, the application will probably not exit since it thinks it needs to handle incoming web requests.
The approach you’ve taken in your main method looks sensible to me. You need to tell Spring Boot that it isn’t a web application, and therefor shouldn’t hang around listening for incoming requests once it’s been started.
I’d do something like this:
public static void main(String[] args) {
SpringApplication application = new SpringApplication(AutoDbServiceApplication.class);
application.setWeb(ObjectUtils.isEmpty(args);
application.run(args);
}
That should start the application in the correct mode. Then you can use a CommandLineRunner bean in the same way as you currently do. You might also want to look at ApplicationRunner which has a slightly better API:
#Component
public class AutoDbApplicationRunner implements ApplicationRunner {
public void run(ApplicationArguments args) {
if (ObjectUtils.isEmpty(args.getSourceArgs)) {
return; // Regular web application
}
// Do something with the args.
if (args.containsOption(“foo”)) {
// …
}
}
}
If you really don’t want the AutoDbApplicationRunner bean to even be created you could look at setting a profile in the main method that you could use later (see SpringApplication.setAdditionalProfiles).

how to run spring-boot integration tests with multiple embeded servers with gradle

i have some integration tests for my spring boot application. based on dependencies (and classpath jars) spring boot chooses a server to start: (tomcat is there is only spring-boot-starter-web, undertow if there is spring-boot-starter-undertow or jetty if there is spring-boot-starter-jetty)
i'm writing a filter that is supposed to work on many different servers. i don't have compile dependency on any server but i would like to test my code on many servers. how can i do it?
for sure one way is to make gradle script set dependencies based on some env variable and then just call gradle test a few times with different env variable values. is there any simpler way, so i can test everything at once? like starting the servers programmatically in tests? or using some gradle/spring plugin?
My suggestion would be to add test-scoped dependencies for all three servers but create three separate Spring Boot application classes in your test code, each application class disabling the EmbeddedServletContainerAutoConfiguration and importing the appropriate server's configuration:
#Profile("tomcat")
#SpringBootApplication(exclude = EmbeddedServletContainerAutoConfiguration.class)
#Import(EmbeddedServletContainerAutoConfiguration.EmbeddedTomcat.class)
public class TomcatApplication {
public static void main(String[] args) {
TomcatApplication.run(TomcatApplication.class, args);
}
}
#Profile("undertow")
#SpringBootApplication(exclude = EmbeddedServletContainerAutoConfiguration.class)
#Import(EmbeddedServletContainerAutoConfiguration.EmbeddedUndertow.class)
public class UndertowApplication {
public static void main(String[] args) {
UndertowApplication.run(UndertowApplication.class, args);
}
}
#Profile("jetty")
#SpringBootApplication(exclude = EmbeddedServletContainerAutoConfiguration.class)
#Import(EmbeddedServletContainerAutoConfiguration.EmbeddedJetty.class)
public class JettyApplication {
public static void main(String[] args) {
JettyApplication.run(JettyApplication.class, args);
}
}
Then, write your test with the appropriate #ActiveProfiles set and you should be ready to go.

Categories

Resources