springBoot starter原理

通常搭建一个基于spring的web应用,我们需要做以下工作:

  1. pom文件中引入相关的jar包,包括spring、spring mvc、redis、mybatis、log4j、mysql-connector-java等相关的jar
  2. 配置web.xml,listenter配置、Filter配置、Servlet配置、log4j配置、error配置
  3. 配置数据库连接、配置spring事务
  4. 配置视图解析器
  5. 开启注解、自动扫描功能
  6. 配置完成后部署tomcat、启动调试

springBoot拥有很多方便使用的starter,spring提供的stater命名规范spring-boot-stater-xxx.jar,第三方提供的starter命名规范xxx-spring-boot-stater.jar。比如spring-boot-starter-log4j、mybatis-spring-boot-starter.jar等,各自代表了一个相对完整的功能模块。

以mybatis-spring-boot-starter为例,我们分析一下starter做了哪些操作

springBoot starter原理_第1张图片

可以看到在mybatis-spring-boot-starter中,并没有任何的源码,只有一个pom文件,它的作用就是帮我们引入mybatis-spring-boot-autoconfigure这个包

springBoot starter原理_第2张图片

@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnBean({DataSource.class})
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MybatisAutoConfiguration {
    private static Log log = LogFactory.getLog(MybatisAutoConfiguration.class);
    @Autowired
    private MybatisProperties properties;
    @Autowired(
        required = false
    )
    private Interceptor[] interceptors;
    @Autowired
    private ResourceLoader resourceLoader = new DefaultResourceLoader();
    @Autowired(
        required = false
    )
    private DatabaseIdProvider databaseIdProvider;


    public MybatisAutoConfiguration() {
    }


    @PostConstruct
    public void checkConfigFileExists() {
        if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
            Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
            Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis " + "configuration)");
        }


    }


    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }


        factory.setConfiguration(this.properties.getConfiguration());
        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }


        if (this.databaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.databaseIdProvider);
        }


        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }


        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }


        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
            factory.setMapperLocations(this.properties.resolveMapperLocations());
        }


        return factory.getObject();
    }


    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
    }

@Configuration @Bean一起使用就可以创建一个基于java代码的配置类,用来代替相应的xml配置文件。MybatisAutoConfiguration自动帮我们生成了SqlSessionFactory这些Mybatis的重要实例并交给spring容器管理,从而完成bean的自动注册。

从MybatisAutoConfiguration这个类中使用的注解可以看出,要完成自动配置是有以来条件的。

这些是springboot特有的,常见的条件依赖注解有:

  • @ConditionalOnBean,仅在当前上下文中存在某个bean时,才会实例化这个bean
  • @ConditionalClass,某个class位于类路径上,才会实例化这个bean
  • @ConditionalOnExperssion,当表达式为true时,才会实例化这个Bean
  • @ConditionalOnMissingBean,仅当当前上下文不存在某个bean时,才会实例化这个Bean
  • @ConditionalOnMissingClass,某个class在类路径上不存在的时候,才会实例化这个Bean
  • @ConditionalOnNotWebApplication,不是web应用时才会实例化这个Bean
  • @AutoConfigureAfter,在某个bean完成自动配置后实例化这个bean
  • @AutoConfigureBefore,在某个bean完成自动配置前实例化这个bean

所以要完成Mybatis的自动配置,需要在类路径中存在SqlsessionFactory.class、SqlSessionFactoryBean.class这两个类,需要存在DataSource这个Bean且bean完成自动注册
bean参数的获取

springBoot starter原理_第3张图片

springBoot starter原理_第4张图片

  • @EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效
  • @ConfigurationProperties 注解的作用是把yml或者properties配置文件转换为bean

springboot默认扫描启动类所在的包下的主类与子类的所有组件,但并没有包括依赖包中的类,那么依赖包中的bean是如何被发现和加载的?

springBoot starter原理_第5张图片

在springboot项目中,启动类通常都有@SpringBootApplication注解

springBoot starter原理_第6张图片

@SpringBootApplication注解中又包含

  • @SpringBootConfiguration == @Configuration,标识被注解的类将成为一个bean的配置类
  • @ComponentScan 扫描并加载符合条件的组件,比如@Controller @Service等
  • @EnableAutoConfiguration 这个注解借助@Import的支持,收集和注册依赖包中相关的bean定义

springBoot starter原理_第7张图片

虽然根据说明我们应该看EnableAutoConfigurationImportSelector。但是该类在SpringBoot 1.5.x版本已经过时了。因此我们来看下它的父类AutoConfigurationImportSelector

springBoot starter原理_第8张图片

AutoConfigurationImportSelector首先实现了DeferredImportSelector接口,这个接口继承了ImportSelector。ImportSelector接口主要是为了导入@Configuration的配置项,而DeferredImportSelector是延期导入,当所有的@Configuration都处理后才会执行。我们主要看下AutoConfigurationImportSelector的selectImports方法:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    private static final String[] NO_IMPORTS = new String[0];
    private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
    private ConfigurableListableBeanFactory beanFactory;
    private Environment environment;
    private ClassLoader beanClassLoader;
    private ResourceLoader resourceLoader;


    public AutoConfigurationImportSelector() {
    }


    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            try {
                AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
                AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
                List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
                configurations = this.removeDuplicates(configurations);
                configurations = this.sort(configurations, autoConfigurationMetadata);
                Set exclusions = this.getExclusions(annotationMetadata, attributes);
                this.checkExcludedClasses(configurations, exclusions);
                configurations.removeAll(exclusions);
                configurations = this.filter(configurations, autoConfigurationMetadata);
                this.fireAutoConfigurationImportEvents(configurations, exclusions);
                return (String[])configurations.toArray(new String[configurations.size()]);
            } catch (IOException var6) {
                throw new IllegalStateException(var6);
            }
        }
    }

    protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List 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;
    }
public static List loadFactoryNames(Class factoryClass, ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();


    try {
        Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
        ArrayList result = new ArrayList();


        while(urls.hasMoreElements()) {
            URL url = (URL)urls.nextElement();
            Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
            String factoryClassNames = properties.getProperty(factoryClassName);
            result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
        }


        return result;
    } catch (IOException var8) {
        throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
    }
}

可以看到该方法会使用SpringFactoryLoader,它会读取META-INF/spring.factories文件里所配置的EnableAutoConfiguration。经过exclude和filter等操作,最终确定要装配的类

自动配置的关键几步,以及相应的注解如下:

  1. @Configuration与@Bean -》基于java代码的bean配置
  2. @Conditional -》设置自动配置条件依赖
  3. @EnableConfigurationProperties 与@ConfigurationProperties 读取配置文件转换为bean
  4. @EnableAutoConfiguration、@AutoConfiguration Package 、@Import实现bean的发现与加载

让一个普通类交给spring 容器管理,通常有以下方法

  1. 使用@Configuration与@Bean注解
  2. 使用@Controller、@Service、@Repository

你可能感兴趣的:(面试)