5. Spring Boot 底层原理基础

1. 底层注解@Configuration

@Configuration 注解主要用于给容器添加组件(Bean),下面实践其用法:

项目基本结构:

5. Spring Boot 底层原理基础_第1张图片

 两个Bean组件:

User.java

package com.menergy.boot.bean;

/**
 * 用户
 */
public class User {
    private String name;
    private Integer age;

    public User() {
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Pet.java

package com.menergy.boot.bean;

/**
 * 宠物
 */
public class Pet {
    private String name;

    public Pet() {
    }

    public Pet(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Pet{" +
                "name='" + name + '\'' +
                '}';
    }
}

以前Spring 配置文件方式是这样给容器添加组件的:



    
        
        
    

    
        
    

现在Spring Boot 已经不写上面的xml配置了,在Spring Boot 底层可以用@Configuration 注解给容器中添加组件。如下:

注解类MyConfig.java

package com.menergy.boot.config;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2. 配置类本身也是组件
 * 3. proxyBeanMethods: 代理Bean 方法:
 *      Full(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
 *      Lite(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
 *      用于解决组件依赖场景
 */
@Configuration(proxyBeanMethods = true)  //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {

    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean   //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
    public User user01(){
        return new User("dragon",18);

    }

    @Bean("tomcatPet")
    public Pet pet01(){
        return new Pet("dragonPet");
    }

}

主类MainApplication.java 中测试调用:

package com.menergy.boot;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import com.menergy.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import sun.awt.geom.AreaOp;

/**
 * 主程序类
 * 这个注解相当于告诉Spring Boot: 这是一个Spring boot 应用
 */
//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.menergy.boot")
public class MainApplication {

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

        // 1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        // 2.查看容器里面的容器
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        // 3. 从容器中获取组件

        Pet pet1 = run.getBean("tomcatPet", Pet.class);

        Pet pet2 = run.getBean("tomcatPet", Pet.class);

        System.out.println("组件: " + (pet1 == pet2));


        // 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1
        MyConfig myConfig = run.getBean(MyConfig.class);
        System.out.println(myConfig);

        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法, Spring Boot 总会检查这个组件是否在容器中有,如果有则不会新建,保持组件单实例。
        User user01 = myConfig.user01();
        User user02 = myConfig.user01();

        System.out.println(user01 == user02);


    }
}

输出的部分结果:

5. Spring Boot 底层原理基础_第2张图片

上面的例子,重点落在@Configuration(proxyBeanMethods = true) 注解。 该注解告诉SpringBoot ,被注解的类是一个配置类, 相当于以前的配置文件xml中的“bean配置”。该注解有如下特性:

1. 该注解的配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的。

2. 被这个注解的配置类本身也是组件。

3. 该注解的属性proxyBeanMethods 可以通过“true” 和 “false” 配置值,来控制使用的模式:

        (1)Full模式(proxyBeanMethods = true): 为true时,外部无论对配置类中的组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象。

        (2)Lite模式(proxyBeanMethods = false): 为false时,在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象。

        这两种模式的存在主要用于解决组件依赖场景。

1和2 两点特性上面的例子中都有体现, 接下来重点实践第三点特性:

实践proxyBeanMethods:

基于上面的例子,首先修改User.java类,加上宠物Pet的依赖:

package com.menergy.boot.bean;

/**
 * 用户
 */
public class User {
    private String name;
    private Integer age;

    private Pet pet;

    public User() {
    }

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public User(String name, Integer age, Pet pet) {
        this.name = name;
        this.age = age;
        this.pet = pet;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Pet getPet() {
        return pet;
    }

    public void setPet(Pet pet) {
        this.pet = pet;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", pet=" + pet +
                '}';
    }
}

 在配置类MyConfig.java 中加入user01对象对用pet对象,同时使用Full模式(proxyBeanMethods = true):

package com.menergy.boot.config;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2. 配置类本身也是组件
 * 3. proxyBeanMethods: 代理Bean 方法:
 *      Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
 *      Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
 *      用于解决组件依赖场景
 */
@Configuration(proxyBeanMethods = true)  //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {

    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean   //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
    public User user01(){
        User dragonUser = new User("dragon",18);
        // User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
        dragonUser.setPet(pet01());
        return dragonUser;

    }

    @Bean("tomcatPet")
    public Pet pet01(){
        return new Pet("dragonPet");
    }

}

主类MainApplication.java:

package com.menergy.boot;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import com.menergy.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import sun.awt.geom.AreaOp;

/**
 * 主程序类
 * 这个注解相当于告诉Spring Boot: 这是一个Spring boot 应用
 */
//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.menergy.boot")
public class MainApplication {

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

        // 1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        // 2.查看容器里面的容器
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }

        // 3. 从容器中获取组件

        Pet pet1 = run.getBean("tomcatPet", Pet.class);

        Pet pet2 = run.getBean("tomcatPet", Pet.class);

        System.out.println("组件: " + (pet1 == pet2));


        // 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1
        MyConfig myConfig = run.getBean(MyConfig.class);
        System.out.println(myConfig);

        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法, Spring Boot 总会检查这个组件是否在容器中有,如果有则不会新建,保持组件单实例。
        User user01 = myConfig.user01();
        User user02 = myConfig.user01();
        System.out.println(user01 == user02);

        //测试 @Configuration(proxyBeanMethods = true/false)
        User user011 = run.getBean("user01", User.class);
        Pet tomcatPet = run.getBean("tomcatPet", Pet.class);
        System.out.println("用户的宠物:" + (user011.getPet() == tomcatPet));

    }
}

运行结果:

5. Spring Boot 底层原理基础_第3张图片

可以看出,Full模式(proxyBeanMethods = true)时,输出true,说明是从容器中获取的同一个组件(用户的宠物就是容器中的宠物)。

接下来,改用Lite模式(proxyBeanMethods = false):即基于上面实例,将配置类MyConfig.java 中的注解的属性proxyBeanMethods 改成false值,如下:

MyConfig.java:

package com.menergy.boot.config;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2. 配置类本身也是组件
 * 3. proxyBeanMethods: 代理Bean 方法:
 *      Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
 *      Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
 *      用于解决组件依赖场景
 */
@Configuration(proxyBeanMethods = false)  //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {

    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean   //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
    public User user01(){
        User dragonUser = new User("dragon",18);
        // User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
        dragonUser.setPet(pet01());
        return dragonUser;

    }

    @Bean("tomcatPet")
    public Pet pet01(){
        return new Pet("dragonPet");
    }

}

运行结果:

5. Spring Boot 底层原理基础_第4张图片

 可以看出,Lite模式(proxyBeanMethods = false)时,输出false,说明是从容器中获取的不是同一个组件(用户的宠物不是容器中的宠物, 相当于new 了另一个对象)。

总结:配置类包括了全模式(Full)和轻量级模式(Lite)两种。当proxyBeanMethods 是true时,Spring Boot 每次都会检查容器中是否有相应的组件,如果proxyBeanMethods 是false, 则不检查容器中是否有没有相应的组件,而是直接new一个。这也是Spring Boot 新增的一个很重要的特性。

最佳实战:如果只是向容器中增加组件,别的地方也不会调用这个组件,我们可以将其调为false 模式,这样Spring Boot 启动起来非常快,加载起来也非常快。 如果别的地方明显要用,要依赖,我们就把其调成true,保证依赖的组件就是容器中的组件。

注: 前面的例子中,在配置类中用到@Been 注解来指定组件, 其实Spring Boot 底层还用到了其他一些以前常用的注解来指定组件,包括@Component、@Controller、@Service、@Repository。这些类似于@Been 原理,也是用于向容器中注册组件。

除此之外,底层还用到@ComponentScan 注解来说明容器的包扫描,还有@Import @Conditional 来向容器添加组件。很多注解是以前常用的,接下来主要说明@Import 和@Conditional 注解。

2. 底层注解@Import

首先,从@Import 注解类中可以看到该注解的定义,以及知道其属性是一个Class类型的数组,说明这个注解的作用是向容器中导入一批组件

5. Spring Boot 底层原理基础_第5张图片

接下来,实践一下:

 首先在配置类上加入@Import 注解,并向容器中导入两个组件,一个是自己定义的类,一个是从第三方Jar 包中任意的一个类:

5. Spring Boot 底层原理基础_第6张图片

主类加入如下测试:

5. Spring Boot 底层原理基础_第7张图片

 运行结果:

5. Spring Boot 底层原理基础_第8张图片

 结果说明:

“com.menergy.boot.bean.User” 是通过@Import 导入的组件。(默认的组件名称是全类名

“user01” 是之前用@Bean 方法添加进去的

“org.apache.logging.log4j.util.StringBuilders@4482469c” 也是通过@Import 导入的组件。

3. 底层注解@Conditional

@Conditional 是条件装配:当满足@Conditional指定的条件时, 才向容器中注入组件,或干相应的事。

在全局Jar包中搜索@Conditional 类:双击Shift键,选择Classes,输入@Conditional搜索。

5. Spring Boot 底层原理基础_第9张图片

注:如果调不出这个窗口,请参考: (98条消息) IDEA 操作与设置笔记_龙泉太阿的博客-CSDN博客https://blog.csdn.net/menergy/article/details/123827363?spm=1001.2014.3001.5501

打开Conditional 类后,选中“Conditional” 类名,“Ctrl + H” 键调出这个类的继承树:

5. Spring Boot 底层原理基础_第10张图片

 注:如果快捷键失效,请确定如下快捷键设置:

5. Spring Boot 底层原理基础_第11张图片

从前面的@Conditional 的继承树可以看出,@Conditional 有非常多的派生注解,每个注解都代表不同的功能,从派生注解的注解名称可以大概知道其功能用意,例如@ConditionalOnBean 注解代表当容器中存在某个Bean时才干某些事情, @ConditionalOnMissingBean 注解代表当容器中不存在某个Bean时才干某些事情。

接下来,以@ConditionalOnBean 为例,进行实践:

首先,我们将前面的MyConfig.java 中宠物的“@Bean” 注解注释掉,那么这个方法相当于常规方法,没有向容器中注册该组件,如下:

5. Spring Boot 底层原理基础_第12张图片

完整代码:

package com.menergy.boot.config;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.apache.logging.log4j.util.StringBuilders;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2. 配置类本身也是组件
 * 3. proxyBeanMethods: 代理Bean 方法:
 *      Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
 *      Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
 *      用于解决组件依赖场景
 *
 * 4. @Import({User.class, StringBuilders.class})
 *      自动调用类的无参构造器创建出这两个类型的组件
 *
 */
@Import({User.class, StringBuilders.class}) // 例子中的User 是自定义的类,StringBuilders 是第三方Jar包中的类
@Configuration(proxyBeanMethods = false)  //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {

    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean   //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
    public User user01(){
        User dragonUser = new User("dragon",18);
        // User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
        dragonUser.setPet(pet01());
        return dragonUser;

    }

//    @Bean("tomcatPet")
    public Pet pet01(){
        return new Pet("dragonPet");
    }

}

为了方便测试,将主方法中前面的测试代码注释掉 ,加入如下测试代码:

 完整代码:

package com.menergy.boot;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import com.menergy.boot.config.MyConfig;
import org.apache.logging.log4j.util.StringBuilders;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * 主程序类
 * 这个注解相当于告诉Spring Boot: 这是一个Spring boot 应用
 */
//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.menergy.boot")
public class MainApplication {

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

        // 1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        // 2.查看容器里面的容器
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
//
//        // 3. 从容器中获取组件
//
//        Pet pet1 = run.getBean("tomcatPet", Pet.class);
//
//        Pet pet2 = run.getBean("tomcatPet", Pet.class);
//
//        System.out.println("组件: " + (pet1 == pet2));
//
//
//        // 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1
//        MyConfig myConfig = run.getBean(MyConfig.class);
//        System.out.println(myConfig);
//
//        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法, Spring Boot 总会检查这个组件是否在容器中有,如果有则不会新建,保持组件单实例。
//        User user01 = myConfig.user01();
//        User user02 = myConfig.user01();
//        System.out.println(user01 == user02);
//
//        //测试 @Configuration(proxyBeanMethods = true/false)
//        User user011 = run.getBean("user01", User.class);
//        Pet tomcatPet = run.getBean("tomcatPet", Pet.class);
//        System.out.println("用户的宠物:" + (user011.getPet() == tomcatPet));
//
//        // 5. 从容器中获取组件。测试@Import({User.class, LogBuilder.class})
//        String[] beanNamesForType = run.getBeanNamesForType(User.class);
//        System.out.println("-----------------------------------------");
//        for (String s : beanNamesForType) {
//            System.out.println(s);
//        }
//
//        StringBuilders bean1 = run.getBean(StringBuilders.class);
//        System.out.println(bean1);

        boolean user01 = run.containsBean("user01"); // 容器中是否包括这个组件
        System.out.println("容器中user01组件是否存在:" + user01);

        boolean tomcatPet = run.containsBean("tomcatPet"); // 容器中是否包括这个组件
        System.out.println("容器中tomcatPet 组件是否存在:" + tomcatPet);

    }
}

运行结果:

5. Spring Boot 底层原理基础_第13张图片

  结果表示在容器中存在“user01”组件,不存在“tomcatPet”组件。

基于这个场景,接着我们来实践@ConditionalOnBean注解:

点击进入ConditionalOnBean 类,可以看到很多对应的字段属性,这里试着选择name 属性来注解:

5. Spring Boot 底层原理基础_第14张图片

 5. Spring Boot 底层原理基础_第15张图片

 完整代码:

package com.menergy.boot.config;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.apache.logging.log4j.util.StringBuilders;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2. 配置类本身也是组件
 * 3. proxyBeanMethods: 代理Bean 方法:
 *      Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
 *      Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
 *      用于解决组件依赖场景
 *
 * 4. @Import({User.class, StringBuilders.class})
 *      自动调用类的无参构造器创建出这两个类型的组件
 *
 */
@Import({User.class, StringBuilders.class}) // 例子中的User 是自定义的类,StringBuilders 是第三方Jar包中的类
@Configuration(proxyBeanMethods = false)  //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {

    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
     * @return
     */
    @ConditionalOnBean(name = "tomcatPet")  //表示容器中有tomcatPet组件时,才在容器中注入user01组件
    @Bean   //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
    public User user01(){
        User dragonUser = new User("dragon",18);
        // User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
        dragonUser.setPet(pet01());
        return dragonUser;

    }

//    @Bean("tomcatPet")
    public Pet pet01(){
        return new Pet("dragonPet");
    }

}

注解@ConditionalOnBean(name = "tomcatPet") 表示容器中有tomcatPet组件时,才在容器中注入user01组件。这里加上这个条件装配注解后,看一下运行结果:

5. Spring Boot 底层原理基础_第16张图片

 可以看出跟前面相比,这里时false表示容器中没有user01组件(因为加了条件注解)。

也可以把条件注解标注在类上,表示条件成立时,整个类的方法才生效,否则都不生效:

5. Spring Boot 底层原理基础_第17张图片

再看运行结果:

 5. Spring Boot 底层原理基础_第18张图片

其他条件注解也是类似的原理,比如下面我实践@ConditionalOnMissingBean(name = "tomcatPet") 表示容器中不存在tomcatPet组件时,该类中标注的全部组件才生效。这里可以看到“tomcatPet” 的@Bean注解已经被去掉了,所以容器中不存在“tomcatPet” 组件,符合条件注解,所以容器中会注册“user01”组件,如下:

5. Spring Boot 底层原理基础_第19张图片

运行结果:

5. Spring Boot 底层原理基础_第20张图片

4. 底层注解@ImportResource

@ImportResource 表示将指定路径下的配置文件导入为组件,使用场景一般在一下旧系统还在使用xml配置Bean的方法注册主键时,为了不想手动一个个改成注解方式,可以采用这个@ImportResource注解,或者引用了一些旧版本的包,使用了xml方式,对第三方包又不方便修改,这时采用@ImportResource就很方便将其转成注解方式。

下面实践这个注解:

先看以前配置方式下,这里用beans.xml配置了两个组件,但是系统没有引用它,所以这两个组件应该不生效,如下:

beans.xml:



    
        
        
    

    
        
    


组件名称分别为“user02” 和“pet02”

主类MainApplication.java:

5. Spring Boot 底层原理基础_第21张图片

 完整代码:

package com.menergy.boot;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import com.menergy.boot.config.MyConfig;
import org.apache.logging.log4j.util.StringBuilders;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * 主程序类
 * 这个注解相当于告诉Spring Boot: 这是一个Spring boot 应用
 */
//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.menergy.boot")
public class MainApplication {

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

        // 1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

        // 2.查看容器里面的容器
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
//
//        // 3. 从容器中获取组件
//
//        Pet pet1 = run.getBean("tomcatPet", Pet.class);
//
//        Pet pet2 = run.getBean("tomcatPet", Pet.class);
//
//        System.out.println("组件: " + (pet1 == pet2));
//
//
//        // 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1
//        MyConfig myConfig = run.getBean(MyConfig.class);
//        System.out.println(myConfig);
//
//        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法, Spring Boot 总会检查这个组件是否在容器中有,如果有则不会新建,保持组件单实例。
//        User user01 = myConfig.user01();
//        User user02 = myConfig.user01();
//        System.out.println(user01 == user02);
//
//        //测试 @Configuration(proxyBeanMethods = true/false)
//        User user011 = run.getBean("user01", User.class);
//        Pet tomcatPet = run.getBean("tomcatPet", Pet.class);
//        System.out.println("用户的宠物:" + (user011.getPet() == tomcatPet));
//
//        // 5. 从容器中获取组件。测试@Import({User.class, LogBuilder.class})
//        String[] beanNamesForType = run.getBeanNamesForType(User.class);
//        System.out.println("-----------------------------------------");
//        for (String s : beanNamesForType) {
//            System.out.println(s);
//        }
//
//        StringBuilders bean1 = run.getBean(StringBuilders.class);
//        System.out.println(bean1);

        boolean user01 = run.containsBean("user01"); // 容器中是否包括这个组件
        System.out.println("容器中user01组件是否存在:" + user01);

        boolean tomcatPet = run.containsBean("tomcatPet"); // 容器中是否包括这个组件
        System.out.println("容器中tomcatPet 组件是否存在:" + tomcatPet);


        boolean user02 = run.containsBean("user02"); // 容器中是否包括这个组件
        System.out.println("容器中user02组件是否存在:" + user02);

        boolean pet02 = run.containsBean("pet02"); // 容器中是否包括这个组件
        System.out.println("容器中pet02组件是否存在:" + pet02);

    }
}

运行结果:

5. Spring Boot 底层原理基础_第22张图片

 可以看到,组件“user02” 和“pet02”都不存在。

基于这个场景,接着我们来加入@ImportResource 注解,这个注解可以加入到任意一个配置类上都可以生效,这里就选择加入到MyConfig.java 配置类上:、

5. Spring Boot 底层原理基础_第23张图片

 完整代码:

package com.menergy.boot.config;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.apache.logging.log4j.util.StringBuilders;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;

/**
 * 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2. 配置类本身也是组件
 * 3. proxyBeanMethods: 代理Bean 方法:
 *      Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
 *      Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
 *      用于解决组件依赖场景
 *
 * 4. @Import({User.class, StringBuilders.class})
 *      自动调用类的无参构造器创建出这两个类型的组件
 *
 */
@Import({User.class, StringBuilders.class}) // 例子中的User 是自定义的类,StringBuilders 是第三方Jar包中的类
@Configuration(proxyBeanMethods = false)  //告诉SpringBoot 这是一个配置类 == 以前的配置文件
//@ConditionalOnBean(name = "tomcatPet")  //表示容器中有tomcatPet组件时,该类中标注的全部组件才生效
//@ConditionalOnMissingBean(name = "tomcatPet")   //表示容器中不存在tomcatPet组件时,该类中标注的全部组件才生效
@ImportResource("classpath:beans.xml")
public class MyConfig {

    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
     * @return
     */
//    @ConditionalOnBean(name = "tomcatPet")  //表示容器中有tomcatPet组件时,才在容器中注入user01组件
    @Bean   //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
    public User user01(){
        User dragonUser = new User("dragon",18);
        // User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
        dragonUser.setPet(pet01());
        return dragonUser;

    }

//    @Bean("tomcatPet")
    public Pet pet01(){
        return new Pet("dragonPet");
    }

}

再次运行主类,看结果:

5. Spring Boot 底层原理基础_第24张图片

 结果都为true,表示容器中导入了这两个配置的组件。

5. 底层注解@ConfigurationProperties

@ConfigurationProperties 注解实现配置绑定功能。主要运用场景是将properties配置文件中的配置绑定到具体的Java Bean里面,或者反过来将Java Bean中的属性抽取到可配置的文件中。(如果这个过程用基本的Java原生代码来实现是比较麻烦的,需要读取配置文件的内容,然后从配置文件中遍历出所要的属性值,再封装到具体的Java Bean里面。)

下面通过具体实践说明:

先定义一个Car.java类:

package com.menergy.boot.bean;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 只有在容器中的组件,才拥有Spring Boot 提供的强大功能,比如配置绑定
 */
@Component  //表示将Car加入到容器中
@ConfigurationProperties(prefix = "mycar")
public class Car {

    private String brand;   //品牌
    private Integer price;  //价格

    public Car() {
    }

    public Car(String brand, Integer price) {
        this.brand = brand;
        this.price = price;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                '}';
    }
}

现在把这个Car类中相关的属性配置在配置文件 application.properties 中,如下:

5. Spring Boot 底层原理基础_第25张图片

 以前要把配置文件中汽车的两个信息封装到Car类中的属性比较麻烦,现在采用@ConfigurationProperties 注解就很简单:

先看ConfigurationProperties 类定义的属性:

5. Spring Boot 底层原理基础_第26张图片

 prefix属性表示对应配置文件中的前缀, 配置如下:

(同时加入@Component注解,将Car加入到容器中,因为只有在容器中的组件,才拥有Spring Boot 提供的强大功能,比如配置绑定)

5. Spring Boot 底层原理基础_第27张图片

写个Controller进行测试:

5. Spring Boot 底层原理基础_第28张图片

 完整代码:

package com.menergy.boot.controller;

import com.menergy.boot.bean.Car;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

//@ResponseBody
//@Controller

@RestController
public class HelloController {

    @Autowired  //自动装配
    Car car;

    @RequestMapping("/car")
    public Car car(){
        return car;
    }


//    @ResponseBody
    @RequestMapping("/hello")
    public String handle01(){
        return "Hello, Spring Boot 2";
    }
}

运行服务,浏览器访问"localhost:8888/car":

 结果是配置文件里的值,实践成功。

上面的采用的是“@Component + @ConfigurationProperties” 方式实现,也可以采用“@EnableConfigurationProperties + @ConfigurationProperties” 方式实现,如下:

首先,在配置类中加入@EnableConfigurationProperties注解,表示开启属性配置功能。

5. Spring Boot 底层原理基础_第29张图片

@EnableConfigurationProperties(Car.class) 有两个作用:
1. 开启Car的配置绑定功能
2. 把这个Car这个组件自动注册到容器中

(应用场景:有时欧美绑定的类是第三方Jar包中的类,我们不能在其上加入@Component ,这时就可以采用这种配置绑定方式)

运行结果:

6. Spring Boot 的底层自动配置原理

基于上面的注解基础,我们可以深入Spring Boot 的代码底层,了解Spring Boot是如何实现自动配置的。

所有的分析从主程序开始,先看一下主程序:

5. Spring Boot 底层原理基础_第30张图片

 点击@SpringBootApplication 注解进入SpringBootApplication 类:

5. Spring Boot 底层原理基础_第31张图片

从中可以看出,@SpringBootApplication 注解相当于下面三个注解:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

6.1 分析@SpringBootConfiguration 注解

之间点击@SpringBootConfiguration注解进入SpringBootConfiguration类,发现该类上有@Configuration 注解,可以看出SpringBootConfiguration 就是一个配置类, 也就有说明主程序MainApplication 也是一个配置类(核心配置类):

5. Spring Boot 底层原理基础_第32张图片

6.2 分析@ComponentScan 注解

该注解指定扫描哪些包,也是常用的注解,这里也不做深入分析。

6.3 分析@EnableAutoConfiguration 注解

所有,三个主要注解中,分析的重心就是这个@EnableAutoConfiguration 注解。

点击进入这个注解类,发现出了语言信息注解外,主要就两个注解:

5. Spring Boot 底层原理基础_第33张图片

 也就是说 @EnableAutoConfiguration 注解是 

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)

这两个注解的合成。

6.3.1 分析@AutoConfigurationPackage 注解

这个注解的字面意思是自动配置包。点击进入这个注解,发现其实是一个@Import 类型的注解:

5. Spring Boot 底层原理基础_第34张图片

 基于前面的的实践,我们知道@Import其实就是给容器中导入组件。这里这个组件叫“AutoConfigurationPackages.Registrar” , 然后点击进入Registrar类:

5. Spring Boot 底层原理基础_第35张图片

 发现Registrar 有两个方法,其实这里是利用Registrar 给容器中批量导入(注册)组件。

究竟批量注册哪些组件呢?

这里采用Debug方式看一下:

5. Spring Boot 底层原理基础_第36张图片

 可以看出,其中一个参数是AnnotationMetadata 类型的元信息,这个注解元信息是标在了主方法MainApplication 上:

5. Spring Boot 底层原理基础_第37张图片

 从这里可以看出,该方法是首先拿到主方法,然后获取主方法的包名,然后转成字符串。

具体看一下,选中“new PackageImports(metadata).getPackageNames().toArray(new String[0])” 右键:

5. Spring Boot 底层原理基础_第38张图片

 计算一下:

5. Spring Boot 底层原理基础_第39张图片

 得到包名“com.menergy.boot”, 所以相当于Registrar 是将某个包下面的所有组件全部注册进容器里。

总结:@AutoConfigurationPackage 注解的功能是将指定的一个包下的所有组件导入进来。(默认情况下,将Spring Boot 将主程序所在包下的所有组件注册到容器中)

(所有,每一个默认规则在底层都有源码的体现)

6.3.2 分析@Import(AutoConfigurationImportSelector.class) 注解

待续。。。

你可能感兴趣的:(Spring,Boot,Spring,Boot,Spring)