大家好我是初晨,之前写了很多关于SpringBoot的文章,相信大家已经感受到了SpringBoot相对于传统Spring带来的便捷,那么本篇文章我们就来分析一下SpringBoot带来的便捷到底便捷在哪
不知道大家有没有注意到,当我们创建一个springboot项目时,都会用到如下的启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}复制代码
从代码上来看,显然注解@SpringBootApplication和SpringApplication类和他的run方法为核心。
那么本篇文章我们先来分析一下@SpringBootApplication
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}复制代码
其中重要的三个注解,分别为@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan。下面我们来分别看一下。
一:@SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}复制代码
我们发现他是应用了@Configuration。
@Configuration这个注解用于以JavaConfig的方式来定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。也是SpringBoot社区推荐使用的配置形式。
@Configuration与传统xml配置文件的区别:
- 文件结构传统xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd" default-lazy-init="false"> </beans>复制代码
采用@Configuration:@Configuration public class TestConfig { }复制代码
- bean的定义传统xml:
<bean id="testService" class="TestServiceImpl"> </bean>复制代码
采用@Configuration:@Configuration public class TestConfig { @Bean public TestService testService(){ return new TestServiceImpl(); } }复制代码
所以任何一个java类上使用了@Configuration,都代表他是一个配置类。
任何一个标注了@Bean的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。
二:@ComponentScan
这个注解在spring中很重要,如果你理解了@ComponentScan,你就理解了spring。
大家都知道spring是一个依赖注入的框架,所有的内容都是围绕bean定义及其依赖关系。
但是spring并不知道你定义了哪些个bean,除非你告诉spring可以从哪里找到你定义的那些bean。
而@ComponentScan的作用就是告诉Spring可以从哪里找到定义的bean
通过basePackages属性可以控制@ComponentScan自动扫描的范围,如果没有指定@ComponentScan的扫描范围,那么默认的扫描范围是从声明@ComponentScan所在类的package进行扫描。
所以我们可以发现,创建的springboot项目目录结构一般是这样的
启动类一直在项目的根目录下,这样才能在不配置扫描范围的情况下扫描到所有定义的bean
三:@EnableAutoConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class[] exclude() default {};
String[] excludeName() default {};
}复制代码
这里重要的注解有两个,分别是@AutoConfigurationPackage和@Import
@AutoConfigurationPackage
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
}复制代码
这里发现他使用@Import注解引入了一个类Registrar
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
Registrar() {
}
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
}
public Set determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
}
}复制代码
那么这个类是用来干嘛的呢?我们断点跟一下,看一下registerBeanDefinitions方法是做什么的。
这里发现
new PackageImport(metadata).getPackageName()
返回的返回了当前主程序类的同级以及子级的包组件。这也证明了@ComponentScan默认扫描其所在类的package。
@Import({AutoConfigurationImportSelector.class})
接下来我们具体看一看AutoConfigurationImportSelector这个类
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}复制代码
这个方法调用了getAutoConfigurationEntry方法
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.filter(configurations, autoConfigurationMetadata);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}复制代码
这里我们重点看一下getCandidateConfigurations方法,先断点看一下方法的返回值
该方法返回的是需要实例化的类信息列表,有了他spring就可以通过类加载器将需要实例化的类加载到jvm中。
现在我们看一下该方法的代码,发现他是借用了SpringFactoriesLoader类的方法
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;
}复制代码
再来看一下loadFactoryNames方法
public static List loadFactoryNames(Class factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
//....省略
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}复制代码
发现他读取了一个名为spring.factories的文件
比如我们找一下redis的配置
点击去看一下
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
......
}复制代码
@ConditionalOnClass({RedisOperations.class}) 表示必须存在RedisOperations这个类,否则不解析该注解修饰的配置类。
再看看RedisProperties这个类
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String password;
private int port = 6379;
private boolean ssl;
private Duration timeout;
private RedisProperties.Sentinel sentinel;
private RedisProperties.Cluster cluster;
private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
......省略
}复制代码
是不是眼熟了!这个就是我们在使用redis时写在application.properties里面,需要配置redis的信息
对应的配置应为:
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.timeout=0
spring.redis.password=复制代码
同时也可以看到redis默认为我们配置了host和port属性。所以在配置redis时,如果端口号围为默认的6379,我们也可以不写的原因。
所以@EnableAutoConfiguration的大致原理就是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。
四:结尾
@SpringBootApplication我们分析完了,下篇文章我们分析SpringApplication这个类
对于springBoot还不了解的朋友可以看我的SpringBoot系列教程