причина
@Value — одна из наиболее часто используемых аннотаций в процессе разработки, ее функция заключается в присвоении значения, соответствующего ключу в файле конфигурации, атрибуту, который она аннотирует. Для общеизвестных моментов мы должны понимать суть реализации его функции. Это очень полезно для нашего собственного технического совершенствования.
принцип
Весной наступаетAutowiredAnnotationBeanPostProcessor
Анализ обработки аннотаций @Value.AutowiredAnnotationBeanPostProcessor
ЯвляетсяBeanPostProcessor
, так что каждый экземпляр класса проходит черезAutowiredAnnotationBeanPostProcessor
своего рода. когдаpost-processor
При обработке bean-компонентов будут проанализированы все свойства класса bean-компонента, и будет определено, отмечены ли свойства@Value
Аннотация, если есть, разберите это@Value
Значение атрибута , поместите проанализированный результат вAutowiredFieldElement
типInjectionMetaData.checkedElements
, который используется при присвоении значения свойствуcheckedElements
, чтобы получить@Value
аннотированныйFiled
собственность, звонитеAutowiredFieldElement.inject()
метод парсинга, который будет использоваться при парсингеDefaultListableBeanFactory
(для разбора ${}) иTypeConverter
(для преобразования типов), в результате чегоage
Стоимость недвижимости, последний звонокfield.set(bean, value)
, так что полученное значение присваиваетсяbean的field
.
Весь процесс такой, он немного абстрактный, давайте вместе отлаживать исходный код, чтобы понять принцип @Value более четко и лаконично.
Отладка исходного кода — лучший способ понять его принципы.
Примечание. Хотя эта статья представляет собой анализ@Value
Аннотация, но многие места подходят и для другого разбора. Если да${}
Этот анализ распространен.${}
Может появляться в объектно-зависимом свойстве (как в этом примере) или в переменной компонента (как в@ConfigurationProperties
бобовая переменная)
Подготовить
- Рабочий весенний загрузочный проект
- Предоставьте классу контроллера свойство с аннотацией @Value:
int age
@RestController
@Slf4j
public class MyController {
@Value("${user.age:11}")
private int age;
public int getAge(){
log.info("age:{}", age)
return age;
}
}
Отлаживайте вместе, чтобы разгадать тайну
Вход
Ядро: сохранить. Выгрузите всю информацию аннотации класса @Value в коллекцию InjectionMetadata.InjectedElement.
@Value
такой же@Autowired
то же, поAutowiredAnnotationBeanPostProcessor
разбор, обработка@Value
Вход естьAutowiredAnnotationBeanPostProcessor.buildAutowiringMetadata(Class clazz)
. загляни внутрь кода
AutowiredAnnotationBeanPostProcessor class
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
// 解析targetClass的所有属性
ReflectionUtils.doWithLocalFields(targetClass, field -> {
AnnotationAttributes ann = findAutowiredAnnotation(field);
if (ann != null) {
// 由此可知,static修饰的属性无法使用@Value注解赋值
if (Modifier.isStatic(field.getModifiers())) {
return;
}
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
});
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
// 由此可知,static修改的方法无法使用@Value注解赋值
if (Modifier.isStatic(method.getModifiers())) {
return;
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return new InjectionMetadata(clazz, elements);
}
Этот метод используется для обхода и анализа всех файлов и методов clazz, а также для анализа вышеперечисленных@Value
,@Autowired
,@Inject
аннотацию, затем поместите тип какInjectionMetadata.InjectedElement
изelements
середина,elements
вставить сноваmetadata(=new InjectionMetadata(clazz, elements))
середина. сноваmetadata
положить в кешinjectionMetadataCache
, значение будет извлечено из кеша позже
Далее мы сосредоточимся на процессе разбора атрибутов, код выглядит следующим образом
AutowiredAnnotationBeanPostProcessor class
private AnnotationAttributes findAutowiredAnnotation(AccessibleObject ao) {
if (ao.getAnnotations().length > 0) { // autowiring annotations have to be local
for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ao, type);
if (attributes != null) {
return attributes;
}
}
}
return null;
}
autowiredAnnotationTypes
содержит@Value
,@Autowired
,@Inject
. Если атрибут разрешается в аннотацию ответа, информация аннотации возвращается на верхний уровень. Подробно процесс разбора аннотаций здесь не описан
Разобрать
Core: используется. Используйте Injection Methentadata. ИНВЕЖДЕНИЕ, чтобы разрешить и назначить ценность атрибуту CLAZS, то есть назначение MyController.age.age.age
Вышеприведенная логика в основном предназначена для анализа информации @Value и ее сохранения.InjectionMetadata.InjectedElement
Коллекция, используется следующая логикаInjectionMetadata.InjectedElement
, который анализирует реальное значение и присваивает его свойству. Давайте посмотрим, как анализировать и присваивать значения
Во-первых, мы получаемInjectionMetadata.InjectedElement
Данные объекта, фактически изinjectionMetadataCache
полученный из кэша. Полученный код местоположения выглядит следующим образом
AutowiredAnnotationBeanPostProcessor class
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 获取,从injectionMetadataCache缓存获取
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
// 使用
metadata.inject(bean, beanName, pvs);
return pvs;
}
Мы сосредоточимся на части использования, а именноmetadata.inject(bean, beanName, pvs)
, вы можете увидеть имя метода, вам нужно ввести значение свойства. загляни внутрь кода
InjectionMetadata class
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) {
if (!checkedElements.isEmpty()) {
for (InjectedElement element : checkedElements) {
element.inject(target, beanName, pvs);
}
}
}
Логика метода очень проста, проходите по коллекции и вызываете ее отдельноinject()
метод. Посмотрите на его внутреннюю логику кода
InjectionMetadata.InjectedElement class
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) {
Field field = (Field) this.member;
Object value;
if (this.cached) {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
else {
DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
desc.setContainingClass(bean.getClass());
Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
// 获取类型转换器
TypeConverter typeConverter = beanFactory.getTypeConverter();
// 核心逻辑:解析field注解
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
// ··· 值缓存起来 略
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
Суть метода заключается в использованииDependencyDescriptor
Чтобы обернуть поле, используйтеbeanFactory
РазобратьDependencyDescriptor
从而得到属性值。 Смотри нижеbeanFactory.resolveDependency()
внутренний код. (Примечание:beanFactory
черезBeanFactoryAware
введено, мы можем изучить это использование)
DefaultListableBeanFactory class
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
// 处理Optional类型的field,与@Value无关,暂略
if (Optional.class == descriptor.getDependencyType()) {
return createOptionalDependency(descriptor, requestingBeanName);
}
// 处理ObjectFactory和ObjectProvider类型的field,与@Value无关,暂略
else if (ObjectFactory.class == descriptor.getDependencyType() ||
ObjectProvider.class == descriptor.getDependencyType()) {
return new DependencyObjectProvider(descriptor, requestingBeanName);
}else {
// 核心: 真正解析field的方法
result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
return result;
}
}
Как видно из параметров метода, этот преобразователь типаTypeConverter
Он был передан и используется, когда требуется преобразование типов.TypeConverter
Это преобразование,TypeConverter
ОтBeanFactory.getTypeConverter()
полученный.
doResolveDependency() — это большая экономка для разбора аннотаций @Value.Он не отвечает за конкретный разбор, но объясняет весь процесс разбора. СмотретьdoResolveDependency()
логика кода метода
DefaultListableBeanFactory class
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
// field属性的类型
Class<?> type = descriptor.getDependencyType();
// 1. 获取(不只是)@Value注解的value方法的值 如此例中value方法值为: ${test.age:11} (1)
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
// 如果value是String类型,走这里。此例value为: ${test.age:11}字符串
if (value instanceof String) {
// 2. 开始解析value的值
String strVal = resolveEmbeddedValue((String) value);
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
// 3. 开始解析value的值
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
...
}
}
... 省略解析@Autowired注解的逻辑
}finally {
ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
}
}
Этот метод анализирует@Value
Основной метод аннотирования значений свойств. Логика разделена на четыре шага:
- Получить (не только)
@Value
Значение метода значения аннотации. пройти черезdescriptor
разобрал аннотацииvalue方法
значение - Начать разбор значения value
- Если значение имеет тип String, значение будет проанализировано специальным образом, что означает, что если значение
${test.age:11}
, который анализирует значение как:"11"
, этот процесс анализа используетPropertySourcesPlaceholderConfigurer.processProperties()
метод
Это немного абстрактно, давайте возьмем пример. Таким образом, определяется переменная, аннотированная с помощью @Value.
@Value("${user.age:11}")
private int age;
第一步:通过descriptor得到${user.age:11}
第二步:拆解${user.age:11},得到user.age:11,获取值,没有获取到,以:或${}为标准再拆解,最后得到值
第三步:此时得到的值是String类型的,需要转换成目标变量声明的类型,此处类型为int
one step
Сначала узнайте больше о следующем шаге. Получать@Value的value()
значение метода
QualifierAnnotationAutowireCandidateResolver class
public Object getSuggestedValue(DependencyDescriptor descriptor) {
Object value = findValue(descriptor.getAnnotations());
if (value == null) {
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
value = findValue(methodParam.getMethodAnnotations());
}
}
return value;
}
protected Object findValue(Annotation[] annotationsToSearch) {
if (annotationsToSearch.length > 0) { // qualifier annotations have to be local
AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(
AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);
if (attr != null) {
return extractValue(attr);
}
}
return null;
}
Вышеуказанные два метода в основном аналитические.@Value
Примечания и черезAnnotatedElementUtils.getMergedAnnotationAttributes()
Метод получает набор атрибутов аннотации, чтобы получитьvalue()
значение метода
two step
Проанализируйте входящую строку в форме ${key:defaultValue}, чтобы получить фактическое значение ключа.
public String resolveEmbeddedValue(@Nullable String value) {
String result = value;
for (StringValueResolver resolver : this.embeddedValueResolvers) {
result = resolver.resolveStringValue(result);
return result;
}
}
Основным методом являетсяresolver.resolveStringValue(result)
метод, резольвер на самом делеStringValueResolver类型的lambda表达式
, это выражение определено вPropertySourcesPlaceHolderConfigurer.processProperties()
方法中,这里debug是需要注意下,如果你不了解lambda,可能会比较朦胧。 код показывает, как показано ниже
PropertySourcesPlaceHolderConfigurer class
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
propertyResolver.setValueSeparator(this.valueSeparator);
StringValueResolver valueResolver = strVal -> {
String resolved = (this.ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
propertyResolver.resolveRequiredPlaceholders(strVal));
if (this.trimValues) {
resolved = resolved.trim();
}
return (resolved.equals(this.nullValue) ? null : resolved);
};
doProcessProperties(beanFactoryToProcess, valueResolver);
}
лямбда будет мешать отображению стека вызовов, следующая информация верна, и на снимках экрана показан стек вызовов.
в приведенном выше кодеprocessProperties
метод вызоветPropertySourcesPropertyResolver.resolveRequiredPlaceholders()
метод для разбора входных параметров, который, в свою очередь, вызываетPropertySourcesPropertyResolver.getPropertyAsRawString()
иPropertyPlaceholderHelper.replacePlaceholders()
. код показывает, как показано ниже
AbstractPropertyResoler class
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
return doResolvePlaceholders(text, this.strictHelper);
}
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
return helper.replacePlaceholders(text, this::getPropertyAsRawString);
}
Логика метода PropertySourcesPropertyResolver.getPropertyAsRawString()
PropertySourcesPropertyResolver.getPropertyAsRawString()
Ответственность за получение стоимости ключа, потому чтоPropertySourcesPropertyResolver.持有propertySources变量
, эта переменная такая же, какEnvironment的propertySources
переменные синхронизированы, поэтомуpropertySource.getProperty(key)
Вы можете получить значение в файле конфигурации, код выглядит следующим образом
PropertySourcesPropertyResolver class
protected String getPropertyAsRawString(String key) {
return getProperty(key, String.class, false);
}
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
return null;
}
Как видите, наконецpropertySource.getProperty(key)
Вернуть значение.
PropertyBoholderHelper.reseacholders () Метод логики
PropertyPlaceholderHelper.replacePlaceholders()
отвечает за разбор${key: defaultValue}
, разберите его, чтобы получить ключ, а затем используйтеpropertySource.getProperty(key)
Получите значение. И конкретная логика демонтажа вparseStringValue()
метод, код выглядит следующим образом
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, null);
}
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, @Nullable Set<String> visitedPlaceholders) {
int startIndex = value.indexOf(this.placeholderPrefix);
if (startIndex == -1) {
return value;
}
StringBuilder result = new StringBuilder(value);
while (startIndex != -1) {
... 略 具体拆解${xx:yy},不展示了,自己来看吧,否则代码太多影响了要主要的逻辑
// 递归调用
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// 获取placeholder实际为key,最终调用propertySource.getProperty(key)获取值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
... 略 同上
}
return result.toString();
}
Вы можете видеть, что этот ключ разбирается по одному слою за раз, чтобы получить новый ключ, попробуйте вызватьplaceholderResolver.resolvePlaceholder(新key)
Получите значение, если нет, разберите еще один слой, чтобы получить новый ключ, а затем вызовитеplaceholderResolver.resolvePlaceholder(新key)
Циклический процесс получения значения до процесса получения значения, которое невозможно разобрать (поскольку форма ключа может быть "${xx:${yy:zz}}"). Полученное в это время значение имеет тип String. Это не тип переменной, которую мы присваиваем, поэтому преобразование типа выполняется дальше.
Three step
Преобразует значение типа String в объявленный тип целевой переменной.
мы возвращаемсяDefaultListableBeanFactory.doResolveDependency()
метод, в этот момент приходит кодconverter.convertIfNecessary
метод, то есть преобразование типов. см. код метода
TypeConverterSupport class
public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
return this.typeConverterDelegate.convertIfNecessary(null, null, value, requiredType, typeDescriptor);
}
TypeConverterDelegate class
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) {
// conversionService包含着所有的转换器,如下图
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
}
Метод сначала находит конвертер, который может конвертировать (string -> int), а затем запускает конвертацию. Как найти конвертер, который можно конвертировать, здесь описываться не будет, просто следуйте сами. Суть начала преобразования заключается в определении соответствующегоxxxtoyyyConverter
, xxxtoyyyConverter
Внутренний вызов является основным классом инструментов. Например, String to int, класс инструмента:NumberUtils.parseNumber(source, this.targetType)
Этот метод класса инструментов более знаком и добрый, поэтому многие весной в конечном итоге вызывают наибольшую природу классов инструментов Java.xxxtoyyyConverter
Это много, как показано ниже
Вышеупомянутое@Value
Весь процесс модифицированного присвоения переменных, начиная с разбора аннотацийvalue()
Ключ значения метода, затем анализировать ключ как новый ключ, затем перейти к файлу конфигурации, чтобы получить значение ключа (новый ключ), и, наконец, выполнить преобразование типа для полученного значения и, наконец, передатьfield.set(bean, value)
присвоить целевой переменной
Стек вызовов всего процесса синтаксического анализа приведен ниже.
Общий стек вызовов
AbstractAutowireCapableBeanFactory.createBean()
├AbstractAutowireCapableBeanFactory.doCreateBean()
├─AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors()
├──AutowiredAnnotationBeanPostProcessor.postProcessMergedBeanDefinition()
├───AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata()
├────AutowiredAnnotationBeanPostProcessor.buildAutowiringMetadata()
========以上是存逻辑,以下是用逻辑========
> "存"指把需要解析的数据放到InjectionMetadata中
> "用"指对放到InjectionMetadata的数据进行解析
├─AbstractAutowireCapableBeanFactory.populateBean()
├──AutowiredAnnotationBeanPostProcessor.postProcessProperties()
├───AutowiredAnnotationBeanPostProcessor.findAutowiringMetadata()
├────InjectionMetadata.inject()
├─────(InjectionMetadata.InjectedElement)AutowiredFieldElement.inject()
├──────DefaultListableBeanFactory.resolveDependency()
├───────DefaultListableBeanFactory.doResolveDependency() ---解析依赖上@value(${})的核心入口
├────────AbstractBeanFactory.resolveEmbeddedValue() ---解析所有用到${}的放的核心入口
├─────────StringValueResolver lambda子类.resolveStringValue() ---这个lambda使用方式值得琢磨下
├──────────PropertySourcesPropertyResolver.resolveRequiredPlaceholders()
├───────────AbstractPropertyResolver.getPropertyAsRawString()
├────────────PropertyPlaceholderHelper.replacePlaceholders()
├─────────────PropertySource.getProperty()
├────────────PropertyPlaceholderHelper.parseStringValue() 递归
├───────────AbstractPropertyResolver.resolvePlaceholder()
├────────────PropertySource.getProperty()
├───────SimpleTypeConverter.convertIfNecessary()
├────────TypeConverterDelegate.convertIfNecessarT()
├─────────ConversionService.canConvert()
├─────────ConversionService.convert()
Оригинальный (да, оригинальный) адрес:Как много вы знаете о деталях - весенний анализ аннотаций @Value