Spring SPI: BeanDefinition Spring SPI: BeanDefinition Spring SPI: BeanDefinition Spring SPI: BeanDefinition
【 Spring source code series 】 【BeanDefinition】
BeanDefinition has three implementation classes: ChildBeanDefinition, GenericBeanDefinition, RootBeanDefinition, All three inherit AbstractBeanDefinition to abstract the common class information of the three subclasses. If a parent and child are defined in the configuration file, the parent is RootBeanDefinition, the child is ChildBeanDefinition, and the non-parent is RootBeanDefinition. GenericBeanDefinition is a one-stop service class. 2. BeanDefinition properties by BeanDefintionParserDelegate parseBeanDefinitionAttributes method.
public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName, @Nullable BeanDefinition containingBean, AbstractBeanDefinition bd) {if (el.hasattribute (SINGLETON_ATTRIBUTE)) {error("Old 1.x 'singleton') attribute in use - upgrade to 'scope' declaration", ele); } else if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { bd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE)); } else if (containingBean ! = null) { // Take default from containing bean in case of an inner bean definition. bd.setScope(containingBean.getScope()); } // parse the abstract tag if (el.hasattribute (ABSTRACT_ATTRIBUTE)) { bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE))); } // parse the lazy-init tag String lazyInit = el.getAttribute (LAZY_INIT_ATTRIBUTE); if (isDefaultValue(lazyInit)) { lazyInit = this.defaults.getLazyInit(); } bd.setLazyInit(TRUE_VALUE.equals(lazyInit)); String autowire = el.getAttribute (AUTOWIRE_ATTRIBUTE); bd.setAutowireMode(getAutowireMode(autowire)); If (el.hasattribute (DEPENDS_ON_ATTRIBUTE)) {String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE); bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, MULTI_VALUE_ATTRIBUTE_DELIMITERS)); } // Autowire-candidate tag String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE); if (isDefaultValue(autowireCandidate)) { String candidatePattern = this.defaults.getAutowireCandidates(); if (candidatePattern ! = null) { String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern); bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName)); } } else { bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate)); } // Parse the primary tag if (el.hasattribute (PRIMARY_ATTRIBUTE)) { bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE))); } // parse init-method tag if (el.hasattribute (INIT_METHOD_ATTRIBUTE)) {String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE); bd.setInitMethodName(initMethodName); } else if (this.defaults.getInitMethod() ! = null) { bd.setInitMethodName(this.defaults.getInitMethod()); bd.setEnforceInitMethod(false); } // parse the destroy-method tag if (el.hasattribute (DESTROY_METHOD_ATTRIBUTE)) {String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE); bd.setDestroyMethodName(destroyMethodName); } else if (this.defaults.getDestroyMethod() ! = null) { bd.setDestroyMethodName(this.defaults.getDestroyMethod()); bd.setEnforceDestroyMethod(false); } // Parse the factory-method tag if (ele.hasattribute (FACTORY_METHOD_ATTRIBUTE)) { bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE)); } // Parse the factory-bean tag if (el.hasattribute (FACTORY_BEAN_ATTRIBUTE)) { bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE)); } return bd; }Copy the code
Since the implementation classes of BeanDefinition all inherit from the parent class AbstractBeanDefinition, Have the properties of three references in the parent class ConstructorArgumentValues, MutablePropertyValues, MethodOverrides, so GenericBeanDefinition eventually contain attributes is as follows:
- Id: The Bean’s unique identifying name. It must be legal XMLID, unique throughout the XML document;
- Name: Used to create one or more aliases for the ID. It can be any letter conforming. Multiple aliases are separated by commas or Spaces.
- Class: Fully qualified name (package name + class name) used to define a class. Only subclass beans do not define this property;
- Parent: The subclass Bean defines the parent Bean that it refers to. The class attribute is invalid. The subclass Bean inherits all the attributes of the parent Bean. Subclass beans and superclass beans are the same Java class;
- Abstract (default “false”) : Defines whether a Bean is abstract. It indicates that the Bean will not be instantiated, and is generally used for parent beans, because parent beans are mainly used by subclass beans to inherit;
- Lazy-init (default “false”) : Defines whether this Bean implements lazy initialization. If it is “false”, it initializes all SingletonBeans when the BeanFactory starts. Conversely, if it is “true”, it starts creating singletonBeans only when the Bean requests it;
- Autowire (autowire, default” default”) : This defines how beans are automatically loaded; –“no” : do not use the automatic assembly function; –“byName” : automatic assembly by Bean property name; –“byType” : automatic assembly by Bean type; –“constructor” : similar to byType, but an automatic assembly of arguments to the constructor; “Autodetect” : Determines whether to use “constructor” or” byType” through the Bean class’s introspection mechanism.
- Depends -on: The object that the Bean depends on when it is initialized. This object will be created before the Bean is initialized.
- Init-method: Defines the Bean initialization method, which is called after the Bean is assembled. It must be a parameterless method;
- Destroy-method: Defines the Bean’s destruction method, which is called when the BeanFactory is closed. Again, it must be a parameterless method. It can only be applied to Singleton Beans.
- Factory-method: Defines the factory method that creates the Bean object. It is used for the following “factory-bean”, indicating that the bean was created using the factory method, in which case the “class” attribute is invalidated.
- Factory-bean: Defines the factory class that creates the bean object. If “factory-bean” is used, the “class” attribute is invalid
- Autowire – candidate: When configuring beans in XML format, set the element’s Autowier-candidate property to false so that the container will not consider the bean when looking for an autowire object, that is, it will not be considered as a candidate for the autowire of other beans. But the bean itself can be injected into other beans using autowiring;
- MutablePropertyValues: It encapsulates the information of the tag, so inside the class is a list, and inside the list is the PropertyValue object, and the PropertyValue is a name and value property that encapsulates the name and value of the tag
- ConstructorArgumentValues: for packaging label information, class is actually a map, the map using the constructor parameter sequence as a key value as the value stored in the map;
- MethodOverrides: Encapsulates information about the lookup-method and replace-method tags. The same class has a Set object that adds LookupOverride and ReplaceOverride.
3. Component-scan Label parsing process
3.1 Process Overview
3.2 Detailed Process
Article mentioned, in front of the custom tag parsed BeanDefinitionParserDelegate class, perform parseCustomElement method;
public BeanDefinition parseCustomElement(Element ele, @nullable BeanDefinition containingBd) {String namespaceURI = getNamespaceURI(ele); if (namespaceUri == null) { return null; } // Parse the handler class corresponding to namespaceURI NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }Copy the code
Step1: obtain namespaceURI; Step2: Parse the handler class corresponding to namespaceURI; Step3: execute handler method to parse. Step1 and step2 has handled in the analysis, component – scan, for example, analysis of step3, code into ComponentScanBeanDefinitionParser parse method
public BeanDefinition parse(Element element, ParserContext parserContext) { /** * 1. GenericBeanDefinition GenericBeanDefinition = new GenericBeanDefinition(); GenericBeanDefinition(); GenericBeanDefinition(); GenericBeanDefinition(); GenericBeanDefinition(); GenericBeanDefinition(); GenericBeanDefinition(); GenericBeanDefinition(); * genericBeanDefinition.setBeanClass(BeanClass.class); BasePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE); basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE); basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage); String[] basePackages = StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); / / later scan for bean definitions and register them. / / create the scanner ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); // Set<BeanDefinitionHolder> beanDefinitions = scanner.doscan (basePackages); / / registered registerComponents bean contained components (parserContext. GetReaderContext (), beanDefinitions, element). return null; }Copy the code
Step1: create scanner with configureScanner method; Step2: Scanning with doScan method; Step3: registerComponents registers the components contained in the bean.
Into the above step2, into ClassPathBeanDefinitionScanner doScan method,
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : BasePackages) {// Run the following command to write the beanDefinition script: // Run the following command to write the beanDefinition script: // Run the basePackages script: // Run the basePackages script: // Run the basePackages script: // Run the basePackages script: // Run the basePackages script. for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); if (candidate instanceof AbstractBeanDefinition) { postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName); } if (candidate instanceof AnnotatedBeanDefinition) {// support @lazy @primary @dependon annotation AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate); } if (checkCandidate(beanName, candidate)) { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); beanDefinitions.add(definitionHolder); registerBeanDefinition(definitionHolder, this.registry); } } } return beanDefinitions; }Copy the code
The above doScan method mainly does the following three steps: Step1: findCandidateComponents scans annotated classes and encapsulates them into beanDefinition objects; Step2: processCommonDefinitionAnnotations method support @ Lazy @ Primary @ DependOn annotations; Step3: register BeanDefinition.
Continue to enter the above methods of findCandidateComponents of step1, ClassPathScanningCandidateComponentProvider scanCandidateComponents method of a class to complete the following steps: Step1: getResources recursively obtains files with the. Class suffix; Step2: getMetadataReader method, obtain metadata AnnotationMetadataReadingVisitor object, any information in the class of meta data collected scans; Step3: determine if the includeFilters match the annotations in the metadata. If so, instantiate the class and create a BeanDefinition object.
There is one more step earlier, step3: the registerComponents registration bean contains components that have not yet been parsed and enter the method
protected void registerComponents( XmlReaderContext readerContext, Set<BeanDefinitionHolder> beanDefinitions, Element element) { Object source = readerContext.extractSource(element); CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source); for (BeanDefinitionHolder beanDefHolder : beanDefinitions) { compositeDef.addNestedComponent(new BeanComponentDefinition(beanDefHolder)); } boolean annotationConfig = true; if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) { annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE)); } if (annotationConfig) { Register the annotation configuration handler Set<BeanDefinitionHolder> processorDefinitions = AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source); for (BeanDefinitionHolder processorDefinition : processorDefinitions) { compositeDef.addNestedComponent(new BeanComponentDefinition(processorDefinition)); } } readerContext.fireComponentRegistered(compositeDef); }Copy the code
Then enter AnnotationConfigUtils registerAnnotationConfigProcessors,
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, @Nullable Object source) { DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry); if (beanFactory ! = null) { if (! (beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) { beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); } if (! (beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) { beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver()); } } Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8); / / @ the Configuration of annotation processor ConfigurationClassPostProcessor if (! registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)); } / / the @autowired annotation processor AutowiredAnnotationBeanPostProcessor if (! registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)); } / / CommonAnnotationBeanPostProcessor processor. The if (jsr250Present &&! registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)); } // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor. if (jpaPresent && ! registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(); try { def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, AnnotationConfigUtils.class.getClassLoader())); } catch (ClassNotFoundException ex) { throw new IllegalStateException( "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex); } def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)); } if (! registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME)); } if (! registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) { RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME)); } return beanDefs; }Copy the code
The above mentioned three kinds of processor ConfigurationClassPostProcessor AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, Each annotation is processed separately, and finally encapsulated in a BeanDefinition and registered in a container.
Enter the ConfigurationClassPostProcessor processConfigBeanDefinitions method is as follows:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { ...... / / added @ Configuration parse all annotations of class ConfigurationClassParser parser = new ConfigurationClassParser (enclosing metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates); Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); Do {// parse @component @componentScan @componentScans @bean @import @importResource Parser. Parse (candidates); parser.validate(); . }Copy the code
This method resolves the @Configuration class and the @Component @ComponentScan @ComponentScans @componentScans @import @importResource annotation, which is done using the parse method. Walked all the way into the parse method down back to processConfigurationClass method, the diagram below
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException { ...... do { sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter); } while (sourceClass ! = null); . }Copy the code
Then enter the doProcessConfigurationClass method, complete @ Component @ ComponentScan @ ComponentScans @ Bean @ Import @ ImportResource annotations parsing.
protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException {// Parse @component if (configClass.getMetadata().isAnnotated(Component.class.getName())) { // Recursively process any member (nested) classes first processMemberClasses(configClass, sourceClass, filter); } @annottysource for (AnnotationAttributes PropertySource: AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), PropertySources.class, org.springframework.context.annotation.PropertySource.class)) { if (this.environment instanceof ConfigurableEnvironment) { processPropertySource(propertySource); } else { logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() + "]. Reason: Environment must implement ConfigurableEnvironment"); }} @componentScan @componentScans Set<AnnotationAttributes> ComponentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); if (! componentScans.isEmpty() && ! this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) { for (AnnotationAttributes componentScan : componentScans) { // The config class is annotated with @ComponentScan -> perform the scan immediately Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // Check the set of scanned definitions for any further config classes and parse recursively if needed for (BeanDefinitionHolder holder : scannedBeanDefinitions) { BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition(); if (bdCand == null) { bdCand = holder.getBeanDefinition(); } if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); }}}} // parse @import processImports(configClass, sourceClass, getImports(sourceClass), filter, true); // parse @importResource AnnotationAttributes ImportResource = AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class); if (importResource ! = null) { String[] resources = importResource.getStringArray("locations"); Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader"); for (String resource : resources) { String resolvedResource = this.environment.resolveRequiredPlaceholders(resource); configClass.addImportedResource(resolvedResource, readerClass); Analytical @}} / / Bean method Set < MethodMetadata > beanMethods = retrieveBeanMethodMetadata (sourceClass); for (MethodMetadata methodMetadata : beanMethods) { configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass)); } // Process default methods on interfaces processInterfaces(configClass, sourceClass); // Process superclass, if any if (sourceClass.getMetadata().hasSuperClass()) { String superclass = sourceClass.getMetadata().getSuperClassName(); if (superclass ! = null && ! superclass.startsWith("java") && ! this.knownSuperclasses.containsKey(superclass)) { this.knownSuperclasses.put(superclass, configClass); // Superclass found, return its annotation metadata and recurse return sourceClass.getSuperClass(); } } // No superclass -> processing is complete return null; }Copy the code
Also tracking AutowiredAnnotationBeanPostProcessor class, you can see the class complete the @autowired @ the parsing of the Value, the diagram below:
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.
}
}
Copy the code
Similar tracking CommonAnnotationBeanPostProcessor class, you can see the class complete @ Resource @ PostConstruct @ PreDestroy parsing, the diagram below:
static { webServiceRefClass = loadAnnotationType("javax.xml.ws.WebServiceRef"); ejbClass = loadAnnotationType("javax.ejb.EJB"); resourceAnnotationTypes.add(Resource.class); if (webServiceRefClass ! = null) { resourceAnnotationTypes.add(webServiceRefClass); } if (ejbClass ! = null) { resourceAnnotationTypes.add(ejbClass); }}... public CommonAnnotationBeanPostProcessor() { setOrder(Ordered.LOWEST_PRECEDENCE - 3); setInitAnnotationType(PostConstruct.class); setDestroyAnnotationType(PreDestroy.class); ignoreResourceType("javax.xml.ws.WebServiceContext"); }Copy the code
Example 4.
Create a BeanDefinitionTest class, realize BeanDefinitionRegistryPostProcessor interface, and the method of complete set the type of Bean to BeanClass, then set BeanClass the attribute and the value of an object, Finally, the container is registered as follows
@Component
public class BeanDefinitionTest implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
genericBeanDefinition.setBeanClass(BeanClass.class);
MutablePropertyValues propertyValues = genericBeanDefinition.getPropertyValues();
propertyValues.addPropertyValue("username","wzj");
registry.registerBeanDefinition("beanClass",genericBeanDefinition);
}
Copy the code
The BeanClass class is as follows:
@Data
public class BeanClass {
private String username;
}
Copy the code
The test classes are as follows:
public class TestSpring {
@Autowired
private ApplicationContext applicationContext;
@Test
public void testComponentScan() {
applicationContext = new AnnotationConfigApplicationContext("com.wzj");
BeanClass beanClass = (BeanClass)applicationContext.getBean("beanClass");
BeanDefinitionTest beanDefinitionTest = (BeanDefinitionTest)applicationContext.getBean("beanDefinitionTest");
System.out.println("BeanClass-->" + beanClass.getUsername());
System.out.println("BeanDefinitionTest-->" + beanDefinitionTest.getClass());
}
Copy the code
The code directory structure is as follows and the run result is as follows
5. To summarize
In this paper, the Conmponent-Scan tag is taken as an example to analyze the main process, and the analysis, encapsulation and registration of the BeanDefinition attribute into the container are described in combination with the source code. At last, a mind map is used to summarize the general steps in each process
In addition, static source code can focus on the main process, and do annotations, dynamic debug example into the source code can intuitively feel the value during the operation, source code analysis is not easy, make clear the main process and thought is more important than the source itself.
- That’s the BeanDefinition of the Spring source code series.
- Also welcome everybody exchange discussion, if this article has incorrect place, hope everybody many forgive.
- Your support is my biggest motivation, if you help out, give me a thumbs up