spring data mongodb: access default POJO converter from within custom converter - spring

I have spring data mongo custom converters setup via xml as follows
<mongo:mapping-converter id="mongoConverter" db-factory-ref="mongoDbFactory">
<mongo:custom-converters>
<mongo:converter ref="customWriteConverter" />
<mongo:converter ref="customReadConverter" />
</mongo:custom-converters>
</mongo:mapping-converter>
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongoDbFactory"/>
<constructor-arg ref="mongoConverter"/>
</bean>
<bean id="customWriteConverter" class="package.WriteConverter" />
<bean id="customReadConverter" class="package.ReadConverter" />
In the custom read/write converter, I would like to re-use spring-data-mongo's default pojo converter to save certain properties as subdocuments.
consider a simplified example -
class A {
B b;
String var1;
int var2;
}
class B {
String var3;
String var4;
}
I want to handle conversion of class A using customWriteConverter and customReadConverter, but in my custom converters I also want to delegate conversion of class B back to spring-data-mongo's default POJO converter.
How can I do this? I have not been able to successfully autowire a MongoConverter or MongoTemplate into the custom converter since the MongoConverter/MongoTemplate bean creation is in progress when it tries to create the custom converter. Is it possible to get access to the default converter and use that from within the custom converter?

This method is used in MongoTemplate class to get a default converter.
private static final MongoConverter getDefaultMongoConverter(MongoDbFactory factory) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
converter.afterPropertiesSet();
return converter;
}
MappingMongoConverter is not final and so can be overridden for a specific purpose. As mentioned in my comment above, look at this question to maybe find out solution to your problem.

If you are converting TO mongo database and want to default some conversions, you could do something like this:
...
#Resource
private ObjectFactory<MappingMongoConverter>
mappingMongoConverterObjectFactory;
private MappingMongoConverter
mappingMongoConverter;
...
//Otherwise, use default MappingMongoConverter
//
if (result == null)
result =
getMappingMongoConverter()
.convertToMongoType(
value
);
...
MappingMongoConverter getMappingMongoConverter() {
if (mappingMongoConverter == null)
mappingMongoConverter =
mappingMongoConverterObjectFactory.getObject();
return
mappingMongoConverter;
}
The MappingMongoConverter cannot be directly #Resource (ed) in my case since it's in the process of being constructed when other converters are being built. So, Spring detects a circular reference. I am not sure if there is a better "lazy" method of doing this without all the run-around of ObjectFactory, getter method, and caching.
Now, if someone can figure out a method of defaulting to standard processing while going back (from DBObject to java Object) that would complete this circle.

This may not be exactly the same use case, but I had to modify existing mongo documents on a lazy basis (without using $project, etc).
Basically, I copied Spring's getDefaultMongoConverter method (which changed since earlier answers here and may change again in the future) and added an argument to pass a custom converter(s). When creating the custom converter itself (FooConverter), I pass in an empty list for the customer converters (this may differ if you have additional converters for sub-documents). Then when creating the final converter I pass in my FooConverter.
Here is some (untested) sample code. This assumes auto-configuration is enabled and thus MongoDbFactory is already wired in. If not, you'll be creating your own MongoDbFactory bean but everything else is pretty much the same.
#Bean
public MongoTemplate mongoTemplate(final MongoDbFactory mongoDbFactory) throws Exception {
FooReadConverter fooConverter = new FooReadConverter(mongoDbFactory);
MongoConverter converter = getMongoConverter(mongoDbFactory, List.of(fooConverter));
return new MongoTemplate(mongoDbFactory, converter);
}
/**
* Get a mongo converter
* #see org.springframework.data.mongodb.core.MongoTemplate#getDefaultMongoConverter
*/
static MongoConverter getMongoConverter(MongoDbFactory factory, List<?> customConverters) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(factory);
MongoCustomConversions conversions = new MongoCustomConversions(customConverters);
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
mappingContext.afterPropertiesSet();
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext);
converter.setCustomConversions(conversions);
converter.setCodecRegistryProvider(factory);
converter.afterPropertiesSet();
return converter;
}
#ReadingConverter
static class FooReadConverter implements Converter<Document, Foo> {
private final MongoConverter defaultConverter;
public FooReadConverter(MongoDbFactory dbFactory) {
this.defaultConverter = getMongoConverter(dbFactory, List.of());
}
#Override
public Foo convert(Document source) {
boolean isOldFoo = source.containsKey("someKeyOnlyInOldFoo");
Foo foo;
if (isOldFoo) {
OldFoo oldFoo = defaultConverter.read(OldFoo.class, source);
foo = oldFoo.toNewFoo();
} else {
foo = defaultConverter.read(Foo.class, source);
}
return foo;
}
}

Try to inject BeanFactory in your converter and in convert method fetch mongoTemplate from BeanFactory...

I know it's quite late but today I just faced this problem and I thought maybe it's better to share it here.
Since MongoTemplate (and its default converters) are initialized after our custom-converters, it's not possible to inject those directly into our converters, but we can access those by implementing ApplicationContextAware in our converters.
After accessing to mongoTemplate, we can delegate the read/write conversion to it by calling mongoTemplate.getConverter().read and mongoTemplate.getConverter().write methods respectively.
Let's examine an example. Assume we have two POJOs:
public class Outer {
public String var1;
public int var2;
public Inner inner;
}
public class Inner {
public String var3;
public String var4;
}
The WriteConverter could be something like this:
import org.bson.Document;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;
#Component
public class CustomWriteConverter implements Converter<Outer, Document>, ApplicationContextAware {
private ApplicationContext applicationContext;
private MongoTemplate mongoTemplate;
#Override
public Document convert(Outer source) {
// initialize the mongoTemplate
if (mongoTemplate == null) {
this. mongoTemplate = applicationContext.getBean(MongoTemplate.class);
}
// do some custom stuff
Document document = new Document();
document.put("var1", source.var1);
document.put("var2", source.var2);
// Using MongoTemplate's converters
Document inner = new Document();
mongoTemplate.getConverter().write(source.inner, inner);
document.put("inner", inner);
return document;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
And the ReadConverter:
import org.bson.Document;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;
#Component
public class CustomReadConverter implements Converter<Document, Outer>, ApplicationContextAware {
private ApplicationContext applicationContext;
private MongoTemplate mongoTemplate;
#Override
public Outer convert(Document source) {
// initialize the mongoTemplate
if (mongoTemplate == null) {
this. mongoTemplate = applicationContext.getBean(MongoTemplate.class);
}
// do some custom stuff
Outer outer = new Outer();
outer.var1 = source.getString("var1");
outer.var2 = source.getInteger("var2");
// Using MongoTemplate's converters
Inner inner = mongoTemplate.getConverter().read(Inner.class, (Document) source.get("inner"));
outer.inner = inner;
return outer;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
The mongoTemplate could be initialized by multiple threads (because it's in a race condition), but since it has a scope of singleton, there would be no problem.
Now the only thing to do is to register our converters.

Here this working with spring-boot-starter-data-mongodb version 2.5.2
package com.example.mongo;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.springframework.data.mongodb.core.convert.DbRefResolver;
import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions.MongoConverterConfigurationAdapter;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
#Configuration
public class MongoConfig extends AbstractMongoClientConfiguration {
private #Value("${spring.data.mongodb.database}") String database;
private #Autowired MongoDatabaseFactory mongoDatabaseFactory;
#Override
protected String getDatabaseName() {
return database;
}
#Override
protected void configureConverters(MongoConverterConfigurationAdapter converterConfigurationAdapter) {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDatabaseFactory);
MongoConverter mongoConverter = new MappingMongoConverter(dbRefResolver, new MongoMappingContext());
converterConfigurationAdapter.registerConverters(customConverters(mongoConverter));
}
public List<Converter<?, ?>> customConverters(MongoConverter mongoConverter) {
MyCustomConverter custom = new MyCustomConverter(mongoConverter);
return List.of(custom);
}
}

Related

Accessing spring loaded properties in BeanDefinitionRegistryPostProcessor

How can I access properties loaded by <context:property-placeholder> in BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry.
I am unable to use fields annotated with #Value, as they do not seem to be initialized (their values are null).
Setting the value of fields annotated with #Value happens only after the post-processing of the BeanDefinitionRegistry, meaning they are not usable at this stage of the initialization process.
You can however explicitly scan the configuration environment and read the relevant properties' values from there, then use them in your dynamic bean definitions.
To gain access to the configuration environment, you can create your BeanDefinitionRegistryPostProcessor in a method annotated with #Bean, that takes the ConfigurableEnvironment as a parameter.
See the following example:
package com.sample.spring;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
#Configuration
public class DynamicBeanConfig {
private static final String PROPERTY_KEY = "somename";
#Bean
public BeanDefinitionRegistryPostProcessor beanPostProcessor(ConfigurableEnvironment environment) {
return new PostProcessor(environment);
}
class PostProcessor implements BeanDefinitionRegistryPostProcessor {
private String propertyValue;
/*
* Reads property value from the configuration, then stores it
*/
public PostProcessor(ConfigurableEnvironment environment) {
propertyValue = readProperty(environment);
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {}
/*
* Creates the bean definition dynamically (using the configuration value), then registers it
*/
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SampleDynamicBean.class);
builder.addPropertyValue("property", propertyValue);
registry.registerBeanDefinition("sampleDynamicBean", builder.getBeanDefinition());
}
/*
* Iterates over all configuration sources, looking for the property value.
* As Spring orders the property sources by relevance, the value of the first
* encountered property with the correct name is read and returned.
*/
private String readProperty(ConfigurableEnvironment environment) {
for (PropertySource<?> source : environment.getPropertySources()) {
if (source instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> propertySource = (EnumerablePropertySource<?>) source;
for (String property : propertySource.getPropertyNames()) {
if (PROPERTY_KEY.equals(property))
{
return (String)propertySource.getProperty(PROPERTY_KEY);
}
}
}
}
throw new IllegalStateException("Unable to determine value of property " + PROPERTY_KEY);
}
}
class SampleDynamicBean {
private String property;
public void setProperty(String property)
{
this.property = property;
}
public String getMessage()
{
return "This message is produced by a dynamic bean, it includes " + property;
}
}
}
The sample code is adapted from this blog post, https://scanningpages.wordpress.com/2017/07/28/spring-dynamic-beans/

How to use spring to marshal and unmarshal xml?

I have a spring boot project. I have a few xsds in my project. I have generated the classes using maven-jaxb2-plugin. I have used this tutorial to get a sample spring boot application running.
import org.kaushik.xsds.XOBJECT;
#SpringBootApplication
public class JaxbExample2Application {
public static void main(String[] args) {
//SpringApplication.run(JaxbExample2Application.class, args);
XOBJECT xObject = new XOBJECT('a',1,2);
try {
JAXBContext jc = JAXBContext.newInstance(User.class);
Marshaller marshaller = jc.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(xObject, System.out);
} catch (PropertyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
But my concern is that I need to have all the jaxb classes of the schema mapped. Also is there something in Spring that I can use to make my task easier. I have looked at the Spring OXM project but it had application context configured in xml. Does spring boot have anything that I can use out of the box. Any examples will be helpful.
Edit
I tried xerx593's answer and I ran a simple test using main method
JaxbHelper jaxbHelper = new JaxbHelper();
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(XOBJECT.class);
jaxbHelper.setMarshaller(marshaller);
XOBJECT xOBJECT= (PurchaseOrder)jaxbHelper.load(new StreamSource(new FileInputStream("src/main/resources/PurchaseOrder.xml")));
System.out.println(xOBJECT.getShipTo().getName());
It ran perfectly fine. Now I just need to plug it in using spring boot.
OXM is definitely the right for you!
A simple java configuration of a Jaxb2Marshaller would look like:
//...
import java.util.HashMap;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
//...
#Configuration
public class MyConfigClass {
#Bean
public Jaxb2Marshaller jaxb2Marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(new Class[]{
//all the classes the context needs to know about
org.kaushik.xsds.All.class,
org.kaushik.xsds.Of.class,
org.kaushik.xsds.Your.class,
org.kaushik.xsds.Classes.class
});
// "alternative/additiona - ly":
// marshaller.setContextPath(<jaxb.context-file>)
// marshaller.setPackagesToScan({"com.foo", "com.baz", "com.bar"});
marshaller.setMarshallerProperties(new HashMap<String, Object>() {{
put(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, true);
// set more properties here...
}});
return marshaller;
}
}
In your Application/Service class you could approach like this:
import java.io.InputStream;
import java.io.StringWriter;
import javax.xml.bind.JAXBException;
import javax.xml.transform.Result;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.springframework.oxm.jaxb.Jaxb2Marshaller;
#Component
public class MyMarshallerWrapper {
// you would rather:
#Autowired
private Jaxb2Marshaller marshaller;
// than:
// JAXBContext jc = JAXBContext.newInstance(User.class);
// Marshaller marshaller = jc.createMarshaller();
// marshalls one object (of your bound classes) into a String.
public <T> String marshallXml(final T obj) throws JAXBException {
StringWriter sw = new StringWriter();
Result result = new StreamResult(sw);
marshaller.marshal(obj, result);
return sw.toString();
}
// (tries to) unmarshall(s) an InputStream to the desired object.
#SuppressWarnings("unchecked")
public <T> T unmarshallXml(final InputStream xml) throws JAXBException {
return (T) marshaller.unmarshal(new StreamSource(xml));
}
}
See Jaxb2Marshaller-javadoc, and a related Answer
If you just want serializing/deserializing bean with XML. I think jackson fasterxml is one good choice:
ObjectMapper xmlMapper = new XmlMapper();
String xml = xmlMapper.writeValueAsString(new Simple()); // serializing
Simple value = xmlMapper.readValue("<Simple><x>1</x><y>2</y></Simple>",
Simple.class); // deserializing
maven:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
Refer: https://github.com/FasterXML/jackson-dataformat-xml
Spring BOOT is very smart and it can understand what you need with a little help.
To make XML marshalling/unmarshalling work you simply need to add annotations #XmlRootElement to class and #XmlElement to fields without getter and target class will be serialized/deserialized automatically.
Here is the DTO example
package com.exmaple;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.Serializable;
import java.util.Date;
import java.util.Random;
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Setter
#XmlRootElement
public class Contact implements Serializable {
#XmlElement
private Long id;
#XmlElement
private int version;
#Getter private String firstName;
#XmlElement
private String lastName;
#XmlElement
private Date birthDate;
public static Contact randomContact() {
Random random = new Random();
return new Contact(random.nextLong(), random.nextInt(), "name-" + random.nextLong(), "surname-" + random.nextLong(), new Date());
}
}
And the Controller:
package com.exmaple;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
#RequestMapping(value="/contact")
public class ContactController {
final Logger logger = LoggerFactory.getLogger(ContactController.class);
#RequestMapping("/random")
#ResponseBody
public Contact randomContact() {
return Contact.randomContact();
}
#RequestMapping(value = "/edit", method = RequestMethod.POST)
#ResponseBody
public Contact editContact(#RequestBody Contact contact) {
logger.info("Received contact: {}", contact);
contact.setFirstName(contact.getFirstName() + "-EDITED");
return contact;
}
}
You can check-out full code example here: https://github.com/sergpank/spring-boot-xml
Any questions are welcome.
You can use StringSource / StringResult to read / read xml source with spring
#Autowired
Jaxb2Marshaller jaxb2Marshaller;
#Override
public Service parseXmlRequest(#NonNull String xmlRequest) {
return (Service) jaxb2Marshaller.unmarshal(new StringSource(xmlRequest));
}
#Override
public String prepareXmlResponse(#NonNull Service xmlResponse) {
StringResult stringResult = new StringResult();
jaxb2Marshaller.marshal(xmlResponse, stringResult);
return stringResult.toString();
}

Spring autowired...pass source class to autowired class

Caught up in a weird requirement. I need to attach unique error id to log4j message and return that message id back to interface.So, I though lets create a spring service, like this
public class LoggingService {
protected static Logger logger = LoggerFactory.getLogger(LoggingService.class);
public String debug(String debug_msg)
{
String uniqueMsgId = generateUniqueId();
logger.debug(concatIdWithMsg(uniqueMsgId, debug_msg));
return uniqueMsgId;
}
}
and autowired this to wherever i need it.
public class LoginLogoutController {
#Autowired
LoggingService logger;
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String getLoginPage()
{
logger.debug("Login page requested");
}
}
Although it worked fine, but the source class in logger msg is LoggingService which is obvious. What i want is to pass the class in which LoggingService is autowired so that the logger message shows the original source of problem. I tried somehow to change the service
but got no further idea how to pass source class
public class LoggingService<T> {
protected static Logger logger = null;
Class<T> sourceClass;
public void construct(Class<T> sourceClass)
{
this.sourceClass = sourceClass;
logger = LoggerFactory.getLogger(sourceClass);
}
public String debug(String debug_msg)
{
String uniqueMsgId = generateUniqueId();
logger.debug(concatIdWithMsg(uniqueMsgId, debug_msg));
return null;
}
}
I used this mechanism to inject a logger.
Create this annotation..
/**
* Indicates InjectLogger of appropriate type to
* be supplied at runtime to the annotated field.
*
* The injected logger is an appropriate implementation
* of org.slf4j.Logger.
*/
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target(FIELD)
#Documented
public #interface InjectLogger {
}
Now lets define a class that actually does the job of injecting the logger implementation.
/**
* Auto injects the underlying implementation of logger into the bean with field
* having annotation <code>InjectLogger</code>.
*
*/
import java.lang.reflect.Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ReflectionUtils;
import static org.springframework.util.ReflectionUtils.FieldCallback;
public class LoggerInjector implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
public Object postProcessBeforeInitialization(final Object bean,
String beanName) throws BeansException {
ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {
public void doWith(Field field) throws IllegalArgumentException,
IllegalAccessException {
// make the field accessible if defined private
ReflectionUtils.makeAccessible(field);
if (field.getAnnotation(InjectLogger.class) != null) {
Logger log = LoggerFactory.getLogger(bean.getClass());
field.set(bean, log);
}
}
});
return bean;
}
}
Using it is even simpler. Just add the Logger annotation created above to the Log field in the required class.
import org.slf4j.Logger;
public class Demo {
#InjectLogger
private Logger log;
public void doSomething() {
log.info("message");
log.error("Lets see how the error message looks...");
}
}
Why dont you use Spring AOP. AOP provides you much accessibility and features, and you can exploit its interesting features later also, when your application becomes heavy. Spring AOP

How do I enumerate all the BeanFactories in Spring?

Is there a way to discover all the BeanFactories defined by Spring programmatically. I want to create a status debug page which prints out the names and class types of every bean in the spring application context, however I don't know how to obtain a list of all the ApplicationContexts.
You can wire a BeanFactoryPostProcessor with your ApplicationContext that will allow you to traverse the BeanDefinition's of a passed ConfigurableListableBeanFactory which will represent all of the beans from your ApplicationContext.
With this instance of ConfigurableListableBeanFactory, you can find all beans of a type (getBeansOfType()), or all beans with a given annotation (getBeansWithAnnotation()), among other things.
You can use the ApplicationContext aware to do this.
#Component
public class PrintSpringBeansInContext implements ApplicationContextAware
{
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException
{
this.applicationContext = applicationContext;
}
public void print()
{
String[] beanNames = this.applicationContext.getBeanDefinitionNames();
StringBuilder printBuilder = new StringBuilder("Spring Beans In Context: ");;
for(String beanName : beanNames)
{
printBuilder.append("\n");
printBuilder.append(" Bean Name: ");
printBuilder.append(beanName);
printBuilder.append(" Bean Class: ");
printBuilder.append(this.applicationContext.getBean(beanName).getClass());
}
System.out.println(printBuilder.toString());
}
}
You can test this
#ContextConfiguration(locations={"classpath:context.xml"})
#RunWith(SpringJUnit4ClassRunner.class)
public class PrintContextTest
{
#Autowired
private PrintSpringBeansInContext service;
#Test
public void printBeans()
{
Assert.assertNotNull(service);
service.print();
}
}
Code below is a Spring listener which can be registered with the main spring.xml file for the web application, it builds a map of all the child application contexts and exposes this map as a property. The class below can be injected into any spring bean that needs it using #Autowired.
import java.util.Hashtable;
import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ApplicationContextEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStartedEvent;
public class ContextsApplicationListener implements ApplicationListener<ApplicationContextEvent> {
private Map<String,ApplicationContext> contextMap = new Hashtable<String,ApplicationContext>();
#Override
public void onApplicationEvent(ApplicationContextEvent event) {
if( event instanceof ContextStartedEvent || event instanceof ContextRefreshedEvent){
this.getContextMap().put(event.getApplicationContext().getDisplayName(), event.getApplicationContext());
}
}
public Map<String,ApplicationContext> getContextMap() {
return contextMap;
}
}
enter code here

Access properties file programmatically with Spring?

We use the code below to inject Spring beans with properties from a properties file.
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:/my.properties"/>
</bean>
<bean id="blah" class="abc">
<property name="path" value="${the.path}"/>
</bean>
Is there a way we can access the properties programmatically? I'm trying to do some code without dependency injection. So I'd like to just have some code like this:
PropertyPlaceholderConfigurer props = new PropertyPlaceholderConfigurer();
props.load("classpath:/my.properties");
props.get("path");
How about PropertiesLoaderUtils?
Resource resource = new ClassPathResource("/my.properties");
Properties props = PropertiesLoaderUtils.loadProperties(resource);
If all you want to do is access placeholder value from code, there is the #Value annotation:
#Value("${settings.some.property}")
String someValue;
To access placeholders From SPEL use this syntax:
#('${settings.some.property}')
To expose configuration to views that have SPEL turned off, one can use this trick:
package com.my.app;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.stereotype.Component;
#Component
public class PropertyPlaceholderExposer implements Map<String, String>, BeanFactoryAware {
ConfigurableBeanFactory beanFactory;
#Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = (ConfigurableBeanFactory) beanFactory;
}
protected String resolveProperty(String name) {
String rv = beanFactory.resolveEmbeddedValue("${" + name + "}");
return rv;
}
#Override
public String get(Object key) {
return resolveProperty(key.toString());
}
#Override
public boolean containsKey(Object key) {
try {
resolveProperty(key.toString());
return true;
}
catch(Exception e) {
return false;
}
}
#Override public boolean isEmpty() { return false; }
#Override public Set<String> keySet() { throw new UnsupportedOperationException(); }
#Override public Set<java.util.Map.Entry<String, String>> entrySet() { throw new UnsupportedOperationException(); }
#Override public Collection<String> values() { throw new UnsupportedOperationException(); }
#Override public int size() { throw new UnsupportedOperationException(); }
#Override public boolean containsValue(Object value) { throw new UnsupportedOperationException(); }
#Override public void clear() { throw new UnsupportedOperationException(); }
#Override public String put(String key, String value) { throw new UnsupportedOperationException(); }
#Override public String remove(Object key) { throw new UnsupportedOperationException(); }
#Override public void putAll(Map<? extends String, ? extends String> t) { throw new UnsupportedOperationException(); }
}
And then use the exposer to expose properties to a view:
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" id="tilesViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
<property name="attributesMap">
<map>
<entry key="config">
<bean class="com.my.app.PropertyPlaceholderExposer" />
</entry>
</map>
</property>
</bean>
Then in view, use the exposed properties like this:
${config['settings.some.property']}
This solution has the advantage that you can rely on standard placeholder
implementation injected by the context:property-placeholder tag.
Now as a final note, if you really need a to capture all placeholder properties and their values, you have to pipe them through StringValueResolver to make sure that placeholders work inside the property values as expected. The following code will do that.
package com.my.app;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.util.StringValueResolver;
public class AppConfig extends PropertyPlaceholderConfigurer implements Map<String, String> {
Map<String, String> props = new HashMap<String, String>();
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
throws BeansException {
this.props.clear();
for (Entry<Object, Object> e: props.entrySet())
this.props.put(e.getKey().toString(), e.getValue().toString());
super.processProperties(beanFactory, props);
}
#Override
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
super.doProcessProperties(beanFactoryToProcess, valueResolver);
for(Entry<String, String> e: props.entrySet())
e.setValue(valueResolver.resolveStringValue(e.getValue()));
}
// Implement map interface to access stored properties
#Override public Set<String> keySet() { return props.keySet(); }
#Override public Set<java.util.Map.Entry<String, String>> entrySet() { return props.entrySet(); }
#Override public Collection<String> values() { return props.values(); }
#Override public int size() { return props.size(); }
#Override public boolean isEmpty() { return props.isEmpty(); }
#Override public boolean containsValue(Object value) { return props.containsValue(value); }
#Override public boolean containsKey(Object key) { return props.containsKey(key); }
#Override public String get(Object key) { return props.get(key); }
#Override public void clear() { throw new UnsupportedOperationException(); }
#Override public String put(String key, String value) { throw new UnsupportedOperationException(); }
#Override public String remove(Object key) { throw new UnsupportedOperationException(); }
#Override public void putAll(Map<? extends String, ? extends String> t) { throw new UnsupportedOperationException(); }
}
I have done this and it has worked.
Properties props = PropertiesLoaderUtils.loadAllProperties("my.properties");
PropertyPlaceholderConfigurer props2 = new PropertyPlaceholderConfigurer();
props2.setProperties(props);
That should work.
CREDIT: Programmatic access to properties in Spring without re-reading the properties file
I've found a nice implementation of accessing the properties programmatically in spring without reloading the same properties that spring has already loaded. [Also, It is not required to hardcode the property file location in the source]
With these changes, the code looks cleaner & more maintainable.
The concept is pretty simple. Just extend the spring default property placeholder (PropertyPlaceholderConfigurer) and capture the properties it loads in the local variable
public class SpringPropertiesUtil extends PropertyPlaceholderConfigurer {
private static Map<String, String> propertiesMap;
// Default as in PropertyPlaceholderConfigurer
private int springSystemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;
#Override
public void setSystemPropertiesMode(int systemPropertiesMode) {
super.setSystemPropertiesMode(systemPropertiesMode);
springSystemPropertiesMode = systemPropertiesMode;
}
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) throws BeansException {
super.processProperties(beanFactory, props);
propertiesMap = new HashMap<String, String>();
for (Object key : props.keySet()) {
String keyStr = key.toString();
String valueStr = resolvePlaceholder(keyStr, props, springSystemPropertiesMode);
propertiesMap.put(keyStr, valueStr);
}
}
public static String getProperty(String name) {
return propertiesMap.get(name).toString();
}
}
Usage Example
SpringPropertiesUtil.getProperty("myProperty")
Spring configuration changes
<bean id="placeholderConfigMM" class="SpringPropertiesUtil">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
<property name="locations">
<list>
<value>classpath:myproperties.properties</value>
</list>
</property>
</bean>
Hope this helps to solve the problems you have
You can also use either the spring utils, or load properties via the PropertiesFactoryBean.
<util:properties id="myProps" location="classpath:com/foo/myprops.properties"/>
or:
<bean id="myProps" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location" value="classpath:com/foo/myprops.properties"/>
</bean>
Then you can pick them up in your application with:
#Resource(name = "myProps")
private Properties myProps;
and additionally use these properties in your config:
<context:property-placeholder properties-ref="myProps"/>
This is also in the docs: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#xsd-config-body-schemas-util-properties
Create a class like below
package com.tmghealth.common.util;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
#Component
#Configuration
#PropertySource(value = { "classpath:/spring/server-urls.properties" })
public class PropertiesReader extends PropertyPlaceholderConfigurer {
#Override
protected void processProperties(
ConfigurableListableBeanFactory beanFactory, Properties props)
throws BeansException {
super.processProperties(beanFactory, props);
}
}
Then wherever you want to access a property use
#Autowired
private Environment environment;
and getters and setters then access using
environment.getProperty(envName
+ ".letter.fdi.letterdetails.restServiceUrl");
-- write getters and setters in the accessor class
public Environment getEnvironment() {
return environment;
}`enter code here`
public void setEnvironment(Environment environment) {
this.environment = environment;
}
You can get your properties through Environment class. As documentation stands:
Properties play an important role in almost all applications, and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Maps, and so on. The role of the environment object with relation to properties is to provide the user with a convenient service interface for configuring property sources and resolving properties from them.
Having Environment as a env variable, simply call:
env.resolvePlaceholders("${your-property:default-value}")
You can get your 'raw' properties through:
env.getProperty("your-property")
It will search through all properties source that spring has registered.
You can either obtain Environment through:
inject ApplicationContext by implementing ApplicationContextAware and then call getEnvironment() on context
implement EnvironmentAware.
It's obtain through implementation of a class because properties are resolved on early stage of application startup, as they may be required for bean construction.
Read more on documentation: spring Environment documentation
As you know the newer versions of Spring don't use the PropertyPlaceholderConfigurer and now use another nightmarish construct called PropertySourcesPlaceholderConfigurer. If you're trying to get resolved properties from code, and wish the Spring team gave us a way to do this a long time ago, then vote this post up! ... Because this is how you do it the new way:
Subclass PropertySourcesPlaceholderConfigurer:
public class SpringPropertyExposer extends PropertySourcesPlaceholderConfigurer {
private ConfigurableListableBeanFactory factory;
/**
* Save off the bean factory so we can use it later to resolve properties
*/
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
super.processProperties(beanFactoryToProcess, propertyResolver);
if (beanFactoryToProcess.hasEmbeddedValueResolver()) {
logger.debug("Value resolver exists.");
factory = beanFactoryToProcess;
}
else {
logger.error("No existing embedded value resolver.");
}
}
public String getProperty(String name) {
Object propertyValue = factory.resolveEmbeddedValue(this.placeholderPrefix + name + this.placeholderSuffix);
return propertyValue.toString();
}
}
To use it, make sure to use your subclass in your #Configuration and save off a reference to it for later use.
#Configuration
#ComponentScan
public class PropertiesConfig {
public static SpringPropertyExposer commonEnvConfig;
#Bean(name="commonConfig")
public static PropertySourcesPlaceholderConfigurer commonConfig() throws IOException {
commonEnvConfig = new SpringPropertyExposer(); //This is a subclass of the return type.
PropertiesFactoryBean commonConfig = new PropertiesFactoryBean();
commonConfig.setLocation(new ClassPathResource("META-INF/spring/config.properties"));
try {
commonConfig.afterPropertiesSet();
}
catch (IOException e) {
e.printStackTrace();
throw e;
}
commonEnvConfig.setProperties(commonConfig.getObject());
return commonEnvConfig;
}
}
Usage:
Object value = PropertiesConfig.commonEnvConfig.getProperty("key.subkey");
This help me:
ApplicationContextUtils.getApplicationContext().getEnvironment()
Here is another sample .
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml"));
PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer();
cfg.setLocation(new FileSystemResource("jdbc.properties"));
cfg.postProcessBeanFactory(factory);
This will resolve any nested properties.
public class Environment extends PropertyPlaceholderConfigurer {
/**
* Map that hold all the properties.
*/
private Map<String, String> propertiesMap;
/**
* Iterate through all the Property keys and build a Map, resolve all the nested values before building the map.
*/
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) throws BeansException {
super.processProperties(beanFactory, props);
propertiesMap = new HashMap<String, String>();
for (Object key : props.keySet()) {
String keyStr = key.toString();
String valueStr = beanFactory.resolveEmbeddedValue(placeholderPrefix + keyStr.trim() + DEFAULT_PLACEHOLDER_SUFFIX);
propertiesMap.put(keyStr, valueStr);
}
}
/**
* This method gets the String value for a given String key for the property files.
*
* #param name - Key for which the value needs to be retrieved.
* #return Value
*/
public String getProperty(String name) {
return propertiesMap.get(name).toString();
}
This post also explatis howto access properties: http://maciej-miklas.blogspot.de/2013/07/spring-31-programmatic-access-to.html
You can access properties loaded by spring property-placeholder over such spring bean:
#Named
public class PropertiesAccessor {
private final AbstractBeanFactory beanFactory;
private final Map<String,String> cache = new ConcurrentHashMap<>();
#Inject
protected PropertiesAccessor(AbstractBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
public String getProperty(String key) {
if(cache.containsKey(key)){
return cache.get(key);
}
String foundProp = null;
try {
foundProp = beanFactory.resolveEmbeddedValue("${" + key.trim() + "}");
cache.put(key,foundProp);
} catch (IllegalArgumentException ex) {
// ok - property was not found
}
return foundProp;
}
}
This is the finest way I got it to work:
package your.package;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
public class ApplicationProperties {
private Properties properties;
public ApplicationProperties() {
// application.properties located at src/main/resource
Resource resource = new ClassPathResource("/application.properties");
try {
this.properties = PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException ex) {
Logger.getLogger(ApplicationProperties.class.getName()).log(Level.SEVERE, null, ex);
}
}
public String getProperty(String propertyName) {
return this.properties.getProperty(propertyName);
}
}
create .properties file in classpath of your project and add path configuration in xml`<context:property-placeholder location="classpath*:/*.properties" />`
in servlet-context.xml after that u can directly use your file everywhere
Please use the below code in your spring configuration file to load the file from class path of your application
<context:property-placeholder
ignore-unresolvable="true" ignore-resource-not-found="false" location="classpath:property-file-name" />
I know this is an old thread, however, this topic in my opinion becomes of great importance for those using the functional approach for all those usecases where you need a microservice that loads "instantly" and therefore you avoid using annotations.
The problem that remained unsolved was to load eventually the environment variables which I had in my application.yml.
public class AppPropsLoader {
public static Properties load() {
var propPholderConfig = new PropertySourcesPlaceHolderConfigurer();
var yaml = new YamlPropertiesFactoryBean();
ClassPathResource resource = new ClassPathResource("application.yml");
Objects.requireNonNull(resource, "File application.yml does not exist");
yaml.setResources(resource);
Objects.requireNonNull(yaml.getObject(), "Configuration cannot be null");
propPholderConfig.postProcessBeanFactory(new DefaultListableBeanFactory());
propPholderConfig.setProperties(yaml.getObject());
PropertySources appliedPropertySources =
propPholderConfig.getAppliedPropertySources();
var resolver = new PropertySourcesPlaceholderResolver(appliedPropertySources);
Properties resolvedProps = new Properties();
for (Map.Entry<Object, Object> prop: yaml.getObject().entrySet()) {
resolvedProps.setProperty((String)prop.getKey(),
getPropertyValue(resolver.resolvePlaceHolders(prop.getValue()));
}
return resolvedProps;
}
static String getPropertyValue(Object prop) {
var val = String.valueOf(prop);
Pattern p = Pattern.compile("^(\\$\\{)([a-zA-Z0-9-._]+)(\\})$");
Matcher m = p.matcher(val);
if(m.matches()) {
return System.getEnv(m.group(2));
}
return val;
}
}

Resources