为了后面深入的理解 springboot 的自动配置原理,先来看一下底层的注解,了解一下它们是如何完成相关的功能。

组件添加

新建User组件

  1. package com.jaded.boot.bean;
  2. public class User {
  3. private String name;
  4. private Integer age;
  5. public User() {
  6. }
  7. public User(String name, Integer age) {
  8. this.name = name;
  9. this.age = age;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public Integer getAge() {
  18. return age;
  19. }
  20. public void setAge(Integer age) {
  21. this.age = age;
  22. }
  23. @Override
  24. public String toString() {
  25. return "User{" +
  26. "name='" + name + '\'' +
  27. ", age=" + age +
  28. '}';
  29. }
  30. }

image.png

新建Pet组件

  1. package com.jaded.boot.bean;
  2. public class Pet {
  3. private String name;
  4. public String getName() {
  5. return name;
  6. }
  7. public void setName(String name) {
  8. this.name = name;
  9. }
  10. public Pet() {
  11. }
  12. public Pet(String name) {
  13. this.name = name;
  14. }
  15. @Override
  16. public String toString() {
  17. return "Pet{" +
  18. "name='" + name + '\'' +
  19. '}';
  20. }
  21. }

image.png

以前是如何向容器中添加组件的?

新建 beans.xml 配置文件

image.png
image.png

在里面写入:

  1. <bean id="user01" class="com.jaded.boot.bean.User">
  2. <property name="name" value="zhangsan"></property>
  3. <property name="age" value="18"></property>
  4. </bean>
  5. <bean id="cat01" class="com.jaded.boot.bean.Pet">
  6. <property name="name" value="tomcat"></property>
  7. </bean>

image.png

这是以前用 spring xml 的方式配置的。

现在 springboot 是怎么给容器添加组件的?

使用 @Configuration 注解。

新建一个 MyConfig 配置类

image.png

给配置类加上 @Configuration 注解

给这个类加上 @Configuration 注解,告诉 springboot 这是一个配置类(等于配置文件)
image.png

编写组件方法

编写一个 User 类型返回值,组件id为方法名的方法。并加上 @Bean 注解。
@Bean注解:给容器中添加组件,以方法名作为组件的id,返回类型就是组件类型,方法返回的值就是容器保存的实例。如果想指定组件名,可以使用:@Bean(“组件名”) ,这样组件名就不是方法名,而是我们指定的组件名了。

  1. @Bean
  2. public User user01() {
  3. return new User("zhangsan", 18);
  4. }

image.png

同样的,编写一个 Pet 类型返回值,组件id为方法名的方法。并加上 @Bean 注解

  1. @Bean
  2. public Pet cat01() {
  3. return new Pet("tomcat");
  4. }

image.png

修改一下主程序类的 main 方法

  1. // 返回 IOC 容器
  2. ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
  3. // 查看容器里的组件
  4. String[] names = run.getBeanDefinitionNames();
  5. for (String name : names) {
  6. System.out.println(name);
  7. }

运行,等待 springboot启动完成后,就可以看到刚才注册的两个组件了。(user01、 cat01)
image.png

Lite 和 Full 模式

  1. Lite(proxyBeanMethods = false)
  2. Full(proxyBeanMethods = true)

组件注册默认为单实例

以上我们给组件注册的两个组件,默认是单实例的。也就是说,无论我们从容器中获取多少次,都是同一个实例。

修改主程序的 main 方法:

  1. // 返回 IOC 容器
  2. ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
  3. // 查看容器里的组件
  4. String[] names = run.getBeanDefinitionNames();
  5. for (String name : names) {
  6. System.out.println(name);
  7. }
  8. // 从容器中获取组件
  9. Pet cat1 = run.getBean("cat01", Pet.class);
  10. Pet cat2 = run.getBean("cat01", Pet.class);
  11. System.out.println("******* 测试组件是否为单实例 **********************");
  12. System.out.println(cat1==cat2);

运行:
image.png
从结果上来看,我们可以判断确实是单实例。

配置类本身也是一个组件

修改主程序的 main 方法为:

  1. // 返回 IOC 容器
  2. ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
  3. // 查看容器里的组件
  4. String[] names = run.getBeanDefinitionNames();
  5. for (String name : names) {
  6. System.out.println(name);
  7. }
  8. // 从容器中获取组件
  9. Pet cat1 = run.getBean("cat01", Pet.class);
  10. Pet cat2 = run.getBean("cat01", Pet.class);
  11. System.out.println("******* 测试组件是否为单实例 **********************");
  12. System.out.println(cat1==cat2);
  13. // 查看配置类组件
  14. MyConfig myConfig=run.getBean(MyConfig.class);
  15. System.out.println("******* 查看配置类组件 **********************");
  16. System.out.println(myConfig);

运行:
image.png
从运行结果上来看,它本身确实也是一个组件。

proxyBeanMethods 属性

如果在主程序中多调用几次配置类的方法,那此时是从容器中直接获取,还是普通的方法调用?

修改主程序的 main 方法:

  1. // 返回 IOC 容器
  2. ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
  3. // 查看容器里的组件
  4. String[] names = run.getBeanDefinitionNames();
  5. for (String name : names) {
  6. System.out.println(name);
  7. }
  8. // 从容器中获取组件
  9. Pet cat1 = run.getBean("cat01", Pet.class);
  10. Pet cat2 = run.getBean("cat01", Pet.class);
  11. System.out.println("******* 测试组件是否为单实例 **********************");
  12. System.out.println(cat1 == cat2);
  13. // 查看配置类组件
  14. MyConfig myConfig = run.getBean(MyConfig.class);
  15. System.out.println("******* 查看配置类组件 **********************");
  16. System.out.println(myConfig);
  17. // 外部调用配置类的
  18. User user1 = myConfig.user01();
  19. User user2 = myConfig.user01();
  20. System.out.println("******* 是从容器中获取,还是普通的方法调用? **********************");
  21. System.out.println(user1 == user2);

运行:
image.png
得到的仍然是同一个对象。

这是因为 @Configuration 的 proxyBeanMethods 属性默认为 true。

此时我们获取到的 MyConfig 不是一个普通的对象,而是一个代理对象 EnhancerBySpringCGLIB ,一个被 springboot 增强了的代理对象。

如果是代理对象调用组件的注册方法,springboot 的默认逻辑就是,先检查容器中是否已经有组件的实例,如果有就拿,如果没有就再创建。

如果将配置类的 proxyBeanMethods 属性设为 false 。

  1. @Configuration(proxyBeanMethods = false)

image.png
image.png
可以看到配置类此时就不是代理类了,判断结果也是false,说明这是两个组件的不同实例。

这是用来解决什么场景的?

组件依赖。

如何选择Full 模式和 Lite 模式?

如果设置为 Lite 模式,springboot 不会检查在容器中是否已经注册过该组件,springboot 启动和加载的速度会更快。

如果只是单单注册组件,别的组件也不依赖它,推荐使用 Lite 模式,那 springboot 启动和加载的速度会更快。

反之,如果这个组件将会被另一个组件作为依赖时,可以使用 Full 模式(单实例模式)。