Spring Boot初识-2
1. 整合Redis
Spring传统的整合Redis:
- 导入jedis包
- 利用IoC和DI帮你实现Jedis连接实例的管理
- 原本:
JedisPool(JedisPoolConfig,主机地址,数据库索引,密码,超时时间);
JedisPool -> Jedis getResouce();
- IoC和DI:
<bean id="jedisPoolConfig" class="xxx"/>
<bean id="jedisPool" class=""/> 构造注入相应信息
<bean id="redisUtil" class="xxx"/>
SpringBoot现代的整合Redis:
- 导入
spring-boot-starter-data-redis
- 配置Redis的数据源信息
- 使用RedisTemplate来进行Redis操作
2. 自动配置的原理
反斜杠表示换行
官方-HttpEncodingAutoConfiguration
- 在spring.factories中发现了一系列的自动配置类
- 自动配置类的结构:原来自动配置关键就在于 Java代码实现Spring IoC的配置类
@Configuration // Java实现Spring配置文件的注解 applicationContext.xml 【配置类】
@EnableConfigurationProperties({HttpEncodingProperties.class}) // 启用配置属性 【加载属性配置类】
// SpringBoot提供的一套@Conditionalxxxxx条件注解 能够进行条件判断
@ConditionalOnWebApplication // 判断当前是否是处于一个web应用程序
@ConditionalOnClass({CharacterEncodingFilter.class}) // 判断当前的classpath下是否有该类
@ConditionalOnProperty( // 判断当前的属性列表中是否有spring.http.encoding前缀的属性信息 如果enabled属性不存在 则默认的值为true
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
// 实例属性:属性配置类
private final HttpEncodingProperties properties;
// 构造方法:注入了一个属性配置类实例(自动进行注入) 和他@EnableConfigurationProperties对应
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean // <bean id="方法名" class="返回值类型"></bean>
// 判断当前Spring容器中是否有该bean配置 没有则生效
@ConditionalOnMissingBean({CharacterEncodingFilter.class})
public CharacterEncodingFilter characterEncodingFilter() {
// 创建对象
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
// 设置属性【来自于属性配置类】
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
// .....
}
-
属性配置类结构
@ConfigurationProperties(
prefix = "spring.http.encoding"
)
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Charset charset;
private Boolean force;
private Boolean forceRequest;
private Boolean forceResponse;
private Map<Locale, Charset> mapping;
public HttpEncodingProperties() {
this.charset = DEFAULT_CHARSET;
}
// ....
}
MyBatis实现的第三方自动配置 MyBatisAutoConfiguration
- 发现了META-INF 有 spring.factories
-
自动配置类的结构
@Configuration
// 判断classpath下是否有下方两个类
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
// 判断数据源是否是一个单例
@ConditionalOnSingleCandidate(DataSource.class)
// 启用属性配置类
@EnableConfigurationProperties({MybatisProperties.class})
// 自动配置生效应该是在DataSourceAutoConfiguration MybatisLanguageDriverAutoConfiguration之后
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
// 属性配置类的实例
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final TypeHandler[] typeHandlers;
private final LanguageDriver[] languageDrivers;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
// 提供了构造方法 注入了属性配置类
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
this.interceptors = (Interceptor[])interceptorsProvider.getIfAvailable();
this.typeHandlers = (TypeHandler[])typeHandlersProvider.getIfAvailable();
this.languageDrivers = (LanguageDriver[])languageDriversProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = (DatabaseIdProvider)databaseIdProvider.getIfAvailable();
this.configurationCustomizers = (List)configurationCustomizersProvider.getIfAvailable();
}
public void afterPropertiesSet() {
this.checkConfigFileExists();
}
private 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则创建bean
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// 创建SqlSessionFactoryBean对象
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
// 判断属性配置类中是否有 mybatis的核心配置文件路径
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
this.applyConfiguration(factory);
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 (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = (Set)Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
// 从SqlSessionFactoryBean中获取一个对象(SqlSessionFactory)
return factory.getObject();
}
}
-
MyBatis属性配置类结构
@ConfigurationProperties(
prefix = "mybatis"
)
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
private String configLocation;
private String[] mapperLocations;
private String typeAliasesPackage;
private Class<?> typeAliasesSuperType;
private String typeHandlersPackage;
private boolean checkConfigLocation = false;
private ExecutorType executorType;
private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
private Properties configurationProperties;
@NestedConfigurationProperty
private Configuration configuration;
public MybatisProperties() {
}
// ....
}
3. 手动实现一个自动配置
需求介绍
需求:模仿MyBatis的自动配置实现以下功能。
我们开发了一个框架hello
,这个框架有一个类HelloService(类似于MyBatis中的SqlSession、SqlSessionFactory…)。第三方想使用我们这个框架:
-
如果没有Spring的话
// 是一个狠狠狠厉害的功能类
public class HelloService{
private String msg;
public String hello(){
return "Hello "+msg;
}
// ....
}
// ----------------------------------------
HelloService helloService = new HelloService();
helloService.setMsg("World");
String msg = helloService.hello(); // Hello World
-
利用Spring的IoC
<bean id="helloService" class="xxx">
<property name="msg" value="xxx"></property>
</bean>
public class XxxController{
@Resource
private HelloServicce helloService;
}
我们希望给客户更好的体验,未来客户使用我们的框架,只需要导入我们提供的starter[autoconfigure],然后客户就可以直接使用,而不用进行任何配置。
或者如果客户需要一些调整,例如:更改msg的值信息
允许客户在application.yml中直接通过:
hello:
service:
msg: xxx
实现步骤
-
创建Maven项目命名为:hello-spring-boot-autoconfigure
-
导入依赖 springboot提供的自动配置包
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!-- 此版本未来可以改动 牵一发而动全身 -->
<version>1.5.22.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- Compile dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
-
提供属性配置类
/**
* 从配置文件中读取信息
*/
@ConfigurationProperties(
prefix = "hello.service"
)
public class HelloProperties {
private final String DEFAULT_MSG_VALUE = "World";
private String msg;
public HelloProperties(){
this.msg = DEFAULT_MSG_VALUE;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
-
提供自动配置类
@Configuration
@ConditionalOnClass({HelloService.class})
@EnableConfigurationProperties(HelloProperties.class)
public class HelloAutoConfiguration {
private final HelloProperties helloProperties;
public HelloAutoConfiguration(HelloProperties helloProperties){
this.helloProperties = helloProperties;
}
@Bean
@ConditionalOnMissingBean
public HelloService helloService(){
HelloService helloService = new HelloService();
if(StringUtils.hasText(this.helloProperties.getMsg())){
helloService.setMsg(this.helloProperties.getMsg());
}
return helloService;
}
}
-
在META-INF中准备spring.factories然后进行启用自动配置
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.hello.spring.boot.autoconfigure.HelloAutoConfiguration
-
将项目进行打包 提供给对应的starter或者直接供给客户使用
补充:如果希望在IDEA中能够编写有提示,可以在自动配置包中添加spring-configuration-metadata.json文件
{
"properties": [
{
"name": "hello.service.msg",
"type": "java.lang.String",
"description": "A NX Framework msg.",
"sourceType": "cn.hello.spring.boot.autoconfigure.HelloProperties",
"defaultValue": "World"
}
],
"hints": []
}