spring boot的核心,下面我结合spring来说说springboot的部分核心
对博主感兴趣可以加博主的抖音号langbei57048
1、Spring IoC容器
1. pring Ioc容器是餐馆,bean是菜 :你去餐馆吃饭,你点菜,餐馆就给你菜,至于这个菜是怎么做出来的你并不用关心,也就是说,你只需要告诉它需要某个bean,它就把对应的实例(instance)扔给你,至于这个bean是否依赖其他组件,怎样完成它的初始化,根本就不需要你关心。(
通俗餐馆存bean的地方,要就来这里拿)
2. 什么是bean
凡是子类及带有方法或属性的类都要加上注册Bean到Spring IoC的注解
bean分为两类:
一类是注册bean(@Component , @Repository , @ Controller , @Service , @Configration)这些注解都是把你要实例化的对象转化成一个Bean,放在IoC容器中。(通俗的说就是把这个注册成一个bean放在IOc容器中)
一类是使用Bean,即是把已经在xml文件中配置好的Bean拿来用,完成属性、方法的组装;比如@Autowired , @Resource,可以通过byTYPE(@Autowired)、byNAME(@Resource)的方式获取Bean;(通俗就是把bean拿出来用,IoC 容器负责管理容器中所有bean的生命周期)
3.BeanDefinition是菜的原料菜谱(用来记录用到了哪些原料) :
IoC 容器想要管理各个业务对象以及它们之间的依赖关系,需要通过某种途径来记录和管理这些信息。 BeanDefinition
对象就承担了这个责任:**容器中的每一个 bean 都会有一个对应的 BeanDefinition 实例,该实例负责保存bean对象的所有必要信息,包括 bean 对象的 class 类型、是否是抽象类、构造方法和参数、其它属性等等。**当客户端向容器请求相应对象时,容器就会通过这些信息为客户端返回一个完整可用的 bean 实例。(菜谱记载所用原料即bean的重要信息)
4、BeanDefinitionRegistry
和 BeanFactory
就是菜谱(记录这道菜怎么做出来的) :
BeanDefinitionRegistry 抽象出 bean 的注册逻辑,而 BeanFactory 则抽象出了 bean 的管理逻辑,而各个 BeanFactory 的实现类就具体承担了 bean 的注册以及管理工作。(这两个接口是制作成bean的菜谱,
BeanDefinitionRegistry
是用原料BeanDefinition做出菜bean,BeanFactory
是管理bean)
5.DefaultListableBeanFactory
:实现BeanFactory接口 ,它同时也实现了 BeanDefinitionRegistry 接口(BeanFactory的实现类一般会实现BeanDefinitionRegistry 接口),因此它就承担了 Bean 的注册管理工作。从图中也可以看出,BeanFactory 接口中主要包含 getBean、containBean、getType、getAliases 等管理 bean 的方法,而 BeanDefinitionRegistry 接口则包含 registerBeanDefinition、removeBeanDefinition、getBeanDefinition 等注册管理 BeanDefinition 的方法。
上面解释了Spring IOC容器里面的作用及相关的东西,下面来看看IOC容器的工作流程
①、容器启动阶段
容器启动时,会通过某种途径加载 ConfigurationMetaData
(xml的配置文件)。除了代码方式比较直接外,在大部分情况下,容器需要依赖某些工具类,比如: BeanDefinitionReader
,BeanDefinitionReader 会对加载的 ConfigurationMetaData
(xml的配置文件)进行解析和分析,并将分析后的信息组装为相应的 BeanDefinition,最后把这些保存了 bean 定义的 BeanDefinition,注册到相应的 BeanDefinitionRegistry,这样容器的启动工作就完成了。这个阶段主要完成一些准备性工作,更侧重于 bean 对象管理信息的收集,当然一些验证性或者辅助性的工作也在这一阶段完成。
来看一个简单的例子吧,过往,所有的 bean 都定义在 XML 配置文件中,下面的代码将模拟 BeanFactory 如何从配置文件中加载 bean 的定义以及依赖关系:
// 通常为BeanDefinitionRegistry的实现类,这里以DeFaultListabeBeanFactory为例
BeanDefinitionRegistry beanRegistry = new DefaultListableBeanFactory();
// XmlBeanDefinitionReader实现了BeanDefinitionReader接口,用于解析XML文件
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReaderImpl(beanRegistry);
// 加载配置文件
beanDefinitionReader.loadBeanDefinitions("classpath:spring-bean.xml");
// 从容器中获取bean实例 //这一步是获取bean
BeanFactory container = (BeanFactory)beanRegistry;
Business business = (Business)container.getBean("beanName");
总结 :(其实上面所说的和模拟的过程就是在IOC启动时读取xml配置文件注册bean的流程,会先加载(
ConfigurationMetaData
)xml的配置文件,然后解析和分析它,将xml文件中的每一个元素转化成BeanDefintion然后注册到相应的 BeanDefinitionRegistry就完成了,只是注册到 BeanDefinitionRegistry,bean并没有实例化,这就是下面所说的初始化延迟)
②、Bean的实例化阶段
经过第一阶段,所有 bean 定义都通过 BeanDefinition 的方式注册到 BeanDefinitionRegistry 中,当某个请求通过容器的 getBean 方法请求某个对象,或者因为依赖关系容器需要隐式的调用 getBean 时,就会触发第二阶段的活动:容器会首先检查所请求的对象之前是否已经实例化完成。如果没有,则会根据注册的 BeanDefinition 所提供的信息实例化被请求对象,并为其注入依赖。当该对象装配完毕后,容器会立即将其返回给请求方法使用。
BeanFactory 只是 Spring IoC 容器的一种实现,如果没有特殊指定,它采用采用延迟初始化策略:只有当访问容器中的某个对象时,才对该对象进行初始化和依赖注入操作。而在实际场景下,我们更多的使用另外一种类型的容器: ApplicationContext
,它构建在 BeanFactory 之上,属于更高级的容器,除了具有 BeanFactory 的所有能力之外,还提供对事件监听机制以及国际化的支持等。它管理的 bean,在容器启动时全部完成初始化和依赖注入操作。( BeanFactory在被请求getBean要获取bean时才实例化,而如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的躯体了,ApplicationContext由BeanFactory派生而来,比它强大)
上面我们讲了ioc容器的内容和启动过程,它是要读取xml文件来注册bean的那么就会产生许多的xml文件,人们就产生了不满,下面就是完善后改成用注释的方式来注入bean
二、夯实基础:JavaConfig与常见Annotation
2.1、JavaConfig
我们知道 bean
是Spring IOC中非常核心的概念,Spring容器负责bean的生命周期的管理。在最初,Spring使用XML配置文件的方式来描述bean的定义以及相互间的依赖关系,但随着Spring的发展,越来越多的人对这种方式表示不满,因为Spring项目的所有业务类均以bean的形式配置在XML文件中,造成了大量的XML文件,使项目变得复杂且难以管理。
后来,基于纯Java Annotation依赖注入框架 Guice
出世,其性能明显优于采用XML方式的Spring,甚至有部分人认为, Guice
可以完全取代Spring( Guice
仅是一个轻量级IOC框架,取代Spring还差的挺远)。正是这样的危机感,促使Spring及社区推出并持续完善了 JavaConfig
子项目,它基于Java代码和Annotation注解来描述bean之间的依赖绑定关系。(总结: 上面我们提到bean的定义和相互间是用xml描述的,因为产生了许多的xml文件因此人们对它不满,所以完善了用Annotation来注册bean就是我们所见的@注释)
比如,下面是使用XML配置方式来描述bean的定义:
<bean id="bookService" class="cn.moondev.service.BookServiceImpl"></bean>
而基于JavaConfig的配置形式是这样的:
@Configuration
public class MoonBookConfiguration {
// 任何标志了@Bean的方法,其返回值将作为一个bean注册到Spring的IOC容器中
// 方法名默认成为该bean定义的id
@Bean
public BookService bookService() {
return new BookServiceImpl();
}
}
如果两个bean之间有依赖关系的话,在XML配置中应该是这样:
<bean id="bookService" class="cn.moondev.service.BookServiceImpl">
<property name="dependencyService" ref="dependencyService"/>
</bean>
<bean id="otherService" class="cn.moondev.service.OtherServiceImpl">
<property name="dependencyService" ref="dependencyService"/>
</bean>
<bean id="dependencyService" class="DependencyServiceImpl"/>
而在JavaConfig中则是这样:
@Configuration
public class MoonBookConfiguration {
// 如果一个bean依赖另一个bean,则直接调用对应JavaConfig类中依赖bean的创建方法即可
// 这里直接调用dependencyService()
@Bean
public BookService bookService() {
return new BookServiceImpl(dependencyService());
}
@Bean
public OtherService otherService() {
return new OtherServiceImpl(dependencyService());
}
@Bean
public DependencyService dependencyService() {
return new DependencyServiceImpl();
}
}}
你可能注意到这个示例中,有两个bean都依赖于dependencyService,也就是说当初始化bookService时会调用 dependencyService()
,在初始化otherService时也会调用 dependencyService()
,那么问题来了?这时候IOC容器中是有一个dependencyService实例还是两个?这个问题留着大家思考吧,这里不再赘述。
2.2、@ComponentScan
(总结 :这个注释是启动组件扫描,扫描所有通过注解配置的bean,注册bean。)
@ComponentScan
注解对应XML配置形式中的 <context:component-scan>
元素,表示启用组件扫描,Spring会自动扫描所有通过注解配置的bean,然后将其注册到IOC容器中。我们可以通过 basePackages
等属性来指定 @ComponentScan
自动扫描的范围,如果不指定,默认从声明 @ComponentScan
所在类的 package
进行扫描。正因为如此,SpringBoot的启动类都默认在 src/main/java
下。
2.3、@Import
(总结 :注解用于导入配置类,比如配置类中的bean依赖于另一个别的类的bean,那么就可以借助@import把依赖的那个bean引进配置类)
@Import
注解用于导入配置类,举个简单的例子:
@Configuration
public class MoonBookConfiguration {
@Bean
public BookService bookService() {
return new BookServiceImpl();
}
}
现在有另外一个配置类,比如: MoonUserConfiguration
,这个配置类中有一个bean依赖于 MoonBookConfiguration
中的bookService,如何将这两个bean组合在一起?借助 @Import
即可:
@Configuration
// 可以同时导入多个配置类,比如:@Import({A.class,B.class})
@Import(MoonBookConfiguration.class)
public class MoonUserConfiguration {
@Bean
public UserService userService(BookService bookService) {
return new BookServiceImpl(bookService);
}
}
需要注意的是,在4.2之前, @Import
注解只支持导入配置类,但是在4.2之后,它支持导入普通类,并将这个类作为一个bean的定义注册到IOC容器中。
2.4、@Conditional
(总结:@Conditional
注解表示在满足某种条件后才初始化一个bean或者启用某些配置。详情见下面)
@Conditional
注解表示在满足某种条件后才初始化一个bean或者启用某些配置。它一般用在由 @Component
、 @Service
、 @Configuration
等注解标识的类上面,或者由 @Bean
标记的方法上。如果一个 @Configuration
类标记了 @Conditional
,则该类中所有标识了 @Bean
的方法和 @Import
注解导入的相关类将遵从这些条件。
在Spring里可以很方便的编写你自己的条件类,所要做的就是实现 Condition
接口,并覆盖它的 matches()
方法。举个例子,下面的简单条件类表示只有在 Classpath
里存在 JdbcTemplate
类时才生效:
public class JdbcTemplateCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
try {
conditionContext.getClassLoader().loadClass("org.springframework.jdbc.core.JdbcTemplate");
return true;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return false;
}
}
当你用Java来声明bean的时候,可以使用这个自定义条件类:
@Conditional(JdbcTemplateCondition.class)
@Service
public MyService service() {
......
}
这个例子中只有当 JdbcTemplateCondition
类的条件成立时才会创建MyService这个bean。也就是说MyService这bean的创建条件是 classpath
里面包含 JdbcTemplate
,否则这个bean的声明就会被忽略掉。
SpringBoot
定义了很多有趣的条件,并把他们运用到了配置类上,这些配置类构成了 SpringBoot
的自动配置的基础。 SpringBoot
运用条件化配置的方法是:定义多个特殊的条件化注解,并将它们用到配置类上。下面列出了 SpringBoot
提供的部分条件化注解:
2.5、@ConfigurationProperties与@EnableConfigurationProperties
当某些属性的值需要配置的时候,我们一般会在 application.properties
文件中新建配置项,然后在bean中使用 @Value
注解来获取配置的值,比如下面配置数据源的代码。
// jdbc config
jdbc.mysql.url=jdbc:mysql://localhost:3306/sampledb
jdbc.mysql.username=root
jdbc.mysql.password=123456
......
// 配置数据源
@Configuration
public class HikariDataSourceConfiguration {
@Value("jdbc.mysql.url")
public String url;
@Value("jdbc.mysql.username")
public String user;
@Value("jdbc.mysql.password")
public String password;
@Bean
public HikariDataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(url);
hikariConfig.setUsername(user);
hikariConfig.setPassword(password);
// 省略部分代码
return new HikariDataSource(hikariConfig);
}
}
使用 @Value
注解注入的属性通常都比较简单,如果同一个配置在多个地方使用,也存在不方便维护的问题(考虑下,如果有几十个地方在使用某个配置,而现在你想改下名字,你改怎么做?)。对于更为复杂的配置,Spring Boot提供了更优雅的实现方式,那就是 @ConfigurationProperties
注解。我们可以通过下面的方式来改写上面的代码:
@Component
// 还可以通过@PropertySource("classpath:jdbc.properties")来指定配置文件
@ConfigurationProperties("jdbc.mysql")
// 前缀=jdbc.mysql,会在配置文件中寻找jdbc.mysql.*的配置项
pulic class JdbcConfig {
public String url;
public String username;
public String password;
}
@Configuration
public class HikariDataSourceConfiguration {
@AutoWired
public JdbcConfig config;
@Bean
public HikariDataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(config.url);
hikariConfig.setUsername(config.username);
hikariConfig.setPassword(config.password);
// 省略部分代码
return new HikariDataSource(hikariConfig);
}
}
@ConfigurationProperties
对于更为复杂的配置,处理起来也是得心应手,比如有如下配置文件:
#App
app.menus[0].title=Home
app.menus[0].name=Home
app.menus[0].path=/
app.menus[1].title=Login
app.menus[1].name=Login
app.menus[1].path=/login
app.compiler.timeout=5
app.compiler.output-folder=/temp/
app.error=/error/
可以定义如下配置类来接收这些属性
@Component
@ConfigurationProperties("app")
public class AppProperties {
public String error;
public List<Menu> menus = new ArrayList<>();
public Compiler compiler = new Compiler();
public static class Menu {
public String name;
public String path;
public String title;
}
public static class Compiler {
public String timeout;
public String outputFolder;
}
}
@EnableConfigurationProperties
注解表示对 @ConfigurationProperties
的内嵌支持,默认会将对应Properties Class作为bean注入的IOC容器中,即在相应的Properties类上不用加 @Component
注解。