前言
在我们开发的过程中,有时候为了让开发人员更好的进行业务逻辑开发,我们可能会定制开发一个个组件,并起开箱即用的效果。有玩过springboot的朋友可能知道,springboot提供了一系列的starter,这个starter很像就是可插拔的组件,它能够实现自动配置,达到开箱即用,很好的降低了使用框架时的复杂度,让开发人员更容易的使用。今天我们就演示一下如何通过自定义stater来实现一个简单的自动配置例子
定义
基于约定大于配置的原则,实现Spring组件自动装配的目的
装配的依赖(方式)
模式注解、@Enable模块、条件装配、工厂加载机制
激活自动化装配、实现自动化装配、配置自动装配实现
底层装配技术
- Spring 模式注解装配
- Spring @Enable模块装配
- Spring条件装配
- Spring工厂加载机制
- 实现类: SpringFactoriesLoader
- 配置资源:META-INF/spring.factories
实现方式-常见的是实现方式有两种
1、激活自动装配
比如 使用@EnableAutoConfiguration
或者使@EnableConfigurationProperties(DbProperties.class)
接下来会对这两个注解进行实战应用
2、实现自动装配
通过定义 ***AutoConfiguration
注解
3、配置自动装配
在META-INF/spring.factories
文件中进行配置
# Auto Configure
// 这里是配置自动装配的注解, 下面都是这个自动装配需要装配的东西,通过这种配置的方式,只要使用EnableAutoConfiguration注解,就会将下面的这些配置全部自动装配进去
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
上面提到的两种注解,现在开始实战演示了:
一、@EnableAutoConfiguration
第一步:激活自动装配
//新建了一个启动类,使用这个注解
//这里我需要获取helloWorld的bean,所以我需要在将helloWorld的bean通过这个注解进行自动装配
@EnableAutoConfiguration
public class EnableAutoConfigurationBootStrap {
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(EnableAutoConfigurationBootStrap.class)
.web(WebApplicationType.NONE)
.run(args);
String helloWorld = context.getBean("helloWorld", String.class);
System.out.println("helloWorld:" + helloWorld);
}
}
第二步:实现自动装配
新建了一个> HelloWorldAutoConfiguration
@Configuration // 模式注解装配
@EnableHelloWorldConfiguration // @Enable模块装配,这个装配是会生成一个helloWorldBean的
@TestCondition(name = "user.name", value = "Administrator") // 条件装配
public class HelloWorldAutoConfiguration {
}
这里的执行顺序是:
- 先通过条件装配,如果条件装配不成立就不会进行下一步的操作
- 条件装配成立后,进入
EnableHelloWorldConfiguration
,使用@import
导入了配置bean
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(HelloWorldConfiguration.class)
public @interface EnableHelloWorldConfiguration {
}
public class HelloWorldConfiguration {
@Bean
public String helloWorld() {
return "hello World!";
}
}
- 这里其实到这一步已经可以将bean获取到了,不需要使用
Configuration
,只是为了说明也可以用这个方法生成bean
第三步:配置自动装配
在resources目录下新建META-INF文件夹,在下面新建spring.factories文件,在这里面进行配置,将EnableHelloWorldConfiguration绑定给@EnableAutoConfiguration
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.imooc.study.configration.HelloWorldAutoConfiguration
通过上面的三个步骤就能实现简单的自动装配,SpringBoot的自动装配其实是基于SpringFramework的实现,在这个基础上添加了> spring.factories
二、@EnableConfigurationProperties(DbProperties.class)
自定义starter
1、创建autoconfigure模块项目
这个项目包含需要自动配置的代码逻辑
pom.xml引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>com.github.lybgeek</groupId>
<artifactId>springboot-dbtemplate</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<scope>provided</scope>
</dependency>
其中spring-boot-configuration-processor这个jar,可以让我们在编写application.properties/yml会有智能提示,形如下
springboot-dbtemplate为需要自动配置的具体功能模块,具体实现可以查看
编写starter自动化配置
@ConfigurationProperties(prefix = "db")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class DbProperties {
/**
* if you want to use a dbTempate to operate datasouce,dbTemplateEnabled must be true,default false
*/
private boolean dbTemplateEnabled = false;
/**
* jdbc driverClassName must can not be null
*/
private String driverClassName;
/**
* datasource password must can not be null
*/
private String password;
/**
* datasource username must can not be null
*/
private String username;
/**
* datasource url must can not be null
*/
private String url;
}
@Configuration
@Slf4j
@EnableConfigurationProperties(DbProperties.class)
@ConditionalOnProperty(name="db.db-template-enabled",havingValue = "true")
public class DbTemplateAutoConfiguration {
@Autowired
private DbProperties dbProperties;
@Bean
@ConditionalOnMissingBean(value= DbTemplate.class)
public DbTemplate dbTemplate(){
DbTemplate dbTemplate = new DbTemplate(new QueryRunner(),dataSource());
return dbTemplate;
}
@Bean
@ConditionalOnMissingBean(value= DataSource.class)
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(dbProperties.getDriverClassName());
dataSource.setUrl(dbProperties.getUrl());
dataSource.setUsername(dbProperties.getUsername());
dataSource.setPassword(dbProperties.getPassword());
return dataSource;
}
}
编写自定义spring.factories
在src/main/resource目录下创建META-INF目录,并在目录内添加文件spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.lybgeek.autoconfigure.dbtemplate.DbTemplateAutoConfiguration
另开一个项目,新建一个starter项目
这个项目只用来做依赖导入,可以是一个空jar文件,仅提供辅助性依赖管理,这些依赖可能用于自动装配或其它类库,因此可以建一个只含pom.xml文件,不含其它内容的maven项目。其pom.xml内容形如下
<dependencies>
<dependency>
<groupId>com.github.lybgeek</groupId>
<artifactId>springboot-dbtemplate-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.github.lybgeek</groupId>
<artifactId>springboot-dbtemplate</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
编写一个引入自定义的starter的项目测试
通过注解来实现自动装配
上面都是通过在src/main/resource目录下创建META-INF目录,并在目录内添加文件spring.factories来实现的,但是还有一种方式可以不需要这样做的,当然除了和总结里面说的一种情况(jar包中的目录结构和项目结构完全一致)也可以,但是很少见
package com.xyhua.autoconfig;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.NoConnectionReuseStrategy;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass({HttpClient.class})
@EnableConfigurationProperties({HttpClientProperties.class})
public class HttpClientAutoConfiguration {
private final HttpClientProperties properties;
public HttpClientAutoConfiguration(HttpClientProperties properties) {
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean({HttpClient.class})
public HttpClient mHttpClient() {
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(properties.getConnectTimeOut())
.setSocketTimeout(properties.getSocketTimeOut()).build();//构建requestConfig
HttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).setUserAgent(properties.getAgent())
.setMaxConnPerRoute(properties.getMaxConnPerRoute()).setConnectionReuseStrategy(new NoConnectionReuseStrategy()).build();
return client;
}
}
之前是这样做的
1 MET-INF 下 spring.factories
* # Auto Configure
* org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
* com.xyhua.autoconfig.HttpClientAutoConfiguration(主入类的全路径)
但是现在不适用这种方式
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(HttpClientAutoConfiguration.class)
public @interface EnableHttpClient {
}
这种方式通过可以作为jar依赖到项目中能够扫描到
总结
1、当你需要实现可插拔、按需集成、开箱即用的组件,可以考虑下模仿一下springboot的starter。另外为了和整个系列的springboot实战项目名称命名统一,所有的项目名称统一都以springboot为前缀打头,因此就没按官方推荐的命名来写starter。官方推荐starter的命名方式是
官方starter都是spring-boot-starter-,外部的自定义starter推荐使用-spring-boot-starter
2、 上面的两种实现方式根据不同的应用场景需求来觉得,当然这里所谓的两种方式只是针对应用层面去划分的,为什么这么说呢,因为如果你熟悉spirngboot自动装配的原理就会发现其实都是一样的方式,只是注解不同导致不同的使用方式而已
3、大家发现无论你如何百度或者查阅资料都会发现最后都需要自定义自定义> spring.factories一个工厂模式去触发这个自动装配的机制吗,但是我想告诉你的是并不是一味的去模仿,因为当你的项目目录和引入这个ja的项目的目录是一致的话就不需要去自定义这个> spring.factories,换言之当A是一个starter目录。而B项目需要引入A,当时如果A和B的项目是一致的话,就不需要了,因为最终目的就是为了加载这个> DbTemplateAutoConfiguration 或者> HelloWorldAutoConfiguration