Spring运行时值注入

作者: Alex的路 | 来源:发表于2018-09-09 14:48 被阅读0次

    0 前言

    Spring中的Bean配置方式一文中我简单介绍了在Spring中如何配置Bean,通过Bean的配置我们可以让类进入Spring的容器中,这样就可以在需要该类对象的时候直接进行注入。在类注入的过程中,我们可能需要对类中某些属性进行初始值设置,常见的是在配置第三方库的时候。譬如在使用Guava的LoadingCache做本地缓存的是可能需要设置最大容量maximumSize、在使用JestClient做elasticsearch操作的时候需要指定url等。
    解决该问题的方法大致有三种:

    1. 直接在相关的方法中写入默认值
    .maximumSize(1000000)
    
    1. 在类中定义静态的常量值
    private static int MAXIMUM_SIZE = 100000;
    .maximumSize(MAXIMUM_SIZE);
    
    1. 在属性文件中进行设置,然后在类中读取属性值
    @Value("${loading_cache.maximum_size}")
    private int maximumSize;
    .maximumSize(maximumSize);
    

    第一种和第二种方法看起来简单,但是实际上存在着一些问题:

    • 方法1是一种很不好的编程习惯,直接在代码中通过硬编码赋值,这种值也称为魔法值。当原先指定的值要修改的时候,要寻找代码中所有赋值的地方,非常容易出错。
    • 方法2如果在单独的文件中设置默认值,例如常见的枚举值和一些常量值,那么就可以避免到处修改的情况。但是它和方法1都存在一个共同的问题——值实在编译的时候确定的。

    实际项目开发中,通常会有多个不同的环境,譬如开发环境、测试环境和线上环境,在不同的环境中,系统配置的属性值可能不一样,例如数据库在不同环境下的相关配置。为了解决这个问题,一方面我们要对不同环境进行不同的配置;另一方面属性的配置要在运行时确定,这样才不会去代码中手动修改。

    1 运行时注入值

    在Spring中实现运行时注入值的方法有三种:

    • 通过Environment类来检索属性
    • 通过属性占位符
    • 通过Spring表达式语句SpEL

    1.1 mock场景

    假定我们现在采用Guava的LoadingCache做本地缓存,需要在运行时指定maximumSizeexpireTime

    • maximumSize:缓存最大容量
    • expireTime:过期时间,按秒算

    代码如下:

    @Configuration
    public class ConfigTest {
        private int maximumSize;
        private int expireTime;
    
        @Bean
        public LoadingCache<Integer, Integer> testLoadingCache() {
            return CacheBuilder.newBuilder()
                    .maximumSize(maximumSize)
                    .expireAfterAccess(expireTime, TimeUnit.SECONDS)
                    .build(
                            new CacheLoader<Integer, Integer>() {
                                @Override
                                public Integer load(Integer key) throws Exception {
                                    return key + 1;
                                }
                            }
                    );
        }
    }
    

    此时maximumSizeexpireTime都没有初始化值,需要在运行时注入值。项目开发中,我们往往将属性配置都放在一个或者多个后缀为.properties文件中,例如如下的application.properties.

    # 最大容量
    guava.loadingcache.maximumsize=100000
    # 过期时间
    guava.loadingcache.expiretime=100
    

    1.1 通过Environment检索属性

    步骤:

    1. 通过@PropertySource指定属性源
    2. 通过Environment检索属性

    代码如下:

    @Configuration
    # 1. 指定属性源
    @PropertySource("classpath:application.properties") 
    public class ConfigTest {
        private int maximumSize;
        private int expireTime;
    
        # 2. 注入Environment
        @Autowired
        private Environment environment;
    
        @Bean
        public LoadingCache<Integer, Integer> testLoadingCache() {
            # 通过getProperty获取值
            maximumSize = environment.getProperty("guava.loadingcache.maximumsize", Integer.class);
            expireTime = environment.getProperty("guava.loadingcache.expiretime", Integer.class);
            return CacheBuilder.newBuilder()
                    .maximumSize(maximumSize)
                    .expireAfterAccess(expireTime, TimeUnit.SECONDS)
                    .build(
                            new CacheLoader<Integer, Integer>() {
                                @Override
                                public Integer load(Integer key) throws Exception {
                                    return key + 1;
                                }
                            }
                    );
        }
    }
    
    

    1.2 通过属性占位符

    属性占位符需要用到@Value属性,形如@Value("${property.name}")
    代码如下:

    @Configuration
    public class ConfigTest {
        @Value("${guava.loadingcache.maximumsize}")
        private int maximumSize;
        
        @Value("${guava.loadingcache.expiretime}")
        private int expireTime;
    
        @Autowired
        private Environment environment;
    
        @Bean
        public LoadingCache<Integer, Integer> testLoadingCache() {
            return CacheBuilder.newBuilder()
                    .maximumSize(maximumSize)
                    .expireAfterAccess(expireTime, TimeUnit.SECONDS)
                    .build(
                            new CacheLoader<Integer, Integer>() {
                                @Override
                                public Integer load(Integer key) throws Exception {
                                    return key + 1;
                                }
                            }
                    );
        }
    }
    

    需要注意的是,采用这种方法必须提供PropertyPlaceholderConfigurerbean或者PropertySourcesPlaceholderConfigurerbean。有如下两种方式:

    1. JavaConfig中配置PropertySourcesPlaceholderConfigurer
    2. 配置文件根节点下添加<context:property-placeholder/>

    1.3 通过Spring表达式SpEL

    SpEL功能很强大,形如@Value("#{expression}")。它和属性占位符的区别在于:

    1. 不需要PropertySourcesPlaceholderConfigurerbean的帮助
    2. expression能够执行简单的运算,调用其他对象或者bean中的方法

    但是在实际开发中,我并没有碰到多少需要采用SpEL才能完成的任务,一般属性占位符就可以了。对上文提到的虚拟场景并不怎么适用。如果感兴趣,可以去官网了解一下。

    2 总结

    一般情况下,我们回在配置中的默认值放在属性文件中,采用属性占位符的方式去捕获属性值。但是由于Spring中Bean一般是单例的,所以只能加载一次。如果项目中需要定时的加载配置文件,则可以采用Environment的方法去尝试(注意不是采用@Autowired方式自动注入)。SpEL用在View的很方便,在实际项目代码中有时候并不需要那么强大的计算能力。

    相关文章

      网友评论

        本文标题:Spring运行时值注入

        本文链接:https://www.haomeiwen.com/subject/feirgftx.html