SpringBoot底层注解

@Configuration

该注解是Spring的注解,传统的Spring中注册bean要通过xml去配置,使用该注解可以脱离xml,完全使用java类去配置

对比两种方式注册bean

  • 传统方式用xml注册bean

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <bean id="user01" class="com.plasticine.boot.bean.User">
            <property name="name" value="吴签"/>
            <property name="age" value="31"/>
        </bean>
    
    </beans>
    
  • 使用@Configuration注解写一个配置类MyConfig

    @Configuration
    public class MyConfig {
        @Bean
        public User user01() {
            return new User("吴签", 31);
        }
    }
    

两种注册方式是可以一 一对应的,@Configuration注解了的类就代表了一个xml配置文件,@Bean注解的方法就代表了xml中的一个<bean>标签,id就是方法名,返回的类型就是bean组件的类型,返回的值就是组件在IoC容器中的实例

@Bean组件可以接收一个参数,这个参数用来替代id,getBean的时候就可以用这个参数的值去获取Bean

@Configuration
public class MyConfig {
    @Bean("wuqian")
    public User user01() {
        return new User("吴签", 31);
    }
}

查看注册的组件

  • 查看容器中的组件,看看我们刚刚注册的组件是否存在容器中
@SpringBootApplication
public class MainApplication {

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

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

}
查看容器中是否有@Bean注册的组件

可以看到容器中确实有我们的myConfig配置类和user01组件

获取组件

@SpringBootApplication
public class MainApplication {

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

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

        // 3. 获取组件
        User user01 = run.getBean("user01", User.class);
        System.out.println(user01);
    }

}
获取组件

可以看到能够获取到注册的组件

配置类的特点

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

    // 3. 获取组件
    User user01 = run.getBean("user01", User.class);
    User user02 = run.getBean("user01", User.class);
    System.out.println(user01 == user02);    // true
    
  2. 配置类本身也是组件

    MyConfig myConfig = run.getBean(MyConfig.class);
    System.out.println(myConfig);   // com.plasticine.boot.config.MyConfig$$EnhancerBySpringCGLIB$$37c595a1@411291e5
    
  3. 配置类注解@Configuration中有一个参数 -- proxyBeanMethods,这个参数用于控制是否代理控制bean

    @Configuration(proxyBeanMethods = false)
    

    该参数默认是false,表示不是单例的,即表示每次调用组件注册方法都是重新new出来的一个对象而不是直接从容器中拿的

    // 4. 验证 proxyBeanMethods = false 的情况
    MyConfig myConfig = run.getBean(MyConfig.class);
    User user1 = myConfig.user01();
    User user2 = myConfig.user01();
    System.out.println(user1 == user2);     // false
    
    • 调用了两次配置类的方法,获取到的实例是不同的,说明不是单例的
    • proxyBeanMethodstrue时,user1 == user2的结果为true,是单例的

    所以proxyBeanMethods这个参数就是用来控制组件注册方法的调用是否是单例的

    • true --> 单例
    • false --> 非单例

    从更深层次去理解,proxyBeanMethods置为true时,拿到的myConfig这个对象是一个代理对象,这样每次调用user01()都是让代理对象去调用,代理对象会做处理,如果已经有了user01的实例就会直接返回这个实例,而不是去new一个返回,如果proxyBeanMethods置为false,返回的myConfig是一个真实对象,自然每次调用user01()的时候都会new一个了,所以user1和user2自然就不是同一个实例

进而引申出springboot代理bean的两种模式

  1. Full --> proxyBeanMethods=true
  2. Lite --> proxyBeanMethods=false

这两种模式的特性用在什么地方呢?

  • 根据组件之间是否有依赖而定

    • 比如现在再注册一个宠物组件,并且假设一个用户会有一只狗,那么这个时候就要求是单例模式的

      @Configuration(proxyBeanMethods = false)
      public class MyConfig {
          @Bean
          public User user01() {
              User user = new User("吴签", 31);
              user.setPet(pet01());
              
              return user;
          }
      
          @Bean
          public Pet pet01() {
              return new Pet("吴签的狗");
          }
      }
      

      User依赖于Pet -- 通过组合的方式

      public class User {
          private String name;
          private Integer age;
          private Pet pet;
      }
      

      如果不是单例模式的话,即proxyBeanMethods = false,那么在getBean获取到的user实例中,他的pet和getBean获取的pet不是同一个

      // 5. 验证组件依赖
      User user = run.getBean("user01", User.class);
      Pet pet = run.getBean("pet01", Pet.class);
      System.out.println(user.getPet() == pet);   // false
      

      如果是单例模式的话,即proxyBeanMethods = true,那么结果会是true

    • 为什么名字是叫Full模式和Lite模式呢?

      • Full 代表每次获取组件的时候,都会先去被@Configuration注解的类中查看是否有已存在的实例,这个过程要耗费一些性能
      • Lite 代表每次获取组件都是直接去new,并不会经过代理对象去获取,所以如果组件之间没有依赖,即组件之间没有通过组合建立关联的话,就可以设为false,这样会快一些

@Import

当需要用到不在包扫描范围中的组件时,可以用该注解进行导入

public @interface Import {
    Class<?>[] value();
}

该注解接收一个类型为反射类数组的参数,也就是我们要导入的组件,这里就随便导入一个没有注册过的组件为例,然后查看容器中是否有这个组件

  • 导入
@Import({DBAppender.class})
@Configuration
public class MyConfig {
    ...
}
  • 查看容器中是否有该组件
// 6. 验证 @Import 注解导入不在包扫描范围内的组件
DBAppender testBean = run.getBean(DBAppender.class);
System.out.println(testBean);   // ch.qos.logback.classic.db.DBAppender[null]

可以看到外部的组件被导入到容器中了


@Conditional条件装配注解

顾名思义,就是根据条件是否成立从而去确定是否进行装配

所有的Conditional注解的条件

@Conditional下有多个条件装配注解,都是看名字就能知道条件是什么,这里就不一 一解释了

这里就以@ConditionalOnBean为例,根据是否有某一个Bean组件来决定是否装配

@Configuration
public class MyConfig {
    @ConditionalOnBean(name="myPet")
    @Bean
    public User user01() {
        User user = new User("吴签", 31);
        user.setPet(pet01());

        return user;
    }

//    @Bean("myPet")
    @Bean
    public Pet pet01() {
        return new Pet("吴签的狗");
    }
}
  • 这里对user01这个组件进行条件装配,如果有myPet这个组件,才会装配user01,否则不会装配
// 7. 验证条件装配 --> @ConditionalOnBean
boolean isContainMyPet = run.containsBean("myPet");
boolean isContainUser01 = run.containsBean("user01");
System.out.println("isContainMyPet = " + isContainMyPet);   // false
System.out.println("isContainUser01 = " + isContainUser01); // false

由于不存在myPet这个组件,所以user01这个组件也就不会装配,自然也就获取不到了


@ImportResource

当要把已有的xml配置文件中的bean注册进来的时候,一个一个手动转成java代码的方式太繁琐,可以直接把整个xml导入

@ImportResource("classpath:other-bean.xml")
@Configuration
public class MyConfig {
    ...
}

@ConfigurationProperties

用于从配置文件中去注入Bean

  • 比如有一个汽车bean,要根据配置文件中的配置去注入汽车bean的品牌和价格

汽车bean

  1. 把Car声明为一个组件,用@Component注解

    @Component
    @ConfigurationProperties(prefix = "mycar")
    public class Car {
        private String brand;
        private Integer price;
        // ... getter and setter
    }
    
  2. 使用@EnableConfigurationProperties开启Car的配置文件绑定功能

    • @EnableConfigurationProperties注解用在配置类上
    @EnableConfigurationProperties(Car.class)
    @Configuration
    public class MyConfig {
        ...
    }
    
    • 汽车Bean只需要使用@ConfigurationProperties注解即可
    @ConfigurationProperties(prefix = "mycar")
    public class Car {
        private String brand;
        private Integer price;
        // ... getter and setter
    }
    
  • prefix是用于匹配配置文件中的前缀的

要使用该注解需要先在pom.xml中引入依赖

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

application.properties中添加汽车配置

mycar.brand=BYD
mycar.price=1000000

在controller中自动装配并使用

@RestController
public class HelloController {

    @Autowired
    private Car car;

    @RequestMapping("/car")
    public Car car() {
        return car;
    }
}
查看装配结果
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容