To better illustrate the design principles of nacos-Spring-Project, we will first introduce how system properties and user configuration properties are managed in the Spring framework. This article will start with a common attribute use example in a project, and briefly analyze the process of attribute parsing injection. In the following article, we will analyze how Spring implements global attribute management from the perspective of Environment.

@ Value annotation

@Service
public class Test {

    @Value("${test}")
    private String test;

}
Copy the code

In a project, as shown in the code snippet above, we typically use the @Value annotation in a Service Bean to inject some custom configuration properties in a configuration file (such as application.properties), These attributes are actually made Spring Environment responsible for unified management and resolution, and shall be the responsibility of the AutowiredAnnotationBeanPostProcessor Value annotations in the Bean @ parsed into attributes.

AutowiredAnnotationBeanPostProcessor SmartInstantiationAwareBeanPostProcessor and MergedBeanDefinitionPostProcessor interface is achieved, Two main things are done during program initialization:

  • Parse each Bean in postProcessMergedBeanDefinition interface BeanDefinition, find all defined in the Bean @ the Value and the @autowired (not a detailed analysis of this article), And parse into InjectionMetadata

  • In the postProcessProperties interface, find the Bean and InjectionMetadata for the corresponding property. InjectionMetadata is responsible for injecting PropertyValues

postProcessMergedBeanDefinition

The most important tasks in postProcessMergedBeanDefinition is constructed from the BeanDefinition InjectionMetadata, InjectionMetadata just as its name implies is expressed each Bean into meta information. AutowiredAnnotationBeanPostProcessor injectionMetadataCache cache of all the InjectionMetadata Bean, Each point in InjectionMetadata that needs to be injected is represented by an InjectedElement.

InjectionMetadata is resolved by buildAutowiringMetadata, which uses reflection to resolve the fields and methods in the Bean Class. The properties to be injected are encapsulated as AutowiredFieldElement. The method to be injected (with annotations on method parameters) is encapsulated as autowiredMethod delement.

private InjectionMetadata buildAutowiringMetadata(finalClass<? > clazz) {
   if(! AnnotationUtils.isCandidateClass(clazz,this.autowiredAnnotationTypes)) {
      return InjectionMetadata.EMPTY;
   }

   List<InjectionMetadata.InjectedElement> elements = newArrayList<>(); Class<? > targetClass = clazz;do {
      final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

      ReflectionUtils.doWithLocalFields(targetClass, field -> {
         // Get the @value or @autowired annotation on the FieldMergedAnnotation<? > ann = findAutowiredAnnotation(field);if(ann ! =null) {
            // Ignore static attributes
            if (Modifier.isStatic(field.getModifiers())) {
               if (logger.isInfoEnabled()) {
                  logger.info("Autowired annotation is not supported on static fields: " + field);
               }
               return;
            }
            // Determine the required parameter on the property
            boolean required = determineRequiredStatus(ann);
            currElements.add(newAutowiredFieldElement(field, required)); }}); ReflectionUtils.doWithLocalMethods(targetClass, method -> { Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);if(! BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {return; } 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); }}boolean required = determineRequiredStatus(ann);
            PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
            currElements.add(newAutowiredMethodElement(method, required, pd)); }}); elements.addAll(0, currElements);
      targetClass = targetClass.getSuperclass();
   }
   while(targetClass ! =null&& targetClass ! = Object.class);return InjectionMetadata.forElements(elements, clazz);
}
Copy the code

postProcessProperties

InjectionMetadata is injected into the Bean by InjectionMetadata. InjectionMetadata is injected into the Bean by InjectionMetadata. The inject function iterates through all injectionElements and calls its inject method to complete the injection of each injection point. Here we use AutowiredFieldElement as an example to see that the configuration properties are parsed and injected into the Bean.

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
   Field field = (Field) this.member;
   Object value;
   if (this.cached) {
      try {
         value = resolvedCachedArgument(beanName, this.cachedFieldValue);
      }
      catch (NoSuchBeanDefinitionException ex) {
         // Unexpected removal of target bean for cached argument -> re-resolvevalue = resolveFieldValue(field, bean, beanName); }}else {
      value = resolveFieldValue(field, bean, beanName);
   }
   if(value ! =null) { ReflectionUtils.makeAccessible(field); field.set(bean, value); }}Copy the code

The current value of the Field is resolved by resolveFieldValue in the InjectionElement inject function, which is then injected into the Bean again using reflection. In resolveFieldValue will call DefaultListableBeanFactory doResolveDependency to complete attribute analysis. The doResolveDependency function first calls resolveEmbeddedValue to parse the Value attribute on the @Value annotation, If the parsed is SpEL expression will use evaluateBeanDefinitionString function secondary parsing.

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

   InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
   try {
      Object shortcut = descriptor.resolveShortcut(this);
      if(shortcut ! =null) {
         returnshortcut; } Class<? > type = descriptor.getDependencyType(); Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);if(value ! =null) {
         if (value instanceofString) { 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 {
            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())); }}/ /...
   }
   finally{ ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint); }}Copy the code

The focus here is on resolveEmbeddedValue, which is a function that iterates through all registered StringValueresolvers to parse the Value in the Value annotation. So where does StringValueResolver get added to the BeanFactory, and if we look at it by reference we can see that there are two places where StringValueResolver gets added, One is in PlaceholderConfigurerSupport, one is the BeanFactory in AbstractApplicationContext in the end of the initialization function.

When no registered StringValueResolver AbstractApplicationContext StringValueResolver will inject a default, So, the default StringValueResolver uses the Environment to resolve the property (strVal -> getEnvironment().resolveders (strVal)).

In Spring the Boot program that commonly automatically configure a PropertySourcesPlaceholderConfigurer Bean to assist analytical placeholder, the beans on the one hand provides a placeholder for parsing, secondary packaging of the Environment on the other hand, User configurable custom attribute resolution is added to enrich the data source of attribute resolution.

In fact the two for attributes parsing StringValueResolver eventually use are PropertySourcesPropertyResolver, PropertySourcesPropertyResolver in resolvePlaceholders function mainly through two steps, first using PropertyPlaceholderHelper parsing out all placeholder in the string (such as ${test}). Then use PropertySourcesPropertyResolver getPropertyAsRawString parsing out should be replaced in the placeholder attribute (that is, to find the properties test attribute in the source) to replace, finally returned to the value.

PropertyPlaceholderHelper traversal of the string, by recursive parse all placeholder, logic is relatively simple don’t do in-depth analysis. GetPropertyAsRawString function in the final attribute search using the PropertySourcesPropertyResolver PropertySources properties, PropertySources contains multiple PropertySource, each representing a data configuration source, Can be system environment variables, JVM, configuration files, or a custom configuration of the local variable (PropertySourcesPropertyResolver function), and so on. According to the previous analysis we can know, PropertySourcesPropertyResolver in Spring framework code two instantiation, One is the ApplicationContext PropertySourcesPropertyResolver created by default when creating the Enviroment, the PropertySources provided by the Enviroment, One is PropertySourcesPlaceholderConfigurer created PropertySourcesPropertyResolver which PropertySources from the merger of Enviroment and local custom attributes.

In the Spring framework, Environment in the ApplicationContext is the default PropertySource manager of Spring. Each PropertySource corresponds to a PropertySource. Properties of gain and is done through PropertySourcesPropertyResolver parsing is final. Want custom attribute source can have two implementations, one is to configure PropertySourcesPlaceholderConfigurer, 2 it is to inject new attribute source by the Environment.

Finally, attach a photo of a @ Value annotation properties resolve key function calls in the sequence diagram, in the next article we will Spring from the perspective of Environment analysis – the core org. Springframework. Core. The env about attribute to parse and load part of the package.