SpringBoot 底层原理

一、前言

两大优势:自动装配,起步依赖

优先级配置

有三种配置文件类型 (执行优先级由高到低排序)

properties 

yml 

yaml 

打包测试

需要在 pom.xml 中引入插件 但这个插件总爆红,但是不会影响运行,不用搭理他。

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

二、Bean 管理

1 注解及衍生注解 注入

spring 提供的注解有注解@Component以及它的三个衍生注解(@Controller、@Service、@Repository)来声明IOC容器中的bean对象

2 如何获取 Bean

创建测试类

Spring 在启动时会将 ApplicationContext 对象纳入容器中。表示容器对象

如何获取容器?

        Java中万物皆为对象,容器我们也抽取成为对象 ApplicationContext(程序员) --> BeanFactory(底层)

三种方式 getBean 获取

根据 name 名字默认类名,首字母小写 (缺点:要强转不安全)(若给的名字错误 -- NoSuchBeanDefin)

根据 type 同一类型出现多个(如果一个接口有两个实现类)则抛异常 -- NoUnquieBeanDefin

根据 name 和 type

    @Autowired
    private ApplicationContext ac;
​
    @Test
    public void BeanText(){
​
        // 按类名字获取
        EmployeeController employeeControllerByName = (EmployeeController) ac.getBean("employeeController");
        // 按类型获取
        EmployeeController employeeControllerByType = ac.getBean(EmployeeController.class);
        // 按名字和类型获取
        EmployeeController employeeControllerByNameAndType = ac.getBean("employeeController", EmployeeController.class);
​
        System.out.println("我是employeeControllerByName" + employeeControllerByName);
        System.out.println("我是employeeControllerByType" + employeeControllerByType);
        System.out.println("我是employeeControllerByNameAndType" + employeeControllerByNameAndType);

    }

测试发现这三种都是获取同一个 Bean  因为用的是单例设计模式

启动的时候会创建好所有的Bean 为了增加用户体验度

实际开发最多用的还是Autoware获取

3 Bean作用域问题

五种作用域:singleton prototype request session application

主要使用的是 singleton (单例,默认)和prototype(多例)

贴注解 Scope -- 默认为singleton 改成 prototype 变成多例但是变成多例就不归spring管理

贴注解Lazy -- 懒加载 -- 启动时不创建,用时才创建 (循环调用的时候用)

3 使用第三方Bean

例如Dom4j(解析xml的)

引入依赖

<!--Dom4j-->
<dependency>
    <groupId>org.dom4j</groupId>
    <artifactId>dom4j</artifactId>
    <version>2.1.3</version>
</dependency>

创建个类作为spring容器的配置类

贴注解 证明他是配置类,需要在配置类中添加第三方 Bean 并对其配置

写个方法返回第三方对象(也需要new)贴上bean 将方法的返回值纳入Spring容器中 方法名小写就是首字母小写后的名字

@Configuration
public class MyBeanConfig {
​
    @Bean
    public SAXReader MyBeanConfig(){
        return new SAXReader();
    }
​
}

使用参数时,会从 Spring 容器中现根据类型再根据名字找到 Bean 并将其拿出作为实参传递给该方法的形参中

@Bean
public SAXReader MyBeanConfig(EmployeeController employeeController){
    return new SAXReader();
}
@Autowired
private SAXReader saxReader;
​
@Test
public void SAXReaderTest() throws DocumentException {
    Document document = saxReader.read(this.getClass().getClassLoader().getResource("test.xml"));
    Element rootElement = document.getRootElement();
    String name = rootElement.element("name").getText();
    String age = rootElement.element("age").getText();
    System.out.println(name + " : " + age);
}

住:这个bean是可以直接写在启动类中

三、SpringBoot 原理

SpringBoot 只是简化 Spring

1 两大优势:自动装配,起步依赖

I 起步依赖 -- 带 starter 的依赖

起步依赖的原理是maven依赖传递

II 自动装配

启动时如何将 jar 包中的所有对象加载到容器中?

以Gson为例

@Autowired
private Gson gson;
​
@Test
public void testJson(){
    String json = gson.toJson(" {\"code\":1,\"msg\":\"success\",\"data\":null}");
    System.out.println(json);
}

IIII 自动装配原理实操

创建springboot项目

添加web依赖

修改版本

创建几个类

使用装配类的方法将类存进spring

在其他项目(后用A项目表示)中导入写好的项目的pom.xml(后用B项目表示) -- 模拟引 jar 包的过程

这里要特别注意一下引入其他项目的时候总会自动给你改maven记得改回来!!!

此时B项目中的Bean并没有在A项目中显示

在 pom.xml 中引入B项目的依赖

注入

会发现找不到 Bean

原因:ComponentScan 注解 若没有 参数,则从当前自身所在位置开始向下扫描(包含平级,说人话就是扫描平级及子级的包)我们也可以传递指定路径,让其扫描指定的包,默认扫描的是自己项目下面 java 下面的

解决方法:

1 在主方法上贴 ComponentScan({"要扫描的路径1","要扫描的路径2"}) (不推荐)

问题:导入大量依赖就要扫描很多,繁琐,不利于维护且降低性能

@SpringBootApplication
@ComponentScan({"cn.wolfcode","com.example"})
public class SpringWebConfigApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(SpringWebConfigApplication.class, args);
    }
​
}

2 在主方法上贴 @Import方式导入 -- 三种

1 导入普通类

@Import(类名.class)

@SpringBootApplication
@Import(TokenParser.class)
public class SpringWebConfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringWebConfigApplication.class, args);
    }

}

2 导入配置类 -- 导入配置类可以搞到他下面的所有 Bean

@Import(配置类名.class)

@SpringBootApplication
@Import(HeaderConfig.class)
public class SpringWebConfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringWebConfigApplication.class, args);
    }

}

3 导入 ImportSelector --- spring用的这个 因为spring并不知道第三方要装配什么类,这个类只有第三方知道,所以需要将要装的类写配置文件中。

先在自己的项目中创建 select 包写 ImportSelector 类 实现 ImportSelector 内的 selectImports 方法

需要返回一个字符串数组封装的就是需要装配类的全限定名

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.example.config.HeaderConfig"};
    }
}

@Import(自己注册的ImportSelector .class)

@SpringBootApplication
@Import(MyImportSelector.class)
public class SpringWebConfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringWebConfigApplication.class, args);
    }

}

第三方可以带一个配置文件告诉springboot需要自动装配的名字

在B项目中resources下新建一个配置文件

classNames=com.example.config.HeaderConfig,com.example.config.TokenParseConfig

这时候在ImportSelector 类中值需要读取配置文件信息,切割为字符串数组最后返回,这样就可以读取到这些配置类或Bean信息,此时需要再 springboot 导入其中读取第三方的配置文件信息,根据key获取文件中对应的Value(全类名)

创建 properties 集合对象,接受流

用流读取文件

将流转成集合并加载到properties中

通过key去读value拿到字符串

最后用 "," 切割成字符串数组返回给Import

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        Properties properties = new Properties();
        InputStream is = this.getClass().getClassLoader().getResourceAsStream("configuration.properties");
        try {
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String url = properties.getProperty("classNames");
        return url.split(",");
    }
}

4 自定义注解 --- 在自己的项目写注解 -- 就是模仿了 这个@SpringBootApplication注解 这个真正模仿了springboot的自动装配

anno包下创建一个注解

除了正常要用的注解之外在封装一个@import(自己注册的ImportSelector .class)

直接将自定义的注解贴在启动类上

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(MyImportSelector.class)
public @interface MyAutowareConfigration {
}
@SpringBootApplication
@MyAutowareConfigration
public class SpringWebConfigApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringWebConfigApplication.class, args);
    }

}

5 上面方法比较草率,SpringBoot 真正的封装比他高级一点,去可以被自动装配的 jar 包下去找 META-INF 的 spring.factories 其中有一个 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 只需要把自己需要自动装配的配置类或者普通类作为 value 传进去,就通过 springboot 自动装配到A项目中

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.config.HeaderConfig,com.example.config.TokenParseConfig

IV 主启动类解析 -- 源码分析

1 方法中的:SpringApplication.run(SpringWebConfigApplication.class, args);

作用:启动springboot程序后,将springboot 程序加载在tomcat服务器并启动服务器

参数解析:

参数1:传递了一个类,会在传递的类上面去找注解@SpringBootApplication(一个牛逼的复合注解)有这个注解的就是启动类

参数2:启动springboot程序后从外界(例如控制台)传递进来的一切参数都有 args 去接收tomcat

2 一个非常牛逼的注解 @SpringBootApplication

@SpringBootApplication 中的注解解释

元注解:

@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited

配置类:@SpringBootConfiguration -- 因为有他所有在主启动类中配置 Bean 有效

扫描注解(注意扫描范围):@ComponentScan() 里面那一大堆就是排除文件没啥用

可以自动装配的注解:@EnableAutoConfiguration 其中有 @Import()

3 查看 @EnableAutoConfiguration 下的 AutoConfigurationImportSelector.class

这个类实现了 ImportSelector 的子接口, 同时重写找到 ImportSelector 下的 selectImports 方法,最终返回一个 String[] 。

返回的字符串数组是从 AutoConfigurationImportSelector 类中下面的 getAutoConfigurationEntry 获取

这个字符串数组所需的路径则是通过 getAutoConfigurationEntry 下 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); 中去获取,这个 configurations 的结果是所有要自动装配的类,但是这些类不是全都要装进去。

为了研究这些需要自动装配的类从哪来,所以要进入 getCandidateConfigurations 方法中,这个方法需要返回一个 configurations ,而这个 configurations 是从 SpringFactoriesLoader 中 的 loadFactoryNames 得到,而 loadFactoryNames 则是调用本类方法 loadSpringFactories ,由下面的代码中拿到

Enumeration<URL> urls = (classLoader != null ?
      classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
      ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));

这些url从本地仓拿到依赖,经过遍历获取并添加所有带有META-INF 的 spring.factories的类

注意:会拿到很多自动装配的的类,但是并不是全都自动装配了,因为

V @Conditional 注解

作用:按照一定的条件去判断,满足指定条件之后才会将 Bean 对象注入到 Spring 的 IoC 容器中

位置:方法、类

常用的三种子注解:

  • @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。

    @Configuration
    public class HeaderConfig {
    ​
        @Bean
        @ConditionalOnClass(name="io.jsonwebtoken.Jwts")
        public HeaderParser headerParser(){
            return new HeaderParser();
        }
        
    }
    <!--JWT令牌-->
    <dependency>
         <groupId>io.jsonwebtoken</groupId>
         <artifactId>jjwt</artifactId>
         <version>0.9.1</version>
    </dependency>
    @SpringBootTest
    public class AutoConfigurationTests {
        @Autowired
        private ApplicationContext applicationContext;
    ​
        @Test
        public void testHeaderParser(){
            System.out.println(applicationContext.getBean(HeaderParser.class));
        }
        
    }

  • @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。

    @Configuration
    public class HeaderConfig {
    ​
        @Bean
        @ConditionalOnMissingBean
        public HeaderParser headerParser(){
            return new HeaderParser();
        }
    ​
    }

    这个注解可以传参数,可以指定不存在的 bean(通过bean名和类)

    @Configuration
    public class HeaderConfig {
    ​
        @Bean
        @ConditionalOnMissingBean(name="deptController2") 
        public HeaderParser headerParser(){
            return new HeaderParser();
        }
        
        @Bean
        @ConditionalOnMissingBean(HeaderConfig.class)
        public HeaderParser headerParser(){
            return new HeaderParser();
        }
    ​
    }

    如果指定的Bean存在则会报错,所以不要慌!

  • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。

    先在 application.yml 配置文件中添加如下的键值对:

    name: testName

    在声明bean的时候就可以指定一个条件 @ConditionalOnProperty

    @Configuration
    public class HeaderConfig {
    ​
        @Bean
        @ConditionalOnProperty(name ="name",havingValue = "testName")
        public HeaderParser headerParser(){
            return new HeaderParser();
        }
    ​
        @Bean
        public HeaderGenerator headerGenerator(){
            return new HeaderGenerator();
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值