Using properties defined by <context:property-placeholder> in a factory method - java

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

Related

Spring decide bean during runtime

I have multiple provider classes (Provider1 and Provider2), how do I decide what bean I use depending on the input parameter in the Processor class?
public class Processor{
private Provider provider;
public void process(String providerName) throws Exception {
// What should I do here to invoke either provider1 or provider2 depending on the providerName?
provider.doOperation();
}
}
public class Provider1 {
public void doOperation(Exchange exchange) throws Exception {
//Code
}
}
public class Provider2 {
public void doOperation(Exchange exchange) throws Exception {
//Code
}
}
This is the case of Factory pattern. You can create a (ProviderFactory) class, register all the providers and get provider based on value, e.g.:
class ProviderFactory(){
private List<Provider> providers = new ArrayList<>();
public Provider getProvider(String input){
if(input.equals("test1")){
//Find based on criteria
return provider1;
}else if(input.equals("test2")){
//Find based on criteria
return provider2;
}
}
public void registerProvider(Provider provider){
providers.add(provider);
}
}
You can call registerProvider method on application startup and add as many providers as you want. Once that is initialised, you can call getProvider method and return appropriate instance based on some criteria.
Please note that providers doesn't necessarily need to be a list, it can be any data structure. It depends on which structure suits your criteria the best.
Here's documentation/more examples for Factory pattern.
What about somthing like this?
1# into your processor class :
public class Processor{
private Map<Provider> providers;
public void process(String providerName) throws Exception {
Provider provider = providers.get(providerName);
provider.doOperation();
}
}
2# in your spring config:
<bean id="provider1" class="xx.yy.zz.Provider1"/>
<bean id="provider2" class="xx.yy.zz.Provider2"/>
<bean id="processor" class="xx.yy.zz.Processor">
<property name="providers">
<map>
<entry key="provider1" value-ref="provider1" />
<entry key="provider2" value-ref="provider2" />
</map>
</property>
</bean>
now for example if you call processor.process("provider1") it will call provider1.doOperation()

Spring bean injection - inject properties after bean has been defined

I have an application made up of 2 projects - UI and data. In the Data project, I have added a spring bean to the xml application context:
<bean id="mail-notification-service" class="com.test.DefaultEmailNotificationManager">
</bean>
This manager sends out notifications on request, and the parameters use a simple enum and a parameters object (both of which use classes only in the data project) to select an IEmailGenerator and use it to send the emails.
The manager is defined something like:
public class DefaultEmailNotificationManager implements IEmailNotificationManager {
public MailResult sendEmail( EmailType type ) { .. }
public void register( IEmailGenerator generator ) { .. }
}
public interface IEmailGenerator {
public EmailType getType();
}
Trouble is, the generators are defined in the UI project, so they can do things like get hold of wicket page classes, the request cycle, and application resources. Thus I can't add them to the bean in the data projects' applicationContext so that other modules in both the data and UI projects can use them.
Is there any way in the applicationContext of the UI project to do something like:
<bean id="exclusionNotifier" class="com.test.ui.ExclusionEmailNotifier"/>
<bean id="modificationNotifier" class="com.test.ui.ModificationEmailNotifier"/>
<call-method bean-ref="mail-notification-service" method="register">
<param name="generatorImplementation", ref="exclusionNotifier"/>
</call-method>
<call-method bean-ref="mail-notification-service" method="register">
<param name="generatorImplementation", ref="modificationNotifier"/>
</call-method>
I can manually tie the beans together in the WicketApplication.init method but would prefer something more elegant. Has anyone done anything like this?
Using Spring 4.1.4
Thanks in advance.
Inject generators into mail-notification-service bean (e.g. using autowire="byType") and register them right after bean construction using init-method (see Initialization callbacks in Spring docs)
public class DefaultEmailNotificationManager implements IEmailNotificationManager {
private Collection<IEmailGenerator> generators;
public void init() {
for( IEmailGenerator g : generators ) {
register(g);
}
}
public void setGenerators( Collection<IEmailGenerator> generators ) {
this.generators = generators;
}
public MailResult sendEmail( EmailType type ) { .. }
private void register( IEmailGenerator generator ) { .. }
}
data's applicationContext:
<bean id="mail-notification-service"
class="com.test.DefaultEmailNotificationManager"
init-method="init"
autowire="byType" />
UI's applicationContext:
<bean id="exclusionNotifier" class="com.test.ui.ExclusionEmailNotifier"/>
<bean id="modificationNotifier" class="com.test.ui.ModificationEmailNotifier"/>

Spring: Environment specific configuration

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.

Looping through all the properties in a file, with spring and java

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

Logging bean id into log4j logfile without BeanNameAware interface

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;
}
}

Categories