无论是Spring Cloud Netflix还会Spring Cloud Alibaba,都是基于SpringBoot这个微服务框架来构建的,所以SpringBoot对于大家来说还是十分重要的。
1、Spring 是什么?
对于Spring框架而言,我们接触比较深的应该是SpringMVC和Spring。而Spring的核心在于IOC控制反转和DI依赖注入。而这些的使用需要我们去配置大量的XML,过程十分繁琐。
SpringBoot就是为了帮助使用Spring框架的开发者快速高效的构建一个基于Spring框架以及Spring 生态体系的应用解决方案。它是对** “约定优于配置” **理念的最佳实践。因此它是一个服务于框架的框架,可以帮助我省去很多繁琐的配置。
2、约定优于配置的体现
1、Maven的目录结构
默认有resources文件存放配置文件。
默认打包方式为jar。
2、Spring-Boot-Srarter-Web中默认包含SpringMVC相关依赖以及内置的Tomcat容器,使得构建一个web应用更加简单。
3、默认提供application.properties/yml文件。
4、默认通过Spring.profiles.active属性来觉得运行环境时读取的配置文件。
5、EnableAutoConfiguration默认对于依赖的starter进行自动装载。
SpringBoot项目的标志性注解就是@SpringBootApplication因此,我们要想要了解SpringBoot当然需要从它开始
3、@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}
)}
)
SpringBootApplication本质上是又3个注解组成
- @SpringBootConfiguration
- @EnableAutoConfiguration
- @ComponentScan
我们直接使用这个三个注解其实也可以启动SpringBoot应用,只是每次配置三个注解比较麻烦,因此SpringBoot帮我们整合了他们。
3.1 @SpringBootConfiguration
这个注解你点开源码看实质上就是一个@Configuration,对于这个注解使用我们应该都不会陌生,它是JavaConfig形式的基于SpringIOC容器的配置类使用的一个注解。因为SpringBoot本身就是一个Spring应用,所以通过这个这个注解来加载IOC容器的配置也很正常。因此在启动类中标注了此注解,说明它是一个IOC的配置类。
在传统意义上的Spring应用都是基于XML形式来配置Bean的依赖关系,然后通过Spring容器在启动的时候,把Bean进行初始化,如果Bean之间存在依赖关系,则Fenix这些已经在IOC容器中的Bean根据依赖关系进行组装。
到了JAVA5中,引入了Annotations这个特性,Spring框架也推出了基于Java代码和Annotation元信息的依赖关系绑定描述的方式,也就是JavaConfig。
从Spring3开始,spring就支持了两种bean的配置方式,一种是基于xml,另一种就是JavaConfig任何一个标注了@Configuration的java类定义都是一个JavaConfig配置类。而在这个配置类中,任何标注了@Bean的方法,它的返回值都会作为Bean定义注册到Spring的IOC容器,方法名默认成为这个bean的id。
@Configuration简单使用:
创建一个实体类
//希望这个类被Spring托管
public class DemoClass {
public void say(){
System.out.println("Say hello Ccc");
}
}
在以前我们需要在<bean class = “com.xxx.DemoClass”>来进行注册Bean
在后来我们使用注解配置
@Configuration //表示当前是一个配置类
public class ConfigurationDemo {
//单例的
@Bean
public DemoClass demoClass(){
return new DemoClass();
}
}
我们声明一个配置类去进行Bean的注入,然而Bean注入不是每次都会去创建一个对象,因为在Spring中他默认是单例的。因为@Scope()注解中默认是单例的。我在之前的文章也介绍了这个注解,有兴趣的朋友可以了解一下。跳转
写个方法简单测试下
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigurationDemo.class);
DemoClass demoClass = applicationContext.getBean(DemoClass.class); //DL
demoClass.say();
}
//可以在日志中看到DemoClass的注入和最后类中的方法打印。
3.2 @ComponentScan
ComponentSccan我接触的很频繁,它相当于xml中的<context:component-scan>。主要作用是扫描到指定路径下的标识了需要装配的类,自动装配到Spring的IOC容器中。
例如:@Component、@Repository、@Service、@Controller这些注解标识的类。
ComponentSccan会默认扫描单签package下所有的加了相关注解的类注入到IOC容器中。
我们修改一下上述的例子:
1.去掉ConfigurationDemo中Bean的创建
2.在DemoClass类中使用上述4个注解中一个这里使用@Service
//希望这个类被Spring托管
@Service
public class DemoClass {
public void say(){
System.out.println("Say hello Ccc");
}
}
3.在main方法的类上添加@ComponentScan注解
@ComponentScan
public class ConfigurationMain {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ConfigurationMain.class);
String[] defNames = applicationContext.getBeanDefinitionNames();
for (String defName : defNames) {
System.out.println(defName);
}
}
}
//你会发现带有@Service注解的DemoClass同样也注入了。
3.3 @EnableAutoConfiguration
在我们了解到上面两个注解之后我们再来探讨一下此注解,从名字上不难猜出 开始自动配置。对于SpringBoot来说它才是意义最大的。
你会见到很多类似EnableXxxxxBxxx格式的注解。那么Enable又是什么?
在Spring3.1版本中,提供了一系列的@Enable开头的注解,Enable是在JavaConfig上进一步的完善,使得用户在使用Spring相关的框架时,避免配置大量的代码从而降低使用难度。
例如:
@EnableWebMvc:此注解会引入MVC框架在Spring应用中需要用到的所有bean
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({DelegatingWebMvcConfiguration.class})
@EnableScheduling:开始计划任务的支持。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({SchedulingConfiguration.class})
@Documented
打开这些Enable开头的注解,你会发现每一个带有Enable开头的注解都会存在一个@Import的注解。
例如在@EnableAutoConfiguration中
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
我们先来探讨一下Import注解的作用。
3.3.1@Import
在xml文件中会存在一个<import resource>形式的标签,就不难猜出它的意义,因为在JjavaConfig中所表达的意义是一样的。请看:
public class OtherBean {} //创建一个Bean
//------------------------------------------
//在创建一个配置类注入Bean
@Configuration
public class OtherConfig {
@Bean
public OtherBean otherBean(){
return new OtherBean();
}
}
public class DefaultBean {} //创建一个Bean
//---------------------------------------------
//创建一个配置类
@Configuration
//使用Import注入上述中的Bean对象
@Import(OtherConfig.class) //也可以单独注入OtherBean.class
public class SpringConfig {
@Bean
public DefaultBean defaultBean(){
return new DefaultBean();
}
}
说白了就是可以注入外部资源。
我们在回到@EnableAutoConfiguration。它的作用就是帮助SpringBoot应用把所有符合条件的@Configuration配置都加载到当前SpringBoot的IOC容器中。
我们观察一下它的@Import({AutoConfigurationImportSelector.class})
注解
那么AutoConfigurationImportSelector 是什么?
在上述案例中使用@Import注入的简单案例之外,他还可以支持另外两种配置。
1、上述案例中的配置 使用普通的Bean进行注入
2、实现ImportSelector接口进行动态注入。
3、实现ImportBeanDefinitionRegistrar接口接口进行动态注入(此处的实现在@AutoConfigurationPackage中)。
很显然AutoConfigurationImportSelector就是进行动态注入的ImportSelector接口实现
我们找到AutoConfigurationImportSelector的类图:顶层接口就是ImportSelector!
而在ImportSelector接口定义的方法selectImports()中返回的数组(类的全类名)都会被纳入到 spring容器中。
我们定位到AutoConfigurationImportSelector类中的selectImports方法,本质上来说其实EnableAutoConfiguration会帮助SpringBoot应用把所有符合@Configuration配置都加载到倩倩的SpringBoot创建的IOC容器,而在这里借助了Spring框架提供的工具类SpringFactoriesLoader的支持,以及用到的Spring提供的条件注解@Conditional,选择性加载需要bean。
首先SpringFactoriesLoader的作用是从classpath/META-INF/spring.factories文件中,根据key来加载到对应的类到IOC容器中。
在AutoConfigurationImportSelector执行中,会先扫描到spring-autoconfiguration-metadata.properties 文件,最后在扫描到spring.factories时,会结合前面的元数据进行过滤?为什么需要过滤?因为很多的@Configuration是依托于其他的框架来加载的,如果当前的classpath环境下没有相关联的依赖,那么意味着这些类没有必要加载,所以通过这种方式过滤可以有效的减少@Configuration类的数量从而降低启动时间。
我们来看看SpringBoot中的配置:
在SpringBoot启动时会如果没有条件注解配置情况下它会默认载入改key下所有的value。很显然我们不需要用到这里全部。
在这里博主也简单写了一个案例来帮助大家更好的理解一下。
首先展示一下动态注入的方式
1.首先我们定义一个@EnableDefineService注解来模仿@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited //表示是否允许被继承
@Import({LoggerDefinitionRegistrar.class,CacheImportSelector.class})
public @interface EnableDefineService {
Class<?>[] exclude() default {};
}
这里的**@Import({LoggerDefinitionRegistrar.class,CacheImportSelector.class})**我将上述中
2、实现ImportSelector接口进行动态注入。
3、实现ImportBeanDefinitionRegistrar接口接口进行动态注入(此处的实现在@AutoConfigurationPackage中)。
两种实现方式进行整合到一个注解中,减少一下演示代码。
2.创建两个需要注入类CacheService和LoggerService一个通过方式2注入一个通过方式3注入
public class CacheService {
}
public class LoggerService {
}
3.创建CacheImportSelector类实现ImportSelector接口。
public class CacheImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//AnnotationMetadata 注解元数据 例如:@Bean(name="xxx") name就是元数据了
//读取元信息
Map<String,Object> maps = annotationMetadata.getAnnotationAttributes(EnableDefineService.class.getName());
//这里简单返回固定对象。
return new String[]{CacheService.class.getName()};
}
}
4.创建LoggerDefinitionRegistrar实现ImportBeanDefinitionRegistrar
public class LoggerDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Class beanClass = LoggerService.class; //拿到类
RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass); //进行包装
String name = StringUtils.uncapitalize(beanClass.getSimpleName()); //将类名首字母小写
registry.registerBeanDefinition(name,beanDefinition);
}
}
写一个测试类
@SpringBootApplication
//模仿@EnableAutoConfiguration的注解
@EnableDefineService
public class EnableDemoMain {
public static void main(String[] args) {
ConfigurableApplicationContext ca = SpringApplication.run(EnableDemoMain.class,args);
System.out.println(ca.getBean(CacheService.class));
System.out.println(ca.getBean(LoggerService.class));
}
}
你会发现加上@EnableDefineService就会成功注入这两个对象信息。
条件过滤案例
我们选择有条件的进行过滤掉我们需要的Bean对象
1.另外创建一个Maven工程
里面的类
//bean对象
public class Core {
public String study(){
System.out.println("good good study day day up");
return "www.cxhorange.com";
}
}
//配置类
@Configuration
public class Config {
@Bean
public Core core(){
return new Core();
}
}
//过滤条件
public class TestClass {
}
我们模仿SpringBoot的内容,在resources下创建一个META-INF目录并创建两个文件
1.spring-autoconfigure-metadata.properties 空文件加就行
2.spring.factories:key为springboot中的key,value指向我们类名。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ccc.core.Config
我们文章中也提到了在SpringBoot中AutoConfigurationImportSelector会依次扫描这两个文件。
如果不存在条件过滤那么springBoot会默认加载spring.factories文件中该key下所有类
打个jar包
复制maven地址
随便在一个SpringBoot应用中进行测试。
导入上面的maven依赖
<dependency>
<groupId>com.ccc.core</groupId>
<artifactId>ccc-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
测试
@SpringBootApplication
public class FouthMain {
public static void main(String[] args) {
ConfigurableApplicationContext ca = SpringApplication.run(FouthMain.class,args);
System.out.println(ca.getBean(Core.class).study());
}
}
输出内容
修改maven工程中的spring-autoconfigure-metadata.properties 文件
加上内容
com.ccc.core.Config.ConditionalOnClass = com.ccc.core.TestClass
在进行打包测试 你会发现同样注入成功,我们如果将value替换成com.ccc.core.Test 在进行打包测试你会发现报错了
原因是在我们maven中并没有创建Test这个类。
另外关于条件注解几种类型
@ Conditions | 描述 |
---|---|
@ConditionalOnBean | 在存在某个 bean 的时候 |
@ConditionalOnMissingBean | 不存在某个 bean 的时候 |
@ConditionalOnClass | 当前 classpath 可以找到某个类型的类时 |
@ConditionalOnMissingClass | 当前 classpath 不可以找到某个类型的类 时 |
@ConditionalOnResource | 当前 classpath 是否存在某个资源文件 |
@ConditionalOnProperty | 当前 jvm 是否包含某个系统属性为某个值 |
@ConditionalOnWebApplication | 当前 spring context 是否是 web 应用程序 |
总结
以上便是本人对于SpringBoot原理的总结,如果能够帮助到一些人便是再好不过,如果您在浏览中发现了错误,也希望您能批评指正,在此感谢!