5. Spring Boot 底层原理基础

1. 底层注解@Configuration

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

项目基本结构:

 两个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 配置文件方式是这样给容器添加组件的:

<beans>

    <bean id="user01" class="com.menergy.boot.bean.User">
        <property name="name" value="dragon"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="pet01" class="com.menergy.boot.bean.Pet">
        <property name="name" value="dragonPet"></property>
    </bean>

</beans>

现在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);


    }
}

输出的部分结果:

上面的例子,重点落在@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));

    }
}

运行结果:

可以看出,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");
    }

}

运行结果:

 可以看出,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类型的数组,说明这个注解的作用是向容器中导入一批组件

接下来,实践一下:

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

主类加入如下测试:

 运行结果:

 结果说明:

“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搜索。

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

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

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

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

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

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

完整代码:

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);

    }
}

运行结果:

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

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

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

 

 完整代码:

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组件。这里加上这个条件装配注解后,看一下运行结果:

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

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

再看运行结果:

 

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

运行结果:

4. 底层注解@ImportResource

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

下面实践这个注解:

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

beans.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:batch="http://www.springframework.org/schema/batch" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/batch http://www.springframework.org/schema/batch/spring-batch-2.1.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <bean id="user02" class="com.menergy.boot.bean.User">
        <property name="name" value="dragon"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="pet02" class="com.menergy.boot.bean.Pet">
        <property name="name" value="dragonPet"></property>
    </bean>


</beans>

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

主类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.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);

    }
}

运行结果:

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

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

 完整代码:

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");
    }

}

再次运行主类,看结果:

 结果都为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 中,如下:

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

先看ConfigurationProperties 类定义的属性:

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

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

写个Controller进行测试:

 完整代码:

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注解,表示开启属性配置功能。

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

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

运行结果:

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

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

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

 点击@SpringBootApplication 注解进入SpringBootApplication 类:

从中可以看出,@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 也是一个配置类(核心配置类):

6.2 分析@ComponentScan 注解

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

6.3 分析@EnableAutoConfiguration 注解

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

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

 也就是说 @EnableAutoConfiguration 注解是 

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)

这两个注解的合成。

6.3.1 分析@AutoConfigurationPackage 注解

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

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

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

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

这里采用Debug方式看一下:

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

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

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

 计算一下:

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

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

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

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

待续。。。

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
[JAVA工程师必会知识点之并发编程]1、现在几乎100%的公司面试都必须面试并发编程,尤其是互联网公司,对于并发编程的要求更高,并发编程能力已经成为职场敲门砖。2、现在已经是移动互联和大数据时代,对于应用程序的性能、处理能力、处理时效性要求更高了,传统的串行化编程无法充分利用现有的服务器性能。3、并发编程是几乎所有框架的底层基础,掌握好并发编程更有利于我们学习各种框架。想要让自己的程序执行、接口响应、批处理效率更高,必须使用并发编程。4、并发编程是中高级程序员的标配,是拿高薪的必备条件。 【主讲讲师】尹洪亮Kevin:现任职某互联网公司首席架构师,负责系统架构、项目群管理、产品研发工作。10余年软件行业经验,具有数百个线上项目实战经验。擅长JAVA技术栈、高并发高可用伸缩式微服务架构、DevOps。主导研发的蜂巢微服务架构已经成功支撑数百个微服务稳定运行【推荐你学习这门课的理由:知识体系完整+丰富学习资料】1、 本课程总计122课时,由五大体系组成,目的是让你一次性搞定并发编程。分别是并发编程基础、进阶、精通篇、Disruptor高并发框架、RateLimiter高并发访问限流吗,BAT员工也在学。2、课程附带附带3个项目源码,几百个课程示例,5个高清PDF课件。3、本课程0基础入门,从进程、线程、JVM开始讲起,每一个章节只专注于一个知识点,每个章节均有代码实例。 【课程分为基础篇、进阶篇、高级篇】一、基础基础篇从进程与线程、内存、CPU时间片轮训讲起,包含线程的3种创建方法、可视化观察线程、join、sleep、yield、interrupt,Synchronized、重入锁、对象锁、类锁、wait、notify、线程上下文切换、守护线程、阻塞式安全队列等内容。二、进阶篇进阶篇课程涵盖volatied关键字、Actomic类、可见性、原子性、ThreadLocal、Unsafe底层、同步类容器、并发类容器、5种并发队列、COW容器、InheritableThreadLocal源码解析等内容。三、精通篇精通篇课程涵盖JUC下的核心工具类,CountDownLath、CyclicBarrier、Phaser、Semaphore、Exchanger、ReentrantLock、ReentrantReadWriteLock、StampedLock、LockSupport、AQS底层、悲观锁、乐观锁、自旋锁、公平锁、非公平锁、排它锁、共享锁、重入锁、线程池、CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadExecutor、自定义线程池、ThreadFactory、线程池切面编程、线程池动态管理等内容,高并发设计模式,Future模式、Master Worker模式、CompletionService、ForkJoin等课程中还包含Disruptor高并发无锁框架讲解:Disruptor支持每秒600万订单处理的恐怖能力。深入到底层原理和开发模式,让你又懂又会用。高并发访问限流讲解:涵盖木桶算法、令牌桶算法、Google RateLimiter限流开发、Apache JMeter压力测试实战。 【学完后我将达到什么水平?】1、 吊打一切并发编程相关的笔试题、面试题。2、 重构自己并发编程的体系知识,不再谈并发色变。3、 精准掌握JAVA各种并发工具类、方法、关键字的原理和使用。4、 轻松上手写出更高效、更优雅的并发程序,在工作中能够提出更多的解决方案。  【面向人群】1、 总感觉并发编程很难、很复杂、不敢学习的人群。2、 准备跳槽、找工作、拿高薪的程序员。3、 希望提高自己的编程能力,开发出更高效、性能更强劲系统的人群。4、 想要快速、系统化、精准掌握并发编程的人群。【课程知识体系图】

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值