2

本文解析SpringBoot中重要注解@AliasFor注解的作用,对于理解SpringBoot和后面阅读SpringBoot源码都很有帮助。

我们都知道@SpringBootApplication注解,等于@EnableAutoConfiguration,@ComponentScan,@SpringBootConfiguration三个注解的组合。
Spring是怎样将三个注解的整合到一个注解的呢?
这就要说到@AliasFor了

AliasFor可以定义一个注解中的两个属性互为别名。

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

ComponentScan中的value和basePackages作用是一样的。

@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]);
    }
}

结果都是com.binecy

有了AliasFor的好处是,如果我们只需要指定basePackages,可以使用value属性,并且省略value属性
@ComponentScan("com.binecy")
如果除了basePackages,还有其他属性,可以使用
@ComponentScan(basePackages = "com.binecy", lazyInit = true)
将value属性换成basePackages,更明确清晰。

跨注解的属性别名
不仅是一个注解内不同属性可以声明别名,不同注解的属性也可以声明别名(注解可以作用于注解)

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

@Service#value为@Component#value的别名,@Service#value的值可以映射到@Component#value。
(这里我们将@Service,@Component看做一种特殊的继承关系,@Component是父注解,@Service是子注解,@Service#value覆盖@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);
    }
}

输出

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

可以看到,虽然ServiceAlias上只有@Service,但通过AnnotationUtils.getAnnotation方法会解析得到@Component,而通过AnnotatedElementUtils.getMergedAnnotation方法还可以将@Service#value的值赋给@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);
    }

Spring内部实现并不复杂,在java中,注解是使用动态代理类实现,Spring中同理。

回来看@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 {};

}

通过@AliasFor,即使用户使用的是@SpringBootApplication,
Spring还是可以通过AnnotationUtils#getAnnotation,AnnotatedElementUtils#getMergedAnnotation等方法,解析到@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan等注解,并取得对应属性。

理解这点对后面看SpringBoot源码帮助很大。

还有@Repeatable注解是jdk8新增的注解,可以将多个注解替换为一个数组注解

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

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

ComponentScans中values属性是一个ComponentScan数组,这里@Repeatable表示当配置了多个@ComponentScan时,@ComponentScan可以被@ComponentScans代替(jdk8中支持重复的注解)

@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]);
        }
    }
}

ComponentScansService 上配置了两个ComponentScan,这时两个@ComponentScan可以被解析@ComponentScans。

如果您觉得本文不错,欢迎关注我的微信公众号,您的关注是我坚持的动力!


binecy
49 声望18 粉丝