专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

SpringBoot深入理解 -- @AliasFor注解的作用

SpringBoot深入理解 — @AliasFor注解的作用
SpringBoot源码解析 — SpringBoot启动过程
SpringBoot源码解析 — AutoConfigure的实现原理
SpringBoot源码解析 — @ComponentScan的实现原理
SpringBoot源码解析 — @Value,@Autowired实现原理
SpringBoot源码解析 — Tomcat,SpringMVC启动
SpringBoot源码解析 — Logging,Environment启动

本文解析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。

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

94_1.png

文章永久链接:https://tech.souyunku.com/28308

未经允许不得转载:搜云库技术团队 » SpringBoot深入理解 -- @AliasFor注解的作用

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们