Creation is not easy, reprint please note the author: nuggets @ small xizi + source link ~

If you want to learn more about Spring source code, click on the rest of the Spring series to parse line by line

What is a custom tag?

Last time we looked at the default tag -bean tag parsing, today we will look at the custom tag parsing.

1. Customize the label definition

In the Spring XML configuration file, we can divide all tags into two categories: custom tags and default tags. The differences are as follows

<! -- It's on the front of the labelxxx:For spring's custom tag, we can also define a tag of xiaozize: - we'll talk about that later ->
<context:component-scan base-package="com.xiaoxizi.spring"/>
<! The corresponding namespace of this tag is declared in the beans tag in the XML file header.
<beans xmlns:context="http://www.springframework.org/schema/context" . />

<! -- Default tag without xx: prefix -->
<bean class="com.xiaoxizi.spring.service.AccountServiceImpl" 
      id="accountService" scope="singleton" primary="true"/>
<! The corresponding namespace is also declared in the beans tag in the XML file header.
<beans xmlns="http://www.springframework.org/schema/beans" . />
Copy the code

It is important to note that the concept of custom tags does not refer entirely to tags defined by us during development, but rather to extension points reserved by spring developers for future extensions that we can use and spring developers can use when adding new features to Spring.

About 2.springBuilt-in custom tagscontext:component-scan

More often than not, we use @Configuration, @Component, @Service annotations to declare beans rather than XML bean tags.

So why should a class with these annotations be managed by Spring?

These extensions are actually extended by Spring’s own custom tag extension points, using the Context: Component-scan tag.

Context: Component-scan Context: Component-scan Context: Component-scan Context: Component-scan Context: Component-scan Take a look at how tags like @Component work.

Two, source code analysis

1. Customize the label parsing process

Since I followed the XML source code in the previous article, This issue between our positioning to the corresponding code org. Springframework. Beans. Factory. XML. DefaultBeanDefinitionDocumentReader# parseBeanDefinitions

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    // Check if it is the default namespace
    if (delegate.isDefaultNamespace(root)) {
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    // Parse the default tags
                    parseDefaultElement(ele, delegate);
                }
                else {
                   	// You can see that the proxy mainly does custom tag parsingdelegate.parseCustomElement(ele); }}}}else {
        // You can see that the proxy mainly does custom tag parsingdelegate.parseCustomElement(root); }}@Nullable
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
    // Get the tags for the namespaceUrl, that is, XMLNS: XXX =www.xxx.com in the beans tag in the header of the configuration file
    String namespaceUri = getNamespaceURI(ele);
    if (namespaceUri == null) {
        return null;
    }
    // Get the NamespaceHandler for the custom tag. There should be a unique NamespaceHandler for each 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;
    }
    // Delegate custom tags to the corresponding NamespaceHandler for parsing
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
Copy the code

Let’s first look at the structure of NamespaceHandler, the parser interface for custom tags:

public interface NamespaceHandler {
    We can reasonably guess that this method will be called after NamespaceHandler is instantiated but before it is used
	void init(a);
	// XML parsing entry
	@Nullable
	BeanDefinition parse(Element element, ParserContext parserContext);
	After the default bean tag is parsed, it gives you an opportunity to decorate the parsed beanDefinition, which is rarely used in development
    / / interested students will be able to see the source code, source code in the org. Springframework. Beans. Factory. XML. DefaultBeanDefinitionDocumentReader# processBeanDefinition
	@Nullable
	BeanDefinitionHolder decorate(Node source, BeanDefinitionHolder definition, ParserContext parserContext);
}
Copy the code

Next, of course, we need to look at the process of obtaining NamespaceHandler:

public NamespaceHandler resolve(String namespaceUri) {
    // Get a handlerMapping
    Map<String, Object> handlerMappings = getHandlerMappings();
    // Get an object with namespaceUri
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    }
    // If handlerOrClassName is a NamespaceHandler object, return the corresponding handler
    else if (handlerOrClassName instanceof NamespaceHandler) {
        return (NamespaceHandler) handlerOrClassName;
    }
    else {
        // If handlerOrClassName is not a NamespaceHandler, it is a String
        String className = (String) handlerOrClassName;
        // Get a Class object from a String, then the String must be the fully qualified name of a ClassClass<? > handlerClass = ClassUtils.forName(className,this.classLoader);
        // handlerClass must inherit from NamespaceHandler, which makes sense, since it is an extension point provided by Spring and needs to conform to the rules it defines
        if(! NamespaceHandler.class.isAssignableFrom(handlerClass)) {throw new FatalBeanException("...");
        }
        // Create an instance directly through reflection, click on it to see that it is called without arguments constructor
        NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
        / /!!!!!! The init() method is called, as we guessed earlier
        namespaceHandler.init();
        / /!!!!!! The handler object is inserted back into handlerMappings, so the next time we get it through namespaceUri, we will get a NamespaceHandler object directly
        // The namespaceUri handler for each namespaceUri is singleton, and init() is called only once
        handlerMappings.put(namespaceUri, namespaceHandler);
        return namespaceHandler;
        // Remove exception handling}}Copy the code

/ / “namespaceUri”; / / “namespaceUri”; / / “namespaceUri”; / / “namespaceUri”; / / “namespaceUri”; / / “namespaceUri”; / / “namespaceUri”; / / getHandlerMappings(

private Map<String, Object> getHandlerMappings(a) {
    Map<String, Object> handlerMappings = this.handlerMappings;
    if (handlerMappings == null) {
        synchronized (this) {
            handlerMappings = this.handlerMappings;
            // Double check the lock, it looks like our handlerMappings will be loaded once after
            if (handlerMappings == null) {
                // You can see that the file is loaded
                // The file is loaded in the same way as the file is loaded in the same way
                / / this. Where is handlerMappingsLocation
                Properties mappings =
                    PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
                handlerMappings = new ConcurrentHashMap<>(mappings.size());
                // Then merge all the kev-value attributes in the file into a map
                CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings);
                this.handlerMappings = handlerMappings;
                // The exception handling code is dead}}}return handlerMappings;
}
/ / field definition, need to say the current class is DefaultNamespaceHandlerResolver, like his exploring students can directly airborne
/** Resource location to search for. */
private final String handlerMappingsLocation;
// We can see that this value is set in the Resolver constructor
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader, String handlerMappingsLocation) {
    this.classLoader = (classLoader ! =null ? classLoader : ClassUtils.getDefaultClassLoader());
    this.handlerMappingsLocation = handlerMappingsLocation;
}
// The default is DEFAULT_HANDLER_MAPPINGS_LOCATION
public DefaultNamespaceHandlerResolver(a) {
    this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
// Let's look at the value of this constant
public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";
Copy the code

And for those of you who are familiar with SPI, you already know what this is, and you’re familiar with the meta-INF directory, so let’s take a look at what’s actually written in this meta-INF/Spring.Handlers file, Take the context: Component-scan tag as an example. We know that this tag is provided in the Spring-Context package. Go to the jar file and look at the contents:

## We can clearly see a key=value structure
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
Copy the code

Let’s recall the definition of a custom tag:

<! -- It's on the front of the labelxxx:For spring's custom tag, we can also define a tag of xiaozize: - we'll talk about that later ->
<context:component-scan base-package="com.xiaoxizi.spring"/>
<! The corresponding namespace of this tag is declared in the beans tag in the XML file header.
<beans xmlns:context="http://www.springframework.org/schema/context" . />
Copy the code

You can see in our meta-INF/Spring. handlers file that the key is the namespaceUri of the custom tag and the value is the fully qualified name of the corresponding NamespaceHandler.

So to summarize, our custom tag parsing process is:

  1. Handlers; / / Add handlerMappings to the fully qualified names of namespaceUri and NamespaceHandler in the META-INF/ Spring. handlers file in all jars

  2. Get an object from handlerMappings according to namespaceUri

    • Return if the object obtained from handlerMappings is empty

    • If you get a NamespaceHandler object, use it

    • If the object is a string, instantiate NamespaceHandler with the fully qualified name of the string and call init(), Then put the Namespaceuri-NamespaceHandler object relationship back into handlerMappings

  3. Call parse (if 2 does not get the corresponding NamespaceHandler object, the custom tag cannot be parsed and will be skipped)

2. context:component-scanWorking Principle of labels

The spring-context meta-INF/Spring. handlers file shows how the context: Component-scan tag works.

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
Copy the code

Find this class directly:

public class ContextNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init(a) {
        // Removed some Parser injection code for tags we don't care about...
        // We can see that a BeanDefinitionParser is registered, and the first argument to this registration method is obviously
        // Delete the prefix from the 'context: Component-scan' tag
        registerBeanDefinitionParser("component-scan".new ComponentScanBeanDefinitionParser());
        // Removed some Parser injection code for tags we don't care about...}}Copy the code

As you can see, ContextNamespaceHandler inherits from NamespaceHandlerSupport, which is a typical template method design pattern. Instead of extending it, let’s go straight to NamespaceHandlerSupport:

// Here we save only the parse-related code and adjust the source order
// The decoration-related code is removed. It's not that it doesn't exist in NamespaceHandlerSupport, but the logic and parsing are basically the same
// If you still remember where the beanDefinition is decorated and are interested, you can learn about it by yourself (* ̄))
public abstract class NamespaceHandlerSupport implements NamespaceHandler {
    // The container that holds the tag name-parser relationship
	private final Map<String, BeanDefinitionParser> parsers = new HashMap<>();
    // Save the container for the label name-decorator relationship
	private final Map<String, BeanDefinitionDecorator> decorators = new HashMap<>();
    // Save the container for the attribute name-decorator relationship
	private final Map<String, BeanDefinitionDecorator> attributeDecorators = new HashMap<>();
    // Register in init() simply puts the elementname-parser object into the map
	protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
		this.parsers.put(elementName, parser);
	}
	public BeanDefinition parse(Element element, ParserContext parserContext) {
        / / get the Parser
		BeanDefinitionParser parser = findParserForElement(element, parserContext);
        // Delegate to Parser
		return(parser ! =null ? parser.parse(element, parserContext) : null);
	}
	private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
        Context :component-scan --> component-scan context:component-scan --> component-scan
		String localName = parserContext.getDelegate().getLocalName(element);
        // Get the corresponding Parser from map
		return this.parsers.get(localName); }}Copy the code

So far is really quite simple, we gave the corresponding labels entrust Parser to handle, so we now look at some of the component – scan the corresponding ComponentScanBeanDefinitionParser logic, we look at the parse method, Also our entry method:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // Get the base-package attribute configured and processed on the tag
    String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
    // Handle placeholders
    basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
    // We get an array - we can configure multiple arrays
    String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
                                                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

    // Get a scanner - this is very important, as we will see later
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
    // Well, the scanner scans, so it looks like this method will scan the annotations
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    // Register some components
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
    return null;
}
Copy the code

Let’s take a look at how the scanner was created:

// Get rid of some exception handling
protected ClassPathBeanDefinitionScanner configureScanner(ParserContext parserContext, Element element) {
    // Parse whether to use the default filter -> as explained here, this filter actually refers to our @service annotations.
    // This is to define the annotations that we will put into IOC management after we scan them. The specific code will be seen later when parsing
    boolean useDefaultFilters = true;
    if (element.hasAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE)) {
        useDefaultFilters = Boolean.parseBoolean(element.getAttribute(USE_DEFAULT_FILTERS_ATTRIBUTE));
    }
	// Create a scanner directly
    ClassPathBeanDefinitionScanner scanner = createScanner(parserContext.getReaderContext(), useDefaultFilters);
    // The default beanDefinition configuration obtained from parserContext, that is, the default configuration of the beanDefinition that is parsed later
    scanner.setBeanDefinitionDefaults(parserContext.getDelegate().getBeanDefinitionDefaults());
    // Get the default autowiring mode from parserContext, byType, byName, etc
    scanner.setAutowireCandidatePatterns(parserContext.getDelegate().getAutowireCandidatePatterns());
	// Scan the resource path, generally we do not configure
    if (element.hasAttribute(RESOURCE_PATTERN_ATTRIBUTE)) {
        scanner.setResourcePattern(element.getAttribute(RESOURCE_PATTERN_ATTRIBUTE));
    }
    // It doesn't work... It is also not customizable, even if the strategy for generating the bean's name is customizable when using annotations
    parseBeanNameGenerator(element, scanner);
    // The bean will exist in any scope
    parseScope(element, scanner);
    Parsing type filters - this is important because we can customize which annotations need to be scanned
    parseTypeFilters(element, scanner, parserContext);

    return scanner;
}
Copy the code

If useDefaultFilters=true, createScanner calls the constructor directly. Let’s look at the constructor logic: useDefaultFilters= True

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, 
                                      boolean useDefaultFilters,
                                      Environment environment, 
                                      @Nullable ResourceLoader resourceLoader) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    this.registry = registry;
    if (useDefaultFilters) {
        // Register the default filter
        registerDefaultFilters();
    }
    setEnvironment(environment);
    setResourceLoader(resourceLoader);
}
protected void registerDefaultFilters(a) {
    // includeFilters add an AnnotationTypeFilter. The filter constructor passes in the Component Class object
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    / / two JSR specification omitted registration code annotated, we usually use less than, @ javax.mail. The annotation. ManagedBean and @ javax.mail inject. Named
}
Copy the code

Since the Filter matching process is not the main process, I will not write more here, but I will write a piece of source code parsing at the end of this section, interested students can also have a look.

Let’s look at the type filter tag parsing:

protected void parseTypeFilters(Element element, ClassPathBeanDefinitionScanner scanner, ParserContext parserContext) {
    // ...
    NodeList nodeList = element.getChildNodes();
    for (int i = 0; i < nodeList.getLength(); i++) {
        Node node = nodeList.item(i);
        // Find each child node
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            String localName = parserContext.getDelegate().getLocalName(node);
            if (INCLUDE_FILTER_ELEMENT.equals(localName)) {
                // If it is the 
       tag, create a filter and add includeFilters
                TypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext);
                scanner.addIncludeFilter(typeFilter);
            }
            else if (EXCLUDE_FILTER_ELEMENT.equals(localName)) {
                // If the 
       tag creates a filter and adds includeFiltersTypeFilter typeFilter = createTypeFilter((Element) node, classLoader, parserContext); scanner.addExcludeFilter(typeFilter); }}}}Copy the code

So let’s see what createTypeFilter does:

// The logic is straightforward
protected TypeFilter createTypeFilter(Element element, @Nullable ClassLoader classLoader,
                                      ParserContext parserContext) {
    String filterType = element.getAttribute(FILTER_TYPE_ATTRIBUTE);
    String expression = element.getAttribute(FILTER_EXPRESSION_ATTRIBUTE);
    expression = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(expression);
    if ("annotation".equals(filterType)) {
        // If we want to scan for custom annotations, we can use the annotation type, expression, to specify the fully qualified name of the annotation
        return new AnnotationTypeFilter((Class<Annotation>) ClassUtils.forName(expression, classLoader));
    }
    else if ("assignable".equals(filterType)) {
        // Scan configured classes and their subclasses. Expression fills in the fully qualified name of the class. This is also used occasionally to specify beans that can be scanned for some binary libraries
        return new AssignableTypeFilter(ClassUtils.forName(expression, classLoader));
    }
    else if ("aspectj".equals(filterType)) {
        // Scan the class that the section expression matches
        return new AspectJTypeFilter(expression, classLoader);
    }
    else if ("regex".equals(filterType)) {
        // Scans the classes that the regular expression matches
        return new RegexPatternTypeFilter(Pattern.compile(expression));
    }
    else if ("custom".equals(filterType)) {
        // Custom filter, corresponding to the class needs to implement TypeFilter interfaceClass<? > filterClass = ClassUtils.forName(expression, classLoader);if(! TypeFilter.class.isAssignableFrom(filterClass)) {throw new IllegalArgumentException(
                "Class is not assignable to [" + TypeFilter.class.getName() + "]." + expression);
        }
        return (TypeFilter) BeanUtils.instantiateClass(filterClass);
    }
    else {
        throw new IllegalArgumentException("Unsupported filter type: "+ filterType); }}Copy the code

Context: Component-scan {context:component-scan}} Context :component-scan {context:component-scan}} context: Component-scan {context:component-scan}} context: Component-scan {context:component-scan}} So we then look at the back, what is the scanner to scan logic, students can airborne ComponentScanBeanDefinitionParser# parse, and then we look at the access to the scanner, Scanner. DoScan (basePackages) logic:

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    for (String basePackage : basePackages) {
        // Find all scanned BeanDefinitions
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        for (BeanDefinition candidate : candidates) {
            // Get beanName. Remember that when we use annotations, there is no such thing as an XML tag attribute to get a name
            // The beanNameGenerator is used to obtain the beanName. The default is the corresponding attribute or class name in the annotation. Will anyone interested in can see AnnotationBeanNameGenerator
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            // Delete the assignment of unimportant attributes
            if (candidate instanceof AnnotatedBeanDefinition) {
            // This is where public annotations on the class are handled, such as @primary, @lazy, etc
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            // It is the first time that beanDifinition has been registered
            // If it is not registered for the first time, it will not be registered again
            if (checkCandidate(beanName, candidate)) {
				// ... 
                // Register the beanDefinition in the beanDefinition
                // beanDefinitionMap and beanDefinitionNames
                registerBeanDefinition(definitionHolder, this.registry); }}}return beanDefinitions;
}
Copy the code

. Let’s look at how AnnotationConfigUtils processCommonDefinitionAnnotations () is how to deal with the annotation on the class:

static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
    AnnotationAttributes lazy = attributesFor(metadata, Lazy.class);
    if(lazy ! =null) {
        abd.setLazyInit(lazy.getBoolean("value"));
    }
    else if(abd.getMetadata() ! = metadata) { lazy = attributesFor(abd.getMetadata(), Lazy.class);if(lazy ! =null) {
            abd.setLazyInit(lazy.getBoolean("value")); }}if (metadata.isAnnotated(Primary.class.getName())) {
        abd.setPrimary(true);
    }
    AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
    if(dependsOn ! =null) {
        abd.setDependsOn(dependsOn.getStringArray("value"));
    }

    AnnotationAttributes role = attributesFor(metadata, Role.class);
    if(role ! =null) {
        abd.setRole(role.getNumber("value").intValue());
    }
    AnnotationAttributes description = attributesFor(metadata, Description.class);
    if(description ! =null) {
        abd.setDescription(description.getString("value")); }}Copy the code

I’m sure the smart guys have figured it out, which is to look at the class and see if it has an annotation on it, and then stuff that attribute into the beanDefinition object. Isn’t this exactly the same process as when XML parses to get a beanDefinition?

Yes, in fact, whether annotation-based or XML-based, some information describing the bean is gathered and summarized into the appropriate beanDefinition. The beanDefinition property determines how the bean will be instantiated, what properties need to be injected, and so on.

There are many ways to gather information to annotate a beanDefinition — it’s even possible to write your own component that parses json files, but the results are all the same.

This also shows the power of Spring’s design, its modular approach to design and its commitment to the single responsibility principle (intuitively delegating to specialized classes) and the open/close principle (to where we are now: The design of custom tags, in the case of not touching the original core logic, we can very simple to do some custom extension of Spring) practice, we can also use for reference in daily development?

Now, back to the source code, let’s look at how the scanner scans for classes that are annotated (which is the application of a previously registered filter). In findCandidateComponents(), we call scanCandidateComponents(), Between us look at scanCandidateComponents() :

// Remove exception handling and log printing
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
        resolveBasePackage(basePackage) + '/' + this.resourcePattern;
    // This section of logic is extremely complex, and it is not necessary to understand the main process.
    // We can find all the class files
    Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
    for (Resource resource : resources) {
        // Parses the file information, loading it into memory, which is complicated by some bytecode parsing techniques
        // We just need to know that after doing this, the MetadataReader gets all the information about our class
        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
        // This is where our filter comes into play. Beandefinitions are generated only if the class meets the criteria
        if (isCandidateComponent(metadataReader)) {
            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
            sbd.setResource(resource);
            sbd.setSource(resource);
            // The class we matched is a qualified bean
            For example, if we annotated the interface, we would not add the beanDefinition to the returned container
            if(isCandidateComponent(sbd)) { candidates.add(sbd); }}}return candidates;
}
// Filter to determine if it is the class we care about, logic is straightforward
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {	
    // First judge excludeFilters
    for (TypeFilter tf : this.excludeFilters) {
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            return false; }}// Determine the includeFilters
    for (TypeFilter tf : this.includeFilters) {
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            // If it is the class we care about, we also need to deal with the @Conditional annotation on the class
            // I'm not going to go any further here, but I'll give you a brief logic:
            // 1. Find annotations for all @Conditional clusters above the class
            // 2. Instantiate all corresponding Conditional classes and sort them
            // 3. Call all conditions.matches () in order to return true
            // You can take a look at the details for yourself
            returnisConditionMatch(metadataReader); }}return false;
}
// Check if it is not the interface
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return (metadata.isIndependent() // Not an instance inner class and
            && (metadata.isConcrete() // Not an interface or abstract class or
                ||
                                         (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())))); 
    // is an abstract class, but some of the methods are marked by the @lookup annotation, which I mentioned a little earlier. The xmltag has the same meaning as the lookup-method tag, which is equivalent to delegating the method to another bean. So even an abstract class can become a bean -> Spring dynamic proxy generates a subclass
}
Copy the code

3. Filter Matching process

I didn’t want to write the Filter matching process, because it’s not really the main process, but I think I’ll write it, otherwise some students might struggle.

AnnotationTypeFilter constructor (AnnotationTypeFilter)

// Let's take a quick look at AnnotationTypeFilter's constructor
public AnnotationTypeFilter(Class<? extends Annotation> annotationType) {
    this(annotationType, true.false);
}
// As you can see, when we scan @Component annotations, we consider the source annotations, not the interface annotations
public AnnotationTypeFilter// Annotation type Class<? Extends Annotation> annotationType, // Whether source annotations are consideredbooleanAnnotations, let metaannotations, // Consider the interfaceboolean considerInterfaces) {
    // The first argument is whether to consider inherited annotations
    super(annotationType.isAnnotationPresent(Inherited.class), considerInterfaces);
    this.annotationType = annotationType;
    this.considerMetaAnnotations = considerMetaAnnotations;
}
Copy the code

Taking a look at the core match method, here is also a template method pattern:

// Start with the top-level class
public abstract class AbstractTypeHierarchyTraversingFilter implements TypeFilter {
    @Override
    public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
        throws IOException {
		The template method, implemented by subclasses, returns false by default
        if (matchSelf(metadataReader)) {
            return true;
        }
        // Provide a hook that checks for a match by className
        ClassMetadata metadata = metadataReader.getClassMetadata();
        if (matchClassName(metadata.getClassName())) {
            return true;
        }
        if (this.considerInherited) {
            // If you consider inherited annotations, find the corresponding parent class
            String superClassName = metadata.getSuperClassName();
            if(superClassName ! =null) {
                // See if the subclass has the logic to determine whether the parent class matches
                Boolean superClassMatch = matchSuperClass(superClassName);
                if(superClassMatch ! =null) {
                    // Write this logic directly to return the result
                    if (superClassMatch.booleanValue()) {
                        return true; }}else {
                    // If there is no logic to determine whether the parent class matches, the current matching logic is used directly
                    if (match(metadata.getSuperClassName(), metadataReaderFactory)) {
                        return true; }}}}if (this.considerInterfaces) {
            // If you consider the annotation of the interface, find the corresponding interface, because there are multiple interfaces, so loop
            // The logic is similar to that of the parent class
            for (String ifc : metadata.getInterfaceNames()) {
                Boolean interfaceMatch = matchInterface(ifc);
                if(interfaceMatch ! =null) {
                    if (interfaceMatch.booleanValue()) {
                        return true; }}else {
                    if (match(ifc, metadataReaderFactory)) {
                        return true; }}}}return false; }}Copy the code

Look again at the core methods of AnnotationTypeFilter:

public class AnnotationTypeFilter extends AbstractTypeHierarchyTraversingFilter {
	protected boolean matchSelf(MetadataReader metadataReader) {
		AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
        // Class has target annotations
		return metadata.hasAnnotation(this.annotationType.getName()) ||
            // If you can get it from the source annotation, then check to see if the class has the same source annotation as the target annotation
				(this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName()));
	}
	protected Boolean matchSuperClass(String superClassName) {
		return hasAnnotation(superClassName);
	}
	protected Boolean matchInterface(String interfaceName) {
		return hasAnnotation(interfaceName);
	}

	@Nullable
	protected Boolean hasAnnotation(String typeName) {
		if (Object.class.getName().equals(typeName)) {
			return false;
		}
        // The matching logic of the parent class and interface can only match JDK built-in classes (starting with Java)
        // It seems that the default implementation should be used to support the annotations of the JSR standard
		else if (typeName.startsWith("java")) {
			/ /... Don't focus on
		}
		return null; }}Copy the code

As you can see, our default AnnotationTypeFilter takes source annotations into account, so what exactly is a source annotation?

public @interface Controller {
	@AliasFor(annotation = Component.class)
	String value(a) default "";
}
public @interface Service {
	@AliasFor(annotation = Component.class)
	String value(a) default "";
}
public @interface Repository {
	@AliasFor(annotation = Component.class)
	String value(a) default "";
}
Copy the code

@aliasfor (Annotation = component.class), which is why our default includeFilters only register an AnnotationTypeFilter of @Component, But we @service etc can also be scanned for the reason! The AnnotationTypeFilter we constructed takes source annotations into account!

4. Register public components

We’ve already seen how the context: Component-scan tag scans and supports the @Component annotation, but those of you who are paying attention may have noticed that now we can actually scan the @Component annotation. But how are those properties in our bean injected? How do @autowrite and @Resource annotations work? And what about @Configuration and @Bean?

These functions are actually done in the corresponding BeanPostProcessor, whose registration is injected during the parsing of our Context: Component-scan tag. If students have impression, should remember ComponentScanBeanDefinitionParser parse method, we create a scanner and scan again, also do some common components registered:

public BeanDefinition parse(Element element, ParserContext parserContext) {
    // ...
    // Get a scanner - this is very important, as we will see later
    ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
    // Well, the scanner scans, so it looks like this method will scan the annotations
    Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
    // Register some components
    registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
    return null;
}
Copy the code

Let’s look at the registerComponents method:

protected void registerComponents( XmlReaderContext readerContext, Set
       
         beanDefinitions, Element element)
        {
	// ...
    // Register annotation config processors, if necessary.
    boolean annotationConfig = true;
    if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
        annotationConfig = Boolean.parseBoolean(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
    }
    // Look at the annotation-config configuration, which is true by default
    if (annotationConfig) {
        Set<BeanDefinitionHolder> processorDefinitions =
            // Register some Processors that support annotations
            AnnotationConfigUtils.registerAnnotationConfigProcessors(readerContext.getRegistry(), source);
		// ...
    }
	// ...
}
Copy the code

So which processors are registered?

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
    BeanDefinitionRegistry registry, @Nullable Object source) {
	// ...
    Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
	
    / / registered a ConfigurationClassPostProcessor here, as the name implies, this should be a support @ Configuration related annotation
    if(! registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) { RootBeanDefinition def =new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        def.setSource(source);
        // Register the logic registerPostProcessor
        beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }
	/ / register a AutowiredAnnotationBeanPostProcessor, used to handle @ Autowire, @ Value of annotation
    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));
    }

    // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
    // Here are the @resource, @postConstruct, @predestroy annotations supporting the JSR-250 specification
    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.
    // Here is the BeanPostProcessor of JPA that supports annotation form
    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));
    }
	// Support the processor for spring-event related annotations, support for @eventListener
    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));
    }
	// Support the processor for spring-event related annotations, support for @eventListener
    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

At this point, everything the context: Component-scan tag does is done. It basically creates a scanner to scan our basic beans that need to be registered, and then registers some processors that support the corresponding annotation functions. For these processors, I won’t go into the details of when each Processor is called and how it implements its functions. Those of you who are interested can find their own classes to see the implementation logic.

Later, when I talk about bean initialization logic and life cycle, I will talk about some of the Processor calls and internal logic in specific extension points. Hopefully, you will remember where these processors are registered.

Three, practice

All say that practice is the real knowledge, we follow the source analysis of such a big wave, but the fact is not as we analyze? To verify this, I’ll briefly use spring’s reserved extension points here.

1. Usecontext:component-scanScan custom annotations

We first need to define a custom annotation:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyService {
}
Copy the code

Then configure the context: Component-scan tag:

<context:component-scan base-package="com.xiaoxizi.spring">
        <context:include-filter type="annotation" expression="com.xiaoxizi.spring.annotation.MyService"/>
    </context:component-scan>
Copy the code

Annotate our business class:

@Data
@MyService
public class MyAnnoClass {
    public String username = "xiaoxizi";
}
Copy the code

Run:

public void test1(a) {
    applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    MyAnnoClass myAnnoClass = applicationContext.getBean(MyAnnoClass.class);
    System.out.println(myAnnoClass);
}
Copy the code

Output result:

MyAnnoClass(username=xiaoxizi)
Copy the code

Our custom annotations have been scanned and have successfully generated the beanDefinition and instantiated the bean!

2. Customize labels

Create a parser class for a specific tag. Let’s make it simple and inherit a class from spring:

public class SimpleBeanDefinitionParse extends AbstractSingleBeanDefinitionParser {
    @Override
    protected String getBeanClassName(final Element element) {
        System.out.println("SimpleBeanDefinitionParse ... getBeanClassName()");
        return element.getAttribute("className"); }}Copy the code

Then create a SimpleNamespaceHandler:

public class SimpleNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init(a) {
        System.out.println("SimpleNamespaceHandler ... init()");
        this.registerBeanDefinitionParser("simpleBean".newSimpleBeanDefinitionParse()); }}Copy the code

Handlers write to the meta-INF /spring.handlers file:

http\://www.xiaoxize.com/schema/simple=com.xiaoxizi.spring.tag.SimpleNamespaceHandler
Copy the code

XML configuration uses:

<xiaoxizi:simple className="com.xiaoxizi.spring.bean.MyAnnoClass"/>
<! The corresponding namespace of this tag is declared in the beans tag in the XML file header.
<beans xmlns:xiaoxizi="http://www.xiaoxize.com/schema/simple" . />
Copy the code

The target class:

@Data
// @MyService
public class MyAnnoClass {
    public String username = "xiaoxizi";
}
Copy the code

Run:

public void test1(a) {
    applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    MyAnnoClass myAnnoClass = applicationContext.getBean(MyAnnoClass.class);
    System.out.println(myAnnoClass);
}
Copy the code

Output results – various errors, hahaha:

Caused by: org.xml.sax.SAXParseException; lineNumber: 21; columnNumber: 91; Cvc-complex-type.2.4.c: wildcard matching is complete, but cannot find declaration of element 'xiaoxizi:simple'.Copy the code

When we declare a namespace, we need to declare and define the corresponding XSD file (here I wrote an XSD file and introduced the workspace through the configuration of IDEA) like this:

<xiaoxizi:simple className="com.xiaoxizi.spring.bean.MyAnnoClass"/>
<! The corresponding namespace of this tag is declared in the beans tag in the XML file header.
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:xiaoxizi="http://www.xiaoxize.com/schema/simple" 
       xsi:schemaLocation=" http://www.xiaoxize.com/schema/simple http://www.xiaoxize.com/schema/simple.xsd"
       . />
Copy the code

And then it doesn’t work:

java.net.UnknownHostException: www.xiaoxize.com org.xml.sax.SAXParseException: schema_reference.4: Can't read the documents' http://www.xiaoxize.com/schema/simple.xsd ', the reason for 1) couldn't find the document; 2) Unable to read the document; The root element of the document is not < XSD :schema>. Caused by: org.xml.sax.SAXParseException; lineNumber: 21; columnNumber: 91; Cvc-complex-type.2.4.c: wildcard matching is complete, but cannot find declaration of element 'xiaoxizi:simple'.Copy the code

XSD file: XSD file: XSD file: XSD file: XSD file: XSD file: XSD file: XSD file: XSD file: XSD file: XSD file: XSD file: XSD file: XSD file: XSD file: XSD file Finally, I threw the XSD file to my server and adjusted the domain name. Finally, IT was ok:

SimpleNamespaceHandler ... init()
SimpleBeanDefinitionParse ... getBeanClassName()
MyAnnoClass(username=xiaoxizi)
Copy the code

You’re done, so it’s pretty easy to customize tags.

Four,

1. Customize the label parsing process

  1. The first custom tag starts parsing, will be fromalljarThe packageMETA-INF/spring.handlersFile load custom label namespace – correspondingNamespaceHandlerFully qualified name into memoryDefaultNamespaceHandlerResolver.handlerMappings
  2. The same prefixCustom tags forFirst parsingIs instantiatedNamespaceHandlerAnd call itinit()Method and then put the custom tag namespace – correspondingNamespaceHandlerInstance in thehandlerMappings, the next time the same tag comes to parse, you can directly get the correspondingNamespaceHandlerThe instance
  3. Use foundNamespaceHandlerThe instanceparseMethod parses custom tags
  4. springIt was very kind of you to prepare it for usNamespaceHandlerAssociated template classesNamespaceHandlerSupportIf our custom processor inherits the template, it only needs to be in theinitMethod to inject corresponding values for specific tagsBeanDefinitionParserorBeanDefinitionDecoratorYou can implement the function

2. context:component-scanWhat did

  1. Custom labelscontext:component-scanThe corresponding parser isComponentScanBeanDefinitionParser(We won’t bore you with the process).
  2. The parser’sparseMethod, we create one from the attribute configured for the tagThe scannerClassPathBeanDefinitionScanner
  3. By default, we register one@ComponentannotationsAnnotationTypeFilterAnd registered to the scannerincludeFiltersIn the
  4. Then the scanner begins to scanbasePackageUnder all thejavaClass, and find all that do not need to be excluded (excludeFilters) candidate class (includeFilters), and generate one for itbeanDefinitionIf the class is a validbeanDefinition(non-interface those judgments), then these will bebeanDefinitionCollect and return
  5. For all the candidatesbeanDefinition, the scanner will scan further on the class@Lazy,@Primary,@DependsOnAnd then set the value tobeanDefinitionIn the corresponding property of
  6. And the last step, all the legitimate ones that we’ve scannedbeanDefinitionRegistered toIOCThe container
  7. Due to the@Componentis@Service,@ControllerAnd so on the source annotations, so@ServiceThe classes marked by these annotations will also beincludeFiltersScan to
  8. Register a series of pairs@Configuration,@Autowired,@ResourceSuch as annotations to supportProcessor

Five, the other

Simple XSD files used in practice

<xsd:attribute name="className" type="xsd:string"></xsd:attribute>

      
<xsd:schema xmlns="Http://your IP address or domain name /simple"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="Http://your IP address or domain name /simple"
            elementFormDefault="qualified">
    <xsd:element name="simple">
        <xsd:complexType>
            <xsd:attribute name="className" type="xsd:string"></xsd:attribute>
            <xsd:attribute name="id" type="xsd:string"></xsd:attribute>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>
Copy the code

After writing this article, I feel I have to squeeze myself dry. It is difficult to write a lot of words… And there is no copy saved, the update will be slower

Creation is not easy, reprint please note the author: nuggets @ small xizi + source link ~

If you want to learn more about Spring source code, click on the rest of the Spring series to parse line by line

٩(* ఠO ఠ)=3⁼³ milk ₃ ³ ₃ ₃ tellurab…

This is the new blogger Xiao Xizi, big guys have seen this, the top left corner of the “like” before you go ~~