SpringBoot底层注解

252 阅读10分钟

@Configuration

@Configuration标注上表明是一个配置类。@Bean标注在方法上,给容器中添加组件,方法名为组件ID,返回类型就是组件类型。返回值就是组件在容器中的实例。

首先创建两个bean,Pet和User

//宠物
public class Pet {

    private String name;

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

    public String getName() {
        return name;
    }

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

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

    public Pet() {

    }
}
//用户
public class User {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

    public User() {

    }

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

接着创建一个config包编写MyConfig类。

image-20201224222949697

//配置类
@Configuration
public class MyConfig {


    //给容器中添加组件,方法名为组件ID,返回类型就是组件类型。返回值就是组件在容器中的实例
    @Bean
    public User user01() {
        return new User("zs", 18);
    }

    @Bean("tomcat")//指定组件ID
    public Pet tomcatPet() {
        return new Pet("tomcat");
    }

}

修改主程序,获取到IOC容器,打印注册的组件。

image-20201224223044441

@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {
        //1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootApplication.class, args);

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

    }

}

image-20201224223332495

这就是JavaConfig配置方式。www.kylin.show/3172093075.…

接着我们从IOC容器中获取两个tomcat组件对象,进行比较。

image-20201224223828757

发现是为ture的,也就是默认是单例的。

我们尝试在IOC容器中获取配置类组件,也是可以获取到的。说明配置类本身也是组件。

image-20201224224120976

@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {
        //1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootApplication.class, args);

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

        //3.从容器中获取组件
        Pet tom01 = run.getBean("tomcat", Pet.class);

        Pet tom02 = run.getBean("tomcat", Pet.class);

        System.out.println("组件:" + (tom01 == tom02));

        MyConfig myConfig = run.getBean(MyConfig.class);
        System.out.println(myConfig);

    }
}

我们查看@Configuration的源码发现从SpringBoot2.0开始多了一个属性,proxyBeanMethods默认为ture。

image-20201224224446585

这个属性有什么用呢。我们来用我们获取到的MyConfig对象来调用方法,获取到两个User对象,进行比较就知道了。

image-20201225093844085

也就是说如果@Configuration(proxyBeanMethods=true)我们获取到的配置类对象是代理对象com.kylin.boot.springboot.config.MyConfig$$EnhancerBySpringCGLIB$$9ec0e404@49b07ee3当代理对象调用方法。SpringBoot总会检查这个组件是否存在容器,从而保持单实例。

我们把@Configuration(proxyBeanMethods=false)修改为false,查看效果。

image-20201225094146019

获取到的MyConfig是MyConfig对象而不是代理对象com.kylin.boot.springboot.config.MyConfig@4dd94a58,多次调用方法也不会在IOC容器检查是否存在。

@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {
        //1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootApplication.class, args);

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

        //3.从容器中获取组件
        Pet tom01 = run.getBean("tomcat", Pet.class);

        Pet tom02 = run.getBean("tomcat", Pet.class);

        System.out.println("组件:" + (tom01 == tom02));

        MyConfig myConfig = run.getBean(MyConfig.class);
        System.out.println(myConfig);

        //如果@Configuration(proxyBeanMethods=true)代理对象调用方法。SpringBoot总会检查这个组件是否存在容器
        //保持组件单实例
        User user = myConfig.user01();
        User user1 = myConfig.user01();
        System.out.println(user == user1);


    }

}

这两种情况又被称为Full,Lite模式。

  • Full (proxyBeanMethods = true)
  • Lite (proxyBeanMethods = false)

两种使用场景如下。修改User类代码,添加Pet属性

//用户
public class User {

    private String name;
    private int age;

    private Pet pet;

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

    public Pet getPet() {
        return pet;
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

    public User() {

    }

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

当前为Full模式。

image-20201225095310109

@Configuration(proxyBeanMethods = true)
public class MyConfig {


    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象。
     *
     * @return
     */
    //给容器中添加组件,方法名为组件ID,返回类型就是组件类型。返回值就是组件在容器中的实例
    @Bean
    public User user01() {
        User user = new User("zs", 18);
        //User组件依赖Pet组件
        user.setPet(tomcatPet());
        return user;
    }

    @Bean("tomcat")//指定组件ID
    public Pet tomcatPet() {
        return new Pet("tomcat");
    }

}

image-20201225095609944

此时用户的宠物组件,就是IOC容器中注册的宠物组件。

如果当我们修改为lite模式后,则此时用户的宠物组件,就不是IOC容器中注册的宠物组件。

image-20201225095811554

@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {
        //1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootApplication.class, args);

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

        //3.从容器中获取组件
        Pet tom01 = run.getBean("tomcat", Pet.class);

        Pet tom02 = run.getBean("tomcat", Pet.class);

        System.out.println("组件:" + (tom01 == tom02));

        MyConfig myConfig = run.getBean(MyConfig.class);
        System.out.println(myConfig);

        //如果@Configuration(proxyBeanMethods=true)代理对象调用方法。SpringBoot总会检查这个组件是否存在容器
        //保持组件单实例
        User user = myConfig.user01();
        User user1 = myConfig.user01();
        System.out.println(user == user1);

        User user01 = run.getBean("user01", User.class);
        Pet tomcat = run.getBean("tomcat", Pet.class);
        System.out.println("用户的宠物:"+(user01.getPet()==tomcat));
    }
}
  • 如果组件之间存在组件依赖则使用Full模式
  • 组件之间不存在组件依赖则使用Lite模式,无需要在IOC容器中检查判断是否存在该组件,从而加速容器启动过程。

@Import

快速给容器中导入一个组件。默认组件的名字就是全类名,也可以导入带@Configuration的配置类(4.2 版本之前只可以导入配置类,4.2版本之后 也可以导入普通类)

image-20201225101209851

这里并不是@Import必须要在加@Configuration类上才能使用,而是需要被Spring容器给扫描注册到才行。也就是下图这样也可以。

image-20201225102117304

@Import({Pet.class})
@Component
public class TestImport {
}

image-20201225102321229

//配置类

/**
 * Full(proxyBeanMethods = true)
 * Lite(proxyBeanMethods = false)
 */

@Import({User.class, StaticMarkerBinder.class})
@Configuration(proxyBeanMethods = true)
public class MyConfig {


    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象。
     *
     * @return
     */
    //给容器中添加组件,方法名为组件ID,返回类型就是组件类型。返回值就是组件在容器中的实例
    @Bean
    public User user01() {
        User user = new User("zs", 18);
        //User组件依赖Pet组件
        user.setPet(tomcatPet());
        return user;
    }

    @Bean("tomcat")//指定组件ID
    public Pet tomcatPet() {
        return new Pet("tomcat");
    }

}
@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {
        //1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootApplication.class, args);

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

        //3.从容器中获取组件
        Pet tom01 = run.getBean("tomcat", Pet.class);

        Pet tom02 = run.getBean("tomcat", Pet.class);

        System.out.println("组件:" + (tom01 == tom02));

        MyConfig myConfig = run.getBean(MyConfig.class);
        System.out.println(myConfig);

        //如果@Configuration(proxyBeanMethods=true)代理对象调用方法。SpringBoot总会检查这个组件是否存在容器
        //保持组件单实例
        User user = myConfig.user01();
        User user1 = myConfig.user01();
        System.out.println(user == user1);

        User user01 = run.getBean("user01", User.class);
        Pet tomcat = run.getBean("tomcat", Pet.class);
        System.out.println("用户的宠物:" + (user01.getPet() == tomcat));

        //获取组件
        String[] beanNamesForType = run.getBeanNamesForType(User.class);
        System.out.println("-----");
        for (String s : beanNamesForType) {
            System.out.println(s);
        }

        String[] beanNamesForType1 = run.getBeanNamesForType(Pet.class);
        System.out.println("-----");
        for (String s : beanNamesForType1) {
            System.out.println(s);
        }
        System.out.println("-----");
        StaticMarkerBinder bean = run.getBean(StaticMarkerBinder.class);
        System.out.println(bean);


    }

}

@Conditional

条件装配:满足Conditional指定的条件,则进行组件注入

image-20201225102835638

@Conditional拥有许多派生注解,每个注解都有用不同的功能。

@Conditional扩展注解作用(判断是否满足当前指定条件)
@ConditionalOnJava系统的java版本是否符合要求
@ConditionalOnBean容器中存在指定Bean
@ConditionalOnMissingBean容器中不存在指定Bean
@ConditionalOnExpression满足SpEl表达式指定
@ConditionalOnClass系统中有指定的类
@ConditionalOnMissingClass系统中没有指定的类
@ConditionalOnSingleCandidate容器中只有一个指定的Bean,或者这个Bean式首选的Bean
@ConditionalOnProperty系统中指定的属性是否有指定的值
@ConditionalOnResource类路径下是否存在指定资源文件
@ConditionalOnWebApplication当前是web环境
@ConditionalOnNotWebApplication当前不是web环境
@ConditionalOnJndiJNDI存在指定项

我们首先把Pet组件不注册到IOC容器中,准备测试环境。

image-20201225103713401

此时容器中注册了user01组件,而没有注册tomcat组件。

image-20201225103756276

image-20201225104002330

image-20201225104114520

因为容器中不存在tomcat组件,条件不成立,所以未注册user01组件。

我们也可以把该注解放在配置类上,就变成了只要该条件不成立,该配置类就不会生效。

image-20201225104405332

image-20201225104453327

//配置类

/**
 * Full(proxyBeanMethods = true)
 * Lite(proxyBeanMethods = false)
 */

@Import({User.class, StaticMarkerBinder.class})
@Configuration(proxyBeanMethods = true)
@ConditionalOnBean(name = "tomcat")
public class MyConfig {


    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象。
     *
     * @return
     */
    //给容器中添加组件,方法名为组件ID,返回类型就是组件类型。返回值就是组件在容器中的实例
    @Bean
    public User user01() {
        User user = new User("zs", 18);
        //User组件依赖Pet组件
        user.setPet(tomcatPet());
        return user;
    }

    @Bean("tomcat")//指定组件ID
    public Pet tomcatPet() {
        return new Pet("tomcat");
    }

}
@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {
        //1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootApplication.class, args);

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

    

        boolean tomcat = run.containsBean("tomcat");
        System.out.println("容器中tomcat组件:" + tomcat);

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:" + user01);

    }

}

@ImportResource

引入原生配置文件,来配置容器。

image-20201225105028588

例如上图就是我们以前使用配置文件导入两个ID为haha,hehe的组件到IOC容器中。但是我们可以知道这种方式在SpringBoot中是不支持的。

image-20201225105256887

所以并没有注册成功。

但是我们可以使用@ImportResource注解来引入原生配置文件,来配置容器。

image-20201225105600910

这样就方便了我们老项目向SpringBoot迁移了。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="haha" class="com.kylin.boot.springboot.bean.User">
        <property name="name" value="zhangsan"></property>
        <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.kylin.boot.springboot.bean.Pet">
        <property name="name" value="tomcat"></property>
    </bean>
</beans>
//配置类

/**
 * Full(proxyBeanMethods = true)
 * Lite(proxyBeanMethods = false)
 */

@Import({User.class, StaticMarkerBinder.class})
@Configuration(proxyBeanMethods = true)
//@ConditionalOnBean(name = "tomcat")
@ImportResource("classpath:bean.xml")
public class MyConfig {


    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象。
     *
     * @return
     */
    //给容器中添加组件,方法名为组件ID,返回类型就是组件类型。返回值就是组件在容器中的实例
    @Bean
    public User user01() {
        User user = new User("zs", 18);
        //User组件依赖Pet组件
        user.setPet(tomcatPet());
        return user;
    }

    @Bean("tomcat")//指定组件ID
    public Pet tomcatPet() {
        return new Pet("tomcat");
    }

}
@SpringBootApplication
public class SpringbootApplication {

    public static void main(String[] args) {
        //1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringbootApplication.class, args);

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


        boolean tomcat = run.containsBean("tomcat");
        System.out.println("容器中tomcat组件:" + tomcat);

        boolean user01 = run.containsBean("user01");
        System.out.println("容器中user01组件:" + user01);

        boolean haha = run.containsBean("haha");
        System.out.println("容器中haha组件:" + haha);

        boolean hehe = run.containsBean("hehe");
        System.out.println("容器中hehe组件:" + hehe);

    }

}

@ConfigurationProperties

配置文件属性与Java Bean属性绑定,称为配置绑定。

image-20201225111051219

编写一个Controller进行测试,查看是否绑定成功。

image-20201225111117451

绑定成功。

image-20201225111154939

@Component//注册到容器中
@ConfigurationProperties(prefix = "mycar")
public class Car {

    private String brand;
    private Integer price;

    public Integer getPrice() {
        return price;
    }

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

    public String getBrand() {
        return brand;
    }

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

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

}
mycar.brand=BYD
mycar.price=99999
@RestController
public class HelloController {

    @Autowired
    Car car;

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

如果想要编写自定类绑定的配置提示有提示可以导入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

我们也可以设置Maven打包时将这个jar包排除掉

<project>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>S

@EnableConfigurationProperties

通过@EnableConfigurationProperties + @ConfigurationProperties来实现属性绑定。

  1. 开启配置绑定功能
  2. 把这个组件自动注册到容器中

image-20201225111636231

image-20201225111731727

绑定成功!

image-20201225111154939

@Import({User.class, StaticMarkerBinder.class})
@Configuration(proxyBeanMethods = true)
//@ConditionalOnBean(name = "tomcat")
@ImportResource("classpath:bean.xml")
@EnableConfigurationProperties(Car.class)
//1. 开启配置绑定功能
//2. 把这个组件自动注册到容器中
public class MyConfig {


    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象。
     *
     * @return
     */
    //给容器中添加组件,方法名为组件ID,返回类型就是组件类型。返回值就是组件在容器中的实例
    @Bean
    public User user01() {
        User user = new User("zs", 18);
        //User组件依赖Pet组件
        user.setPet(tomcatPet());
        return user;
    }

    @Bean("tomcat")//指定组件ID
    public Pet tomcatPet() {
        return new Pet("tomcat");
    }

}
//@Component//注册到容器中
@ConfigurationProperties(prefix = "mycar")
public class Car {

    private String brand;
    private Integer price;

    public Integer getPrice() {
        return price;
    }

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

    public String getBrand() {
        return brand;
    }

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

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

}

声明

以上除了@Configuration注解。其他的注解并不是必须要配合@Configuration注解。而是SpringBoot扫描包路径是能扫描的到该类(也就是注册该组件)这些注解才能生效,所以我们也可以使用@Componet

例如并不是@Configuration+@ImportResource,@ImportResource才会生效。