一、自动装配
SpringBoot提供了注解@Configuration,用来配置多个Bean,想我之前的SpringBoot学习总结总就使用多很多次这个注解类,例如配置Spring的数据源,然后SpringBoot我们大概知道他有自动装配功能,但是为什么能够自动装配,以及什么是自动装配毫不介绍,这节就是主要介绍SpringBoot的自动装配和Configuration。
1.1 @Configuration和@Bean
Spring的java配置的核心就是使用@Configuration作用在类上,并且是联合在此类上的多个@Bean注解的方法,声明Spring管理的Bean,他类似于XML的配置方式。
<beans>
<bean id="testBean" class="xxx.TestBean"></bean>
</beans>
使用@Configuration注解的方式:
package com.mqc.config;
import com.mqc.model.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author maoqichuan
* @ClassName: TestConfig
* @description: @configuration 注解方式注册Bean
* @date 2019-04-1517:28
**/
@Configuration
public class TestConfig {
@Bean(name = "testBean")
public User getUser(){
return new User();
}
}
在以上的代码中,TestConfig类上使用了@Configuration注解,那么则向Spring表明这是一个配置类,类里面的所有标注有@Bean的方法都会被Spring调用,返回的对象都会被Spring容器管理的Bean,注解@Bean的name可以为一个bean指定一个名字,例如上面的代码中我指定的baan的名字是“testBean”,bean的名字我们一般是取类的类名或者某字母小写作为名字,如果该注解不指定name的名字,那么默认取方法名作为Bean的名称。
通常我们的配置类还需要获取外部属性,例如:配置文件、系统变量、环境变量等,Environment提供了获取这些外部属性的API。
可以在@Bean注解的方法参数上提供任意参数来说明依赖,比如我的TestConfig的UserService依赖数据源datasource。
@Bean
public IUserService getUserService(DataSource dataSource){
return new UserServiceImpl(dataSource);
}
datasource是我们SpringBoot已经在其他地方配置好的数据源。
在配置好Bean后,我们可以通过@AutoWired在任何地方注入我们配置好的bean,比如我需要在某业务代码注入我的UserService。
@Autowired
private IUserService userService;
1.2 Bean条件装配
SpringBoot可以通过指定有无Bean来装配Bean,使用@ConditionalOnBean,在当前上下文中存在某个对象,才会实例一个Bean,使用@ConditaionalOnMissBean,在当前上下文中不存在某个对象的时候才会实例化Bean。
/**
* @author maoqichuan
* @ClassName: MysqlConfig
* @description: 当前的上下文中存在datasource这个对象的时候才会实例化该类
* @date 2019-04-1517:44
**/
@Configuration
@ConditionalOnBean(DataSource.class)
public class MysqlConfig {
}
MysqlConfig生效的前提就是在存在Datasource这个类。
package com.mqc.config;
import com.mqc.service.IUserService;
import com.mqc.service.impl.UserServiceImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author maoqichuan
* @ClassName: MyAutoConfiguration
* @description: 自动装配介绍
* @date 2019-04-1517:46
**/
@Configuration
public class MyAutoConfiguration {
/**
* @description: 在当前上下文环境中不存在IUserService才会实例化
* @return IUserService
* @throws
* @author maoqichuan
* @date 2019-04-15 17:48
*/
@Bean(name = "userService")
@ConditionalOnMissingBean(IUserService.class)
public IUserService getUserService(){
return new UserServiceImpl();
}
}
配置装配IUserService就调用这个方法。
1.3 Class条件装配
Class条件是其用途是判断当前classpath下是否存在指定类,若是则将当前的配置装载入spring容器。
package com.mqc.config;
import com.mqc.service.IUserService;
import com.mqc.service.impl.UserServiceImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author maoqichuan
* @ClassName: MyAutoConfiguration
* @description: 自动装配介绍
* @date 2019-04-1517:46
**/
@Configuration
@ConditionalOnClass({IUserService.class})
public class MyAutoConfiguration {
/**
* @description: 在当前上下文环境中不存在IUserService才会实例化
* @return IUserService
* @throws
* @author maoqichuan
* @date 2019-04-15 17:48
*/
@Bean(name = "userService")
@ConditionalOnMissingBean(IUserService.class)
public IUserService getUserService(){
return new UserServiceImpl();
}
}
当在实例化IUserService的时候实例化该类。
1.4 Enviroment装配
可以根据SpringBoot的Enviroment的属性来决定是否生效。
package com.mqc.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
/**
* @author maoqichuan
* @ClassName: MessageAutoConfiguration
* @description: 根据environment的属性来决定是否生效
* @date 2019-04-1518:06
**/
@Configuration
@ConditionalOnProperty(name = "message.center.enabled",havingValue = "true",
matchIfMissing = true)
public class MessageAutoConfiguration {
}
@ConditonalOnProperty注解根据name来读取SpringBoot的Enviroment的变量包含的属性,根据其值与havingValue的值比较结果决定配置是否生效,如果没有指定havingValue,那么只要属性不为false,配置都能生效。
matchMissing为true意味着如果Enviroment没有包含message.center.enabled,配置也能生效,默认为false。
1.5 其他条件装配
- ConditionalOnExpression,当表达式为true时,才会实例化一个Bean,支持SpEL表达式,比如根据配置文件中的某个值来确实能够是否生效。
- ConditionOnJava,当存在指定的Java版本的 时候。
@ConditionalOnJava(range = ConditionalOnJava.Range.EQUAL_OR_NEWER,value = ConditionalOnJava.JavaVersion.EIGHT)
1.6 联合多个条件
以上介绍的注解都可以联合@Configuration来使用,如果满足条件,那么其下所有配置的类都会生效,也可以单独与@Bean使用。
以Spring Boot Cache 为例(支持多种缓存实现,如SimpleCache ),可以用Has协fap 实现,还可以是分布式的Red is 实现方式。无论是哪种实现方式,只需要配置好CacheManage「一缓存的抽象管理类。对应于SimpleCache ,是ConcurrentMapCacheManager 实现;对应于Redis 缓存,是RedisCacheManager 实现。
/**
* @author maoqichuan
* @ClassName: RedisCacheConfiguration
* @description: RedisCacheConfiguration 配置类
* @date 2019-04-1610:08
**/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration . class )
@ConditionalOnBean(RedisTemplate . class)
@ConditionalOnMissingBean(CacheManager . class )
@Conditional(CacheCondition.class)
public class RedisCacheConfiguration {
}
注解@Configuration向Spring表明这是一个配置类,Red is 的缓存配置需要确保Red is T emplate 己经配置, 因此使用了注解@AutoConfigureAfter,表示此配置类需要在Redi s AutoConfiguration 配置类后再生效, ConditionalOnBean 则表示如果
成功配置好R e di sTemplat e ,此配置才能继续生效。@ConditionalOnMissingBean可以接受一个或者多个作为参数,如果还没有配置过CacheManager类则该类生效。
1.7Condition接口
当我们使用SpringBoot的@ConditionalOnBean,@ConditionalOnClass无法满足我们需求的时候,可以自己构造一个Condition实现,使用注解@Conditional来引用此Condition实现。
Condition的接口定义如下:
package com.mqc.condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author maoqichuan
* @ClassName: MyCondition
* @description: 自定义condition
* @date 2019-04-1610:18
**/
public interface MyCondition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
ConditionContext类可以用于帮助条件判断的辅助类:
- Enviroment:可以读取系统属性,环境变量,配置参数等作为条件判断,比如当配置文件的某一项存在的时候生效。
- ResourceLoader,一个String类,用来加载和判断资源文件,比如当某一个配置文件存在时才生效。
- ConfigurableListableBeanFactory,Spring容器。
以下配置了一个对存入数据库的用户手机进行加密的类,使用@Conditional注解,要求存在salt.txt文件且允许手机加密时才生效。
package com.mqc.config;
import com.mqc.condition.MyCondition;
import com.mqc.util.PhoneUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* @author maoqichuan
* @ClassName: ModileEncryptCondition
* @description: 若条件允许则生效
* @date 2019-04-1610:38
**/
@Configuration
@Conditional(MyCondition.class)
public class ModileEncryptCondition {
@Bean(name = "PhoneUtils")
public PhoneUtils getPhoneUtils(){
return new PhoneUtils();
}
}
自定义的Condition:
package com.mqc.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author maoqichuan
* @ClassName: MyCondition
* @description: 手机加密配置类
* @date 2019-04-1610:36
**/
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext ctx, AnnotatedTypeMetadata annotatedTypeMetadata) {
Resource resource = ctx.getResourceLoader().getResource("salt.txt");
Environment environment = ctx.getEnvironment();
return resource.exists() && environment.containsProperty("mobile.encrypt.enabled.");
}
}
我们这前面看到的@ConditionalOnMissBean、@ConditionalOnJava等注解实际上也是通过@Conditional来实现的,这里我们以@ConditionalOnJava为例:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnJavaCondition.class})
public @interface ConditionalOnJava {
ConditionalOnJava.Range range() default ConditionalOnJava.Range.EQUAL_OR_NEWER;
ConditionalOnJava.JavaVersion value();
满足@ConditionalOnJava的条件就是在@OnJavaCondition中实现的,而其具体的实现这里就不解释了。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot.autoconfigure.condition;
import java.util.Map;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.JavaVersion;
import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.Range;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
@Order(-2147483628)
class OnJavaCondition extends SpringBootCondition {
private static final JavaVersion JVM_VERSION = JavaVersion.getJavaVersion();
OnJavaCondition() {
}
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
Range range = (Range)attributes.get("range");
JavaVersion version = (JavaVersion)attributes.get("value");
return this.getMatchOutcome(range, JVM_VERSION, version);
}
protected ConditionOutcome getMatchOutcome(Range range, JavaVersion runningVersion, JavaVersion version) {
boolean match = runningVersion.isWithin(range, version);
String expected = String.format(range == Range.EQUAL_OR_NEWER ? "(%s or newer)" : "(older than %s)", version);
ConditionMessage message = ConditionMessage.forCondition(ConditionalOnJava.class, new Object[]{expected}).foundExactly(runningVersion);
return new ConditionOutcome(match, message);
}
}
其关键代码使用了AnnotatedTypeMetadata 来获取@Conditiona!OnJava 注解的属性信息,比如value () 方法返回的String[]:
Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
Range range = (Range)attributes.get("range");
JavaVersion version = (JavaVersion)attributes.get("value");
到这里,SpringBoot自动配置的已经介绍完了,让我们一起努力,一起进步!加油!!!