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

7,323 阅读3分钟

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。

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