Java开发网 Java开发网
注册 | 登录 | 帮助 | 搜索 | 排行榜 | 发帖统计  

您没有登录

» Java开发网 » Architecture & Framework  

按打印兼容模式打印这个话题 打印话题    把这个话题寄给朋友 寄给朋友    该主题的所有更新都将Email到你的邮箱 订阅主题
flat modethreaded modego to previous topicgo to next topicgo to back
作者 Spring configuration note
floater

Java Jedi

总版主


发贴: 3233
积分: 421
于 2005-10-05 12:06 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
A simple configuration in Spring is like this:

<bean id="simpleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"><value>org.hsqldb.jdbcDriver</value></property>
<property name="url"><value>jdbc:hsqldb:c:/cui/springdao/db/springdb</value></property>
<property name="username"><value>sa</value></property>
<property name="password"><value></value></property>
</bean>

Now the problem is this setting is likely to change, for instance, on app servers, we normally use an JNDI lookup
for a connection pool:

<bean id="jndiDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>jdbc/MyDataSource</value></property>
</bean>

Other possible reasons for changes are:
1. Different environments, like dev, qa, and prod env, they could have different urls/usernames/passwords
2. To work with different vendors, like sybase or oracle for databases, weblogic jms vs tibco jms.

Dependency injection helps us not care how the dependencies are created, where they come in the view of the
callers. However, we still need to integrate all parties together, including the dependencies. Spring provides
several ways to integrate these dependencies:
1. Using alias: This is to use the name attribute of the bean tag to define the alias and use the alias tag to hook
them together with the references or use the ref tag to reference them directly. For example, with the above two
beans defined, we could do this:

<alias name="simpleDataSource" alias="dataSource"/>

or

<alias name="jndiDataSource" alias="dataSource"/>

and then we could call the alias like this:

.....
<bean id="myDao" class="....">
........
<property name="dataSourceField"><ref bean="dataSource"/></property>
</bean>
......

So, this approach works well in the context of fixed caller reference("dataSource") and varying dependency names.
It works particularly well when we have a group of varying dependencies because we could group all alias together
in one place, so that there is less chance we could leave out one of ten changes, say. The downside, however, is that
we can have only one choice - either alias simpleDataSource or alias jndiDataSource, so we have to comment out
the leftover, this requires the configuration code change in the Spring XML file. Therefore, the most suitable usage for
aliases is to hook unchanged dependencies between components, as noted in the blogs of Colin and Alef, Spring team.
2. The second option is to use override feature in Spring - if there are same bean settings in several Spring XML files, the
later setting will override the previous setting. For example, suppose we have the above jndiDataSource bean defined in
some xml file, application.xml, and the simpleDataSource bean defined in application-test.xml, then during testing. If we change
both id to "dataSource", then we could
include the test xml file:

ApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"application.xml", "application-test.xml"});
......

The dataSource in the test xml file will override the one in the application.xml.
Later on, in the prod deployment, we could simply take the test xml file out. Here, the first file is fixed, while the second file
contains the changing portion. This approach works well for testing, but still
requires code change(either like in the above when we create the context, or in the web.xml when we specify the contextLocation)
when we switch between different environments. Though the required code changes in the two above approaches could be
avoid through build by copying different files(The changing portion) to a fixed file, they are still an either-or solution, meaning
we can't have both settings built in.

3. While the above two approaches work well to integrate components, it comes a little bit short for external resource settings. Now
we are going to add one more level of indirection so that we can have a runtime selection of options. The first approach is to use
Spring's PropertyPlaceholderConfigurer class. A simple example is like this:

........
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"><value>${jdbc.driver}</value></property>
<property name="url"><value>${jdbc.url}</value></property>
<property name="username"><value>${username}</value></property>
<property name="password"><value>${password}</value></property>
</bean>

<bean id="placeholderConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="jdbc.properties"/>
</bean>
.......

And the jdbc.properties is like this:

jdbc.driver=...
jdbc.url=...
jdbc.username=...
jdbc.password=....

While the reference of the location in the placeholderConfig bean is fixed, we could change it to a varying location:

<bean id="placeholderConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="jdbc.${env}.properties"/>
</bean>

Now this ${env} can be set through the jvm parameter(e.g., java -Denv=dev ....). So by setting different properties, we could
vary different jdbc settings. In order to be able to switch between the simpleDateSource and jndiDataSource in the beginning
of this article, we could do this:

<bean id="myDao" class="....">
........
<property name="dataSourceField"><ref bean="${dataSource}"/></property>
</bean>

<bean id="placeholderConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="dataSource.${env}.properties"/>
</bean>

and we have two different dataSource files:

dataSource.dev.properties:
dataSource=simpleDataSource

and

dataSource.prod.properties:
dataSource=jndiDataSource

In this way, we could use a jvm parameter(a runtime parameter) to select which properties file to use, and thus which dataSource to use.

Another possible way to add the extra level of indirection is through proxy technics, but I haven't tried it yet.

4. While using a jvm rumtime properties, sometimes we still want to use a file to specify which configuration to use, say we want to have
a file like this:

environment.properties:
env=dev

We want to use this to flag what to use. The PropertyPlaceholderConfigurer class is not sufficient for this purpose, it works only for jvm
system setting. This is because the locations field in PropertyPlaceholderConfigurer is of Resource[] and other PropertyPlaceholderConfigurer beans
don't resolve any ${...} for Resource objects(or it's trying to resolve ${} after the PropertyEditor converts String to Resource). So we create
an enclosure for PropertyPlaceholderConfigurer to do this:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.*;
import org.springframework.core.io.*;

public class NestedPropertyPlaceholderConfigurer implements BeanFactoryPostProcessor
{
private String beanName;

private String[] stringLocations;

private String placeholderPrefix = PropertyPlaceholderConfigurer.DEFAULT_PLACEHOLDER_PREFIX;

private String placeholderSuffix = PropertyPlaceholderConfigurer.DEFAULT_PLACEHOLDER_SUFFIX;

private int systemPropertiesMode = PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_FALLBACK;

private boolean ignoreUnresolvablePlaceholders = false;

private String fileEncoding;

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
if (stringLocations == null || stringLocations.length == 0) return;

Resource[] resources = new Resource[stringLocations.length];
ResourceResolver resolver = new ResourceResolver();
for (int i=0; i<resources.length; i++)
{
resources[i] = resolver.resolveResource(stringLocations[i]);
}

PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();

configurer.setBeanFactory(beanFactory);
configurer.setBeanName(this.beanName);
configurer.setFileEncoding(this.fileEncoding);
//configurer.setIgnoreResourceNotFound(this.ignoreUnresolvablePlaceholders);
configurer.setIgnoreUnresolvablePlaceholders(this.ignoreUnresolvablePlaceholders);
//configurer.setLocalOverride();
configurer.setLocations(resources);
//configurer.setOrder();
configurer.setPlaceholderPrefix(this.placeholderPrefix);
configurer.setPlaceholderSuffix(this.placeholderSuffix);
//configurer.setProperties();
//configurer.setPropertiesPersister();
configurer.setSystemPropertiesMode(this.systemPropertiesMode);
//configurer.setSystemPropertiesModeName();

configurer.postProcessBeanFactory(beanFactory);
}

// all the settings
public void setBeanName(String name) { this.beanName = name; }

public void setLocation(String location) { this.stringLocations = new String[] { location }; }

public void setLocations(String[] locations) { this.stringLocations = locations; }

public void setPlaceholderPrefix(String placeholderPrefix)
{
this.placeholderPrefix = placeholderPrefix;
}

public void setPlaceholderSuffix(String placeholderSuffix)
{
this.placeholderSuffix = placeholderSuffix;
}

public void setSystemPropertiesMode(int systemPropertiesMode)
{
this.systemPropertiesMode = systemPropertiesMode;
}

public void setIgnoreUnresolvablePlaceholders(boolean ignoreUnresolvablePlaceholders)
{
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}

public void setFileEncoding(String fileEncoding)
{
this.fileEncoding = fileEncoding;
}
}

and create a pseudo "editor":

import org.springframework.core.io.*;

public class ResourceResolver extends AbstractPathResolvingPropertyEditor
{
private final ResourceLoader resourceLoader;

/**
* Create a new ResourceEditor with a DefaultResourceLoader.
* @see DefaultResourceLoader
*/
public ResourceResolver()
{
this.resourceLoader = new DefaultResourceLoader();
}

/**
* Create a new ResourceEditor with the given ResourceLoader.
* @param resourceLoader the ResourceLoader to use
*/
public ResourceResolver(ResourceLoader resourceLoader)
{
this.resourceLoader = resourceLoader;
}

public Resource resolveResource(String text)
{
String locationToUse = resolvePath(text).trim();
return this.resourceLoader.getResource(locationToUse);
}
}


All it does is to keep the locations as strings until all dependent PropertyPlaceholderConfigurer objects resolved references and then convert strings to Resources
and apply to references. A simple usage is as follows;

<bean id="myDao" class="....">
........
<property name="dataSourceField"><ref bean="${dataSource}"/></property>
</bean>

<bean id="placeholderConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
depends-on"env">
<property name="location" value="dataSource.${env}.properties"/>
</bean>

<bean id="env"
class="......NestedPropertyPlaceholderConfigurer">
<property name="location" value="environment.properties"/>
</bean>

The env bean will be loaded first and resolve ${env} in the placeholderConfig bean.

The last two approaches can make a selection on options, but through extra properties files. The selection key can be a jvm system parameter during runtime,
or a parameter in a file, which could be set during build time or modified during deployment time(or possibly during runtime with a file watchdog and context refresh()).

In the above, we summarized 4 different ways to specify configurations. The first two approaches are more suitable for component composition(or COA - component
oriented architecture), i.e., integrate components together, rather than considering the options to select, the last two approaches are better for resource settings, i.e., to
select among choices.

Side notes:
Considering gongshi's pointing out: http://www.cjsdn.net/post/view?bid=32&id=158304&sty=1&tpg=1&age=0, I thus have to claim that this article is 100% authenticate homemade bits. If any search through google on the internet has positive result, then it is purely a copycat, Smile. Of course, use it at your own risk, it could be vaporated, Tounge.


floater edited on 2005-10-05 12:23

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
- Martin Fowler, Refactoring - Improving the Design of Existing Code
作者 Re:Spring configuration note [Re:floater]
davidself

猫哥

CJSDN高级会员


发贴: 1506
积分: 333
于 2005-10-07 08:08 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
休假回来就看到好文章,收藏先,上班再看!


--108的上铺--
davidself@twitter
作者 Re:Spring configuration note [Re:floater]
down4u



Jute Pro User


发贴: 119
积分: 51
于 2005-10-09 17:14 user profilesend a private message to usersend email to down4usearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
经验!收藏!谢谢floater!


作者 Re:Spring configuration note [Re:floater]
floater

Java Jedi

总版主


发贴: 3233
积分: 421
于 2005-10-22 02:56 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
Sometimes, if your build process permits, you can put different options in one file, this is especially handy when the options are not groups of settings, but rather a single line setting(i.e., the difference is very small). For instance, the difference among dev, qa, and prod is just the machine name:

dev: target=http://m1/test
qa: target=http://m2/test
prod: target=http://m3/test

So it's kind of tedious to put one line in each file.(Whether this is a good practice is still arguable). Therefore, we need to way to be able to select from a list.

Here is what we want to do:

<beans>
<!-- usage -->
<bean id="usage" class="springsamples.springcore.SinglePropertyUsage">
<property name="testValue">
<value>${app-${env:environment}.target}</value>
</property>
</bean>

<!-- single property selected by the env selector -->
<bean id="myConfigurer" class="springsamples.springcore.SinglePropertyConfigurer">
<property name="values">
<props>
<prop key="app-dev.target">http://m1/test1</prop>
<prop key="app-qa.target">http://m2/test2</prop>
<prop key="app-prod.target">http://m2/test2</prop>
</props>
</property>
</bean>

<!-- env selector -->
<bean id="env" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:springsamples/springcore/version.properties</value>
</property>
<property name="placeholderPrefix"><value>${env:</value></property>
</bean>
</beans>

The logic is from bottom up. We start from some switch, which is a key defined in the version.properties:

version.properties file content:
environment=dev

Then we want to use this key to select from a list of values listed in the second bean, and then set this value to the first bean, which could be anything. Although there are nested ${ there, they should be able resolve themselves.

The last bean is in Spring, the second one is very similar(so we should have a parent class for this one and the one in the first post) to the one in the first post:

package springsamples.springcore;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.*;
import java.util.*;

public class SinglePropertyConfigurer implements BeanFactoryPostProcessor
{
private Properties values;

///////////
private String beanName;

private String placeholderPrefix = PropertyPlaceholderConfigurer.DEFAULT_PLACEHOLDER_PREFIX;

  private String placeholderSuffix = PropertyPlaceholderConfigurer.DEFAULT_PLACEHOLDER_SUFFIX;

  private int systemPropertiesMode = PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_FALLBACK;

  private boolean ignoreUnresolvablePlaceholders = false;

private String fileEncoding;
////////////

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException
{
if (values == null || values.size() == 0) return;

PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();

configurer.setBeanFactory(beanFactory);
configurer.setBeanName(this.beanName);
configurer.setFileEncoding(this.fileEncoding);
//configurer.setIgnoreResourceNotFound(this.ignoreUnresolvablePlaceholders);
configurer.setIgnoreUnresolvablePlaceholders(this.ignoreUnresolvablePlaceholders);
//configurer.setLocalOverride();
configurer.setProperties(values);
//configurer.setOrder();
configurer.setPlaceholderPrefix(this.placeholderPrefix);
configurer.setPlaceholderSuffix(this.placeholderSuffix);
//configurer.setProperties();
//configurer.setPropertiesPersister();
configurer.setSystemPropertiesMode(this.systemPropertiesMode);
//configurer.setSystemPropertiesModeName();

configurer.postProcessBeanFactory(beanFactory);
}

public void setValues(Properties props) { this.values = props; }
// all the settings
public void setBeanName(String name) { this.beanName = name; }

public void setPlaceholderPrefix(String placeholderPrefix)
{
this.placeholderPrefix = placeholderPrefix;
}

public void setPlaceholderSuffix(String placeholderSuffix)
{
this.placeholderSuffix = placeholderSuffix;
}

public void setSystemPropertiesMode(int systemPropertiesMode)
{
this.systemPropertiesMode = systemPropertiesMode;
}

public void setIgnoreUnresolvablePlaceholders(boolean ignoreUnresolvablePlaceholders)
{
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}

public void setFileEncoding(String fileEncoding)
{
this.fileEncoding = fileEncoding;
}
}


The difference between this one and the one in the first post is that in here, we don't set locations, but set the properties directly.

The class in the first bean is just a simple test bean:

package springsamples.springcore;

public class SinglePropertyUsage
{
private String testValue;

public String getTestValue()
{
return testValue;
}

public void setTestValue(String testValue)
{
this.testValue = testValue;
}
}


The testing class is like this:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.*;

public class TestSinglePropertyConfigurer
{
public static void main(String[] args)
{
ApplicationContext ac = new ClassPathXmlApplicationContext(
"springsamples/springcore/singlePropertySelector.xml");

SinglePropertyUsage usage = (SinglePropertyUsage)ac.getBean("usage");

System.out.println("testValue=" + usage.getTestValue());
}
}

I just print out the value set to see whether it has the correct value.

Ideally, when we do a build or run a particular version, we want to be able to select a version. There are basically two ways to select version, through a jvm property during runtime, or a property defined in a file somewhere(There are many others, but these two are mostly used). This is so-called "named" configurations, i.e., we give a name. This enables us to select, rather than override, an option. Once we select it, everything else should automatically config'd to that option. So we need at least two levels of configurations, one for the env, and then another based on the env.

Spring not only enables us to do this, it does it in a "batch" fashion. Another previous difficulty that Spring, as a container, resolved is that in layered COA, it enables us to select something that we are not supposed to know(you don't know, you don't depend on). Allow me to illustrate this with a simple example. In the standard web/middle/database structure, we can make the selection from the web tier. But web tier doesn't know the db tier, so how can we set the db tier with the right selection without even knowing its existence. This is the power of the container. Without this second dimension, we end up with a lot of init() method in the DaoFactory or similar classes.


floater edited on 2005-10-22 03:04

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
- Martin Fowler, Refactoring - Improving the Design of Existing Code
作者 Re:Spring configuration note [Re:floater]
floater

Java Jedi

总版主


发贴: 3233
积分: 421
于 2005-11-01 12:10 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
汗...........

Release Name: 1.2.5

Package org.springframework.core
* refactored AbstractPathResolvingPropertyEditor into SystemPropertyUtils.resolvePlaceholders(text) helper method

So remove the parent class(extend) and just call the static method.



"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
- Martin Fowler, Refactoring - Improving the Design of Existing Code
作者 Re:Spring configuration note [Re:floater]
yeafee

javaholder



发贴: 42
积分: 1
于 2005-11-03 16:59 user profilesend a private message to usersearch all posts byselect and copy to clipboard. 
ie only, sorry for netscape users:-)add this post to my favorite list
SuN034好生景仰!



flat modethreaded modego to previous topicgo to next topicgo to back
  已读帖子
  新的帖子
  被删除的帖子
Jump to the top of page

   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923