Spring two different application context - property placeholder collision - java

I have been created an SDK using Spring framework that will be used
to integrate with a REST backend, to make use of the dependency injection.
In this SDK, I have MapPropertySources to handle PropertyPlaceHolders.
Basically I register programmatically some properties there which I want
to be resolved within the SDK with #Value annotation.
It works fine within the SDK, but when I build the SDK (using the builder)
inside a Spring-boot app, the properties from MapPropertiesPlaceHolder
are not resolved any longer.
I have this piece of code from the builder class:
public MyRepository build() {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext();
StandardEnvironment env = new StandardEnvironment();
context.setEnvironment(env);
Map<String, Object> propertiesMap = new HashMap<>();
propertiesMap.put("baseUrl", baseUrl);
MutablePropertySources mutablePropertySources = context.getEnvironment().getPropertySources();
mutablePropertySources.addFirst(new MapPropertySource("customPropertiesMap", propertiesMap));
if(jerseyClient == null){
jerseyClient = JerseyClientBuilder.createClient();
}
context.getBeanFactory().registerSingleton("jerseyClient", jerseyClient);
context.setParent(null);
context.register(MySdk.class);
context.refresh();
MySdk mySdk = new MySSdk(context);
return mySdk;
}
This is the way I instantiate the SDK and I create a new
Spring context inside it.
The problem is that the properties within the MapPropertySource
are not resolved when I use the SDK as a maven dependency in another
spring-boot application. It might have anything to do with the parent
context? The properties are just not resolved... Where should I investigate?
Long problem short, I have the #Value('${baseUrl}) being resolved within the SDK's tests, but when I include this SDK in another spring-boot application, it will not be resolved anylonger. Why?
Edit:
MySdk class looks like this:
#ComponentScan
#Service
#PropertySource("classpath:application.properties")
public class DeviceRepository {
private ApplicationContext context;
public MySdk(){
}
public MySdk(ApplicationContext context) {
this.context = context;
}
// other methods that calls beans from context like
// this.context.getBean(MyBean.class).doSomething()
Everything works fine in the tests from within the same SDK. The
baseUrl property is resolved fine, but when I wire this SDK in another
spring application, the properties passed in the builder, they are not
recognised by the #Value annotation.

Have you defined a PropertySourcesPlaceholderConfigurer bean in your Spring configuration? Whenever property resolution fails inside of an #Value annotation, this is one of the first things that comes to mind.
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
Link to Javadoc:
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/support/PropertySourcesPlaceholderConfigurer.html

hmm just read something interesting about leveraging the java configuration with property placeholders. Check out this link #3. It looks like you have to register another bean.
http://www.baeldung.com/2012/02/06/properties-with-spring/
Also worth noting there is a whole scope discussion on child vs parent contexts. I don't believe you can have two contexts without creating this relationship.

Related

How to inject extra property sources in SPRING before other beans are created?

I am writing a library which will be used by spring-boot projects. I'd like to inject into the boot projects' SpringEnvironment a property source that I take from the Internet.
I tried the following
#Configuration
public class MyCustomConfiguration {
#Bean
BeanDefinedAbove above() { /* do some work */ }
#Bean
#ConditionalOnBean(BeanDefinedAbove.class)
SmartInitializingSingleton propertySourceSetting(ConfigurableEnvironment env, BeanDefinedAbove bean) {
return () -> {
PropertySource source = bean.getPropertySourceDownloadedFromTheInternet();
env.getPropertySources().addFirst(source);
}
}
}
In my clients' projects when I debug this code what happens is either one of the two:
above() is called
user's #Service or #Controller are called
propertySourceSetting(...) is called
OR
user's #Service or #Controller are called
above() is called
propertySourceSetting(...) is called
Depending whether or not my client's depend on my BeanDefinedAbove bean, which is normal as the #Service is depdent on the bean created in above().
I have also added the FQDN of my class to the EnableAutoConfiguration in the META-INF/spring.factories.
So how to ensure that the logic in propertySourceSetting(..) is called before users' #Service and #Controller
I'll provide you with three options.
Option 1: (THIS IS A BAD APPROACH, BUT A QUICK WORKAROUND)
Add #Lazy(false) annotation to both Beans. Spring will eagerly create those two beans, which they will probably be created before the other ones.
Why this is bad?
This does not ensure order. Spring decides the creation order based on dependencies and some other conditions. This is why it will "probably" work :)
Option 2: Create a main class to bootstrap Spring Boot Initialization (the old way of starting spring boot).
public static void main(String[] args) throws Exception {
SpringApplication application = new SpringApplication(MyApplication.class);
// ... add property source here before start
application.run(args)
}
You also need to specify main class in the manifest for Spring Boot like this: https://www.baeldung.com/spring-boot-main-class
In that main-class you would add your propertysource, kinda like this:
SomeClassThatRetrievesProperties propRetriever = new SomeClassThatRetrievesProperties ();
Map<String,String> properties = propRetriever.getAllPropertiesAsMap();
application.setDefaultProperties(properties);
Option 3: Create a CustomApplicationContext by extending WebApplicationContext and overriding getSpecificConfigurations() method.
This way you will have full control but we aware that you could break some important stuff.

What's the Spring way of accessing properties from an entity?

I'm new to Spring and I'm building an application where some entities (JPA/Hibernate) need access to a property from application.properties. I do have a configuration class in which this is trivial:
#Configuration
public class FactoryBeanAppConfig {
#Value("${aws.accessKeyId}")
private String awsAccessKeyId;
#Value("${aws.secretKey}")
private String awsSecretKey;
}
but since entities do not have and I think they should not have the annotations such as #Configuration or #Component, what's the Spring way for them to access the property?
Now, I know I can create my own class, my own bean, and make it as a simple wrapper around the properties; but is that the Spring way to do it or is there another way?
specify Property file location using #PropertySource
Something like below
#PropertySource("classpath:/application.proerties")
You also need to add below bean in your config
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigIn() {
return new PropertySourcesPlaceholderConfigurer();
}
There is no "Spring way", since JPA entities and Spring have nothing to do with each other. Most importantly, JPA entities are not Spring beans, so Spring doesn't know or care about them as they're not managed by Spring.
You can try to hack around, trying in vain to access Spring's configuration from code that should not be trying to access it, or you can accept the truth that your design is broken and you're trying to do something that's not meant to be done.
As was proposed several times, use a service class for this. It's managed by Spring, so it can access the Spring config, and it can handle entities, so there's no crossing boundaries.
First create a public static variable in some bean managed by Spring, then make the following use of the #Value annotation.
public static String variable;
#Value("${variable}")
private void setVariable(String value) {
variable = value;
}
It will be set at runtime on startup, now you can access it from entities and everywhere else because it is just a public static var.
You can use #PropertySource to load the properties file as follows
#Configuration
#PropertySource("classpath:/com/organization/config/application.proerties")
public class FactoryBeanAppConfig {
...
}
Entities should not acces environment properties. If you are using your entity through a service, then the service can access the properties to act on the entity.

Custom ContextConfiguration Loader issues when upgrading from Spring Framework 4.2.9 to 4.3+

I am having some issues running my integration tests after upgrading the Spring Framework spring-test dependency from 4.2.9 to 4.3.9.
I am using a ContextConfiguration class which implements the spring test SmartContextLoader which allowed me to load different .xml config files which are split by profile. Based on the current spring profile it will run the specific beans for that profile.
This ContextConfigurationLoader I have ran perfectly fine in version 4.2.9 but after upgrading to version 4.3 and I am struggling to resolve this issue.
I am including the ContextConfigurationLoader I created in my integration tests like so.
#ContextConfiguration(loader=ContextConfigurationLoader.class)
#RunWith(SpringJUnit4ClassRunner.class)
public class MyIntegrationTest {
// Test Body
}
The ContextConfigurationLoader looks like this,
public class ContextConfigurationLoader implements SmartContextLoader {
#Override
public void processContextConfiguration(ContextConfigurationAttributes contextConfigurationAttributes) {
}
#Override
public ApplicationContext loadContext(MergedContextConfiguration mergedContextConfiguration) throws Exception {
GenericXmlApplicationContext context = new GenericXmlApplicationContext();
context.getEnvironment().setActiveProfiles(mergedContextConfiguration.getActiveProfiles());
new XmlBeanDefinitionReader(context).
loadBeanDefinitions(mergedContextConfiguration.getLocations());
context.load(
"/development.xml",
"/staging.xml",
"/production.xml",
);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
context.refresh();
context.registerShutdownHook();
return context;
}
#Override
public String[] processLocations(Class<?> aClass, String... strings) {
return new String[0];
}
#Override
public ApplicationContext loadContext(String... strings) throws Exception {
ApplicationContext context = ApplicationContextFactory.create();
context.getBean("dbUnitDatabaseConnection");
return ApplicationContextFactory.create();
}
}
Lastly this is the error response I get after attempting to run my tests.
java.lang.IllegalStateException: ContextConfigurationLoader was unable to detect defaults, and no ApplicationContextInitializers or ContextCustomizers were declared for context configuration attributes [[ContextConfigurationAttributes#53ca01a2 declaringClass = 'com.class.path.to.MyIntegrationTest', classes = '{}', locations = '{}', inheritLocations = true, initializers = '{}', inheritInitializers = true, name = [null], contextLoaderClass = 'com.class.path.to.ContextConfigurationLoader']]
Thanks for your help, let me know if you need anymore information.
One solution I have found is including all of the .xml config files into one file and using the #ContextConfiguration annotation like this.
#ContextConfiguration("/config.xml")
But this would require some other changes to the rest of the code outside of the tests. This also doesn't help explain why my current implementation doesn't work on the latest Spring Framework spring-test version.
From the Javadoc of SmartContextLoader.processContextConfiguration(ContextConfigurationAttributes):
Note: in contrast to a standard ContextLoader, a SmartContextLoader must preemptively verify that a generated or detected default actually exists before setting the corresponding locations or classes property in the supplied ContextConfigurationAttributes. Consequently, leaving the locations or classes property empty signals that this SmartContextLoader was not able to generate or detect defaults.
The behavior described in the last sentence is what is causing you problems.
Thus, a solution to your problem would be to actually implement processContextConfiguration() and set a bogus location in the supplied ContextConfigurationAttributes. That would instruct Spring that your custom loader was able to properly detect defaults (which you hard code in loadContext()). You could then remove the bogus location from a copy of mergedContextConfiguration.getLocations() before passing them to loadBeanDefinitions().
That would make it cleaner for end users; however, an alternative (if you don't really have any end users other than yourself) would be to declare the location of an existing XML configuration file (via #ContextConfiguration) that simply does not actually declare any beans.
Regards,
Sam (author of the Spring TestContext Framework)

Spring data multiple datasources from multiple files

I have 2 (or more) different configuration properties file located in the project and I want to load them for different datasources.
I tried to do as following:
#Bean
#ConfigurationProperties(locations = {"#{myconfigroot.getRootFolder()}/datasource1.properties"}
public static DataSource getFirstDatasource() {
return DataSourceBuilder.create().build();
}
But obviously this won't work as the ConfigurationProperties annotation locations property doesn't go through the spEL. (Or may be I write it wrong?) myconfigroot.getRootFolder() is a static method which returns the path to the datasource1.properties.
Please advice. Thanks.
===== Edited =======
I believe this is a common problem when somebody want their application want to load different configuration files. Due to some reasons the file location and name can't be put in the startup script or command line, or, the path can only be determined in runtime, that would require spring to load them during the bean creation.
I tried once using PropertySourcePlaceHolderConfigurer but seems not work either.
Anybody can share some lights?
Latest Spring boot (version 1.3.5) doesn’t support SpEL in this case.
See JavaDoc of annotation #ConfigurationProperties
Note that contrary to {#code #Value}, SpEL expressions are not
evaluated since property values are externalized.
I found a way to customize Spring boot default behavior as follows:
For example, I have database.properties file in somewhere, for some reason I cannot get the location before runtime.
username=mike
password=password
Accordingly, define POJO mapping to properties:
#Component
#ConfigurationProperties(locations = "myConfiguration")// myConfiguration is customized placeholder
public class MyProperties{
String username;
String password;
//Getters, Setters…
}
Then, to extend default StandardEnvironment:
public class MyEnvironment extends StandardEnvironment {
#Override
public String resolvePlaceholders(String location) {
if (location.equals("myConfiguration")) {
//Whatever you can do, SpEL, method call...
//Return database.properties path at runtime in this case
return getRootFolder() + "datasource.properties";
} else {
return super.resolvePlaceholders(text);
}
}
}
Last, apply it in Spring boot main method entry:
#SpringBootApplication
public class MyApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
new SpeedRestApplication()
.configure(new SpringApplicationBuilder(SpeedRestApplication.class).environment(new MyEnvironment()))//Replace default StandardEnvironment
.run(args);
}
}
Once Spring boot starts up, the MyProperties bean name and password fields are injected from database.properties. Then you could wire the MyProperties bean to other beans as configuration.
Hope it helps!
I finally got it work by using the following mechanism:
public class DatasourcePostProcessor implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Properties p = new Properties();
p.load(new FileInputStream(new File(getRootFolder() + "/datasource1.properties")));
Map<String, Object> propMap = new HashMap<>();
for (Map.Entry<Object, Object> entry : p.entrySet()) {
propMap.put(entry.getKey().toString(), entry.getValue());
}
MapPropertySource source = new MapPropertySource("datasource1", propMap);
environment.getPropertySources().addLast(source);
}
}
and register the environment post processor into the spring.factories:
org.springframework.boot.env.EnvironmentPostProcessor=com.myorg.test.DatasourcePostProcessor
Anyway, hope this helps people and accept the first anwer as it enlight me. Also post the following references from the google search that found during research:
Where I found how to wire the property source with the environment: https://github.com/spring-projects/spring-boot/issues/4595
Where I found how to load the customized properties file: How to configure a custom source to feed Spring Boot's #ConfigurationProperties

Using a Spring CacheManager from dependent jar

I have several WAR projects that include a dependency to a certain utility JAR project. I would like to be able to cache certain methods from the utility project by using spring's #Cacheable annotation, so I tried creating a configuration file on the utility project where I could define a CacheManager bean that could handle the caching of the methods. The configuration file looks like this:
(Note that I'm using a Redis caching implementation, but the spring config should be very similar for other caching providers)
#Configuration
#EnableCaching
public class RedisConfig {
#Value("${redis.hostUrl}")
private String hostUrl = null;
#Value("${redis.port}")
private Integer port = null;
#Value("${redis.defaultExpiration}")
private Integer defaultExpiration = null;
#Bean
public JedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory = new JedisConnectionFactory();
// Defaults
redisConnectionFactory.setHostName(hostUrl);
redisConnectionFactory.setPort(port);
return redisConnectionFactory;
}
#Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory cf) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(cf);
return redisTemplate;
}
#Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
// Number of seconds before expiration. Defaults to unlimited (0)
cacheManager.setDefaultExpiration(defaultExpiration);
cacheManager.setUsePrefix(true);
List<String> cacheNames = new ArrayList<String>();
cacheNames.add("testing");
cacheManager.setCacheNames(cacheNames);
return cacheManager;
}
}
I'm pretty sure the configuration itself is not a problem because I have similar configuration files in other WAR modules and caching works fine on those. However, since this is a JAR that gets loaded by other modules, my guess is that the CacheManager is not being picked up by Spring.
In project A, which has a dependency to the utils project, I have a method like the following (edited for simplicity; ignore the invalid syntax):
#RequestMapping(...)
#ResponseBody
public Dto methodA(...) {
//The relevant part
testCachedMethod(value);
cachedMethodFromProjectB(value);
}
#Cacheable(value="testing")
public String testCachedMethod(String value) {
return value;
}
Project B is another WAR that has its own CacheManager (not tied to utils), and has similar methods cached using #Cacheable. If I hit Project A's methodA, the caches from Project B get stored properly, but the cachedMethod from Project A does not store anything in the cache (and neither do the methods from the utils project annotated with #Cacheable). I also tried the other way around, creating the CacheManager directly on Project A, but it also fails to cache the annotated methods inside the utils project (and I'd prefer declaring the manager on the utils project so it can be reused by other modules).
I know Spring is properly initializing the beans in the utils project because project A fails to run if its context's property placeholder does not find the property files for the #Value annotations from the cache Config file. So I'd suppose the CacheManager bean is there, but somehow it doesn't work. Any ideas on what I'm doing wrong or if it's even possible to use a CacheManager from a dependent JAR (if possible, using Java configuration)? I'm using Spring 3.2.0.RELEASE.
Been trying to figure this out for a couple days now, so any help is greatly appreciated.
Turned out to be an issue with the spring cache abstraction's default proxy mode. All the methods whose caching annotations were not working were being called by other methods within the same object. I can properly manipulate the caches from the dependent jar project using #Autowire on the CacheManager bean and manually performing the caching operations. While it should be possible to use the annotations by enabling the AspectJ weaving mode, I don't want to add more complexity by having to deal with the weaving in the dependent modules.

Categories