案例分析
@Configuration
public class EnvironmentConfig implements EnvironmentAware {
private static Environment env;
@Override
public void setEnvironment(Environment environment) {
env=environment;
}
public static Environment getEnvironment(){
return env;
}
}
public class EnvUtils {
public static final String ENV_PARAM = EnvironmentConfig.getEnvironment().getProperty("env.param");
}
@DependsOn("environmentConfig")
@RestController
public class TestController {
@RequestMapping("/env")
public String getEnv() {
System.out.println("输出结果:" + EnvUtils.ENV_PARAM);
return EnvUtils.ENV_PARAM;
}
}
项目代码是通过以上方式获取配置信息,但存在间歇性的无法读取配置的问题,检查后发现和 Bean 实例化顺序有关。EnvUtils 中的静态变量在编译时初始化,但赋值是在程序运行时进行的。这就导致了一个问题,当系统启动的时候存在先实例化 Controller 再实例化 Config 的情况,而在这种情况下就会报错。
为了验证猜测,在 EnvironmentConfig 类上增加了 @Lazy 注解,随后启动应用,发起请求发现系统报错,顺利重现了线上错误。
解决思路
- 通过
@Value直接注入配置 - 依赖于 EnvironmentConfig 对象
3.
@DependsOn("environmentConfig")2. 注入 EnvironmentConfig
知识点拓展
懒加载
@Lazy 创建 Bean 对象延迟到第一次使用 Bean 的时候
@Lazy
@Configuration
public class LazyInitConfig {
public LazyInitConfig() {
System.out.println("LazyInitConfig init!");
}
public void printInfo() {
int mb = 1024 * 1024;
//Getting the runtime reference from system
Runtime runtime = Runtime.getRuntime();
System.out.println("##### Heap utilization statistics [MB] #####")
//Print used memory
System.out.println("Used Memory:"
+ (runtime.totalMemory() - runtime.freeMemory()) / mb);
//Print free memory
System.out.println("Free Memory:"
+ runtime.freeMemory() / mb);
//Print total available memory
System.out.println("Total Memory:" + runtime.totalMemory() / mb)
//Print Maximum available memory
System.out.println("Max Memory:" + runtime.maxMemory() / mb);
}
}
// 测试类
@SpringBootTest // 见 Tips 1
public class TestLazy {
@Autowired
LazyInitConfig lazyInitConfig;
@Test
public void testLazy() {
lazyInitConfig.printInfo();
}
}
去掉 @Lazy 注解后在应用启动时就会直接调用构造函数创建对象
@Lazy 标记的 Bean 只要使用到就会被实例化。 如果在 BeanA 中注入了 LazyBean 那么在启动的时候仍然会直接实例化,因为在 BeanA 加载的时候,依赖于 LazyBean 所以 LazyBean 就会被实例化。
@RestController
public class TestController {
@Autowired
LazyInitConfig lazyInitConfig;
@RequestMapping("/lazy")
public void getLazy() {
lazyInitConfig.printInfo();
}
}
当我们在依赖代码上加上 @Lazy 注解启动的时候就不会去初始化 Bean 而是创建一个代理对象
@RestController
public class TestController {
@Lazy
@Autowired
LazyInitConfig lazyInitConfig;
@RequestMapping("/lazy")
public void getLazy() {
lazyInitConfig.printInfo();
}
}
Tips
1. @SpringBootTest 注解在 Java 目录下无法使用
实际测试的时候我是在 Java 目录下创建的 Test 类。
结果发现还需要额外引入依赖,但是平时创建测试类都是不需要引入包的,原因在于默认的 pom.xml 文件中引入的 test 启动器默认的 scope 是 test 所以只能在 test 中使用。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!-- <scope>test</scope>-->
</dependency>
注释代码后就可以直接在 Java 目录下使用。