C#8.0 中开启默认接口实现方法
260
2022-08-06
Spring Boot 底层原理基础深度解析
目录1. 底层注解@Configuration2. 底层注解@Import3. 底层注解@Conditional
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) {
public Integer getAge() {
return age;
public void setAge(Integer age) {
@Override
public String toString() {
fsTRCxQ 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) {
@Override
public String toString() {
return "Pet{" +
"name='" + name + '\'' +
'}';
}
以前Spring 配置文件方式是这样给容器添加组件的:
现在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.phttp://rintln(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.pet = pet;
public String getName() {
return name;
public void setName(String name) {
public Integer getAge() {
return age;
public void setAge(Integer age) {
public Pet getPet() {
return pet;
public void setPet(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.SpringBootAfsTRCxQpplication;
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搜索。
注:如果调不出这个窗口,请参考:IDEA 操作与设置笔记
https://jb51.net/article/208232.htm
打开Conditional 类后,“Ctrl + H” 键调出这个类的继承树:
注:如果快捷键失效,请确定如下快捷键设置:
从前面的@Conditional 的继承树可以看出,@Conditional 有非常多的派生注解,每个注解都代表不同的功能,从派生注解的注解名称可以大概知道其功能用意,例如@ConditionalOnBean 注解代表当容器中存在某个Bean时才干某些事情, @ConditionalOnMissingBean 注解代表当容器中不存在某个Bean时才干某些事情。
接下来,以@ConditionalOnBean 为例,进行实践:
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~