本文基于Spring 5.2.7
这是个很大的话题,但是是个非常实在的话题,注解天天用,处处用,请问你知道他是怎么起作用的吗?
如果你使用了注解,那么一定有代码在什么地方检索这个注解,并为这个注解的语义写了对应的逻辑。但是注解本身是没有任何行为的,他只是一个标记而已。所以注解是惰性的,看到注解时并没有行为,你需要找到注解对应的行为才能理解注解实际的作用。
但是要找到一个注解对应的行为很难,因为很多注解都是些三方库提供的,要想找到每个注解对应的行为有如大海捞针,能搞清楚这个知识点很难。
一、SpringBoot启动
SpringBoot要解析哪些注解在代码中已经写死了,所有注解的解析都是按照既定的顺序在执行,如@Configuration,@Import,这些都会在启动过程中解析,所以一切的起点是启动类上,但是,我们会看到启动类上往往是一个汇总的注解,如@EnableTransactionManagement,然后这个类上面会引入其他注解,那Spring是怎么解析到他引入的注解的呢?
原理就是Spring在扫描注解是一种递归的方式,比如在处理@Import注解时,就会递归处理:
org.springframework.context.annotation.ConfigurationClassParser#collectImports(...)
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
这是Spring在启动解析配置时,递归解析注解,这里的sourceClass就是启动类,然后递归检查启动类上的@Import注解,所以才能检查到注解上的注解。
一般而言,spring会扫描所有的类,而且会扫描这些类上面的注解,Spring自身提供了很多注解,你写了Spring提供的注解,他就会扫描到,进而执行响应的动作。
你可以在启动类上写很多注解,必须要写的注解是org.springframework.boot.autoconfigure.SpringBootApplication
我们已经写了main()方法,SpringBoot已经自主启动运行,此时他会回过头来扫描启动类上的注解并解析,其实SpringBootApplication这个注解本身并没有语义,他是一个复合注解,用于引入其他的注解,这就要求Spring在扫描注解时能够递归扫描,实际上Spring就是这么做的。在判断一个bean上是否有某个注解时,Spring都是递归解析注解的。或者说所有的复合注解都是没有语义的,有语义的是复合注解上的单个注解。
@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 {
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
/**
* Base packages to scan for annotated components. Use {@link #scanBasePackageClasses}
* for a type-safe alternative to String-based package names.
* <p>
* <strong>Note:</strong> this setting is an alias for
* {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
* scanning or Spring Data {@link Repository} scanning. For those you should add
* {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
* {@code @Enable...Repositories} annotations.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
/**
* Type-safe alternative to {@link #scanBasePackages} for specifying the packages to
* scan for annotated components. The package of each class specified will be scanned.
* <p>
* Consider creating a special no-op marker class or interface in each package that
* serves no purpose other than being referenced by this attribute.
* <p>
* <strong>Note:</strong> this setting is an alias for
* {@link ComponentScan @ComponentScan} only. It has no effect on {@code @Entity}
* scanning or Spring Data {@link Repository} scanning. For those you should add
* {@link org.springframework.boot.autoconfigure.domain.EntityScan @EntityScan} and
* {@code @Enable...Repositories} annotations.
* @return base packages to scan
* @since 1.3.0
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
/**
* Specify whether {@link Bean @Bean} methods should get proxied in order to enforce
* bean lifecycle behavior, e.g. to return shared singleton bean instances even in
* case of direct {@code @Bean} method calls in user code. This feature requires
* method interception, implemented through a runtime-generated CGLIB subclass which
* comes with limitations such as the configuration class and its methods not being
* allowed to declare {@code final}.
* <p>
* The default is {@code true}, allowing for 'inter-bean references' within the
* configuration class as well as for external calls to this configuration's
* {@code @Bean} methods, e.g. from another configuration class. If this is not needed
* since each of this particular configuration's {@code @Bean} methods is
* self-contained and designed as a plain factory method for container use, switch
* this flag to {@code false} in order to avoid CGLIB subclass processing.
* <p>
* Turning off bean method interception effectively processes {@code @Bean} methods
* individually like when declared on non-{@code @Configuration} classes, a.k.a.
* "@Bean Lite Mode" (see {@link Bean @Bean's javadoc}). It is therefore behaviorally
* equivalent to removing the {@code @Configuration} stereotype.
* @since 2.2
* @return whether to proxy {@code @Bean} methods
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
SpringBoot的run()方法里已经执行了很多事情,他里面就会扫描所有bean,并处理相关注解。启动类上面的注解此时也会被处理。