@componentscan 配置多个扫描包_Spring Boot到底是怎么做到自动配置的?

本文探讨了Spring Boot、Spring MVC和Spring的区别,强调Spring Boot的自动配置特性,解释了如何通过@SpringBootApplication注解实现自动配置。文章详细解析了@Configuration、@ComponentScan和@EnableAutoConfiguration的作用,阐述了SpringFactoriesLoader在自动配置过程中的角色,以及AutoConfigurationImportSelector的import选择逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

来源:http://suo.im/5zy65S

SpringBoot 的故事从一个面试题开始

Spring Boot、Spring MVC 和 Spring 有什么区别?

fba4ec505a90fc57033b75c239d0e1a5.png

先来个 SpringBoot 的启动结构图

分开描述各自的特征

Spring 框架就像一个家族,有众多衍生产品例如 boot、security、jpa等等。但他们的基础都是Spring 的ioc和 aop,ioc 提供了依赖注入的容器, aop解决了面向横切面的编程,然后在此两者的基础上实现了其他延伸产品的高级功能。

Spring MVC提供了一种轻度耦合的方式来开发web应用。它是Spring的一个模块,是一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等。

Spring Boot实现了自动配置,降低了项目搭建的复杂度。它主要是为了解决使用Spring框架需要进行大量的配置太麻烦的问题,所以它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),Spring Boot应用中这些第三方库几乎可以零配置的开箱即用(out-of-the-box)。

所以,用最简练的语言概括就是

. Spring 是一个“引擎”;. Spring MVC 是基于Spring的一个 MVC 框架;. Spring Boot 是基于Spring4的条件注册的一套快速开发整合包。

bda72be480e65dc59cbae85b546eb871.png

SpringBoot到底是怎么做到自动配置的?

从代码里看项目SpringBoot的项目启动类只有一个注解@SpringBootApplication和一个run方法。

@SpringBootApplication

public class Application {

public static void main(String[] args) {

SpringApplication.run(Application.class, args);

}

}

直接看@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 {};

}

@SpringBootApplication:包含了@SpringBootConfiguration(打开是@Configuration),@EnableAutoConfiguration,@ComponentScan注解。

@Configuration

JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。
对比一下传统XML方式和config配置方式的区别:

XML声明和定义配置方式

xml version="1.0" encoding="UTF-8"?>

xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"

xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/aop

http://www.springframework.org/schema/aop/spring-aop-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-3.0.xsd

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx-3.0.xsd

">

id="app" class="com..." />

用一个过滤器举例,JavaConfig的配置方式是这样

@Configuration

public class DruidConfiguration {

@Bean

public FilterRegistrationBean statFilter(){

//创建过滤器

FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());

//设置过滤器过滤路径

filterRegistrationBean.addUrlPatterns("/*");

//忽略过滤的形式

filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");

return filterRegistrationBean;

}

}

任何一个标注了@Configuration的Java类定义都是一个JavaConfig配置类。任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。

@ComponentScan

@ComponentScan对应XML配置中的元素,@ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component和@Repository等)或者bean定义,最终将这些bean定义加载到IoC容器中。
我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明@ComponentScan所在类的package进行扫描。注:所以SpringBoot的启动类最好是放在rootpackage下,因为默认不指定basePackages。

@EnableAutoConfiguration

(核心内容)看英文意思就是自动配置,概括一下就是,借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器。

@Target({ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

@AutoConfigurationPackage

@Import({AutoConfigurationImportSelector.class})

public @interface EnableAutoConfiguration {

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

Class>[] exclude() default {};

String[] excludeName() default {};

}

里面最关键的是@Import(EnableAutoConfigurationImportSelector.class),借助EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。该配置模块的主要使用到了SpringFactoriesLoader。

SpringFactoriesLoader详解

SpringFactoriesLoader为Spring工厂加载器,该对象提供了loadFactoryNames方法,入参为factoryClass和classLoader即需要传入工厂类名称和对应的类加载器,方法会根据指定的classLoader,加载该类加器搜索路径下的指定文件,即spring.factories文件,传入的工厂类为接口,而文件中对应的类则是接口的实现类,或最终作为实现类。

public abstract class SpringFactoriesLoader {

//...

public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {

...

}

public static List<String> loadFactoryNames(Class> factoryClass, ClassLoader classLoader) {

....

}

}

所以文件中一般为如下图这种一对多的类名集合,获取到这些实现类的类名后,loadFactoryNames方法返回类名集合,方法调用方得到这些集合后,再通过反射获取这些类的类对象、构造方法,最终生成实例。

96ac67d25af2bc78810d418754317475.png下图有助于我们形象理解自动配置流程(盗个图)

7eec6bad08ad81e1828c3987918865e5.png

AutoConfigurationImportSelector

继续上面讲的AutoConfigurationImportSelector.class。该类主要关注selectImports方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {

if (!this.isEnabled(annotationMetadata)) {

return NO_IMPORTS;

} else {

AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);

AnnotationAttributes attributes = this.getAttributes(annotationMetadata);

List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);

configurations = this.removeDuplicates(configurations);

Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);

this.checkExcludedClasses(configurations, exclusions);

configurations.removeAll(exclusions);

configurations = this.filter(configurations, autoConfigurationMetadata);

this.fireAutoConfigurationImportEvents(configurations, exclusions);

return StringUtils.toStringArray(configurations);

}

}

该方法在springboot启动流程——bean实例化前被执行,返回要实例化的类信息列表。如果获取到类信息,spring可以通过类加载器将类加载到jvm中,现在我们已经通过spring-boot的starter依赖方式依赖了我们需要的组件,那么这些组件的类信息在select方法中就可以被获取到。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {

List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());

Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");

return configurations;

}

方法中的getCandidateConfigurations方法,其返回一个自动配置类的类名列表,方法调用了loadFactoryNames方法,查看该方法

public static List<String> loadFactoryNames(Class> factoryClass, @Nullable ClassLoader classLoader) {

String factoryClassName = factoryClass.getName();

return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());

}

自动配置器会跟根据传入的factoryClass.getName()到项目系统路径下所有的spring.factories文件中找到相应的key,从而加载里面的类。我们就选取这个mybatis-spring-boot-autoconfigure下的spring.factories文件

# Auto Configure

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

进入org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration中,又是一堆注解

@org.springframework.context.annotation.Configuration

@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})

@ConditionalOnBean({DataSource.class})

@EnableConfigurationProperties({MybatisProperties.class})

@AutoConfigureAfter({DataSourceAutoConfiguration.class})

public class MybatisAutoConfiguration

{

private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);

private final MybatisProperties properties;

private final Interceptor[] interceptors;

private final ResourceLoader resourceLoader;

private final DatabaseIdProvider databaseIdProvider;

private final List<ConfigurationCustomizer> configurationCustomizers;

. @Spring的Configuration是一个通过注解标注的springBean,. @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class})这个注解的意思是:当存在SqlSessionFactory.class, SqlSessionFactoryBean.class这两个类时才解析MybatisAutoConfiguration配置类,否则不解析这一个配置类。我们需要mybatis为我们返回会话对象,就必须有会话工厂相关类. @CondtionalOnBean(DataSource.class):只有处理已经被声明为bean的dataSource. @ConditionalOnMissingBean(MapperFactoryBean.class)这个注解的意思是如果容器中不存在name指定的bean则创建bean注入,否则不执行

以上配置可以保证sqlSessionFactory、sqlSessionTemplate、dataSource等mybatis所需的组件均可被自动配置,@Configuration注解已经提供了Spring的上下文环境,所以以上组件的配置方式与Spring启动时通过mybatis.xml文件进行配置起到一个效果。只要一个基于SpringBoot项目的类路径下存在SqlSessionFactory.class, SqlSessionFactoryBean.class,并且容器中已经注册了dataSourceBean,就可以触发自动化配置,意思说我们只要在maven的项目中加入了mybatis所需要的若干依赖,就可以触发自动配置,但引入mybatis原生依赖的话,每集成一个功能都要去修改其自动化配置类,那就得不到开箱即用的效果了。所以Spring-boot为我们提供了统一的starter可以直接配置好相关的类,触发自动配置所需的依赖(mybatis)如下:

org.mybatis.spring.boot

mybatis-spring-boot-starter

因为maven依赖的传递性,我们只要依赖starter就可以依赖到所有需要自动配置的类,实现开箱即用的功能。也体现出Springboot简化了Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发。

【面试题专栏】

吃透这份pdf,面试阿里、腾讯、百度等一线大厂,顺利拿下心仪offer!

全网最全Spring系列面试题129道(附答案解析)

2万字Java并发编程面试题整理(含答案,建议收藏)

85道Java微服务面试题整理(助力2020面试)

【2020年大厂面试必备】JVM与性能调优知识点整理

2019年面试官最喜欢问的28道ZooKeeper面试题

2019年全网最热门的123个Java并发面试题总结

全网最热门的119个Spring问题,哪些你还不会?

2020面试还搞不懂MyBatis?看看这27道面试题!(含答案和思维导图)

2020年去一线大厂面试先过SSM框架这一关!

Spring Cloud+Spring Boot高频面试题解析

2019年常见的Linux面试题及答案解析,哪些你还不会?

2019年常见Elasticsearch面试题答案解析

18道kafka高频面试题哪些你还不会?(含答案和思维导图)

2019年12道RabbitMQ高频面试题你都会了吗?(含答案解析)

2019年Dubbo你掌握的如何?快看看这30道高频面试题!

2019年228道Java中高级面试题(8),哪些你还不会?

85983d750e20017a20197f3af34887d8.png

0390b57fe16c6dcc87cbf3464e85fffe.png 你在看吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值