SpringBoot原理篇

SpringBoot原理篇

1. 自动配置

1.1. Bean加载方式

1.1.1. 八种创建方式
  1. 方式一
  • 通过xml文件配置Bean
<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);
    }
}
  1. 方式二
  • 通过注解+xml文件扫描包
  • 与方式一相同可以拿到bean。
@Controller
public class BookController {
}
<context:component-scan base-package="com.cqut.controller"/>
  1. 方式三
  • 通过注解+配置类扫描包
@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);
    }
}
  • 同时初始化了Spring01ConfigBookController两个Bean。其中Spring01Config是通过ApplicationContext context = new AnnotationConfigApplicationContext(Spring01Config.class);初始化Bean的,而BookController是通过扫描初始化Bean的。
  1. 方式四
  • 使用@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
  1. 方式五
  • 使用上下文对象在容器中初始化完成后注入Bean
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);
        }
    }
}
  1. 方式六
  • 导入实现了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。
  1. 方式七
  • 导入实现了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);
    }
}
  1. 方式八
  • 导入实现了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对象是才会去实例化一个BookPropertiesBean对象。
@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. 自动装配

  • 自动配置原理
    1. 收集Spring开发者的编程习惯,整理开发过程使用的常用技术列表 (技术集A)
    2. 收集常用技术(技术A)的使用参数,整理开发过程中每个技术的常用设置列表(设置集B)
    3. 初始化SpringBoot的基础环境,加载用户自定义的bean和导入的其他坐标,形成初始化环境
    4. 将技术集A包含的所有技术都定义出来,在Spring/SpringBoot启动时默认全部加载
    5. 将技术集A中具有使用条件的技术约定出来,设置按条件加载,由开发者决定是否使用该技术(与初始化环境比对)
    6. 将设置集作为默认配置加载(约定大于配置),减少开发者配置工作量
    7. 开放设置集B的配置覆盖接口,由开发者根据自身需要决定是否覆盖默认配置
  • 方式
    1. 通过配置文件exclude属性排除自动配置
    2. 通过注解@EnableAutoConfiguration属性排除自动配置属性
    3. 启动自动配置只需要满足自动配置条件即可
    4. 根据需求开发自定义自动配置项**(META-INF/spring.factories)**

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值