Spring运行时值注入

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 testLoadingCache() {
        return CacheBuilder.newBuilder()
                .maximumSize(maximumSize)
                .expireAfterAccess(expireTime, TimeUnit.SECONDS)
                .build(
                        new CacheLoader() {
                            @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 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() {
                            @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 testLoadingCache() {
        return CacheBuilder.newBuilder()
                .maximumSize(maximumSize)
                .expireAfterAccess(expireTime, TimeUnit.SECONDS)
                .build(
                        new CacheLoader() {
                            @Override
                            public Integer load(Integer key) throws Exception {
                                return key + 1;
                            }
                        }
                );
    }
}

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

  1. JavaConfig中配置PropertySourcesPlaceholderConfigurer
  2. 配置文件根节点下添加

1.3 通过Spring表达式SpEL

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

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

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

2 总结

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

你可能感兴趣的:(Spring运行时值注入)