how to package the springboot application with dynamic end points on application.properties file - spring

I have 1 spring boot application. In this application i have configured 1 eCommerce system as elastic path(Configure the end point url of elastic path in application.properties file). Now i have to give my spring boot application to some other guys. which will be deploy on tomcat server. I don't want to give the source code. So i can make the war file but now problem is that they have their own elastic path eCommerce and they wants to configure their own eCommerce.
I want to externalize some properties which will override the existing property.
My springboot application have 2 modules :
1) elasticpath module which is having elasticpath-application.properties
2) salesforce - salesforce-application.properties
Now i have to externalize "C:\apache-tomcat-8.5.29\conf\ep-external.properties" file which will override the existing property. Now problem is that #PropertySource is loading in last position. So my external file is not able to override the property.
#SpringBootApplication
#PropertySource(value = {"classpath:application.properties", "classpath:elasticpath-application.properties", "classpath:salesforce-application.properties")
public class SpringBootDemo extends SpringBootServletInitializer implements CommandLineRunner {
private static final Logger LOG = LoggerFactory.getLogger(SpringBootDemo.class);
private ServletContext servletContext;
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//application = application.properties("file:C:\\apache-tomcat-8.5.29\\conf\\ep-external.properties");
return application.sources(SpringBootDemo.class);
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
this.servletContext = servletContext;
super.onStartup(servletContext);
}
public static void main(String[] args) {
SpringApplication.run(SpringBootDemo.class, args);
}
#Override
public void run(String... args) throws Exception {
}
}

Yes absolutely possible. Basically what you need is changing the property value as needed without changing jar/war
Passing command line args for jar
Package your spring boot application as jar and put the external application.properties file at any location and pass on the same location as command line argument as below :
java -jar app.jar --spring.config.location=file:<property_file_location>
This will pick up the external properties.
Passing command-line/dynamic args for war
1. Extend the SpringBootServletInitializer as below
#SpringBootApplication
class DemoApp extends SpringBootServletInitializer {
private ServletContext servletContext;
public static void main(String[] args){SpringApplication.run(DemoApp.class,args);}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
builder = builder.properties("test.property:${test_property:defaultValue}");
return builder.sources(DemoApp.class)
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
this.servletContext = servletContext;
super.onStartup(servletContext);
}
}
Access the property as usual like below anywhere you want :
#Value("${test.property}")
Before starting tomcat set the env variable named test_property. that's it
Additionally:
You can pass on the property like below as well if you want to supply complete file as external file.
.properties("spring.config.location:${config:null}")
Further reading about externalized configuration : https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html

Related

spring boot - load app config before calling method run

is there a way to read yaml file of a spring boot app before launching the app, more exactly before calling method run.
here is my code :
#SpringBootApplication
public class CometeRestApi extends SpringBootServletInitializer {
private static void setMongoSecure() {
System.setProperty("javax.net.ssl.keyStore", System.getProperty("user.home") + "/.ssl/client-and-key.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "");
System.setProperty("javax.net.ssl.trustStore", System.getProperty("user.home") + "/.ssl/certificateChain.jks");
System.setProperty("javax.net.ssl.trustPassword", "");
}
#Override
protected SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
setMongoSecure();
return application.sources(CometeRestApi.class);
}
public static void main(final String[] args) {
setMongoSecure();
SpringApplication.run(CometeRestApi.class, args);
}
}
thanks in advance for help
Yes, there are several ways to do it, but the recommended way is to use an EventListener. Annotating a method with #EventListener(ApplicationReadyEvent.class) is ideal when your purpose is to automatically execute code after your application startup.

How to check if Spring boot is running in standalone or embedded mode?

Is there some reliable way how to check if spring boot is running in JAR (standalone with embedded tomcat) or WAR (in j2ee server) mode?
There's no built in API to check which environment you're running in. Probably the most robust way would be to use different configuration for your application depending on whether it's started via its main method or via its SpringBootServletInitializer subclass. Exactly what you should do depends on your reason for needing to know and also personal preference.
For example, you could configure a property that you can the query via the Environment, using #Value, etc:
#SpringBootApplication
public class ExampleApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ExampleApplication.class).properties(
"com.example.mode:servlet-container");
}
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder(ExampleApplication.class).properties(
"com.example.mode:standalone").run(args);
}
}
Another option would be to provide a configuration class in addition to ExampleApplication.class that's different depending on what mode you're running in:
#SpringBootApplication
public class ExampleApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ExampleApplication.class,
ServletContainerConfiguration.class);
}
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder(ExampleApplication.class,
StandaloneConfiguration.class).run(args);
}
}
Exactly what you do in ServletContainerConfiguration or StandaloneConfiguration is then up to you. You could, for example, publish a bean that remembers the mode and then query it whenever you need to know.
Yet another option would be to activate different profiles depending on the mode.

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 !

How can i externalize datasource configuration with spring-boot?

I'm currently trying to move an existing spring-application to spring-boot and therefore recreate things that worked without boot.
I want to configure some properties (like spring.datasource.*) from an external source. specificly a folder with several properties files.
I set up a configuration class that creates propertyPlaceholder configurers like this:
#Configuration
public class PropertySourceConfiguration {
#Bean
public static PropertySourcesPlaceholderConfigurer defaultsPlaceHolderConfigurer() throws IOException {
PropertySourcesPlaceholderConfigurer propertyConfigurer = new PropertySourcesPlaceholderConfigurer();
propertyConfigurer.setLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/*-defaults.properties"));
propertyConfigurer.setIgnoreUnresolvablePlaceholders(true);
return propertyConfigurer;
}
#Bean
public static PropertySourcesPlaceholderConfigurer externalPlaceHolderConfigurer() throws IOException {
PropertySourcesPlaceholderConfigurer propertyConfigurer = new PropertySourcesPlaceholderConfigurer();
propertyConfigurer.setLocations(new
PathMatchingResourcePatternResolver().getResources("file:/my-config-path/*.properties"));
propertyConfigurer.setOrder(1);
propertyConfigurer.setIgnoreUnresolvablePlaceholders(true);
return propertyConfigurer;
}
This seems to work for most things (like amqp or my own config properties) but when i try to use spring-data-jpa they are ignored. basicly setting spring.datasource.url (and other things used for auto-config) in those files has no effect.
looking through the logs of the PropertySourcesPropertyResolver i figured out that these configurer fall under the localProperties group which is not used when looking for spring.datasource.*.
is there a way to fix this or a better way to add external properties files to my context?
I know i could set spring.config.location to do something similar but i can not pass command-line properties to my application and need to do this config from within my application. afaik this is not possible with this property.
EDIT: setting spring.config.location:
Attempt 1:
public class ServletInitializer extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(CampaignServiceStarter.class);
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.setInitParameter("spring.config.location", "file:/my-config-path/*.properties");
}
}
Attempt 2:
public class ServletInitializer extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(CampaignServiceStarter.class).properties("spring.config.location=file:/my-config-path/*.properties");
}
}
in both cases the external properties were not picked up at all (even in places where it worked before, like the amqp config)
How to use external configuration is explained in this section of the Spring Boot Reference Guide.
The spring.config.location is a path to the directory which contains your application.properties file. It takes a comma separated list of values so you could specify multiple paths. It doesn't take the wildcard. It is a path so not an expression to match multiple property files. If you want to change the default application then use the spring.config.name to make it something else.
The defaults of Spring Boot are opinionated as the rest of Spring Boot (with the default configuration etc.).
If you want to do more extensive (pre) configuration you should use an ApplicationContextInitializer to manually add the PropertySources to the Environment. This is mentioned here in the Spring Boot Reference Guide.
An example of how the initializer might look.
public class ConfigurationInitializer implements ApplicationContextInitializer {
private static final String DEFAULT_PROPS = "classpath*:/*-defaults.properties";
private static final String EXTERNAL_PROPS = "file:/my-config-path/*.properties";
public void initialize(ConfigurableApplicationContext applicationContext) {
final Resource[] defaultConfigs = applicationContext.getResources(DEFAULT_PROPS);
final Resource[] externalConfigs = applicationContext.getResources(EXTERNAL_PROPS);
final ConfigurableEnvironment env = applicationContext.getEnvironment();
final MutablePropertySources mps = env.getPropertySources();
for (Resource r : externalConfigs) {
mps.addLast(new ResourcePropertySource(r.getFilename(), r);
}
for (Resource r : defaultConfigs) {
mps.addLast(new ResourcePropertySource(r.getFilename(), r);
}
}
}
Then when building your application object add it as follows.
public class ServletInitializer extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(CampaignServiceStarter.class)
.initializers(new ConfigurationInitializer());
}
}
Now the configs should be added to the list of property sources.

How to configure Spring Data REST to return the representation of the resource created for a POST request?

I am following the spring-data-rest guide Accessing JPA Data with REST. When I http post a new record it is inserted (and the response is a 201). That is great, but is there a way to configure the REST MVC code to return the newly created object? I'd rather not have to send a search request to find the new instance.
You don't have to search for the created entity. As the HTTP spec suggests, POST requests returning a status code of 201 Created are supposed to contain a Location header which contains the URI of the resource just created.
Thus all you need to do is effectively issuing a GET request to that particular URI. Spring Data REST also has two methods on RepositoryRestConfiguration.setReturnBodyOnCreate(…) and ….setReturnBodyOnUpdate(…) which you can use to configure the framework to immediately return the representation of the resource just created.
Example with Spring Boot:
#Configuration
#EnableMongoRepositories
#Import(RepositoryRestMvcConfiguration.class)
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args);
RepositoryRestConfiguration restConfiguration = ctx.getBean(RepositoryRestConfiguration.class);
restConfiguration.setReturnBodyOnCreate(true);
}
}
or
#Configuration
#EnableMongoRepositories
#EnableAutoConfiguration
public class Application extends RepositoryRestMvcConfiguration {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
super.configureRepositoryRestConfiguration(config);
config.setReturnBodyOnCreate(true);
}
}
Good Luck!
If you are using Spring Boot, you can add the following lines to your application.properties file for POST (create) and PUT (update) respectively
spring.data.rest.return-body-on-create=true
spring.data.rest.return-body-on-update=true
Here's another variant that uses DI rather than extending RepositoryRestMvcConfiguration or using the ConfigurableApplicationContext.
#SpringBootApplication
#EnableConfigurationProperties
#Configuration
#ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Autowired private RepositoryRestConfiguration repositoryRestConfiguration;
#PostConstruct
public void exposeIds() {
this.repositoryRestConfiguration.setReturnBodyForPutAndPost(true);
}
}

Resources