Springboot in depth understanding the role of @ alias for annotation

Time:2020-9-16

This paper analyzes the function of the annotation @ alias for in springboot, which is helpful to understand springboot and read the source code of springboot.

We all know that the @ springbootapplication annotation is equal to the combination of @ enableautoconfiguration, @ componentscan and @ springbootconfiguration.
How does spring integrate three annotations into one annotation?
This is about @ aliasfor

Aliasfor can define two attributes in an annotation as aliases to each other.
as

public @interface ComponentScan {
    @AliasFor("basePackages")
    String[] value() default {};
    
    @AliasFor("value")
    String[] basePackages() default {};
    
    boolean lazyInit() default false;
    ...
}

The role of value and basepackages in componentscan is the same.

@ComponentScan("com.binecy")
public class SimpleAlias {

    public static void main(String[] args) {
        ComponentScan ann = AnnotationUtils.getAnnotation(SimpleAlias.class, ComponentScan.class);
        System.out.println(ann.value()[0]);
        System.out.println(ann.basePackages()[0]);
    }
}

It all turned out to be com.binecy

The advantage of aliasfor is that if we only need to specify basepackages, we can use the value attribute and omit the value attribute
@ComponentScan("com.binecy")
If there are other properties besides basepackages, you can use the
@ComponentScan(basePackages = "com.binecy", lazyInit = true)
Change the value attribute to basepackages for clarity.

Cross annotation attribute aliases
Not only can different attributes in an annotation declare aliases, but attributes of different annotations can also declare aliases (annotations can act on annotations)

@Component
public @interface Service {
    @AliasFor(annotation = Component.class)
    String value() default "";
}

@Service ා value is the alias of @ component # value, and the value of @ service ා value can be mapped to @ component ා value.
(here we consider @ service, @ component as a special kind ofinheritRelationship, @ component is the parent annotation, @ service is the child annotation, @ service # value covers @ component # value)

demo

@Service("serviceAlias")
public class ServiceAlias {

    public static void main(String[] args) {
        Component component = AnnotationUtils.getAnnotation(ServiceAlias.class, Component.class);
        System.out.println(component);
        
        Component component2 = AnnotatedElementUtils.getMergedAnnotation(ServiceAlias.class, Component.class);
        System.out.println(component2);
    }
}

output

@org.springframework.stereotype.Component(value=)
@org.springframework.stereotype.Component(value=serviceAlias)

You can see that although there is only @ service on servicealias, the AnnotationUtils.getAnnotation Method will parse to get @ component, and through a nnotatedElementUtils.getMergedAnnotation Method can also assign the value of @ service # value to @ component ා value.

AnnotationUtils#getAnnotation -> synthesizeAnnotation

    static <A extends Annotation> A synthesizeAnnotation(A annotation, @Nullable Object annotatedElement) {
        ...

        DefaultAnnotationAttributeExtractor attributeExtractor =
                new DefaultAnnotationAttributeExtractor(annotation, annotatedElement);
        InvocationHandler handler = new SynthesizedAnnotationInvocationHandler(attributeExtractor);

        // Can always expose Spring's SynthesizedAnnotation marker since we explicitly check for a
        // synthesizable annotation before (which needs to declare @AliasFor from the same package)
        Class<?>[] exposedInterfaces = new Class<?>[] {annotationType, SynthesizedAnnotation.class};
        return (A) Proxy.newProxyInstance(annotation.getClass().getClassLoader(), exposedInterfaces, handler);
    }

The internal implementation of spring is not complicated. In Java, annotation is implemented by using dynamic proxy class, which is the same in spring.

Come back to see @ springbootapplication,

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

Through @ aliasfor, even if the user is using @ springbootapplication,
Spring can still parse @ springbootconfiguration, @ enableautoconfiguration, @ componentscan and other annotations through annotationutils ා getannotation, annotatedelementutils ා getmergedannotation, and obtain the corresponding properties.

Understanding this will help you to see the source code of springboot.

The @ repeatable annotation is a new annotation in jdk8, which can replace multiple annotations with an array annotation

@Repeatable(ComponentScans.class)
public @interface ComponentScan {
...
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ComponentScans {
    ComponentScan[] value();
}

The values property in componentscans is a componentscan array, where @ repeatable indicates that @ componentscan can be replaced by @ componentscans when multiple @ componentscans are configured (jdk8 supports duplicate annotations)

@ComponentScan("com.binecy.bean")
@ComponentScan("com.binecy.service")
public class ComponentScansService {
    public static void main(String[] args) {
        ComponentScans scans = ComponentScansService.class.getAnnotation(ComponentScans.class);
        for (ComponentScan componentScan : scans.value()) {
            System.out.println(componentScan.value()[0]);
        }
    }
}

Two componentscans are configured on componentscansservice. At this time, two @ componentscans can be resolved @ componentscans.

If you think this article is good, welcome to pay attention to my WeChat official account, your attention is my insisting power!
Springboot in depth understanding the role of @ alias for annotation