How to read spring boot application property information inside the library - java

I am developing a common library for our projects. It is a maven project. Now It does not have any dependency related to Spring.
I am including the above library in the Spring boot maven project as a dependency.
As Spring Boot application runs, I need to read the value from Spring boot application.properties inside the common library.
Assume that I have environment key inside the application.properties.
environment = STAGE
I need the value of the environment inside the library.

I would suggest, you should add provision in your library to accept external configurations. You can create a Configuration class in your library which will store all required properties. Then you can create an instance of Configuration and provide it to other classes in your library. I see many libraries follows same pattern. Let's take example of Elasticsearch client which requires Elasticsearch URL:
Elasticsearch client
String elasticsearchUrl = environment.getRequiredProperty("elasticsearch-url");
ClientConfiguration clientConfiguration =
ClientConfiguration.builder().connectedTo(elasticsearchUrl).build();
RestHighLevelClient client = RestClients.create(clientConfiguration).rest();
In above example elasticsearch-url is accepted through application.properties file and then provided to Elasticsearch client library.

In the Spring Boot Application
#SpringBootApplication
public class ExampleMsApplication implements ApplicationRunner {
#Value("${props.env}")
private String env;
public static void main(String[] args) {
SpringApplication.run(ExampleMsApplication.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
System.setProperty("env", env);
}
}
application.properties
props.env=local
In the library wherever its required, i am using the below code. Whatever i set in the spring boot application using System.setProperty , i am able to get using System.getProperty
System.getProperty("env")

You can create a #ConfigurationProperties class and create /resources/META-INF/spring.factories file with following property:-
org.springframework.boot.autoconfigure.EnableAutoConfiguration=Full classified name of your class

Related

Binding application.properties to a class from external library in Spring

Let's say I have an external jar (that supposed to work in spring boot env) that has this simple class:
#Component
#ConfigurationProperties("test")
public class NetworkConfig {
//getters/setters
...
}
Now I use this jar as dep in a Spring project (NOT Spring Boot!!).
I have an application.properties file in that project and want to load properties from it to this class and it should be available in a context. How would I do it?
I also need to mention that external jar is my lib and I can modify it if needed.
#ConfigurationPropertiesScan ("path_to_property")

Problem using Spring Cloud Vault with #DynamicPropertySource

When using TestContainers to start a Vault container, the port that is exposed by the container is randomly selected during startup.
#Container
static VaultContainer vaultContainer = new VaultContainer<>("vault:1.7.2")
.withVaultToken(TOKEN)
.withInitCommand("secrets enable --path foo kv-v2")
.withInitCommand("kv put foo/app bar=foo");
Using a #DynamicPropertySource to override properties
#DynamicPropertySource
static void addProperties(DynamicPropertyRegistry registry) {
registry.add("spring.cloud.vault.host",()->vaultContainer.getHost());
registry.add("spring.cloud.vault.port",()->vaultContainer.getFirstMappedPort());
registry.add("spring.cloud.vault.uri",()->"http://"+vaultContainer.getHost()+":"+vaultContainer.getFirstMappedPort());
registry.add("spring.cloud.vault.token",()->TOKEN);
}
does not work since Spring Cloud Vault does not seem to "see" the added properties.
The issue is present in Spring-Boot 2.5.1 and Spring-Cloud-Vault-Config 3.0.3.
A small project showing the issue can be found on GitHub.
Am I doing something wrong or is there an alternative way to override the configuration?
When using Spring-Vault with a #VaultPropertySource instead of Spring-Cloud-Vault things work as expected.
According to:
https://github.com/spring-cloud/spring-cloud-vault/issues/602#event-4926845049
it's a spring-framework issue.

How I can deploy existing web applications (wars) in the Spring Boot embedded server (tomcat)

I have an application which allows to dynamically generate web applications (wars) and I would like to deploy these applications in a server to test them and I think of putting them in the same embedded server of spring, here is how I solved the problem with a simple main java.
public class Main {
private final static Logger logger = LoggerFactory.getLogger(Main.class);
private final static File catalinaHome = new File(
"C:\\Users\\Dev\\Desktop\\demo\\userstory-2\\compiler\\patternHost");
private static Tomcat tomcat = null;
public static void main(String[] args) {
tomcat = new Tomcat();
tomcat.setPort(8080);
tomcat.setBaseDir(catalinaHome.getAbsolutePath());
tomcat.getHost().setAutoDeploy(true);
tomcat.getHost().setDeployOnStartup(true);
tomcat.getServer().addLifecycleListener(new VersionLoggerListener());
tomcat.getHost().addLifecycleListener(new HostConfig());
try {
tomcat.start();
} catch (LifecycleException e) {
logger.error("Tomcat could not be started.");
e.printStackTrace();
}
logger.info("Tomcat started on " + tomcat.getHost());
tomcat.getServer().await();
}
}
How can I do the same with spring boot. ?
I have converted a non-spring app to spring boot in this way and it worked for me. I was able to run it with spring boot embedded tomcat. Hope this helps.
Spring boot is all about speed, it comes with embedded-tomcat server(provided you use spring-boot-starter-web dependency) and now all you need is java to run your standalone spring boot application. It reduces the manual steps of copying war file to tomcat's webapp folder and then starting it.
Try the approach which suits your app.
If your old app is spring based :
Create a new spring boot starter web project and copy your old source code to this new project. Modify application.properties, resources folder, add all required dependencies in pom.xml file and change package as war.
Do a mvn clean install it will generate a war file (with embedded
tomcat) in target folder of your project's root directory. Now to run it all you
need to do is, in your target folder open terminal and run java -jar your_warFileName.war it will start the application.
If your old app is not a spring based:
Again start with new spring boot starter web project and copy your source code but then to use your old code with spring-boot, first you have to do clean-up stuff like adding #RestController to controller classes, declaring beans by marking classes with #Service or #Component and autowiring beans in the appropriate places. Once your code compiles fine then to run it your can use step 2 as above.

Spring boot - Using the implementations defined in the other project

I have 2 Projects.
All the API contract (interfaces) defines in the demo-parent project (spring boot application)
The implementation for those defined in the demo-child project (spring boot application)
demo-parent is a dependency for demo-child, defined in the pom.xml of demo-child
In demo-parent :
AccessAPI.java
public interface AccessAPI {
void call();
}
SpringDemoParentApplication.java
#SpringBootApplication
public class SpringDemoParentApplication {
#Autowired
private AccessAPI accessAPI;
public static void main(String[] args) {
SpringApplication.run(SpringDemoParentApplication.class, args);
}
#PostConstruct
void exec() {
accessAPI.call();
}
}
In demo-child I have the implementation for the service :
AccessAPIImpl.java
#Service
public class AccessAPIImpl implements AccessAPI {
#Override
public void call() {
System.out.println("Executing from AccessAPIImpl");
}
}
Goal trying to achieve :
I must be able to build these projects independently, and pass the demo-child project jar via classpath to pick the implementation and inject all the implementation when running of demo-parent .
Such as :
>java -jar demo-parent.jar -cp demo-child.jar
I expected that the implementation would be picked up from the demo-child and autoinjected but It is not working as expected.
Note : I dont want to add demo-child dependency on demo-parent, the implementations/dependency must be picked up at runtime.
Please check the Git repository :
https://github.com/anthonyvk/spring-demo-child
https://github.com/anthonyvk/spring-demo-parent
TL; TR:
java -cp demo-parent.jar -Dloader.path=demo-child.jar org.springframework.boot.loader.PropertiesLauncher
if the demo-child.jar is a Spring Boot application, i.e. if the jar is repacked by the Spring Boot plugin, you need to change the loader.path:
java -cp demo-parent.jar -Dloader.path='demo-child.jar!/BOOT-INF/classes' org.springframework.boot.loader.PropertiesLauncher
Explanation:
By default, Spring Boot uses JarLauncher, which looks only in BOOT-INF/lib/ inside the jar archive, -cp argument has no effect. The commands above switch to PropertiesLauncher, which honors loader.path parameter (-cp equivalent). For details see The Executable Jar Format
By the way, the project structure seems to be a bit problematic:
adding spring boot application (demo-parent.jar) as a dependency to demo-child.jar is tricky, as demo-parent.jar is repacked by the Spring Boot plugin and maven cannot find AccessAPI class
there are runtime circular dependencies, even if not explicitly specified in the POM
I don't know detailed requirements, but if possible, I would suggest:
extracting interfaces to a separate project, let's say demo-api
making demo-parent and demo-child depended on demo-api, not on each other
making demo-api and demo-child regular jar's, not Spring Boot applications (removing Spring Boot plugin)

Using Spring boot/cloud with Amazon AWS lambda does not inject values

I have an AWS lambda RequestHandler class which is invoked directly by AWS. Eventually I need to get it working with Spring Boot because I need it to be able to retrieve data from Spring Cloud configuration server.
The problem is that the code works if I run it locally from my own dev environment but fails to inject config values when deployed on AWS.
#Configuration
#EnableAutoConfiguration
#ComponentScan("my.package")
public class MyClass implements com.amazonaws.services.lambda.runtime.RequestHandler<I, O> {
public O handleRequest(I input, Context context) {
ApplicationContext applicationContext = new SpringApplicationBuilder()
.main(getClass())
.showBanner(false)
.web(false)
.sources(getClass())
.addCommandLineProperties(false)
.build()
.run();
log.info(applicationContext.getBean(SomeConfigClass.class).foo);
// prints cloud-injected value when running from local dev env
//
// prints "${path.to.value}" literal when running from AWS
// even though Spring Boot starts successfully without errors
}
}
#Configuration
public class SomeConfigClass {
#Value("${path.to.value}")
public String foo;
}
src/main/resources/bootstrap.yml:
spring:
application:
name: my_service
cloud:
config:
uri: http://my.server
failFast: true
profile: localdev
What have I tried:
using regular Spring MVC, but this doesn't have integration with #Value injection/Spring cloud.
using #PropertySource - but found out it doesn't support .yml files
verified to ensure the config server is serving requests to any IP address (there's no IP address filtering)
running curl to ensure the value is brought back
verified to ensure that .jar actually contains bootstrap.yml at jar root
verified to ensure that .jar actually contains Spring Boot classes. FWIW I'm using Maven shade plugin which packages the project into a fat .jar with all dependencies.
Note: AWS Lambda does not support environment variables and therefore I can not set anything like spring.application.name (neither as environment variable nor as -D parameter). Nor I can control the underlying classes which actually launch MyClass - this is completely transparent to the end user. I just package the jar and provide the entry point (class name), rest is taken care of.
Is there anything I could have missed? Any way I could debug this better?
After a bit of debugging I have determined that the issue is with using the Maven Shade plugin. Spring Boot looks in its autoconfigure jar for a META-INF/spring.factories jar see here for some information on this. In order to package a Spring Boot jar correctly you need to use the Spring Boot Maven Plugin and set it up to run during the maven repackage phase. The reason it works in your local IDE is because you are not running the Shade packaged jar. They do some special magic in their plugin to get things in the right spot that the Shade plugin is unaware of.
I was able to create some sample code that initially was not injecting values but works now that I used the correct plugin. See this GitHub repo to check out what I have done.
I did not connect it with Spring Cloud but now that the rest of the Spring Boot injection is working I think it should be straightforward.
As I mentioned in the comments you may want to consider just a simple REST call to get the cloud configuration and inject it yourself to save on the overhead of loading a Spring application with every request.
UPDATE: For Spring Boot 1.4.x you must provide this configuration in the Spring Boot plugin:
<configuration>
<layout>MODULE</layout>
</configuration>
If you do not then by default the new behavior of the plugin is to put all of your jars under BOOT-INF as the intent is for the jar to be executable and have the bootstrap process load it. I found this out while addressing adding a warning for the situation that was encountered here. See https://github.com/spring-projects/spring-boot/issues/5465 for reference.

Categories