The reason

@value is one of the most frequently used annotations in development. It assigns the Value of key in the configuration file to the attribute it annotates. For common knowledge points, we should understand the nature of its function implementation. It is very helpful for our own technical improvement.

The principle of

In spring is made up of AutowiredAnnotationBeanPostProcessor analytical processing @ Value annotation. Is a BeanPostProcessor AutowiredAnnotationBeanPostProcessor, so each instantiation of a class through AutowiredAnnotationBeanPostProcessor class. When the post-processor processes the bean, it will parse all the attributes of the bean Class. During parsing, it will determine whether the attribute is marked with the @value annotation. If so, it will parse the @value attribute Value. The parsed results into AutowiredFieldElement type InjectionMetaData. CheckedElements, when they have been used to attribute assignment checkedElements, @ the Filed of Value annotation attributes is obtained, Call AutowiredFieldElement. Inject parse () method, analytic will use the DefaultListableBeanFactory (${}) used to parse and TypeConverter (for type conversion), so as to get the age attribute’s value, Finally, field-set (bean, value) is called, which assigns the value to the bean’s field.

This is the whole process, a bit abstract, let’s debug source code, a more clear and concise understanding of @value principle.

The DEBUG source code is the best way I find to understand how it works

Note: Although this article deals with the @Value annotation, there are many areas that are equally applicable to other parsing. For example, parsing ${} is universal. ${} can appear on an object dependent property (as in this case) or on a bean variable (as in the @ConfigurationProperties bean variable)

To prepare

  1. A runnable Spring Boot Project
  2. Provide a Controller class that contains a property annotated by @Value:int age
@RestController
@Slf4j
public class MyController {

    @Value("${user.age:11}")
    private int age;
  
    public int getAge(a){
        log.info("age:{}", age)
        returnage; }}Copy the code

Debug the mystery together

The entrance

Core: save. Class annotation @ the Value of all information archived InjectionMetadata InjectedElement collection

@ the Value as the @autowired, is made up of AutowiredAnnotationBeanPostProcessor analytical processing, Processing @ the entrance of the Value of AutowiredAnnotationBeanPostProcessor. BuildAutowiringMetadata (Class clazz). Look inside the code

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<>(); . / / all attributes of the analytic targetClass ReflectionUtils doWithLocalFields (targetClass, field -> { AnnotationAttributes ann = findAutowiredAnnotation(field); if (ann ! 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. The equals (ClassUtils getMostSpecificMethod (method, clazz))) {/ / therefore, 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); }Copy the code

This method is used to traverse and analyze all filed and the method of clazz, analytical its Value, the @autowired, @ @ Inject annotation, then into the type of InjectionMetadata. InjectedElement elements, Elements =new InjectionMetadata(clazz, elements) The metadata is then placed in the cache injectionMetadataCache, which is later evaluated from the cache

Next, we focus on the attribute parsing process, the code is as follows

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; }Copy the code

AutowiredAnnotationTypes contain @Value, @AutoWired, and @Inject. If the property resolves to the response annotation, the annotation information is returned to the upper layer. The parsing annotation process is not detailed here

parsing

Core: Use. Use InjectionMetadata InjectedElement, parsing and assign a value to the clazz attribute, namely the assignment MyController. Age

The above logic mainly for parsing @ the Value of information in InjectionMetadata. InjectedElement set, the following logic to use InjectionMetadata. InjectedElement, resolve the real Value, thus assigned to the attribute. Now how do I parse and assign

. First of all, we by getting InjectionMetadata InjectedElement object data, in fact is to obtain the injectionMetadataCache cache. The location code obtained is as follows

AutowiredAnnotationBeanPostProcessor class public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {// Get, Metadata = findAutowiringMetadata(beanName, bean.getClass(), PVS); Metadata. inject(bean, beanName, PVS); return pvs; }Copy the code

Metadata.inject (bean, beanName, PVS). Inject (bean, beanName, PVS). Look inside the code

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); }}}Copy the code

The method logic is simple, iterating over the collection calls the Inject () method, respectively. Look at the internal code logic

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); / / get type converter TypeConverter TypeConverter. = the beanFactory getTypeConverter (); / / core logic: analytical field annotations value = the beanFactory. ResolveDependency (desc, beanName autowiredBeanNames, typeConverter); } if (value! = null) { ReflectionUtils.makeAccessible(field); field.set(bean, value); }}Copy the code

The core of the method is to wrap the field in DependencyDescriptor and use the beanFactory to parse the attribute value in DependencyDescriptor. See below its the beanFactory. ResolveDependency () internal code. (Note: beanFactory is injected by BeanFactoryAware, we can learn how to use it.)

DefaultListableBeanFactory class public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) { descriptor.initParameterNameDiscovery(getParameterNameDiscoverer()); // Handle fields of Optional type, independent of @value, Temporary slightly if (Optional) class = = descriptor) getDependencyType ()) {return createOptionalDependency (descriptor, requestingBeanName); } // Handle fields of type ObjectFactory and ObjectProvider, independent of @value, Temporary a little else if (the ObjectFactory class = = descriptor. GetDependencyType () | | ObjectProvider. Class = = descriptor.getDependencyType()) { return new DependencyObjectProvider(descriptor, requestingBeanName); }else {// core: Result = doResolveDependency(Descriptor, requestingBeanName, autowiredBeanNames, typeConverter); return result; }}Copy the code

In and you can see from the method, the type converter TypeConverter already coming, need to use TypeConverter it transformation, when the type conversion from the BeanFactory TypeConverter. GetTypeConverter () to get to.

DoResolveDependency () is the big steward of the @Value annotation, which is not responsible for parsing, but describes the process. Look at the doResolveDependency() method code logic

DefaultListableBeanFactory class public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) { InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor); Try {// The type of the field attribute Class<? > type = descriptor.getDependencyType(); // 1. Get (not just) the Value of the @value annotation's Value method. ${test.age:11} (1) Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor); if (value ! = null) {// If value is a String, go here. In this example, the value is ${test.age:11} String if (value instanceof String) {// 2. 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. Begin to parse the value, the value of the return the converter. The convertIfNecessary (value, type, descriptor. GetTypeDescriptor ()); } catch (UnsupportedOperationException ex) { ... }}... Omit the parsing the @autowired annotation logic} finally {ConstructorResolver. SetCurrentInjectionPoint (previousInjectionPoint); }}Copy the code

This method is the core method for parsing the @Value attribute Value. Logic is divided into four steps:

  1. Get (not just)@ValueThe value of the annotated value method. throughdescriptorParse out the annotationsValue methodThe value of the
  2. Start parsing the value of value
  3. If the value is String, the value will be parsed specially, which means if the value is ${test.age:11}, will resolve the value as:"11", this parsing process usesPropertySourcesPlaceholderConfigurer.processProperties()methods

It’s a little abstract. Let me give you an example. Below, we define a variable with an @value annotation

@Value("${user.age:11}") private int age; ${user.age:11}; ${user.age:11}; ${user.age:11}; The resulting value is of type String and needs to be converted to the declared type of the target variable, which in this case is intCopy the code

one step

Take a look at the first step in detail. Gets the Value of the Value () method of @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; }Copy the code

The above two methods mainly parse @ Value annotation, then through AnnotatedElementUtils. GetMergedAnnotationAttributes () method to get annotations the collection of attributes, and to obtain the Value the Value of () method

two step

Parse the passed string of the form ${key:defaultValue} to get the actual value of the key.

public String resolveEmbeddedValue(@Nullable String value) { String result = value; for (StringValueResolver resolver : this.embeddedValueResolvers) { result = resolver.resolveStringValue(result); return result; }}Copy the code

Core method for resolver. ResolveStringValue (result) method, resolver for actual StringValueResolver type of lambda expressions, The expressions defined in the PropertySourcesPlaceHolderConfigurer. ProcessProperties () method, the debug here is need to pay attention to, if you don’t know lambda, may be more hazy. The following code

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);
}
Copy the code

Lambda will mess with your call stack display, the screenshot below shows the actual call stack information

ProcessProperties method in the above code will invoke PropertySourcesPropertyResolver. ResolveRequiredPlaceholders () method to resolve the participation, And it will call PropertySourcesPropertyResolver. GetPropertyAsRawString () and PropertyPlaceholderHelper replacePlaceholders (). The following code

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);
}
Copy the code

Logic PropertySourcesPropertyResolver. GetPropertyAsRawString () method

PropertySourcesPropertyResolver. GetPropertyAsRawString () is responsible for obtaining the key value, because PropertySourcesPropertyResolver. Hold propertySources variables, the variables and the Environment propertySources is synchronous, so propertySource getProperty (key) can get the values in the configuration file, the code is as follows

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; }Copy the code

As you can see, the last propertySource. GetProperty get value is returned (key).

Logic PropertyPlaceholderHelper. ReplacePlaceholders () method

PropertyPlaceholderHelper. ReplacePlaceholders () is responsible for parsing the ${key: defaultValue}, it apart in order to get the key, reoccupy propertySource. GetProperty (key) get the value. The unpacking logic is in the parseStringValue() method, which looks like this

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} ${xx:yy} Placeholder = placeholder (placeholder, placeholder resolver, visitedplaceholder); // getplaceholder is the key, Final call propertySource. GetProperty (key) get the value String propVal = placeholderResolver. ResolvePlaceholder (placeholder); . } return result.tostring (); }Copy the code

Can see the key a dismantling a layer to get a new key, try to call placeholderResolver. ResolvePlaceholder (key) for value, if there is no access to, to disassemble a layer to get a new key, Call again placeholderResolver. ResolvePlaceholder (key) get the value of cycle, until get to get not disassemble process (because the key can be in the form of “${xx: ${yy, zz}}”). The resulting value is of type String. Is not the type we assigned to the variable, so the conversion is next.

Three step

Converts a String value to the declared type of the target variable.

. We went back to DefaultListableBeanFactory doResolveDependency () method, the code to the converter. The convertIfNecessary method, namely type conversion. Look at the method code

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 contains all converters, The following figure ConversionService ConversionService = this. PropertyEditorRegistry. GetConversionService (); TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue); if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) { return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor); }}Copy the code

Method first finds a converter that can convert (string -> int), and then begins the conversion. How to find a converter that can convert, I won’t talk about it here, but follow it yourself. The core of the starting conversion is to determine the corresponding xxxtoyyyConverter, which internally calls the essential utility class. For example, the utility class is numberUtils.parsenumber (source, this.targetType). This utility class method is familiar and familiar, so much of Spring’s functionality ends up calling the most basic Java utility class. XxxtoyyyConverter is a large number, as shown below

This is the process of assigning a Value to the @value modified variable, from parsing the key of the annotated Value () method, to parsing the key into a new key, to the configuration file fetching the Value of the key(new key), and finally casting the Value. Finally, field. Set (bean, value) is assigned to the target variable

The call stack for the entire parsing process is listed below

Global call stack

AbstractAutowireCapableBeanFactory. CreateBean () ├ AbstractAutowireCapableBeanFactory. DoCreateBean () ├ ─ AbstractAutowireCapableBeanFactory. ApplyMergedBeanDefinitionPostProcessors () ├ ─ ─ AutowiredAnnotationBeanPostProcessor. PostProcessMergedBeanDefinition () ├ ─ ─ ─ AutowiredAnnotationBeanPostProcessor. FindAutowiringMetadata () ├ ─ ─ ─ ─ AutowiredAnnotationBeanPostProcessor. BuildAutowiringMetadata () = = = = = = = = that is logical, Here is to use logic = = = = = = = = > "deposit" means to need to parse the data into the InjectionMetadata > in the "use" means to in InjectionMetadata data parsing ├ ─ AbstractAutowireCapableBeanFactory. PopulateBean () ├ ─ ─ AutowiredAnnotationBeanPostProcessor. PostProcessProperties () ├ ─ ─ ─ AutowiredAnnotationBeanPostProcessor. FindAutowiringMetadata () ├ ─ ─ ─ ─ InjectionMetadata. Inject () ├ ─ ─ ─ ─ ─ (InjectionMetadata. InjectedElement) AutowiredFieldElement. Inject () ├ ─ ─ ─ ─ ─ ─ DefaultListableBeanFactory. ResolveDependency () ├ ─ ─ ─ ─ ─ ─ ─ DefaultListableBeanFactory. DoResolveDependency () - analytical dependence on @ value (${}) at the core of the entrance ├ ─ ─ ─ ─ ─ ─ ─ ─ AbstractBeanFactory. ResolveEmbeddedValue () - parse all use ${} put at the core of the entrance ├── StringValueResolver lambda subclass. resolveStringValue() - This lambda use way worth pondering ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ PropertySourcesPropertyResolver. ResolveRequiredPlaceholders () ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ AbstractPropertyResolver. GetPropertyAsRawString () ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ PropertyPlaceholderHelper. ReplacePlaceholders () ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ PropertySource. GetProperty () ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ PropertyPlaceholderHelper. ParseStringValue () recursive ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ AbstractPropertyResolver. ResolvePlaceholder () ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ PropertySource. GetProperty () ├ ─ ─ ─ ─ ─ ─ ─ SimpleTypeConverter. ConvertIfNecessary () ├ ─ ─ ─ ─ ─ ─ ─ ─ TypeConverterDelegate. ConvertIfNecessarT () ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ConversionService. CanConvert () ├ ─ ─ ─ ─ ─ ─ ─ ─ ─ ConversionService. Convert ()Copy the code

Original (yes it is original) address: details know how much – spring @value annotation parsing