Spring内@Value注解默认从Spring环境内(主要是Properties)获取String类型的配置值赋值给Bean内简单数据类型属性,会使用TypeConverter转换String类型以适配属性值。
原理是Spring容器在实例化所有普通类型的Bean之前,添加了一个StringValueResolver接口的实现类:
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
......
// Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(new StringValueResolver() {
@Override
public String resolveStringValue(String strVal) {
return getEnvironment().resolvePlaceholders(strVal);
}
});
}
......
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
}
重写StringValueResolver的resolveStringValue方法,在解析@Value类型的属性时,替换目标的值。内部通过类似PropertyPlaceholderConfigurer形式来替换{}内部的字符串,所以要求@Value内的value格式为${}形式。
自定义StringValueResolver的实现类,支持任意格式的字符串替换,以及任意形式的配置获取。
import com.bob.web.mvc.mapper.BankUserMapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.util.StringValueResolver;
/**
* 通过{@link org.springframework.beans.factory.annotation.Value}实现自定义属性注入
* 属性值可以从环境变量,磁盘,内存,及网络等获取
*
* @author wb-jjb318191
* @create 2018-02-27 11:32
*/
public class CustomizedStringValueResolver implements StringValueResolver, BeanFactoryAware {
@Autowired
private BankUserMapper bankUserMapper;
@Override
public String resolveStringValue(String strVal) {
String value = null;
if (strVal.startsWith("#{") && strVal.endsWith("}")) {
String key = strVal.substring(2, strVal.length() - 1);
value = bankUserMapper.selectByPrimaryKey(Integer.valueOf(key)).getAge().toString();
}
return value == null ? strVal : value;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
((DefaultListableBeanFactory)beanFactory).addEmbeddedValueResolver(this);
}
}
我在此处时使用Mapper从数据库获取,可以自定义任意形式的数据来源。
第二步,要确定此StringValueResolver的实例化顺序,太早了,会导致CustomizedStringValueResolver
依赖的所有Bean提前实例化,很可能会有意想不到的问题,比如这里的Mapper实例化,Mapper依赖SqlSessionFactory等等,这种间接的依赖都会提前触发实例化,所以实例化CustomizedStringValueResolver 的顺序肯定不能太早。同样,也不能太晚,如果CustomizedStringValueResolver 实例化晚于Bean A,而A里面需要用到CustomizedStringValueResolver 的resolveStringValue()方法,那么A里面的@Value的解析就会忽略CustomizedStringValueResolver 的实现。
import java.util.concurrent.atomic.AtomicBoolean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
/**
* 自定义StringValueResolver注册器
*
* @author Administrator
* @create 2018-03-03 9:31
*/
public class StringValueResolverRegistrar extends InstantiationAwareBeanPostProcessorAdapter {
@Autowired
private DefaultListableBeanFactory beanFactory;
private AtomicBoolean registerLock = new AtomicBoolean(false);
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
if (beanFactory.isConfigurationFrozen() && registerLock.compareAndSet(false, true)) {
beanFactory.getBean(CustomizedStringValueResolver.class);
}
return super.postProcessBeforeInstantiation(beanClass, beanName);
}
}
借助BeanPostProcessor的特性,将CustomizedStringValueResolver实例化的时机选择在:
Spring实例化完所有BeanFactoryPostProcessor,BeanPostProcessor后,开始实例化第一个普通Bean,且这个Bean还未有实际对象。
选择这个时机的用意在于CustomizedStringValueResolver能覆盖所有的非PostProcessor类型的Bean,而又不早于BeanProcessor的实例化。
有一点要注意,不要在CustomizedStringValueResolver依赖的Bean内使用其功能,本例中CustomizedStringValueResolver依赖BankUserMapper ,而Mapper又间接依赖SqlSessionFactory,也就是实例化SqlSessionFactory时,不要用到CustomizedStringValueResolver的功能,因为此时BankUserMapper 实例化,会报NullpointException异常。
最后就是使用了。
@Configuration
public class WebContextConfig extends WebMvcConfigurerAdapter {
......
@Bean
public StringValueResolverRegistrar stringValueResolverRegister() {
return new StringValueResolverRegistrar();
}
@Bean
public SpringBeanInstanceAccessor customizedBeanFactoryUtils() {
return new SpringBeanInstanceAccessor();
}
}
@RestController
@RequestMapping("/async")
public class AsyncController {
@Value("#{1}")
private Integer userName;
......
}
Spring在实例化AsyncController 时,会用CustomizedStringValueResolver来解析userName属性值。