Spring自定义@Value属性注入逻辑

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属性值。

你可能感兴趣的:(spring,造轮子)