How SpringBoot works on application servers - spring-boot

I am working on migrating our Spring REST applications to Spring Boot microservices. I have some doubt:
As I know spring boot has a main() and it calls static run() which is present in SpringApplication. So, when we run it as "Java Applicaton" in Eclipse IDE, this main() gets called and #SpringBootApplication annotation do the magic. But when I deploy this application on Websphere Application Servers, then how is this working because now the main() will not gets called. Then how all the beans getting loaded without calling the main().

Spring Boot implicitely starts an embedded server, which is included in spring-boot-starter-tomcat dependency. That's why main() method works in boot environment.
Typical solution is to create two profiles - one for boot development and the other for deployment - then you may have several starting points.
Dev config:
#Configuration
#Profile(Profiles.DEV)
#Import(AppConfig.class)
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.profiles(Profiles.DEV)
.run(args);
}
}
Deploy config (for WAS, tomcat, ...):
#Configuration
#Profile(Profiles.DEPLOY)
#Import(AppConfig.class)
public class ApplicationTomcat extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application
.profiles(Profiles.DEPLOY)
.sources(ApplicationTomcat.class);
}
}
Profiles:
public class Profiles {
public final static String DEV = "dev";
public final static String DEPLOY = "deploy";
}
In your deployment profile, don't forget to exclude spring-boot-starter-tomcat dependency and make it war bundle.
This way you can deploy application on WAS (or tomcat, ...) in standard way and start your application localy also with main() method.

Related

Spring does not load data beans (#Repository) from dependency [duplicate]

I have a myapp parent pom type maven project with myapp-core and myapp-web modules. myapp-core module is added as dependency to myapp-web.
All the classes in myapp-core module reside in root package com.myapp.core and all classes in myapp-web module reside in root package com.myapp.web
The main Application.java is also in com.myapp.web package. As my core module root package is different I am including common base package "com.myapp" for ComponentScan as follows:
#Configuration
#ComponentScan(basePackages="com.myapp")
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Now the surprising thing is if I run this app using Run As -> Spring Boot App it is working fine. But if I run it as Run As -> Java Application it is failing with error saying it can't found beans defined in myapp-core module.
If I move my Application.java to com.myapp package it is working fine.
It should work even if i run it as Java Application also, right?
After enabling debug log level for spring and going through extensive logs I found that scanning for various components like JPA Repositories, JPA Entities etc are depending on the Application.java's package name.
If the JPA Repositories or Entities are not in sub packages of Application.java's package then we need to specify them explicitly as follows:
#Configuration
#ComponentScan(basePackages="com.sivalabs.jcart")
#EnableAutoConfiguration
#EnableJpaRepositories(basePackages="com.sivalabs.jcart")
#EntityScan(basePackages="com.sivalabs.jcart")
public class Application{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
With the above additional #EnableJpaRepositories, #EntityScan I am able to run it using Run As -> Java Application.
But still not sure how it is working fine when Run As -> Spring Boot App!!
Anyway I think it is better to move my Application.java to com.myapp package rather than fighting with SpringBoot!
I have the same problem. Only adding the #EnableJpaRepositories annotation can solve the issue. I tried to define basePackages in #SpringBootApplication, to no avail.
I think the package of the Application class is fed to the scanning process of JpaRepositories, but other packages defined in #SpringBootApplication are ignored.
It looks like a bug/improvement of Spring Boot.
I had a similar issue with Redis repositories that was fixed in a similar way:
#Configuration
#EnableConfigurationProperties({RedisProperties.class})
#RequiredArgsConstructor
#EnableRedisRepositories(basePackages = {"com.example.another"})
public class RedisConfig {
private final RedisConnectionFactory redisConnectionFactory;
#Bean
public RedisTemplate<?, ?> redisTemplate() {
RedisTemplate<byte[], byte[]> template = new RedisTemplate<byte[], byte[]>();
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
return template;
}
}

How do I run a spring-boot-rest application from a different module eg: in CI build via Maven?

|--Integration tests
|--Spring boot rest application
I have two modules,
Spring boot application is where I have the end points,
it runs on its own embedded tomcat, I want to be able to run it as a part of Integration test's maven build and run integration tests on it.
My question is, is there a way to run spring boot application from a different module via maven?
On Spring boot's website I can only see an example of running a spring-boot application through its own pom by using spring-boot-maven-plugin, but not by running the application as a part of different module by specifiying a jar file with in the execution.
Yes, there are several ways to do what you ask, for example:
use the #SpringBootTest annotation on your test classes (since Spring Boot 1.4);
programmatically start the Spring Boot application from within your test.
The first is my favorite one and the simpler one as well but it only works in the context of unit tests, of course. Here's an example.
Let's assume that you have a class named Application annotated with #SpringBootApplication in your REST module. You can test the endpoints by just defining a test like this inside your Integration test module:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class, properties = {"my.overriden.property=true"} )
public class RestEndpointTest
{
// ...
}
By doing so, the entire context of the application will start. You can then further configure your test depending on your needs, also overriding some properties (see my.overridden.property).
Alternatively, you can define your own configuration inside the test module, referencing any required class from the other module, for example:
#Configuration
#ComponentScan(basePackageClasses = {BaseClass.class})
#EnableJpaRepositories
#EntityScan
#EnableAutoConfiguration
public class SupportConfiguration
{
#Bean
public ARequiredBean bean()
{
return new ARequiredBean();
}
// etc...
}
and the using it just like you would do with any other context:
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = SupportConfiguration.class)
public class CustomTest
{
// ...
}
The other method would be to programmatically start an instance of your REST application, with something like this:
public static void main(String[] args) throws IOException
{
try (ConfigurableApplicationContext context = SpringApplication.run(Application.class, args))
{
log.info("Server Started. Press <Enter> to shutdown...");
context.registerShutdownHook();
BufferedReader inReader = new BufferedReader(new InputStreamReader(System.in));
inReader.readLine();
log.info("Closing application context...");
context.stop();
}
log.info("Context closed, shutting down. Bye.");
System.exit(0);
}

about spring boot how to disable web environment correctly

Spring boot non-web application, when start it has below error
Caused by: org.springframework.context.ApplicationContextException: Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.getEmbeddedServletContainerFactory(EmbeddedWebApplicationContext.java:185) ~[spring-boot-1.3.5.RELEASE.jar:1.3.5.RELEASE]
Then I tried below manner
new SpringApplication().setWebEnvironment(false);
then start it still have above error.
Then tried
#SpringBootApplication(exclude={SpringDataWebAutoConfiguration.class})
but still have the same error.
At last I tried add below configuration in application.properties
spring.main.web-environment=false
this time it works.
Why the first two manner cannot work?
Starting from Spring Boot 2.0
-web(false)/setWebEnvironment(false) is deprecated and instead Web-Application-Type can be used to specify
Application Properties
spring.main.web-application-type=NONE
# REACTIVE, SERVLET
or SpringApplicationBuilder
#SpringBootApplication
public class SpringBootDisableWebEnvironmentApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(SpringBootDisableWebEnvironmentApplication.class)
.web(WebApplicationType.NONE) // .REACTIVE, .SERVLET
.run(args);
}
}
Where WebApplicationType:
NONE - The application should not run as a web application and should not start an embedded web server.
REACTIVE - The application should run as a reactive web application and should start an embedded reactive web server.
SERVLET - The application should run as a servlet-based web application and should start an embedded servlet web server.
Courtesy: Another SO Answer
This answer is obsolete. Please note the other answer for Spring Boot 2.0
Original answer for Spring Boot 1.x:
The reason this config is not working because these are two different instances:
new SpringApplication().setWebEnvironment(false);
SpringApplication.run(SpringBootDisableWebEnvironmentApplication.class, args);
You are disabling setWebEnvironment(false) in new SpringApplication() object and calling static method run() on SpringApplication.run(...) which is different one.
I figured out 3 ways to do this:
#SpringBootApplication
public class SpringBootDisableWebEnvironmentApplication implements CommandLineRunner{
public static void main(String[] args) throws Exception {
// Method#1: Using SpringApplicationBuilder.
SpringApplication springApplication =
new SpringApplicationBuilder()
.sources(SpringBootDisableWebEnvironmentApplication.class)
.web(false)
.build();
springApplication.run(args);
//--------------------------------------------------------
// Method#2: Using SpringBootDisableWebEnvironmentApplication.
// SpringBootDisableWebEnvironmentApplication springBootDisableWebEnvironmentApplication =
// new SpringBootDisableWebEnvironmentApplication();
// springBootDisableWebEnvironmentApplication.run(args);
//--------------------------------------------------------
// Method#3: Using SpringApplication().
// SpringApplication springApplication = new SpringApplication();
// springApplication.setWebEnvironment(false);
//
// Set<Object> sources = new HashSet<>();
// sources.add(SpringBootDisableWebEnvironmentApplication.class);
// springApplication.setSources(sources);
// springApplication.run(args);
//--------------------------------------------------------
}
#Override
public void run(String... arg0) throws Exception {
System.out.println("Hello, Spring Boot gives many options ;)");
}
}
Here is the complete working Project.
And you don't need to exclude below config:
#SpringBootApplication(exclude = {EmbeddedServletContainerAutoConfiguration.class,
WebMvcAutoConfiguration.class})
Because you don't have spring-boot-starter-web dependency in your pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
As already noted in other answers the simplest solution is to add a property:
spring.main.web-application-type=NONE for Spring-boot 2.x
spring.main.web-environment=false for Spring-boot 1.x
But the simplest solution is NOT the best one, it's just quick&dirty.
Spring-boot has a lot of autoconfigurations that are triggered by the content of your classpath, so you probably have some unnecessary web-related dependency in your app.
I had a Spring-batch application that was giving
Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
It was caused by the presence of javax.servlet-api in my POM. I removed it and the problem disappeared.
public static void main(String[] args) {
SpringApplication(Application.class)
.setWebApplicationType(WebApplicationType.NONE)
.run(args);
}
is another way instead of using the SpringApplicationBuilder.

Spring Boot : PropertySourcesPlaceholderConfigurer is not loading system properties

I have a Spring Boot application as follows:
#SpringBootApplication
#PropertySource(ignoreResourceNotFound=true,value={"classpath:application.properties","classpath:util-${spring.profiles.active}.properties"})
#ComponentScan("com.jmarts")
#EnableTransactionManagement
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected SpringApplicationBuilderconfigure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
I'm making use of spring profiles and based on active profile, a the correct environment specific file is loaded: utils-local.properties, utils-dev.properties, etc...
When profile is set through application.properties (spring), e.g. spring.profiles.active=local all works great, correct file (utils-local.properties)is loaded.
Providing profile through -D (gradle bootRun -Dspring.profiles.active=local) doesn't load profile. I was able to verify that the system properties is passed (print systemProperties)
I assume spring boot will register a PropertySourcesPlaceholderConfigurer if none is configured.
spring boot officially supports profile-specific properties using the naming convention application-{profile}.properties.
so you can remove "classpath:util-${spring.profiles.active}.properties" and add application-local.properties, application-dev.properties and so on in the classpath.

Why do I need main method if I develop web app as war using Spring Boot?

I am developing web app using Spring Boot. My typical deployment is generating war and place it in webapps folder in Tomcat directory.
I noticed with SpringBoot, I will need a main method. I am wondering why this is needed. If there is a way to avoid it, what would that be?
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
Main method is not required for the typical deployment scenario of building a war and placing it in webapps folder of Tomcat. All you need is:
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
However, if you want to be able to launch the app from within an IDE (e.g. with Eclipse's Run As -> Java Application) while developing or build an executable jar or a war that can run standalone with Spring Boot's embedded tomcat by just java -jar myapp.war command, an entry point class with a main method might be helpful.
To run in a separate web container
You don't need the main method, all you need is to do is to extend SpringBootServletInitializer as Kryger mentioned.
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
....
....
To run in the command line as a standalone application
Here you need the main method, so that you can run it using java -jar from the command line.
public class Application {
public static void main(String[] args){
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
}
....
....
Source: https://spring.io/guides/gs/convert-jar-to-war/
In Spring Boot one will basically need three things :
1) use the #SpringBootApplication annotation
2) extend SpringBootServletInitializer
3) overwrite the configure method as shown above
and that's it !

Resources