1.引入案例
1.1 新建一个Springboot项目
新建时,勾选了spring-web组件依赖,创建了一个TestController,配置文件完全是空的,直接跑起来:
浏览器访问http://localhost:8080/test 看看结果
看到这里,对springboot不熟悉的小伙伴就会有疑问了(大神请忽略),springboot启动的时候,到底帮我们加载了一些什么配置,别忘了,我们在创建项目的时候在pom文件添加了spring-boot-starter-web组件,点击组件进去可以看到具体的依赖项(太多了,就不贴出来了,有tomcat、webMvc的等等),
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
那么Springboot到底是如何加载我们添加的组件依赖默认组件场景的,这个需要回看我们的主启动类头上有个注解:@SpringBootApplication,点开注解进去看,发现它是个组合注解:
@Target(ElementType.TYPE) //注解的作用域
@Retention(RetentionPolicy.RUNTIME) //注解的生存周期
@Documented //可以被文档化
@Inherited //注解用于标注一个父类的注解是否可以被子类继承
@SpringBootConfiguration //声明为一个配置类 proxyBeanMethods是否开启bean代理,默认是true,从IOC容器中取;如果是false则每次获取都是一个新的实例
@EnableAutoConfiguration //开启自动配置---这个将是我们研究的重点
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) //包扫描的规则
经过分析,开启自动配置的注解是 @EnableAutoConfiguration,点开注解进去看到又是一个组合注解,我们开始分析@Target,@Retention,@Documented,@Inherited这几个上面分析了,不是重点我们忽略掉,关注点放在:@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class);
/** @author Phillip Webb
* @author Stephane Nicoll
* @since 1.0.0
* @see ConditionalOnBean
* @see ConditionalOnMissingBean
* @see ConditionalOnClass
* @see AutoConfigureAfter
* @see SpringBootApplication
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
1.2 分析 @AutoConfigurationPackage,
这个类的注释上面写着是“注册packages”,我们看到
/**
* Registers packages with {@link AutoConfigurationPackages}. When no {@link #basePackages
* base packages} or {@link #basePackageClasses base package classes} are specified, the
* package of the annotated class is registered.
*
* @author Phillip Webb
* @since 1.3.0
* @see AutoConfigurationPackages
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
@Import(AutoConfigurationPackages.Registrar.class),点进去,断点打上,看它做了什么,
step into 到方法PackageImports上,拿到了启动类BootTestApplication所在的包路径,然后注册到容器中
,正是因为有这个注解,我们上面的TestController是在这个com.wxq.csdn包路径下的,默认就能识别到,那其它路径能不能识别呢?
针对上面的疑问,我们来做一个测试,在主启动类外边再新建一个包,然后写一个TestController01,看看能不能访问到,这里给出结论是访问不到(如下面的两张图),因为Springboot自动配置包路径的时候默认是从启动类所在的包找的,其它路径它是不认识的;那有没有办法让springboot认识这个包路径,办法是有的,不然读源码干嘛,请继续往下看
针对上面问题,解决com.wxq.csdn01这个包不识别的问题,
在启动类似@ComponentScan(value = “com.wxq.csdn01”),告诉springboot启动的时候来扫描一下这个包下的类,注册和加载进容器,重启项目,访问刚刚404的地址,结果如下:
1.3 分析@Import(AutoConfigurationImportSelector.class)
1.1 先抛出结论:读取所有META-INF/spring.factories配置文件下写死的路径的类加载到容器中,主要是这个spring-boot-autoconfigure包下的spring.factories配置的类;
1.2 看看springboot是怎么找到spring.factories文件的
通过断点调试发现,主启动类的run方法实现里面调用了refreshContext(context);,这个会调用到AutoConfigurationImportSelector里面的 process方法,下面我们一步一步断点进去看,
先进来主启动类的run方法
F9继续走下去(因为我提前打好断点了,第一次调试的朋友请用F7 一步一步调进去),方法执行到上面说的process里面,注意一个getAutoConfigurationEntry,
继续走进去看到这句:List configurations = getCandidateConfigurations(annotationMetadata, attributes); 继续往下走看到这个方法,解析出来的configurations正好是我们上面说的spring.factories配置的Auto Configure
那么到这里,我们已经知道了springboot如何自动加载配置类,但是加载了不一定就生效的,我们拿下面的例子说明
打开spring-boot-autoconfigure包下的源码,看到的都是springboot默认帮我们准备好的配置场景(图没截取到全部)
我们拿jdbc这个举例,因为这个大家应该很熟悉,如图,打开DataSourceAutoConfiguration发现有报错,因为我们根本没有引入Jdbc相关的依赖,所以这个配置类是不生效的,@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class }) 这个条件不满足
我们拿一个生效的配置类举例,因为我们引入了spring-boot-starter-web组件,那么web场景的配置是生效的,我们依旧是看源码,web目录下的配置类,
点开 DispatcherServletAutoConfiguration这个自动配置类,把DispatcherServlet注册到容器中,这个是springMvc的核心,
总结
1.自动配置原理,关键在于2个注解**@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)**,上面写的看起来可能不太好懂,但是动手跟着上面的步骤去调试,相信应该能看懂。
2.自动配置类是否生效,需要看我们pom文件里面有没有使用到具体的业务场景,比如上面的案例,我们没有使用到jdbc相关的场景,它的配置类是不生效的。