How Does Spring Parse @Component and @Service Annotations? A Deep Dive

This article explains the internal workflow of Spring's annotation processing, detailing how @Component and its specializations like @Service are discovered through XML namespace handlers, classpath scanning, metadata readers, and meta‑annotation handling, complete with code excerpts and step‑by‑step analysis.

Programmer DD
Programmer DD
Programmer DD
How Does Spring Parse @Component and @Service Annotations? A Deep Dive

Preface

@Component and @Service are commonly used annotations in Spring; how does Spring resolve them?

1. @Component Parsing Process

Find Entry

Since Spring Framework 2.0, an extensible XML programming mechanism maps XML schema namespaces to handlers, configured in /META-INF/spring.handlers on the classpath.

Find Core Method

Browsing ContextNamespaceHandler reveals that the core scanning is performed by ClassPathBeanDefinitionScanner#doScan, which scans and registers bean definitions.

// Actually scan for bean definitions and register them. ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);

The method essentially iterates over candidate components, resolves scopes, generates bean names, processes annotations, and registers the definitions.

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {<br/>    Assert.notEmpty(basePackages, "At least one base package must be specified");<br/>    Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();<br/>    for (String basePackage : basePackages) {<br/>        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);<br/>        for (BeanDefinition candidate : candidates) {<br/>            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);<br/>            candidate.setScope(scopeMetadata.getScopeName());<br/>            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);<br/>            if (candidate instanceof AbstractBeanDefinition) {<br/>                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);<br/>            }<br/>            if (candidate instanceof AnnotatedBeanDefinition) {<br/>                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);<br/>            }<br/>            if (checkCandidate(beanName, candidate)) {<br/>                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);<br/>                definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);<br/>                beanDefinitions.add(definitionHolder);<br/>                registerBeanDefinition(definitionHolder, this.registry);<br/>            }<br/>        }<br/>    }<br/>    return beanDefinitions;<br/>}<br/>

The key method findCandidateComponents resides in ClassPathScanningCandidateComponentProvider and ultimately delegates to scanCandidateComponents which builds a search path, loads resources, and filters candidates.

public Set<BeanDefinition> findCandidateComponents(String basePackage) {<br/>    if (this.componentsIndex != null && indexSupportsIncludeFilters()) {<br/>        return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);<br/>    } else {<br/>        return scanCandidateComponents(basePackage);<br/>    }<br/>}<br/><br/>private Set<BeanDefinition> scanCandidateComponents(String basePackage) {<br/>    Set<BeanDefinition> candidates = new LinkedHashSet<>();<br/>    try {<br/>        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern;<br/>        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);<br/>        for (Resource resource : resources) {<br/>            if (resource.isReadable()) {<br/>                MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);<br/>                if (isCandidateComponent(metadataReader)) {<br/>                    ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);<br/>                    sbd.setSource(resource);<br/>                    if (isCandidateComponent(sbd)) {<br/>                        candidates.add(sbd);<br/>                    }<br/>                }<br/>            }<br/>        }<br/>    } catch (IOException ex) { /* omitted */ }<br/>    return candidates;<br/>}<br/>

How @Service Is Handled

Spring documentation states that @Component is a generic stereotype and that @Repository, @Service, and @Controller are specializations of it. The source of @Service shows it is meta‑annotated with @Component via @AliasFor.

@Target({ElementType.TYPE})<br/>@Retention(RetentionPolicy.RUNTIME)<br/>@Documented<br/>@Component<br/>public @interface Service {<br/>    @AliasFor(annotation = Component.class)<br/>    String value() default "";<br/>}<br/>

During scanning, AnnotationTypeFilter checks both direct annotations and meta‑annotations via metadata.hasMetaAnnotation. The metadata is built by AnnotationMetadataReadingVisitor, which records meta‑annotation relationships in a metaAnnotationMap while visiting class files.

public AnnotationVisitor visitAnnotation(String desc, boolean visible) {<br/>    String className = Type.getType(desc).getClassName();<br/>    this.annotationSet.add(className);<br/>    return new AnnotationAttributesReadingVisitor(className, this.attributesMap, this.metaAnnotationMap, this.classLoader);<br/>}<br/>

The visitor’s visitEnd method recursively collects meta‑annotations of the visited annotation and stores them in metaAnnotationMap, enabling AnnotationTypeFilter#matchSelf to recognise @Service as a candidate because it inherits from @Component.

Summary

The overall flow for discovering @Component and its derivatives is:

Convert a base package into a class‑loader search path (e.g., classpath*:com/example/**/*.class).

Load all resources matching that path.

For each resource, create a MetadataReader and determine whether it is a candidate component via isCandidateComponent, which relies on AnnotationTypeFilter and meta‑annotation detection.

Add the resulting ScannedGenericBeanDefinition objects to the list returned by findCandidateComponents.

Because @Service is meta‑annotated with @Component, the same scanning logic applies, allowing Spring to treat @Service as a component automatically.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

springAnnotation Processingcomponent scanning
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.