Normally i would populate a field using annotations when I knew the property name like so :
#Value("${myproperties.myValue}")
private String myString
However I now want to loop through all the properties in a file, when their names are unknown, and store both there value and name. What's the best way with spring and java ?
Actually if you need only to read properties from a file and not to use these properties in Spring's property placeholders, then the solution is simple
public class Test1 {
#Autowired
Properties props;
public void printProps() {
for(Entry<Object, Object> e : props.entrySet()) {
System.out.println(e);
}
}
...
<util:properties id="props" location="/spring.properties" />
The #Value mechanism works through the PropertyPlaceholderConfigurer which is in turn a BeanFactoryPostProcessor. The properties used by it are not exposed at runtime. See this previous answer of mine for a possible solution.
I could not find a simpler solution than this
class PropertyPlaceholder extends PropertyPlaceholderConfigurer {
Properties props;
#Override
protected Properties mergeProperties() throws IOException {
props = super.mergeProperties();
return props;
}
}
public class Test1 {
#Autowired
PropertyPlaceholder pph;
public void printProps() {
for(Entry<Object, Object> e : pph.props.entrySet()) {
System.out.println(e);
}
}
...
...
<bean class="test.PropertyPlaceholder">
<property name="locations">
<value>/app.properties</value>
</property>
</bean>
Using enumeration to loop through Properties
Related
I recently switched from using Spring's XML configuration to its Java configuration and am encountering a strange issue.
The XML configuration was:
<util:map id="myMap">
<entry key="a" value="aValue"/>
<entry key="b" value="bValue"/>
<entry key="c" value="cValue"/>
</util:map>
<bean id="myBean" class="my.MyClass">
<property name="myMap" ref="myMap"/>
</bean>
The Java configuration is:
#Bean
public Map<String, Object> myMap() {
Map<String, Object> myMap = new HashMap<>();
myMap.put("a", "aValue");
myMap.put("b", "bValue");
myMap.put("c", "cValue");
return myMap;
}
#Bean
public MyClass myBean(#Qualifier("myMap") final Map<String, Object> myMap) {
MyClass myBean = new MyClass();
myBean.setMyMap(myMap);
return myBean;
}
Both beans are declared in different files, I grouped them here to make it easier to read. The map contains references too, not only strings.
I would expect to be able to use myMap in the second bean but instead Spring injects the following map:
{ myMap = { a=aValue, b=bValue, c=cValue } }
I don't understand why Spring wraps the map into another map, and why it doesn't behave the same way with the XML configuration.
Any ideas?
There is an issue #Autowired-ing a Map even defining the bean name and since as-per the comments you can't use the suggested #Resource annotation there is an alternative by using the #Value annotation defining the bean name:
#Bean
public MyClass myBean(#Value("#{myMap}") final Map<String, Object> myMap) {
//..
}
I want to be able to store a Set of fully qualified Class names as a property of a Node. Given this Node:
#NodeEntity
public TestNode {
Set<Class<?>> classSet;
//getters and setters here
}
And the following custom converters:
public class ClassToStringConverter implements Converter<Class<?>, String> {
#Override
public String convert(final Class<?> source) {
return source.getName();
}
}
public class StringToClassConverter implements Converter<String, Class<?>> {
#Override
public String convert(final String source) {
Class<?> returnVal = null;
try {
returnVal = Class.forName(source);
} catch (ClassNotFoundException e) { }
return returnVal;
}
}
I register the converters with Spring context as such:
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="my.package.ClassToStringConverter"/>
<bean class="my.package.StringToClassConverter "/>
</set>
</property>
</bean>
Then using my repository, I can properly save and retrieve the Node, and it stores the fully qualified class name as expected using my custom converters. However, I would like to be able to query by a Class name as such in my repository:
#Query("MATCH (m:TestNode) where {0} in m.classSet return m;")
public findByClassInClassSet(Class<?> clazz);
However, this seems to convert the Class using Class.toString() instead of using my Converter. So it is searching for the String "class my.package.TestNode" instead of what the Converter correctly stored as "my.package.TestNode".
Am I missing something here or doing something wrong? How can I benefit from these converters if I can't query using the Class type?
NOTE: Please excuse any typos - this code is on a disconnected network so I couldn't copy paste. If there are any typos, I assure you that is not the problem on my actual code.
Should be fixed in the next milestone release for derived finders.
For your annotated query-method, SDN cannot know what you are referring to with your parameter, so it will not be able to convert it, ever.
Using Spring I need some kind of environment (dev|test|prod) specific properties.
I have exactly one configuration file (myapp.properties) and for some reasons I cannot have more than one configuration file (even spring can handle more than one).
So I need the possibility to add properties with a prefix like
dev.db.user=foo
prod.db.user=foo
and tell the application which prefix (environment) to use with a VM-argument like -Denv-target or something like this.
I use for this purpose a subcass of PropertyPlaceholderConfigurer:
public class EnvironmentPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
private static final String ENVIRONMENT_NAME = "targetEnvironment";
private String environment;
public EnvironmentPropertyPlaceholderConfigurer() {
super();
String env = resolveSystemProperty(ENVIRONMENT_NAME);
if (StringUtils.isNotEmpty(env)) {
environment = env;
}
}
#Override
protected String resolvePlaceholder(String placeholder, Properties props) {
if (environment != null) {
String value = props.getProperty(String.format("%s.%s", environment, placeholder));
if (value != null) {
return value;
}
}
return super.resolvePlaceholder(placeholder, props);
}
}
and using it in applicationContext.xml (or any other spring-configuration file):
<bean id="propertyPlaceholder"class="EnvironmentPropertyPlaceholderConfigurer">
<property name="location" value="classpath:my.properties" />
</bean>
In my.properties you can define properties like:
db.driverClassName=org.mariadb.jdbc.Driver
db.url=jdbc:mysql:///MyDB
db.username=user
db.password=secret
prod.db.username=prod-user
prod.db.password=verysecret
test.db.password=notsosecret
Thereby you can prefix properties keys by an environment key (e.g. prod).
Using the vm argument targetEnvironment you can choose the enviroment you like to use, e.g. -DtargetEnvironment=prod.
If no environment-specific-property exists, the default one (without a prefix) is choosen. (You should always define a default one.)
I don't know what are your constraints to avoid having more than one configuration file but you can use something like -Denvtarget=someValue and in java do:
//Obtain the value set in the VM argument
String envTarget= System.getProperty("env-target");
Properties properties;
try {
properties = PropertiesLoaderUtils.loadAllProperties("myapp.properties");
} catch (IOException exception) {
//log here that the property file does not exist.
}
//use here the prefix set in the VM argument.
String dbUser = properties.getProperty(envTarget+".db.user");
If you have environment variable and want to get property according this variable you can declare your properties that way:
<property name="username" value="${${env-target}.database.username}" />
<property name="password" value="${${env-target}.database.password}" />
Also make sure that you use properly configured property-placeholder:
<context:property-placeholder location="classpath*:META-INF/spring/*.properties"/>
Or, if you use special property configurer (e.g. EncryptablePropertyPlaceholderConfigurer), set properties:
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
But as mentioned earlier it is better to use profiles.
I have written a factory bean that creates a cache manager based on the properties that are configured in a application specific properties file.
The concept is that multiple implementations can be chosen, each using other configuration properties.
For example:
noop cache, without parameters,
ehcache with #max objects
memcache with multiple ips and ports configured.
I think it is nice to not specify all cache-application specific parameters in the application-context.xml, but read them from the existing properties sources.
My attempt was using a EnvironementAware interface to get access to the Environement. But it seems that the property file that is configured using <context:property-placeholder> is not contained in the PropertiesSources.
example.properties
cache.implementation=memcached
cache.memcached.servers=server1:11211,server2:11211
application-context.xml
<context:property-placeholder location="example.properties"/>
<bean id="cacheManager" class="com.example.CacheManagerFactory"/>
In CacheManagerFactory.java
public class CacheManagerFactory implements FactoryBean<CacheManager>, EnvironmentAware {
private Environement env;
#Override
public CacheManager getObject() throws Exception {
String impl = env.getRequiredProperty("cache.implementation"); // this fails
//Do something based on impl, which requires more properties.
}
#Override
public void setEnvironment(Environment env) {
this.env = env;
}
#Override
public Class<?> getObjectType() {
return CacheManager.class;
}
#Override
public boolean isSingleton() {
return true;
}
}
In config file like this :
<context:property-placeholder location="classpath:your.properties" ignore-unresolvable="true"/>
...
<property name="email" value="${property1.email}"/>
<!-- or -->
<property name="email">
<value>${property1.email}</value>
</property>
or in code :
#Value("${cities}")
private String cities;
where the your.properties contains this :
cities = my test string
property1.email = answer#stackvoerflow.com
Given a set of classes wired together by spring. There are several classes that are used with different configuration in multiple instances in the environment. They have different beanid of course.
The problems:
When they make log entries, we dont know exactly which bean made the log, since the log4j displays the classname only
I know that I could use logger instantiated by spring InitializationBean+BeanNameAware interface methods, but I do not want to do it, since I do not want to implement them in all classes
The solution could be:
Having some effect on bean factory to store the id of the beans in a map with the bean reference (key is the ref, name is the value)
Creating an aspect to be applied on every method, that would set an "BeanName" MDC entry in Log4j before the call, and would restore it to the previous value after the call. Meanwhile the previous beannames could be stored in a threadlocal in a stack.
The questions:
How can I change/configure the bean factory to do this trick for me? Is there any customization point I could use to this aim?
How can I avoid memory leaks in the map in the beanid registry? Maybe the registry is not needed at all, if somehow spring can look up the id for a reference.
Do you have any better idea, that would not result in changing ten thousand classes?
Thanks in advance.
UPDATE:
- Does anyone have solution for the prototype beans?
I have managed to hack something together based on this Spring AOP Example.
I am not yet up to speed with Spring 3 so I have implemented this using Spring 2.5 - I dare say there are more elegant ways of achieving what you want. I have implemented this using System.out's for simplicity but these could easily be converted to log4j calls.
Initially I create a map between the Spring's bean names and the string representation of the object (InitBean). This map is used inside the MethodInterceptor - I did try making the MethodInterceptor an InitializingBean but the MethodInterceptor stopped working for some reason.
Performing an equals between the bean passed in via the MethodInterceptor and the other beans in the application context did not work. e.g. by using something like "ctx.getBeansOfType(GoBean.class)" inside the MethodInterceptor. I presume this is because the object passed in via the MethodInvocation was a GoBean whereas objects obtained from the application context at this point are proxied (e.g. something like example.GoBean$$EnhancerByCGLIB$$bd27d40e).
This is why I had to resort to a comparison of object string representations (which is not ideal). Also I specifically do not want to activate the MethodInterceptor logic when calling the "toString" method on an object (as since I'm using toString elsewhere leads to infinite loops and StackOverflow).
I hope this is useful,
applicationContext.xml
<beans>
<bean name="initBean" class="example.InitBean"/>
<bean name="methodLoggingInterceptor" class="example.MethodLoggingInterceptor">
<property name="initBean" ref="initBean"/>
</bean>
<bean name="proxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>go*</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>methodLoggingInterceptor</value>
</list>
</property>
</bean>
<bean name="goBean1" class="example.GoBean" />
<bean name="goBean2" class="example.GoBean" />
<bean name="goBean3" class="example.GoBean" />
</beans>
GoBean.java
public class GoBean {
public void execute(){
System.out.println(new Date());
}
}
SimpleTestClass.java
public static void main( String[] args ){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ArrayList<GoBean> goBeans = new ArrayList<GoBean>();
goBeans.add((GoBean) ctx.getBean("goBean1"));
goBeans.add((GoBean) ctx.getBean("goBean2"));
goBeans.add((GoBean) ctx.getBean("goBean3"));
for(GoBean g: goBeans){
g.execute();
}
}
InitBean.java
public class InitBean implements ApplicationContextAware, InitializingBean {
private ApplicationContext ctx;
private Map<String, String> beanMap = new HashMap<String,String>();
public void setApplicationContext(ApplicationContext ac) throws BeansException {
ctx = ac;
}
public void afterPropertiesSet() throws Exception {
for(String beanName: ctx.getBeanNamesForType(GoBean.class)){
beanMap.put(ctx.getBean(beanName).toString(), beanName);
}
}
public Map<String,String> getBeanMap(){
return beanMap;
}
}
MethodLoggingInterceptor.java
public class MethodLoggingInterceptor implements MethodInterceptor{
private InitBean initBean;
public Object invoke(MethodInvocation method) throws Throwable {
if (!"toString".equals(method.getMethod().getName())) {
StringBuilder sb = new StringBuilder();
Object obj = method.getThis();
if (obj instanceof GoBean) {
Map<String,String> beanMap = initBean.getBeanMap();
String objToString = obj.toString();
if (beanMap.containsKey(objToString)) {
System.out.println(beanMap.get(objToString));
sb.append("bean: ");
sb.append(beanMap.get(objToString));
sb.append(" : ");
}
}
sb.append(method.getMethod().getDeclaringClass());
sb.append('.');
sb.append(method.getMethod().getName());
System.out.println(sb.toString() + " starts");
Object result = method.proceed();
System.out.println(sb.toString() + " finished");
return result;
} else {
return method.proceed();
}
}
public void setInitBean(InitBean ib) {
this.initBean = ib;
}
}