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<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 Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 87,400评论 13 122
  • 1.1 spring IoC容器和beans的简介 Spring 框架的最核心基础的功能是IoC(控制反转)容器,...
    simoscode阅读 4,137评论 2 21
  • 1.1 Spring IoC容器和bean简介 本章介绍了Spring Framework实现的控制反转(IoC)...
    起名真是难阅读 1,432评论 0 8
  • 本章内容: Spring profile 条件化的bean声明 自动装配与歧义性 bean的作用域 Spring表...
    谢随安阅读 507评论 0 5
  • 新学期希望每个同学要做到"五心": 一是收心。 把寒假的以玩为主的生活方式转变为以学为主的生活方式。 二是决心。 ...
    游客1976阅读 36评论 0 0