【SpringBoot】【@Value】 SpringBoot 解析@Value注解型解析注入时机以及原理
1 前言
本节我们主要是讲讲@Value的使用以及它是什么时候解析的并且解析后是如何注入值的呢?我们来看看。
2 @Value的使用
@Value 注解可以用来将外部的值动态注入到 Bean 中,在 @Value 注解中,可以使${} 与 #{} ,它们的区别如下:
(1)@Value("${}"):可以获取对应属性文件中定义的属性值。
(2)@Value("#{}"):表示 SpEl 表达式通常用来获取 bean 的属性,或者调用 bean 的某个方法。
根据注入的内容来源,@ Value属性注入功能可以分为两种:通过配置文件进行属性注入和通过非配置文件进行属性注入。
2.1 基于配置文件的注入
2.1.1 单个注入
# application.properties
user.nick = kuku
@RestController public class DemoController { @Value("${user.nick:如果需要默认值格式(:默认值)}") private String name; }
注意点:当文件类型是 xx.properties是如果存在中文的话,比如:
就会出现乱码,这是因为在SpringBoot的CharacterReader类中,默认的编码格式是ISO-8859-1,该类负责.properties文件中系统属性的读取。如果系统属性包含中文字符,就会出现乱码。
解决的方法大概有三种:
(1)改为yml、yaml格式的 (可参考)
(2)配置文件里中文转义掉(一般我们用这个) http://www.jsons.cn/unicode/
(3)自己使用的时候手动转换(这个麻烦)
这里说下yml为什么可以,因为.yml或.yaml格式的配置文件,最终会使用UnicodeReader类进行解析,它的init方法中,首先读取BOM文件头信息,如果头信息中有UTF8、UTF16BE、UTF16LE,就采用对应的编码,如果没有,则采用默认UTF8编码。
2.1.2 静态变量注入
默认被static修饰的变量通过@Value会注入失败,我们注解可以写到方法上:
private static String name; @Value("${user.nick:如果需要默认值格式(:默认值)}") public void setName(String name) { this.name = name; }
2.2 非配置文件的注入
2.2.1 基本类型
@Value注解对这8中基本类型和相应的包装类,有非常良好的支持,例如:
@Value("${user.test:3000}") private Integer num; @Value("${user.test:3000}") private int num;
2.2.2 数组
但只用上面的基本类型是不够的,特别是很多需要批量处理数据的场景中。这时候可以使用数组,它在日常开发中使用的频率很高。我们在定义数组时可以这样写:
@Value("${kuku.test:1,2,3,4,5}") private int[] array;
spring默认使用逗号分隔参数值。如果用空格分隔,例如:@Value("${kuku.test:1 2 3 4 5}") spring会自动把空格去掉,导致数据中只有一个值:12345,所以注意千万别搞错了。
如果我们把数组定义成:short、int、long、char、string类型,spring是可以正常注入属性值的。
但如果把数组定义成:float、double类型,启动项目时就会直接报错。如果使用int的包装类Integer[],比如:
@Value("${user.test:1,2,3,4,5}") private Integer[] array;
启动项目时同样会报异常。此外,定义数组时一定要注意属性值的类型,必须完全一致才可以,如果出现下面这种情况:
@Value("${user.test:1.0,abc,3,4,5}") private int[] array;
属性值中包含了1.0和abc,显然都无法将该字符串转换成int。
2.2.3 集合类
我们看看List是如何注入属性值的:
user.test = 10,11,12,13 @Value("${user.test}") private List<Integer> test;
2.2.4 其它
注入bean,一般都是用的@Autowired或者@Resource注解,@Value注解也可以注入bean,它是这么做的:
@Value("#{roleService}") private RoleService roleService;
通过EL表达式,@Value注解已经可以注入bean了。既然能够拿到bean实例,接下来,可以再进一步,获取成员变量、常量、方法、静态方法:
@Value("#{roleService.DEFAULT_AGE}") private int myAge;
前面的内容都是基于bean的,但有时我们需要调用静态类,比如:Math、xxxUtil等静态工具类的方法,可以这么写:
// 注入系统的路径分隔符到path中 @Value("#{T(java.io.File).separator}") private String path; // 注入一个随机数到randomValue中 @Value("#{T(java.lang.Math).random()}") private double randomValue;
还可以进行逻辑运算:
// 拼接 @Value("#{roleService.roleName + '' + roleService.DEFAULT_AGE}") private String value; // 逻辑判断 @Value("#{roleService.DEFAULT_AGE > 16 and roleService.roleName.equals('苏三')}") private String operation;
上面这么多@Value的用法,归根揭底就是${}和#{}的用法,我们来看看两者的区别:
- ${}:主要用于获取配置文件中的系统属性值,可以设置默认值。如果在配置文件中找不到属性的配置,则注入时用默认值,如果都没有会直接报错
- #{}:主要用于通过spring的EL表达式,获取bean的属性,或者调用bean的某个方法,还有调用类的静态常量和静态方法,如果是调用类的静态方法,则需要加T(包名 + 方法名称)。
3 AutowiredAnnotationBeanPostProcessor 类介绍
首先解析的都是我们的Spring管理的Bean,我们的Bean又有配置型Configuration、服务型Controller、Service等的,但他们都是@Component的,那解析@Value的时候是什么时候呢,其实就是创建Bean的时候,也就是实例化的时候,而实例化又分懒加载的和随着SpringBoot启动就会创建的在刷新方法里的 finishBeanFactoryInitialization 会对不是懒加载的Bean进行实例化,这就涉及到Bean的生命周期啦,其实解析和属性注入都是通过后置处理器进行的。
解析:doCreateBean 方法里的 applyMergedBeanDefinitionPostProcessors执行后置处理器进行收集,实际收集的处理器是:AutowiredAnnotationBeanPostProcessor
注入:populateBean 方法里的 postProcessProperties 执行后置处理器进行注入,实际注入的处理器还是:AutowiredAnnotationBeanPostProcessor
我们先看下 AutowiredAnnotationBeanPostProcessor类图:
那你们知道这个后置处理是什么时候加载进来的么?我们来看下:
4 @Value 解析
我们先看下 AutowiredAnnotationBeanPostProcessor 构造方法:
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4); public AutowiredAnnotationBeanPostProcessor() { this.autowiredAnnotationTypes.add(Autowired.class); this.autowiredAnnotationTypes.add(Value.class); try { this.autowiredAnnotationTypes.add((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader())); logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring"); } catch (ClassNotFoundException ex) { // JSR-330 API not available - simply skip. } }
可以看到实例化的时候,已经把 @Autowired和@Value初始化到 autowiredAnnotationTypes 集合中了。
我们先看下解析的方法:
@Override public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) { // 解析注解信息并缓存,注入的时候直接拿 InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null); // 将已经处理过的注入点缓存到 bd.externallyManagedConfigMembers 中,下次再处理时不会处理已经缓存的注入点 metadata.checkConfigMembers(beanDefinition); }
主要方法就是 findAutowiringMetadata,我们进去看一下:
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) { // Fall back to class name as cache key, for backwards compatibility with custom callers. String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName()); // 先从缓存中拿 双重检查 Quick check on the concurrent map first, with minimal locking. InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey); if (InjectionMetadata.needsRefresh(metadata, clazz)) { synchronized (this.injectionMetadataCache) { metadata = this.injectionMetadataCache.get(cacheKey); if (InjectionMetadata.needsRefresh(metadata, clazz)) { if (metadata != null) { metadata.clear(pvs); } // 解析构建 如果解析不到内容会返回一个内部的 InjectionMetadata.EMPTY 空对象 metadata = buildAutowiringMetadata(clazz); // 放进缓存 this.injectionMetadataCache.put(cacheKey, metadata); } } } return metadata; }
核心方法就是 buildAutowiringMetadata,进行分析我们进去看看:
private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) { /** * 如果没有 Autowired Value 注解信息就返回 EMPTY * this.autowiredAnnotationTypes.add(Autowired.class); * this.autowiredAnnotationTypes.add(Value.class); */ if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) { return InjectionMetadata.EMPTY; } List<InjectionMetadata.InjectedElement> elements = new ArrayList<>(); Class<?> targetClass = clazz; do { final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>(); // 遍历Class中的所有field,根据注解判断每个field是否需要被注入 ReflectionUtils.doWithLocalFields(targetClass, field -> { // 看看field是不是有注解@Autowired 或 @Value MergedAnnotation<?> ann = findAutowiredAnnotation(field); if (ann != null) { // 不支持静态类 if (Modifier.isStatic(field.getModifiers())) { if (logger.isInfoEnabled()) { logger.info("Autowired annotation is not supported on static fields: " + field); } return; } // 确定带注解的字段是否存在required并且是true 默认是true boolean required = determineRequiredStatus(ann); // AutowiredFieldElement 对象包装一下 currElements.add(new AutowiredFieldElement(field, required)); } }); // 遍历Class中的所有method,根据注解判断每个method是否需要注入 ReflectionUtils.doWithLocalMethods(targetClass, method -> { // 桥接方法 什么是桥接方法 大概查了查跟泛型方法有关系 // 我猜的哈 比如某一个泛型方法 没有具体的实现的话 不知道注入何种类型 就会略过吧 知道的还请告知哈 Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) { return; } // 看看方法是不是有注解@Autowired 或 @Value MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod); if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) { // 静态方法略过 if (Modifier.isStatic(method.getModifiers())) { if (logger.isInfoEnabled()) { logger.info("Autowired annotation is not supported on static methods: " + method); } return; } // 参数为空的方法略过 if (method.getParameterCount() == 0) { if (logger.isInfoEnabled()) { logger.info("Autowired annotation should only be used on methods with parameters: " + method); } } // 判断是不是有 required boolean required = determineRequiredStatus(ann); // 获取目标class中某成员拥有读或写方法与桥接方法一致的PropertyDescriptor PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz); // AutowiredMethodElement 对象包装一下 currElements.add(new AutowiredMethodElement(method, required, pd)); } }); elements.addAll(0, currElements); // 递归调用 targetClass = targetClass.getSuperclass(); } while (targetClass != null && targetClass != Object.class); // 包装成 InjectionMetadata 对象 targetClass属性就是当前的类 injectedElements属性就是分析的字段或者方法 return InjectionMetadata.forElements(elements, clazz); }
可以看到会对类的方法的属性进行遍历以及父亲的递归,对于字段会忽略掉static修饰的,对于方法会也会忽略掉static以及参数为空的。最后解析到的属性会包装成 AutowiredFieldElement ,方法会包装成 AutowiredMethodElement ,最后统一放进集合中,包装成 InjectionMetadata 对象返回,并放进缓存。
我们拿个例子:
@Value("${user.list}") private List<Integer> list; private static String name; @Value("${user.kuku}") public void setName(String name) { this.name = name; }
5 @Value 注入
我们这里针对上述的例子进行注入哈,我们看看:
@Override public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { // 从上一步解析的缓存中直接获取 InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); try { // 进行注入 metadata.inject(bean, beanName, pvs); } catch (BeanCreationException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex); } return pvs; }
// 注入 public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Collection<InjectedElement> checkedElements = this.checkedElements; Collection<InjectedElement> elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements); if (!elementsToIterate.isEmpty()) { for (InjectedElement element : elementsToIterate) { // 调用对象对应的方法进行注入 属性的就是 AutowiredFieldElement 方法的就是 AutowiredMethodElement element.inject(target, beanName, pvs); } } }
可以看到最后 element.inject 就是在解析阶段调用对应的注入方法进行注入。
5.1 AutowiredFieldElement # inject 属性注入
@Override protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Field field = (Field) this.member; Object value; // 默认是 false 跳过 if (this.cached) { try { value = resolvedCachedArgument(beanName, this.cachedFieldValue); } catch (NoSuchBeanDefinitionException ex) { // Unexpected removal of target bean for cached argument -> re-resolve value = resolveFieldValue(field, bean, beanName); } } else { // 解析获取属性值 value = resolveFieldValue(field, bean, beanName); } // 不为空的就通过反射设置进去值 if (value != null) { ReflectionUtils.makeAccessible(field); field.set(bean, value); } }
@Nullable private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) { // 构造 DependencyDescriptor 依赖描述 DependencyDescriptor desc = new DependencyDescriptor(field, this.required); desc.setContainingClass(bean.getClass()); Set<String> autowiredBeanNames = new LinkedHashSet<>(1); Assert.state(beanFactory != null, "No BeanFactory available"); // 获取 TypeConverter 类型转换器 TypeConverter typeConverter = beanFactory.getTypeConverter(); Object value; try { // 委托给 beanFactory.resolveDependency 方法获取 value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); } catch (BeansException ex) { throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); } ... }
@Override @Nullable public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { // 参数名称解析器 descriptor.initParameterNameDiscovery(getParameterNameDiscoverer()); // 此处是针对不同的类型对返回值进行处理,但核心逻辑依然到 doResolveDependency 方法 if (Optional.class == descriptor.getDependencyType()) { return createOptionalDependency(descriptor, requestingBeanName); } else if (ObjectFactory.class == descriptor.getDependencyType() || ObjectProvider.class == descriptor.getDependencyType()) { return new DependencyObjectProvider(descriptor, requestingBeanName); } else if (javaxInjectProviderClass == descriptor.getDependencyType()) { return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName); } // 此分支才是我们最常用的,核心逻辑在 doResolveDependency else { Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( descriptor, requestingBeanName); if (result == null) { result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; } }
@Nullable public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor); try { // ShortcutDependencyDescriptor 处理,即缓存处理 Object shortcut = descriptor.resolveShortcut(this); if (shortcut != null) { return shortcut; } Class<?> type = descriptor.getDependencyType(); // 如果是 @Value 注解元素,则获取 value 值 Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value != null) { if (value instanceof String) { // 占位符解析 String strVal = resolveEmbeddedValue((String) value); BeanDefinition bd = (beanName != null && containsBean(beanName) ? getMergedBeanDefinition(beanName) : null); // SpEL 解析 value = evaluateBeanDefinitionString(strVal, bd); } // 类型转换后返回 TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); try { return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor()); } catch (UnsupportedOperationException ex) { // A custom TypeConverter which does not support TypeDescriptor resolution... return (descriptor.getField() != null ? converter.convertIfNecessary(value, type, descriptor.getField()) : converter.convertIfNecessary(value, type, descriptor.getMethodParameter())); } }
}
...
}
关键的方法:resolveEmbeddedValue,由于方法嵌套调用过多哈,我们这里直接就不深究了哈:
5.2 AutowiredMethodElement# inject 方法注入
针对方法实际就是调用的AutowiredMethodElement 的注入方法,这里就不看了哈,(主要是内容太多还涉及到依赖很多细节)我们看看调试:
6 小结
本节我们大概看了下@Value注解的解析和注入过程,对于注入比较复杂,要考虑依赖类型转换等,暂时先放着哈,后续有空了再看哈,有理解不对的地方欢迎指正哈。