SpringBoot原理篇
1. 自动配置
1.1. Bean加载方式
1.1.1. 八种创建方式
方式一
< bean id = " bookController" class = " com.cqut.controller.BookController" />
通过ApplicationContext
类拿到对应类的Bean。
public class App {
public static void main ( String [ ] args ) {
ApplicationContext context = new ClassPathXmlApplicationContext ( "application.xml" ) ;
BookController bookController = context. getBean ( BookController . class ) ;
System . out. println ( bookController) ;
}
}
方式二
通过注解+xml文件扫描包 与方式一相同可以拿到bean。
@Controller
public class BookController {
}
< context: component-scan base-package = " com.cqut.controller" />
方式三
@ComponentScan ( "com.cqut.controller" )
public class Spring01Config {
}
通过AnnotationConfigApplicationContext
来启动获取Bean。
public class App {
public static void main ( String [ ] args ) {
ApplicationContext context = new AnnotationConfigApplicationContext ( Spring01Config . class ) ;
BookController bookController = context. getBean ( BookController . class ) ;
System . out. println ( bookController) ;
}
}
同时初始化了Spring01Config
和BookController
两个Bean。其中Spring01Config
是通过ApplicationContext context = new AnnotationConfigApplicationContext(Spring01Config.class);
初始化Bean的,而BookController
是通过扫描初始化Bean的。
方式四
使用@Import
注解导入要注入的Bean对应的字节码
package com. cqut. config ;
public class JdbcConfig {
}
@Import ( JdbcConfig . class )
public class Spring01Config {
}
被导入的bean无序使用注解声明为bean。 此形式可以有效的降低源代码与Spring技术的耦合度,在Spring技术底层及诸多框架的整合中大量使用。 通过@Import
导入的Bean的id复制默认是全路径名,例如com.cqut.config.JdbcConfig
。
方式五
public class App {
public static void main ( String [ ] args ) {
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext ( Spring01Config . class ) ;
context. register ( UserController . class ) ;
String [ ] beanDefinitionNames = context. getBeanDefinitionNames ( ) ;
for ( String beanDefinitionName : beanDefinitionNames) {
System . out. println ( beanDefinitionName) ;
}
}
}
方式六
导入实现了ImportSelector
接口的类,实现对导入源的编程式处理
@Import ( { JdbcConfig . class , MyImportSelector . class } )
public class Spring01Config {
@Bean
public BookControllerFactory bookController ( ) {
return new BookControllerFactory ( ) ;
}
}
public class MyImportSelector implements ImportSelector {
@Override
public String [ ] selectImports ( AnnotationMetadata annotationMetadata) {
String className = annotationMetadata. getClassName ( ) ;
System . out. println ( "打印结果:" + className) ;
return new String [ ] { "com.cqut.controller.UserController" } ;
}
}
可以获取导入源的各项信息,打印的结果是打印结果:com.cqut.config.Spring01Config
。 可以通过判断导入源的信息来判断控制导入哪些Bean。
方式七
导入实现了ImportBeanDefinitionRegistrar
接口的类,通过BeanDefinition
的注册器注册实名Bean,实现对容器中bean的裁定,例如对现有Bean的覆盖,进而达成不修改源代码的情况下更换实现的效果
@Import ( { MyImportBeanDefinitionRegistrar . class } )
public class Spring01Config {
@Bean
public BookControllerFactory bookController ( ) {
return new BookControllerFactory ( ) ;
}
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions ( AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder
. genericBeanDefinition ( UserController . class )
. getBeanDefinition ( ) ;
registry. registerBeanDefinition ( "MathTest" , beanDefinition) ;
}
}
方式八
导入实现了BeanDefinitionRegistryPostProcessor
接口的类,通过BeanDefinition
的注册实名bean。实现对容器中Bean的最终裁定。 根据执行流程,MyBeanDefinitionRegistryPostProcessor
是初始化Bean阶段最后执行的,它可以覆盖前面所有的bean的定义。
@Import ( { MyBeanDefinitionRegistryPostProcessor . class , JdbcConfig . class ,
MyImportSelector . class , MyImportBeanDefinitionRegistrar . class } )
public class Spring01Config {
@Bean
public BookControllerFactory bookController ( ) {
return new BookControllerFactory ( ) ;
}
}
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry ( BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
BeanDefinition beanDefinition = BeanDefinitionBuilder
. genericBeanDefinition ( UserController . class ) .
getBeanDefinition ( ) ;
beanDefinitionRegistry. registerBeanDefinition ( "English" , beanDefinition) ;
}
@Override
public void postProcessBeanFactory ( ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
1.1.2. 静态工厂创建Bean
继承接口FactoryBean<T>
实现静态工厂实例化Bean
public class BookControllerFactory implements FactoryBean < BookController > {
@Override
public BookController getObject ( ) throws Exception {
return new BookController ( ) ;
}
@Override
public Class < ? > getObjectType ( ) {
return BookController . class ;
}
@Override
public boolean isSingleton ( ) {
return true ;
}
}
在Spring01Config
中使用静态工厂实例化一个Bean对象。 这里并没有使用@Configuration
修饰,Spring01Config
的实例化是通过方式三中的方法来实例化对象的。
public class Spring01Config {
@Bean
public BookControllerFactory bookController ( ) {
return new BookControllerFactory ( ) ;
}
}
使用静态工厂实例化Bean,但静态工厂这个类本身是不会被实例化成Bean的
1.1.3. 配置类导入xml中注册Bean
使用@ImportResource("application.xml")
注解导入xml文件中Bean的注册信息
@ImportResource ( "application.xml" )
public class Spring01Config {
}
如果在导入多个配置文件中,有两个Bean的id重复了,后面导入的Bean会覆盖掉前面导入的Bean,与写配置文件的位置相关。实际与加载Bean的流程有关,后面加载的会覆盖掉前面重复加载的Bean。Spring中大部分都满足后面覆盖前面的策略
1.1.4. @Configuration
与@Component
分析
@Target ( ElementType . TYPE )
@Retention ( RetentionPolicy . RUNTIME )
@Documented
@Indexed
public @interface Component {
String value ( ) default "" ;
}
String value() default ""
来指定修饰类初始化Bean的id值,如果没有设置id则默认为类名第一个字母小写的类名为id值。
@Target ( { ElementType . TYPE } )
@Retention ( RetentionPolicy . RUNTIME )
@Documented
@Component
public @interface Configuration {
@AliasFor (
annotation = Component . class
)
String value ( ) default "" ;
boolean proxyBeanMethods ( ) default true ;
}
@Configuration
除了有@Component
的功能,还有proxyBeanMethods()
来判断是否生成代理对象。默认为True
表示初始化Bean为修饰类的代理对象而不是类对象本身,false
则初始化Bean为类对象本身
@Configuration ( proxyBeanMethods = false )
public class Spring01Config {
@Bean
public Book book ( ) {
return new Book ( ) ;
}
}
proxyBeanMethods
默认为true
创建代理对象,当通过Spring01Config
调用book
方法返回book
这个对象,实际上Spring01Config
的代理对象从IOC容器中找是否有对应类型的Bean,然后返回,没有就创建一个Bean。这样再多次调用book
方法返回对象其实都是同一个对象,在IOC容器中proxyBeanMethods
设置为false
创建的时类本身,通过调用book
方法返回的book
的对象都是新的一个对象,不是同一个。使用proxyBeanMethods=true
可以保障调用此方法得到的对象是从容器中获取的而不是重新创建的。
1.1.5. 注解控制加载Bean
使用@Condition
注解的派生注解设置各种组合条件控制Bean的加载,使用依赖坐标spring-boot-starter
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter</ artifactId>
< version> 2.6.13</ version>
</ dependency>
@Import ( { MyBeanDefinitionRegistryPostProcessor . class , JdbcConfig . class ,
MyImportSelector . class , MyImportBeanDefinitionRegistrar . class } )
public class Spring01Config {
@Bean
@ConditionalOnMissingClass ( "com.cqut.controller.UserController" )
public BookControllerFactory bookController ( ) {
return new BookControllerFactory ( ) ;
}
}
如果com.cqut.controller.UserController
没有则创建bookController
。 @Condition
派生了很多组合条件同时使用,根据条件来判定是否生成Bean,可以用在方法和类上。
1.2. Bean依赖属性配置
将业务功能Bean运行需要的资源抽取成独立的属性类(****Properties),设置读取配置文件信息。 在纯spring框架中,只支持*.properties
的注解资源文件导入,且需要手动导入资源文件。在springboot框架中默认导入classpath下的资源源文件,支持*.yml
、*.yaml
和*.properties
三种格式的资源文件导入,不需要我们手动导入 。资源文件导入,通过setter
注入方式实现的。
@Data
@ConfigurationProperties ( prefix = "bookproperties" )
public class BookProperties {
private Book book;
}
使用@EnableConfigurationProperties
注解设定使用属性类时加载Bean。 业务bean的属性可以为其设定默认值,当需要设置时通过配置文件传递属性。 @ConfigurationProperties
是需要实现实例化Bean才能使用,当BookProperties
并没有使用@Component
注解来实例化Bean,这里是通过BookInitProperties
上的注解@EnableConfigurationProperties(BookProperties.class)
来解决问题的。@EnableConfigurationProperties
能够将指定的类实例化一个Bean。通过这种方式可以减少Spring核心容器对Bean的管理,只有在使用BookInitProperties
对象是才会去实例化一个BookProperties
Bean对象。
@EnableConfigurationProperties ( BookProperties . class )
public class BookInitProperties {
private Book book;
public BookInitProperties ( BookProperties properties) {
book = new Book ( ) ;
book. setId ( properties. getBook ( ) != null && properties. getBook ( ) . getId ( ) != null ? properties. getBook ( ) . getId ( ) : 1 ) ;
book. setName ( properties. getBook ( ) != null && properties. getBook ( ) . getName ( ) != null ? properties. getBook ( ) . getName ( ) : "无" ) ;
book. setSeries ( properties. getBook ( ) != null && properties. getBook ( ) . getSeries ( ) != null ? properties. getBook ( ) . getSeries ( ) : "无" ) ;
book. setDescription ( properties. getBook ( ) != null && properties. getBook ( ) . getDescription ( ) != null ? properties. getBook ( ) . getDescription ( ) : "无" ) ;
}
public void test ( ) {
System . out. println ( book) ;
}
}
定义业务功能Bean,通过使用@Import
导入,解耦强制加载Bean。这里的BookInitProperties
没有使用@Component
去实例化Bean。 业务bean应尽量避免设置强制加载,而是根据需要导入后加载,降低spring容器管理bean的强度。
@Import ( BookInitProperties . class )
public class Spring02Config {
}
@SpringBootApplication
@Import ( { Spring02Config . class } )
public class SpringApplicationMain {
public static void main ( String [ ] args) {
ConfigurableApplicationContext context = SpringApplication . run ( SpringApplicationMain . class ) ;
BookInitProperties bean = context. getBean ( BookInitProperties . class ) ;
bean. test ( ) ;
}
}
1.3. 自动装配
自动配置原理
收集Spring开发者的编程习惯,整理开发过程使用的常用技术列表 (技术集A) 收集常用技术(技术A)的使用参数,整理开发过程中每个技术的常用设置列表(设置集B) 初始化SpringBoot的基础环境,加载用户自定义的bean和导入的其他坐标,形成初始化环境 将技术集A包含的所有技术都定义出来,在Spring/SpringBoot启动时默认全部加载 将技术集A中具有使用条件的技术约定出来,设置按条件加载,由开发者决定是否使用该技术(与初始化环境比对) 将设置集作为默认配置加载(约定大于配置),减少开发者配置工作量 开放设置集B的配置覆盖接口,由开发者根据自身需要决定是否覆盖默认配置 方式
通过配置文件exclude属性排除自动配置 通过注解@EnableAutoConfiguration
属性排除自动配置属性 启动自动配置只需要满足自动配置条件即可 根据需求开发自定义自动配置项**(META-INF/spring.factories)**