Spring Boot - Property placeholders remain unresolved despite setting values with System.setProperty - java

I have a bean with a constructor as follows. The password argument is resolved from the placeholder my.password, with a default value of DEFAULT. If the value of DEFAULT is passed, a warning is logged. Note - this Bean is contained within an imported third-party library.
#Bean
public class EncryptionBean {
public EncryptionBean(#Value("${my.password}") String password) {
if "DEFAULT".equals(password) {
// log warning message
} else {
// do stuff with the password
}
}
}
The password is retrieved at startup from an external system using a client SDK. This SDK object is itself provided as a Bean (also from a third-party library). After retrieving the password, I am setting it as a System property for the above EncryptionBean to have access to at the time of instantiation:
#Configuration
public class MyConfiguration {
#Autowired
public SDKObject sdkObject;
#PostConstruct
public void init() {
System.setProperty("my.password", sdkObject.retrievePassword());
// #Value("${my.password"}) should now be resolvable when EncryptionBean is instantiated
}
}
However, EncryptionBean is still being instantiated with a value of DEFAULT for my.password. I'm wondering if System.setProperty in #PostConstruct might be getting executed AFTER Spring has already instantiated the instance of EncryptionBean?
If so, is there a way to guarantee this property has been set before Spring instantiates EncryptionBean? I came across #DependsOn as a way to control the order Beans get instantiated by Spring, but since EncryptionBean comes from a third-party library, I haven't been able to make this annotation work.

Instead of setting a system property, you should create a Spring EnvironmentPostProcessor class to retrieve the password from the external source and add it to the Spring Environment. That would look something like this:
public class PasswordEnvironmentPostProcessor implements EnvironmentPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
SDKObject sdkObject = applicationContext.getBean(SDKObject.class);
Map<String, Object> properties = Collections.singletonMap("my.password", sdkObject.retrievePassword());
MapPropertySource propertySource = new MapPropertySource("password", properties);
environment.getPropertySources().addFirst(propertySource);
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
Then you'll need to register this class with Spring by adding an entry to the file META-INF/spring.factories that looks like this:
org.springframework.boot.env.EnvironmentPostProcessor=com.example.PaswordEnvironmentPostProcessor
Documentation for this is available here: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.application.customize-the-environment-or-application-context.

I could not figure out a nice clean way to inject a property at runtime without a lot of boilerplate. But I came up with a clean way to do what you want to do without having to refresh the application context or messing with the 3rd party library implementation.
First we exclude the 3rd party bean from our application context:
#ComponentScan(excludeFilters = #ComponentScan.Filter(value = EncryptionBean.class, type = FilterType.ASSIGNABLE_TYPE))
#SpringBootApplication
public class SandboxApplication {
public static void main(String[] args) {
SpringApplication.run(SandboxApplication.class, args);
}
}
Then we create the Bean ourselves with the values we want.
#Configuration
public class MyConfiguration {
public final SDKObject sdkObject;
public MyConfiguration(SDKObject sdkObject) {
this.sdkObject = sdkObject;
}
#Bean
public EncryptionBean encryptionBean() {
return new EncryptionBean(sdkObject.retrievePassword());
}
}

Related

Spring Boot - Auto configuration of a class that contains AutoWired dependencies

I am in the process of developing a common java library with reusable logic to interact with some AWS services, that will in turn be used by several consumer applications. For reasons outlined here, and the fact that Spring Boot seems to provide a lot of boilerplate free code for things like SQS integration, I have decided to implement this common library as a custom spring boot starter with auto configuration.
I am also completely new to the Spring framework and as a result, have run into a problem where my auto-configured class's instance variables are not getting initialized via the AutoWired annotation.
To better explain this, here is a very simplified version of my common dependency.
CommonCore.java
#Component
public class CommonCore {
#AutoWired
ReadProperties readProperties;
#AutoWired
SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging
public CommonCore() {
Properties props = readProperties.loadCoreProperties();
//initialize stuff
}
processEvents(){
// starts processing events from a kinesis stream.
}
}
ReadProperties.java
#Component
public class ReadProperties {
#Value("${some.property.from.application.properties}")
private String someProperty;
public Properties loadCoreProperties() {
Properties properties = new Properties();
properties.setProperty("some.property", someProperty);
return properties;
}
}
CoreAutoConfiguration.java
#Configuration
public class CommonCoreAutoConfiguration {
#Bean
public CommonCore getCommonCore() {
return new CommonCore();
}
}
The common dependency will be used by other applications like so:
#SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
public class SampleConsumerApp implements ApplicationRunner {
#Autowired
CommonCore commonCore;
public SampleConsumerApp() {
}
public static void main(String[] args) {
SpringApplication.run(SampleConsumerApp.class, args);
}
#Override
public void run(ApplicationArguments args) {
try {
commonCore.processEvents();
} catch (Exception e) {
e.printStackTrace();
}
}
}
The main problem I have like I mentioned, is the AutoWired objects in the CommonCore instance are not getting initialized as expected. However, I think the actual problems are more deeply rooted; but due to my lack of understanding of the Spring framework, I am finding it difficult to debug this on my own.
I am hoping for a few pointers along these points
Does this approach of developing a custom starter make sense for my use case?
What is the reason for the AutoWired dependencies to not get initialized with this approach?
Wild guess, but I think it's because of the order of how things are constructed. I am talking about this class:
#Component
public class CommonCore {
#AutoWired
ReadProperties readProperties;
#AutoWired
SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging
public CommonCore() {
Properties props = readProperties.loadCoreProperties();
//initialize stuff
}
processEvents(){
// starts processing events from a kinesis stream.
}
}
You are trying to use a Spring injected component in a constructor, but constructor is called before Spring can do its #Autowire magic.
So one option is to autowire as a constructor argument
Something like this (untested):
#Component
public class CommonCore {
private final ReadProperties readProperties;
private final SqsListener sqsListener; // this will be based on spring-cloud-starter-aws-messaging
#AutoWired
public CommonCore(SqsListener sqsListener, ReadProperties readProperties) {
this.readProperties = readPropertis;
this.sqsListener = sqsListener;
Properties props = readProperties.loadCoreProperties();
//initialize stuff
}
processEvents(){
// starts processing events from a kinesis stream.
}
}
Sidenote: I prefer to use dependency injection via constructor arguments always, wherever possible. This also makes unit testing a lot easier without any Spring specific testing libraries.

SpringBoot - Register a bean before #Component's get scanned

I have a component Login that depends on ValidatorService. ValidatorService is being injected/autowired in the Login constructor. ValidationServiceImpl is provided by an external API, so I can't just annotate it as #Service.
#Component
class Login {
#Autowire
public Login (ValidatorService validator) {
}
}
#SpringBootApplication
public class Starter {
public static void main(String[] args)
{
SpringApplication.run(Starter.class, args);
}
}
I'm looking for a way to register ValidatorService as a bean before #Components get scanned. Is there a way to get ApplicationContext instance before starting the application?
SpringBoot 2.0.4.RELEASE
UPDATE
I need to pass a validationId that I'll get from main(args) to this external API.
public static void main(String[] args) {
String validationId = args[0];
ValidatorService service = ExternalValidationAPI.getValidationServiceImp(validationId);
}
You should be able to declare it as a bean as such in one of your configuration classes:
#Bean
public ValidatorService validatorService(){
return new ValidatorServiceImpl();
}
This will then autowire in the ValidatorService implementation class at the point it is needed. This method needs to go in an #Configuration class (your Starter class is one).
There's a good example of how to do this here.
I believe you can solve your problem with the help of the #Configurable annotation.
Annotate your Login class with #Configurable instead of #Componenet, and when the ValidatorService object becomes available, you can initiate the Login object with it.
You need to define a ValidationService bean :
#Configuration
public class ValidationServiceConfig {
#Bean
public ValidationService validationService(#Value("${validationId}") String validationId) {
return new ValidationServiceImpl(validationId);
}
}
and run the program this way : java -jar program.jar --validationId=xxx
I solved my problem creating a Configuration class and declaring a Bean to handle the instantiation of the external service that will be injected later (as some people have suggested). In order to retrieve the program arguments I autowired DefaultApplicationArguments to retrieve program arguments with getSourceArgs():
#Configuration
public class ValidatorConfig {
#Autowired
DefaultApplicationArguments applicationArguments;
#Bean
public ValidatorService validatorService()
{
String validationId = applicationArguments.getSourceArgs()[0];
return ExternalValidationAPI.getValidationServiceImp(validationId);
}

How to properly overload Spring bean configuration

I'm having two spring(4.2) java configurations, one in a base module and one in a client specific module:
#Configuration
public class BaseConfig {
#Bean
public A getA() {
return new A("aaa");
}
}
#Configuration
public class ClientConfig {
#Bean
public A getA() {
return new A("bbbb");
}
}
During the app load there is always BaseConfig.getA() called, how can I ovverride the base bean factory configuration to have some client specific stuff?
Personally I would NEVER override a bean in spring! I have seen people spend too much time debugging issues related to this. For the same reason I would never use #Primary.
In this case I would have 3 contexts
Context that contains beans unique to the parent context
Context that contains beans unique to the child context
Abstract context that contains all shared beans.
This way you will specify the 2 contexts to load. This could be done programatically, or using profiles. Potentially you will need more contexts, because you probably want some of your beans to be different in tests.
I think that you should take a look at the #Profile annotation. You could simply split configuration into different base and client specific one like:
#Configuration
#Profile("base")
public class BaseConfig {
#Bean
public A getA() {
return new A("aaa");
}
}
#Configuration
#Profile("client")
public class ClientConfig {
#Bean
public A getA() {
return new A("bbbb");
}
}
now run the specific profile by adding
#ActiveProfiles("base") on application main method
spring.profiles.active=base entry in application.properties
or even pass profile name into jvm params
I'm not sure on how to extend bean config classes. One solution is to mark the bean in ClientConfig with #Primary annotation. This will cause the ClientConfig definition of bean A to be used.
If you include both configurations you can check Primary annotation: Primary
Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value.
#Profile Annotation can be used for this...
#Configuration
#Profile("base")
public class BaseConfig {
#Bean
public A getA() {
return new A("aaa");
}
}
#Configuration
#Profile("client")
public class ClientConfig {
#Bean
public A getA() {
return new A("bbbb");
}
}
Use the following link
https://spring.io/blog/2011/02/14/spring-3-1-m1-introducing-profile/
This is an answer to a comment above, but since comments have limited formating and size, I will reply with an answer instead.
how does spring define the ordering and overriding of beans when loading configuration files
It depends what you mean by loading multiple configuration. If you have one spring context and have two classes with #Configuration and do a component scan, then Spring will build the dependency tree, and which ever context (bean) is loaded last will define the bean (as it overrides the fist definition).
If you have have multiple Spring contexts in a parent child relation, then the child and see parent beans, and will also 'override' parent beans if you use child.getBean(type.class). The parent can't see bean from children.
Using #Primary. If you have a Spring context (can come from multiple configurations) that defines two beans of the same type, you will not be able to use context.getBean(type.class) or #AutoWired (without #Qualifier) because you have multiple beans of the same type. This behaviour can be altered if one of the beans is #Primary. I try to avoid the use of #Primary in my own code, but I can see it is used heavily used in Spring boots auto configure system, so I think it has some subtle usage when it comes to framework design.
Here is a small example, note that if you load configuration classes directly, they don't need to have the #Configuration annotation.
public class ParentChildContext {
public static void main(String[] args) {
parentChildContext();
twoConfigurationsSameContext();
}
private static void twoConfigurationsSameContext() {
ApplicationContext ctx = new AnnotationConfigApplicationContext(Parent.class, Child.class);
// if you have two beans of the same type in a context they can be loaded by name
Object childA = ctx.getBean("childA");
System.out.println("childA = " + childA);
Object parentA = ctx.getBean("parentA");
System.out.println("parentA = " + parentA);
// since both configurations define A, you can't do this
ctx.getBean(A.class);
}
private static void parentChildContext() {
ApplicationContext parentCtx = new AnnotationConfigApplicationContext(Parent.class);
A parentA = parentCtx.getBean(A.class);
System.out.println("parent = " + parentA);
AnnotationConfigApplicationContext childCtx = new AnnotationConfigApplicationContext();
childCtx.register(Child.class);
childCtx.setParent(parentCtx);
childCtx.refresh();
A childA = childCtx.getBean(A.class);
System.out.println("child = " + childA);
}
public static class Parent {
#Bean
//#Primary // if you enable #Primary parent bean will override child unless the context is hierarchical
public A parentA() {
return new A("parent");
}
}
public static class Child {
#Bean
public A childA() {
return new A("child");
}
}
public static class A {
private final String s;
public A(String s) {
this.s = s;
}
#Override
public String toString() {
return "A{s='" + s + "'}";
}
}
}

How do I add a JPA based PropertySource to external configuration in Spring boot

I've been trying to add a custom PropertySource to the spring Environment bean but cannot get it to work. I have a Spring boot application and have managed to do the following
#Bean
public Environment environment() {
ConfigurableEnvironment environment = new StandardServletEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.addFirst(new DatabasePropertySource("databaseProperties"));
return environment;
}
public class DatabasePropertySource extends PropertySource<DatabaseReaderDelegate> {
public DatabasePropertySource(String name) {
super(name, new DatabaseReaderDelegate());
}
#Override
public Object getProperty(String name) {
return this.source.getProperty(name);
}
}
public class DatabaseReaderDelegate {
#Autowired ConfigurationDao dao;
public Object getProperty(String property) {
Configuration object = dao.findOneByConfKey(property);
Object value = object.getConfValue();
return value;
}
}
public interface ConfigurationDao extends JpaRepository<Configuration, Long> {
Configuration findOneByConfKey(String name);
}
This definitely adds a DatabasePropertySource to the StandardServletEnvironment but there isn't any data as the ConfigurationDao that is #Autowired is null. I have wired in the ConfigurationDao elsewhere and it does work and data is accessible through it. I just think it is a matter of timing during startup but I'm not sure exactly how to order/time it. Has anyone done something similar and have any help to offer to make this happen.
Getting JPA to start up in time to include it in the Environment is probably impossible (it's chicken and egg). One way to break the cycle is to initialize your database and repository in a parent context, and then use it in the Environment of the child (your main application context). There are convenience methods for building parent and child contexts in SpringApplicationBuilder.

Spring override properties at runtime and keep them persistent

I am using Spring 3.2.8 and keep my settings in a properties file. Now I want some of them override at runtime. ​​I want to keep the new values persistent by overwriting the old values in the properties file​​.
How can I do this in Spring? Some properties ​​I inject with # Value and others get with MessageSource.getMessage(String, Object [], Locale). The beans are already instantiated with these values​​. How can I access the properties, store them and update all beans system wide?
Thanks!
OK, given your follow up answers I would keep this fairly simple and use what you already know of Spring. I'll make some assumptions that annotation configuration is OK for you.
In my example, I'll assume all the properties that you want to configure relate to something called a ServerConfiguration and that initially these are read from server.properties on the classpath.
So part 1, I would define a bean called ServerProperties that has the original values from the server.properties injected into it.
So:
#Component
public class ServerProperties
{
#Value("${server.ip}");
private String ipAddress;
...
public void setIpAddress(String ipAddress)
{
this.ipAddress = ipAddress;
}
public String getIpAddress()
{
return this.ipAddress;
}
}
Secondly, anywhere that relies on these properties, I would inject an instance of ServerProperties rather than using #Value e.g:
#Component
public class ConfigureMe
{
#AutoWired
private ServerProperties serverProperties;
#PostConstruct
public void init()
{
if(serverProperties.getIpAddress().equals("localhost")
{
...
}
else
{
...
}
}
}
Thirdly I would expose a simple Controller into which ServerProperties is injected so that you can use your web page to update system properties e.g:
#Controller
public class UpdateProperties
{
#AutoWired
private ServerProperties serverProperties;
#RequestMapping("/updateProperties")
public String updateProperties()
{
serverProperties.setIpAddress(...);
return "done";
}
Finally, I would use #PreDestroy on ServerProperties to flush the current property values to file when the ApplicationContext is closed e.g:
#Component
public class ServerProperties
{
#PreDestroy
public void close()
{
...Open file and write properties to server.properties.
}
}
That should give you a framework for what you need. I'm sure it can be tweaked, but it will get you there.

Categories