I am looking to replace the Environment bean used by Spring with my own implementation. Is this bad-practice, and if not, how can I do this cleanly? Currently I have created a bean that implements the Environment interface and uses the existing Environment bean, but this means all the configuration code needing the Environment bean must now use my custom Environment bean. I would think it would be cleaner to replace Springs Environment bean with my own, then no configuration needing it would need to change. Currently the only way I can think to do this would be to either create my own ApplicationContext thus setting the environment to my own, or to have something be ApplicationContextAware and set the environment there. Both of these seem to be a bit hokey to me.
Constraints:
- I am using the latest version of Spring3.
- I am using Java based configuration; not XML
Thank you.
Edit: Background
I suppose I should explain why I want to do this in case my thinking is flawed. I was avoid this in order to avoid the nonconstructive "why would you want to do that?" responses.
The Spring Environment bean, when looking for property values, uses a set of property sources. A typical stack looks like this (but is not limited to):
- JNDI
- System Properties (set via -Dmyprop=foo)
- Environment Variables
- ...
For security reason, it is necessary to encrypt some of these properties (e.g. database passwords). The go to solution is to use Jasypt for property encryption. However, the Spring/Jasypt only provide a means of inserting a new property source into the environment. So:
- Property file with potentially encrypted values
- JNDI
- System Properties (set via -Dmyprop=foo)
- Environment Variables
- ...
However, this is not ideal as this means that properties can only be stored in a single file to be maintained by the operations group or that properties would be spread out amongst property files, environment variables, etc. In addition, I feel that properties have the potential of being encrypted regardless of their property source.
So this lead me to thinking that I either need to decrypt the properties in my code wherever I attempt to access them from the environment, or I need to create my own Environment bean that can do this for me.
I'm welcome to hear constructive comments and alternatives.
EDIT: Adding solution based on answer from M. Deinum
public class EnvironmentBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private static final String CONFIGURATION_PROPERTY_PBE_ALGORITHM = "PBE_ALGORITHM";
private static final String CONFIGURATION_PROPERTY_PBE_PASSWORD = "PBE_PASSWORD";
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
StandardEnvironment environment = (StandardEnvironment) beanFactory.getBean("environment");
if (environment != null) {
StringEncryptor encryptor = this.getEncryptor(environment);
MutablePropertySources mutablePropertySources = environment.getPropertySources();
for (PropertySource<?> propertySource : mutablePropertySources) {
mutablePropertySources.replace(
propertySource.getName(),
new EncryptablePropertySourcePropertySource(propertySource.getName(), propertySource, encryptor));
}
}
}
private StringEncryptor getEncryptor(Environment environment) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
String algorithm = environment.getProperty(CONFIGURATION_PROPERTY_PBE_ALGORITHM);
if (algorithm != null) {
encryptor.setAlgorithm(algorithm);
}
String password = environment.getProperty(CONFIGURATION_PROPERTY_PBE_PASSWORD);
if (password != null) {
encryptor.setPassword(password);
}
return encryptor;
}
private class EncryptablePropertySourcePropertySource extends PropertySource<PropertySource<?>> {
private StringEncryptor stringEncryptor;
private TextEncryptor textEncryptor;
public EncryptablePropertySourcePropertySource(final String name, final PropertySource<?> propertySource, final StringEncryptor encryptor) {
super(name, propertySource);
this.stringEncryptor = encryptor;
}
public EncryptablePropertySourcePropertySource(final String name, final PropertySource<?> propertySource, final TextEncryptor encryptor) {
super(name, propertySource);
this.textEncryptor = encryptor;
}
@Override
public Object getProperty(String name) {
Object value = this.source.getProperty(name);
if (value != null && value instanceof String) {
value = this.decode((String) value);
}
return value;
}
private String decode(String encodedValue) {
if (!PropertyValueEncryptionUtils.isEncryptedValue(encodedValue)) {
return encodedValue;
}
if (this.stringEncryptor != null) {
return PropertyValueEncryptionUtils.decrypt(encodedValue, this.stringEncryptor);
}
if (this.textEncryptor != null) {
return PropertyValueEncryptionUtils.decrypt(encodedValue, this.textEncryptor);
}
throw new EncryptionOperationNotPossibleException(
"Neither a string encryptor nor a text encryptor exist "
+ "for this instance of EncryptableProperties. This is usually "
+ "caused by the instance having been serialized and then "
+ "de-serialized in a different classloader or virtual machine, "
+ "which is an unsupported behaviour (as encryptors cannot be "
+ "serialized themselves)");
}
}
}