【Spring Boot】Spring Boot 中的 Starter

avatar
作者
筋斗云
阅读量:0

Spring Boot 中的 Starter

我们都知道,Spring 的功能非常强大,但也有些弊端。比如:我们需要手动去配置大量的参数,没有默认值,需要我们管理大量的 jar 包和它们的依赖。为了提升 Spring 项目的开发效率,简化一些配置,Spring 官方引入了 SpringBoot。当然,引入 SpringBoot 还有其他原因,在这里就不过多描述了。

而 Spring Boot 为了简化配置,提供了非常多的 Starter。它先打包好与常用模块相关的所有 JAR 包,并完成自动配置,然后组装成 Starter(如把 Web 相关的 Spring MVC、容器等打包好后组装成 spring-boot-starter-web)。这使得在开发业务代码时不需要过多关注框架的配置,只需关注业务逻辑即可。

1.常用 Starter

Spring Boot 提供了很多开箱即用的 Starter,大概有近 50 种,常见的如下表所示。

Starter
说明
spring-boot-starter-web用于构建 Web。包含 RESTful 风格框架、SpringMVC 和默认的嵌入式容器 Tomcat
spring-boot-starter-test用于测试
spring-boot-starter-data-jpa带有 Hibernate 的 Spring Data JPA
spring-boot-starter-jdbc传统的 JDBC。轻量级应用可以使用,学习成本低,但最好使用 JPA 或 MyBatis
spring-boot-starter-thymeleaf支持 Thymeleaf 模板
spring-boot-starter-mail支持 Java Mail、Spring Email 发送邮件
spring-boot-starter-integrationSpring 框架创建的一个 API,面向企业应用集成(EAI)
spring-boot-starter-mobileSpring MVC 的扩展,用来简化手机上的 Web 应用程序开发
spring-boot-starter-data-redis通过 Spring Data Redis、Redis Client 使用 Redis
spring-boot-starter-validationBean Validation 是一个数据验证的规范,Hibernate Validator 是一个数据验证框架
spring-boot-starter-websocket相对于非持久的协议 HTTP,Websocket 是一个持久化的协议
spring-boot-starter-web-servicesSOAP Web Services
spring-boot-starter-hateoas为服务添加 HATEOAS 功能
spring-boot-starter-security用 Spring Security 进行身份验证和授权
spring-boot-starter-data-rest用 Spring Data REST 公布简单的 REST 服务

如果想使用 Spring 的 JPA 操作数据库,则需要在项目中添加 spring-boot-starter-data-jpa 依赖,即在 pom.xml 文件中的 <dependencies> 和元素之间加入依赖,具体代如下:

<dependencies> 	... 	<dependency> 		<groupId>org.springframework.boot</groupId> 		<artifactId>spring-boot-starter-data-jpa</artifactId> 	</dependency> 	... </dependencies> 

如果依赖项没有版本号,则 Spring Boot 会根据自己的版本号自动关联。如果需要特定的版本则需要加上 version 元素。

2.为什么要用 Starter

在 SpringBoot 还没有出来之前,我们使用 Spring 开发项目。如果程序需要连接数据库,我们一般会使用 Hibernate 或 Mybatis 等 ORM 框架,这里我以 Mybatis 为例,具体的操作步骤如下:

  • 到 Maven 仓库去找需要引入的 mybatisjar 包,选取合适的版本。
  • 到 Maven 仓库去找 mybatis-spring 整合的 jar 包,选取合适的版本。
  • 在 Spring 的 applicationContext.xml 文件中配置 dataSourcemybatis 相关信息。

当然有些朋友可能会指正,不是还需要引入数据库驱动包吗?

确实需要引入,但数据库驱动有很多,比如:mysqloraclesqlserver,这不属于 mybatis 的范畴,使用者可以根据项目的实际情况单独引入。

如果程序只是需要连接数据库这一个功能还好,按上面的步骤做基本可以满足需求。但是,连接数据库可能只是庞大的项目体系中一个环节,实际项目中往往更复杂,需要引入更多的功能,比如:连接 redis、连接 mongodb、使用 rocketmq、使用 excel 功能等等。

引入这些功能的话,需要再把上面的步骤再重复一次,工作量无形当中增加了不少,而且有很多重复的工作。

另外,还是有个问题,每次到要到 Maven 中找合适的版本,如果哪次找的 mybatis.jar 包和 mybatis-spring.jar 包版本不兼容,程序不是会出现问题?

SpringBoot 为了解决以上两个问题引入了 Starter 机制。

3.Starter 有哪些要素

我们首先一起看看 mybatis-spring-boot-starter.jar 是如何定义的。

在这里插入图片描述
可以看到它的 META-INF 目录下只包含了:

  • pom.protperties:配置 Maven 所需的项目 versiongroupIdartifactId
  • pom.xml:配置所依赖的 jar 包。
  • MANIFEST.MF:这个文件描述了该 jar 文件的很多信息。
  • spring.provides:配置所依赖的 artifactId,给 IDE 使用的,没有其他的作用。

注意一下,没有一行代码。

我们重点看一下 pom.xml,因为这个 jar 包里面除了这个没有啥重要的信息。

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">     <modelVersion>4.0.0</modelVersion>     <parent>         <groupId>org.mybatis.spring.boot</groupId>         <artifactId>mybatis-spring-boot</artifactId>         <version>1.3.1</version>     </parent>     <artifactId>mybatis-spring-boot-starter</artifactId>     <name>mybatis-spring-boot-starter</name>     <dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-jdbc</artifactId>         </dependency>         <dependency>             <groupId>org.mybatis.spring.boot</groupId>             <artifactId>mybatis-spring-boot-autoconfigure</artifactId>         </dependency>         <dependency>             <groupId>org.mybatis</groupId>             <artifactId>mybatis</artifactId>         </dependency>         <dependency>             <groupId>org.mybatis</groupId>             <artifactId>mybatis-spring</artifactId>         </dependency>     </dependencies> </project> 

从上面可以看出,pom.xml 文件中会引入一些 jar 包,其中除了引入 spring-boot-starter,之外重点看一下:mybatis-spring-boot-autoconfigure

我们找到 mybatis-spring-boot-autoconfigure.jar 文件,打开这个文件。

在这里插入图片描述

里面包含如下文件:

  • pom.properties:配置 Maven 所需的项目 versiongroupIdartifactId
  • pom.xml:配置所依赖的 jar
  • additional-spring-configuration-metadata.json:手动添加 IDE 提示功能
  • MANIFEST.MF:这个文件描述了该 jar 文件的很多信息
  • spring.factories:SPI(Service Provider Interface)会读取的文件
  • spring-configuration-metadata.json:系统自动生成的 IDE 提示功能
  • ConfigurationCustomizer:自定义 Configuration 回调接口
  • MybatisAutoConfigurationmybatis 配置类
  • MybatisPropertiesmybatis 属性类
  • SpringBootVFS:扫描嵌套的 jar 包中的类

spring-configuration-metadata.jsonadditional-spring-configuration-metadata.json 的功能差不多,我们在 applicationContext.properties 文件中输入 spring 时,会自动出现下面的配置信息可供选择,就是这个功能了。

在这里插入图片描述

这两个文件有什么区别?

如果 pom.xml 中引入了 spring-boot-configuration-processor 包,则会自动生成 spring-configuration-metadata.json

如果需要手动修改里面的元数据,则可以在 additional-spring-configuration-metadata.json 中编辑,最终两个文件中的元数据会合并到一起。

MybatisProperties 类是 属性实体类

@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX) public class MybatisProperties {      public static final String MYBATIS_PREFIX = "mybatis";     private String configLocation;     private String[] mapperLocations;     private String typeAliasesPackage;     private String typeHandlersPackage;     private boolean checkConfigLocation = false;     private ExecutorType executorType;     private Properties configurationProperties;     @NestedConfigurationProperty     private Configuration configuration;      public String getConfigLocation() {         return this.configLocation;     }      public void setConfigLocation(String configLocation) {         this.configLocation = configLocation;     }      @Deprecated     public String getConfig() {         return this.configLocation;     }      @Deprecated     public void setConfig(String config) {         this.configLocation = config;     }      public String[] getMapperLocations() {         return this.mapperLocations;     }      public void setMapperLocations(String[] mapperLocations) {         this.mapperLocations = mapperLocations;     }      public String getTypeHandlersPackage() {         return this.typeHandlersPackage;     }      public void setTypeHandlersPackage(String typeHandlersPackage) {         this.typeHandlersPackage = typeHandlersPackage;     }      public String getTypeAliasesPackage() {         return this.typeAliasesPackage;     }      public void setTypeAliasesPackage(String typeAliasesPackage) {         this.typeAliasesPackage = typeAliasesPackage;     }      public boolean isCheckConfigLocation() {         return this.checkConfigLocation;     }      public void setCheckConfigLocation(boolean checkConfigLocation) {         this.checkConfigLocation = checkConfigLocation;     }      public ExecutorType getExecutorType() {         return this.executorType;     }      public void setExecutorType(ExecutorType executorType) {         this.executorType = executorType;     }      public Properties getConfigurationProperties() {         return configurationProperties;     }      public void setConfigurationProperties(Properties configurationProperties) {         this.configurationProperties = configurationProperties;     }      public Configuration getConfiguration() {         return configuration;     }      public void setConfiguration(Configuration configuration) {         this.configuration = configuration;     }      public Resource[] resolveMapperLocations() {         ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();         List<Resource> resources = new ArrayList<Resource>();         if (this.mapperLocations != null) {             for (String mapperLocation : this.mapperLocations) {                 try {                     Resource[] mappers = resourceResolver.getResources(mapperLocation);                     resources.addAll(Arrays.asList(mappers));                 } catch (IOException e) {                     // ignore                 }             }         }         return resources.toArray(new Resource[resources.size()]);     } } 

可以看到 Mybatis 初始化所需要的很多属性都在这里,相当于一个 JavaBean。

下面重点看一下 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;     public MybatisAutoConfiguration(MybatisProperties properties,                                     ObjectProvider<Interceptor[]> interceptorsProvider,                                     ResourceLoader resourceLoader,                                     ObjectProvider<DatabaseIdProvider> databaseIdProvider,                                     ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {         this.properties = properties;         this.interceptors = interceptorsProvider.getIfAvailable();         this.resourceLoader = resourceLoader;         this.databaseIdProvider = databaseIdProvider.getIfAvailable();         this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();     }      @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()));         }         Configuration configuration = this.properties.getConfiguration();         if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {             configuration = new Configuration();         }         if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {             for (ConfigurationCustomizer customizer : this.configurationCustomizers) {                 customizer.customize(configuration);             }         }         factory.setConfiguration(configuration);         if (this.properties.getConfigurationProperties() != null) {             factory.setConfigurationProperties(this.properties.getConfigurationProperties());         }         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();         if (executorType != null) {             return new SqlSessionTemplate(sqlSessionFactory, executorType);         } else {             return new SqlSessionTemplate(sqlSessionFactory);         }     }      public static class AutoConfiguredMapperScannerRegistrar             implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {         private BeanFactory beanFactory;         private ResourceLoader resourceLoader;          @Override         public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {              ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);             try {                 if (this.resourceLoader != null) {                     scanner.setResourceLoader(this.resourceLoader);                 }                  List<String> packages = AutoConfigurationPackages.get(this.beanFactory);                 if (logger.isDebugEnabled()) {                     for (String pkg : packages) {                         logger.debug("Using auto-configuration base package '{}'", pkg);                     }                 }                  scanner.setAnnotationClass(Mapper.class);                 scanner.registerFilters();                 scanner.doScan(StringUtils.toStringArray(packages));             } catch (IllegalStateException ex) {                 logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);             }         }          @Override         public void setBeanFactory(BeanFactory beanFactory) throws BeansException {             this.beanFactory = beanFactory;         }          @Override         public void setResourceLoader(ResourceLoader resourceLoader) {             this.resourceLoader = resourceLoader;         }     }      @org.springframework.context.annotation.Configuration     @Import({ AutoConfiguredMapperScannerRegistrar.class })     @ConditionalOnMissingBean(MapperFactoryBean.class)     public static class MapperScannerRegistrarNotFoundConfiguration {          @PostConstruct         public void afterPropertiesSet() {             logger.debug("No {} found.", MapperFactoryBean.class.getName());         }     } } 

这个类就是一个 Configuration(配置类),它里面定义很多 Bean,其中最重要的就是 SqlSessionFactory 的 Bean 实例,该实例是 Mybatis 的核心功能,用它创建 SqlSession,对数据库进行 CRUD 操作。

除此之外,MybatisAutoConfiguration 类还包含了:

  • @ConditionalOnClass 配置了只有包含 SqlSessionFactory.classSqlSessionFactoryBean.class,该配置类才生效。
  • @ConditionalOnBean 配置了只有包含 dataSource 实例时,该配置类才生效。
  • @EnableConfigurationProperties 该注解会自动填充 MybatisProperties 实例中的属性。
  • @AutoConfigureAfter 配置了该配置类在 DataSourceAutoConfiguration 类之后自动配置。

这些注解都是一些辅助功能,决定 Configuration 是否生效,当然这些注解不是必须的。

接下来,重点看看 spring.factories 文件有啥内容:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration 

里面只有一行配置,即 Key 为 EnableAutoConfiguration,Value 为 MybatisAutoConfiguration

好了,介绍了这么多东西,现在我们来总结一下,

Starter 几个要素如下图所示:

在这里插入图片描述
那么,编写 Starter 需要哪些步骤?

  • 需要定义一个名称为 xxx-spring-boot-starter 的空项目,里面不包含任何代码,可以有 pom.xmlpom.properties 文件。
  • pom.xml 文件中包含了名称为 xxx-spring-boot-autoconfigure 的项目。
  • xxx-spring-boot-autoconfigure 项目中包含了名称为 xxxAutoConfiguration 的类,该类可以定义一些 Bean 实例。当然,Configuration 类上可以打一些如:ConditionalOnClassConditionalOnBeanEnableConfigurationProperties 等注解。
  • 需要在 spring.factories 文件中增加 Key 为 EnableAutoConfiguration,Value 为 xxxAutoConfiguration

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!