|
| 1 | +springboot 在启动类上会标注一个注解:`@SpringBootApplication`,本人将从源码解析分析这 个注解的作用。 |
| 2 | + |
| 3 | +`@SpringBootApplication` 代码如下: |
| 4 | + |
| 5 | +``` |
| 6 | +@Target(ElementType.TYPE) |
| 7 | +@Retention(RetentionPolicy.RUNTIME) |
| 8 | +@Documented |
| 9 | +@Inherited |
| 10 | +@SpringBootConfiguration |
| 11 | +@EnableAutoConfiguration |
| 12 | +@ComponentScan(excludeFilters = { |
| 13 | + @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), |
| 14 | + @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) |
| 15 | +public @interface SpringBootApplication { |
| 16 | +
|
| 17 | + /** |
| 18 | + * 自动装配要排除的类,功能来自于 @EnableAutoConfiguration |
| 19 | + */ |
| 20 | + @AliasFor(annotation = EnableAutoConfiguration.class) |
| 21 | + Class<?>[] exclude() default {}; |
| 22 | +
|
| 23 | + /** |
| 24 | + * 自动装配要排除的类名,功能来自于 @EnableAutoConfiguration |
| 25 | + */ |
| 26 | + @AliasFor(annotation = EnableAutoConfiguration.class) |
| 27 | + String[] excludeName() default {}; |
| 28 | +
|
| 29 | + /** |
| 30 | + * 配置扫描的包,功能来自于 @ComponentScan |
| 31 | + */ |
| 32 | + @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") |
| 33 | + String[] scanBasePackages() default {}; |
| 34 | +
|
| 35 | + /** |
| 36 | + * 配置扫描的class,该class所在的包都会被扫描,功能来自于 @ComponentScan |
| 37 | + */ |
| 38 | + @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") |
| 39 | + Class<?>[] scanBasePackageClasses() default {}; |
| 40 | +
|
| 41 | + /** |
| 42 | + * 是否启用 @Bean 方法代理,功能来自于 @Configuration |
| 43 | + */ |
| 44 | + @AliasFor(annotation = Configuration.class) |
| 45 | + boolean proxyBeanMethods() default true; |
| 46 | +
|
| 47 | +} |
| 48 | +
|
| 49 | +``` |
| 50 | + |
| 51 | +1. `@SpringBootApplication` 是一个组合注解,包含了 `@SpringBootConfiguration`、`@EnableAutoConfiguration`、`@ComponentScan` 三个注解的功能; |
| 52 | +2. `@SpringBootApplication` 中也提供了一些配置属性,而这些属性来自于以上三个注解。 |
| 53 | + |
| 54 | +接下来我们来看看这三个注解的作用分别是什么。 |
| 55 | + |
| 56 | +### 1. `@SpringBootConfiguration` |
| 57 | + |
| 58 | +进入 `@SpringBootConfiguration`,代码如下: |
| 59 | + |
| 60 | +``` |
| 61 | +@Target(ElementType.TYPE) |
| 62 | +@Retention(RetentionPolicy.RUNTIME) |
| 63 | +@Documented |
| 64 | +@Configuration |
| 65 | +public @interface SpringBootConfiguration { |
| 66 | +
|
| 67 | + @AliasFor(annotation = Configuration.class) |
| 68 | + boolean proxyBeanMethods() default true; |
| 69 | +
|
| 70 | +} |
| 71 | +
|
| 72 | +``` |
| 73 | + |
| 74 | +这个注解比较简单,上面标记了 `@Configuration`,然后是一个属性 `proxyBeanMethods()`,它来自于 `@Configuration`。因此,`@SpringBootConfiguration` 并没有做什么,仅仅只是将 `@Configuration` 使用了 `@Configuration` 的功能。 |
| 75 | + |
| 76 | +关于 `@Configuration`,它来自于 spring,能被 spring 识别为 `Component`,且 `proxyBeanMethods != false` 时,会被 spring 标记为 `Full` 配置类,在后续对其中的 `@Bean` 方法处理时,会进行 cglib 代理,关于这方面的内容,可参考 [ConfigurationClassPostProcessor(二):处理 @Bean 注解](https://my.oschina.net/funcy/blog/4492878). |
| 77 | + |
| 78 | +### 2. `@EnableAutoConfiguration` |
| 79 | + |
| 80 | +`@EnableAutoConfiguration` 主要 用来开启自动装配功能,代码如下: |
| 81 | + |
| 82 | +``` |
| 83 | +@Target(ElementType.TYPE) |
| 84 | +@Retention(RetentionPolicy.RUNTIME) |
| 85 | +@Documented |
| 86 | +@Inherited |
| 87 | +// 自动装配的包 |
| 88 | +@AutoConfigurationPackage |
| 89 | +// 引入的自动装配类 |
| 90 | +@Import(AutoConfigurationImportSelector.class) |
| 91 | +public @interface EnableAutoConfiguration { |
| 92 | +
|
| 93 | + String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; |
| 94 | +
|
| 95 | + /** |
| 96 | + * 可自行定义排除自动装配的类 |
| 97 | + */ |
| 98 | + Class<?>[] exclude() default {}; |
| 99 | +
|
| 100 | + /** |
| 101 | + * 可自行定义排除自动装配的类名 |
| 102 | + */ |
| 103 | + String[] excludeName() default {}; |
| 104 | +
|
| 105 | +} |
| 106 | +
|
| 107 | +``` |
| 108 | + |
| 109 | +从代码中可以看到, |
| 110 | + |
| 111 | +1. 该注解组合了 `@AutoConfigurationPackage` 注解的功能,该注解用来指定自动装配的包; |
| 112 | +2. 该注解通过 `@Import` 注解引入了一个类 `AutoConfigurationImportSelector`,这个类是自动装配的关键; |
| 113 | +3. 该注解提供了两个配置,用来排除指定的自动装配类,可以根据类来排除 (`Class` 对象),也可以根据类名 (`包名.类名`) 排除。 |
| 114 | + |
| 115 | +接下来我们来关注 `@AutoConfigurationPackage` 及引入的 `AutoConfigurationImportSelector`。 |
| 116 | + |
| 117 | +#### 2.1 `@AutoConfigurationPackage` |
| 118 | + |
| 119 | +`@AutoConfigurationPackage` 指定了自动装配的包,代码如下: |
| 120 | + |
| 121 | +``` |
| 122 | +@Target(ElementType.TYPE) |
| 123 | +@Retention(RetentionPolicy.RUNTIME) |
| 124 | +@Documented |
| 125 | +@Inherited |
| 126 | +@Import(AutoConfigurationPackages.Registrar.class) |
| 127 | +public @interface AutoConfigurationPackage { |
| 128 | +
|
| 129 | +} |
| 130 | +
|
| 131 | +``` |
| 132 | + |
| 133 | +这个注解的内容非常简单,仅使用 `@Import` 注解引入了 `AutoConfigurationPackages.Registrar`,我们来看下它的内容: |
| 134 | + |
| 135 | +``` |
| 136 | +public abstract class AutoConfigurationPackages { |
| 137 | +
|
| 138 | + private static final String BEAN = AutoConfigurationPackages.class.getName(); |
| 139 | +
|
| 140 | + static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports { |
| 141 | +
|
| 142 | + /** |
| 143 | + * 根据 ImportBeanDefinitionRegistrar 的处理,spring将调用 registerBeanDefinitions() 注册内容 |
| 144 | + */ |
| 145 | + @Override |
| 146 | + public void registerBeanDefinitions(AnnotationMetadata metadata, |
| 147 | + BeanDefinitionRegistry registry) { |
| 148 | + register(registry, new PackageImport(metadata).getPackageName()); |
| 149 | + } |
| 150 | +
|
| 151 | + @Override |
| 152 | + public Set<Object> determineImports(AnnotationMetadata metadata) { |
| 153 | + return Collections.singleton(new PackageImport(metadata)); |
| 154 | + } |
| 155 | +
|
| 156 | + } |
| 157 | +
|
| 158 | + /** |
| 159 | + * 处理具体的注册操作 |
| 160 | + * 1\. 如果 beanFacotry 中包含 BEAN,则将传入的包名添加到 BEAN 对应的 BeanDefinition 的构造方法参数值上; |
| 161 | + * 2\. 如果 beanFacotry 中不包含 BEAN,则创建 beanDefinition,设置参数值,然后将其注册到 beanFacotry。 |
| 162 | + * 注册到beanFacotry中的bean为BasePackages |
| 163 | + */ |
| 164 | + public static void register(BeanDefinitionRegistry registry, String... packageNames) { |
| 165 | + if (registry.containsBeanDefinition(BEAN)) { |
| 166 | + BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN); |
| 167 | + // bean 是 BasePackages,构造方法是 BasePackages(String... names),这里获取原本的构造参数的值 |
| 168 | + ConstructorArgumentValues constructorArguments |
| 169 | + = beanDefinition.getConstructorArgumentValues(); |
| 170 | + // 将原本的构造参数值,以及传入的 packageNames 统一添加到构造方法的第0个参数值上 |
| 171 | + constructorArguments.addIndexedArgumentValue(0, |
| 172 | + addBasePackages(constructorArguments, packageNames)); |
| 173 | + } |
| 174 | + else { |
| 175 | + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); |
| 176 | + // 设置BeanClass为BasePackages.class |
| 177 | + beanDefinition.setBeanClass(BasePackages.class); |
| 178 | + // 设置构造方法的参数值 |
| 179 | + beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames); |
| 180 | + beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); |
| 181 | + registry.registerBeanDefinition(BEAN, beanDefinition); |
| 182 | + } |
| 183 | + } |
| 184 | +
|
| 185 | + /** |
| 186 | + * packageName 的包装类 |
| 187 | + * packageName 是传入类所在的包名,在PackageImport的构造方法中获取 |
| 188 | + */ |
| 189 | + private static final class PackageImport { |
| 190 | +
|
| 191 | + private final String packageName; |
| 192 | +
|
| 193 | + PackageImport(AnnotationMetadata metadata) { |
| 194 | + // 获取传入类所在包名 |
| 195 | + this.packageName = ClassUtils.getPackageName(metadata.getClassName()); |
| 196 | + } |
| 197 | +
|
| 198 | + String getPackageName() { |
| 199 | + return this.packageName; |
| 200 | + } |
| 201 | +
|
| 202 | + // 省略 equals/toString/hashCode 方法 |
| 203 | + ... |
| 204 | +
|
| 205 | + } |
| 206 | +
|
| 207 | + /** |
| 208 | + * 注解到 beanFactory 中的类 |
| 209 | + * 该类中有一个List结构,用来保存包扫描路径 |
| 210 | + */ |
| 211 | + static final class BasePackages { |
| 212 | + // 包扫描路径在这里保存 |
| 213 | + private final List<String> packages; |
| 214 | +
|
| 215 | + private boolean loggedBasePackageInfo; |
| 216 | +
|
| 217 | + BasePackages(String... names) { |
| 218 | + List<String> packages = new ArrayList<>(); |
| 219 | + for (String name : names) { |
| 220 | + if (StringUtils.hasText(name)) { |
| 221 | + packages.add(name); |
| 222 | + } |
| 223 | + } |
| 224 | + this.packages = packages; |
| 225 | + } |
| 226 | +
|
| 227 | + // 省略了一些代码 |
| 228 | + ... |
| 229 | + } |
| 230 | +
|
| 231 | +} |
| 232 | +
|
| 233 | +``` |
| 234 | + |
| 235 | +代码有点长,但逻辑并不复杂,流程如下: |
| 236 | + |
| 237 | +1. `AutoConfigurationPackages.Registrar` 实现了 `ImportBeanDefinitionRegistrar`,`registerBeanDefinitions(...)` 方法向 spring 中注册了 `BasePackages`,注册逻辑在 `AutoConfigurationPackages#register` 方法中; |
| 238 | +2. `AutoConfigurationPackages#register` 方法的注册逻辑为,先判断是否已注册了 `BasePackages`,如果注册了,就将当前类所在的包添加到 `BasePackages` 的构造方法参数值中,否则就创建 `BeanDefinition`,设置构造方法的参数值,然后注册到 spring 中; |
| 239 | + |
| 240 | +#### 2.2 `AutoConfigurationImportSelector` |
| 241 | + |
| 242 | +`AutoConfigurationImportSelector` 是处理自动配置的关键,代码如下: |
| 243 | + |
| 244 | +``` |
| 245 | +public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, |
| 246 | +
|
| 247 | + ... |
| 248 | +
|
| 249 | +} |
| 250 | +
|
| 251 | +``` |
| 252 | + |
| 253 | +`AutoConfigurationImportSelector` 实现了 `DeferredImportSelector`,这是一个 `ImportSelector` 类,但处理的优先级最低 (在 `@ComponentScan`、`@Component`、`@Bean`、`@Configuration` 及其他 `@Import` 注解处理完之后再处理),在 `AutoConfigurationImportSelector` 类中会处理自动配置类的加载流程,正是通过这种方式,将自动配置类引入了 spring 容器中。 |
| 254 | + |
| 255 | +关于 spring 对 `@Import` 的处理,可以参考 [ConfigurationClassPostProcessor 之处理 @Import 注解](https://my.oschina.net/funcy/blog/4678152). |
| 256 | + |
| 257 | +关于 `AutoConfigurationImportSelector` 获取自动配置类的流程,将在后面的文章中具体分析,本文就不展开了。 |
| 258 | + |
| 259 | +### 3. `@ComponentScan` |
| 260 | + |
| 261 | +这个注解想必大家已经很熟悉了,它指定了包扫描路径,如果不指定,就扫描所在类的包,关于这些,在 [ConfigurationClassPostProcessor 之处理 @ComponentScan 注解](https://my.oschina.net/funcy/blog/4836178)一文中已经详细分析过了,就不再分析了。 |
| 262 | + |
| 263 | +本文我们来分析这个注解属性中使用的 2 个类: |
| 264 | + |
| 265 | + |
| 266 | + |
| 267 | +#### 3.1 `TypeExcludeFilter` |
| 268 | + |
| 269 | +这个类表示在进行包扫描时,可以排除一些类,代码如下: |
| 270 | + |
| 271 | +``` |
| 272 | +public class TypeExcludeFilter implements TypeFilter, BeanFactoryAware { |
| 273 | +
|
| 274 | + private BeanFactory beanFactory; |
| 275 | +
|
| 276 | + private Collection<TypeExcludeFilter> delegates; |
| 277 | +
|
| 278 | + @Override |
| 279 | + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { |
| 280 | + this.beanFactory = beanFactory; |
| 281 | + } |
| 282 | +
|
| 283 | + @Override |
| 284 | + public boolean match(MetadataReader metadataReader, |
| 285 | + MetadataReaderFactory metadataReaderFactory) throws IOException { |
| 286 | + if (this.beanFactory instanceof ListableBeanFactory |
| 287 | + && getClass() == TypeExcludeFilter.class) { |
| 288 | + // getDelegates() 获取当前容器中所有的 TypeExcludeFilter 实例 |
| 289 | + // 可以自主继承 TypeExcludeFilter,自定义匹配规则 |
| 290 | + for (TypeExcludeFilter delegate : getDelegates()) { |
| 291 | + if (delegate.match(metadataReader, metadataReaderFactory)) { |
| 292 | + return true; |
| 293 | + } |
| 294 | + } |
| 295 | + } |
| 296 | + return false; |
| 297 | + } |
| 298 | +
|
| 299 | + private Collection<TypeExcludeFilter> getDelegates() { |
| 300 | + Collection<TypeExcludeFilter> delegates = this.delegates; |
| 301 | + if (delegates == null) { |
| 302 | + delegates = ((ListableBeanFactory) this.beanFactory) |
| 303 | + .getBeansOfType(TypeExcludeFilter.class).values(); |
| 304 | + this.delegates = delegates; |
| 305 | + } |
| 306 | + return delegates; |
| 307 | + } |
| 308 | +
|
| 309 | + .... |
| 310 | +
|
| 311 | +``` |
| 312 | + |
| 313 | +从代码上来看,如果要排除一些 类,我们可以自主继承 `TypeExcludeFilter` 类,然后重写 `match(...)` 方法,在其中定义匹配逻辑。 |
| 314 | + |
| 315 | +#### 3.1 `AutoConfigurationExcludeFilter` |
| 316 | + |
| 317 | +`AutoConfigurationExcludeFilter` 用来排除自动配置类,也就是说,spring 在进行包扫描时,不会扫描自动配置类,代码如下: |
| 318 | + |
| 319 | +``` |
| 320 | +public class AutoConfigurationExcludeFilter implements TypeFilter, BeanClassLoaderAware { |
| 321 | +
|
| 322 | + private ClassLoader beanClassLoader; |
| 323 | +
|
| 324 | + private volatile List<String> autoConfigurations; |
| 325 | +
|
| 326 | + @Override |
| 327 | + public void setBeanClassLoader(ClassLoader beanClassLoader) { |
| 328 | + this.beanClassLoader = beanClassLoader; |
| 329 | + } |
| 330 | +
|
| 331 | + @Override |
| 332 | + public boolean match(MetadataReader metadataReader, |
| 333 | + MetadataReaderFactory metadataReaderFactory) throws IOException { |
| 334 | + // isConfiguration(...):当前类是否被 @Configuration 标记 |
| 335 | + // isAutoConfiguration(...):当前类是否为自动配置类 |
| 336 | + return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader); |
| 337 | + } |
| 338 | +
|
| 339 | + private boolean isConfiguration(MetadataReader metadataReader) { |
| 340 | + return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName()); |
| 341 | + } |
| 342 | +
|
| 343 | + private boolean isAutoConfiguration(MetadataReader metadataReader) { |
| 344 | + // 获取所有的自动配置类,然后判断当前类是否存在于其中 |
| 345 | + return getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName()); |
| 346 | + } |
| 347 | +
|
| 348 | + protected List<String> getAutoConfigurations() { |
| 349 | + if (this.autoConfigurations == null) { |
| 350 | + this.autoConfigurations = SpringFactoriesLoader |
| 351 | + .loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader); |
| 352 | + } |
| 353 | + return this.autoConfigurations; |
| 354 | + } |
| 355 | +
|
| 356 | +} |
| 357 | +
|
| 358 | +``` |
| 359 | + |
| 360 | +我们主要看 `match(...)` 方法,它的匹配的类为: |
| 361 | + |
| 362 | +1. 被 `@Configuration` 标记; |
| 363 | +2. 是自动配置类。 |
| 364 | + |
| 365 | +满足以上两个条件,spring 就不会对其进行扫描处理。 |
| 366 | + |
| 367 | +那什么是自动配置类呢?从 `isAutoConfiguration(...)` 可以看到,在判断是否为自动配置类上,springboot 先使用 `SpringFactoriesLoader` 加载所有配置类,然后再判断传入的类是否为其中之一。从这里可以看出,自动配置类并不进行包扫描操作。 |
| 368 | + |
| 369 | +关于 `SpringFactoriesLoader` 如何加载配置类,后面的文章会详细分析。 |
| 370 | + |
| 371 | +### 4\. 总结 |
| 372 | + |
| 373 | +本文主要分析 `@SpringBootApplication` 的功能,总结如下: |
| 374 | + |
| 375 | +1. `@SpringBootApplication` 是一个组合注解,包含了 `@SpringBootConfiguration`、`@EnableAutoConfiguration`、`@ComponentScan` 三个注解的功能,同时提供了一些属性配置,也是来自于以上 3 个注解; |
| 376 | +2. `@SpringBootConfiguration` 包含了 `Configuration` 注解的功能; |
| 377 | +3. `@EnableAutoConfiguration` 是开启自动装配的关键注解,其中标记了 `@AutoConfigurationPackage`,会将被 `@SpringBootApplication` 标记的类所在的包,包装成 `BasePackages`,然后注册到 spring 容器中;`@EnableAutoConfiguration` 还通过 `@Import` 注解向容器中引入了 `AutoConfigurationImportSelector`,该类会将当前项目支持的自动配置类添加到 spring 容器中; |
| 378 | +4. `@ComponentScan` 定义了包扫描路径,其 `excludeFilters` 值可以用来排除类的扫描,springboot 指定了 `TypeExcludeFilter`,表明我们可以继承该类来自主定义排除的类 ;同时也指定了 `AutoConfigurationExcludeFilter` ,该 `Filter` 可以用来排除自动配置类,也就是说,自动配置类不会进行包描述操作。 |
| 379 | + |
| 380 | +* * * |
| 381 | + |
| 382 | +_本文原文链接:[https://my.oschina.net/funcy/blog/4870882](https://my.oschina.net/funcy/blog/4870882) ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。_ |
0 commit comments