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;
    }
}
查看装配结果
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容