由浅入深,详细总结 Spring 八种加载 Bean 的方式
最佳答案 问答题库778位专家为你答疑解惑
文章目录
- 方式一:XML 方式声明 bean
- 方式二:XML + 注解方式声明 bean
- 方式三:注解方式声明配置类
- 扩展一:@Bean 返回的对象和真实 Bean 对象可能不是一个
- 扩展二:加载配置类的同时,加载配置文件(系统迁移)
- 扩展三:@ImportResource、@Bean、@Component 加载优先级
- 扩展四:@ImportResource 引入多个配置文件的优先级
- 扩展五:proxyBeanMethods=true 生成代理对象
- 方式四:@Import 注解注入
- 方式五:上下文对象在容器初始化完毕后注入
- 方式六:实现 ImportSelector 接口 ★
- 方式七:实现 ImportBeanDefinitionRegistrar 接口 ★
- 方式八:实现 BeanDefinitionRegistryPostProcessor 接口
方式一:XML 方式声明 bean
目录初始化:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.axy</groupId><artifactId>springboot_bean_init</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.9</version></dependency></dependencies>
</project>
applicationContext.xml:
<?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"><!--xml方式声明自己开发的bean--><bean id="cat" class="com.axy.bean.Cat"/><bean id="dog" class="com.axy.bean.Dog"/>
</beans>
待注入对象:
public class Dog {...
}
public class Cat {...
}
测试类:
public class App {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name); // 打印所有 bean 的名称}}
}
运行结果如下:
注:如果 application.xml 的 bean 标签不指定 id 属性,那么默认 bean 的名称为 全限定类名#索引
的形式,运行结果如下:
xml 方式声明第三方开发的 bean:
pom 中添加如下坐标:
<dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.16</version>
</dependency>
applicationContext.xml:
<?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"><!--xml方式声明自己开发的bean--><bean id="cat" class="com.axy.bean.Cat"/><bean id="dog" class="com.axy.bean.Dog"/><!--xml方式声明第三方开发的bean--><bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
</beans>
那么这样会使用 DruidDataSource 的默认构造函数来创建 Bean 对象。控制台打印结果如下:
相关链接:Spring 从入门到精通系列 05 —— Spring 依赖注入的三种方式
方式二:XML + 注解方式声明 bean
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!--指定加载bean的位置,component--><context:component-scan base-package="com.axy.bean"/>
</beans>
待注入对象:
@Component("dog") // 如果不写 value 属性,当前 value 默认为类名首字母小写
public class Dog {...
}@Component("cat")
public class Cat {...
}
注:如果要注入controller、service 或者 dao 的 Bean 添加至 IOC 容器,要使用其衍生注解 @Controller 、@Service、@Repository
。
相关链接:Spring 从入门到精通系列 06 —— Spring 中的 IOC 常用注解
测试类:
public class App {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name); // 打印所有 bean 的名称}}
}
控制台打印结果如下:
问题: 第三方 bean(如:druid),没有由 ioc 容器创建出来。
解决步骤:① 新建配置类,在其里面定义第三方 bean,并在该配置类上添加 @Component 或 @Configuration 注解使得该方法参与解析。
② 在 application.xml 中添加扫描当前配置类的路径信息。
配置类中添加返回第三方 Bean 对象的方法,并添加相应注解:
//@Component
@Configuration
public class DbConfig {@Beanpublic DruidDataSource dataSource(){ // 方法的名称代表了当前 bean 的名称return new DruidDataSource();}
}
<!--指定加载bean的位置,component-->
<context:component-scan base-package="com.axy.bean, com.axy.config"/>
控制台打印结果如下:
注:@Configuration
注解的定义上添加了 @Component
,因此配置类使用 @Component
也是没问题的,但推荐写 @Configuration
。
方式三:注解方式声明配置类
当前工程目录如下:
声明配置类,并使用 @ComponentScan
注解指定要扫描的包:
@ComponentScan(value = {"com.axy.bean", "com.axy.config"})
public class SpringConfig {
}
测试类:
public class App {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}}
}
控制台打印结果:
注:因为使用 AnnotationConfigApplicationContext
方法指定加载了 SpringConfig
这个类,那么该类会被加载成 bean 对象,并且类上面的 @Configuration
注解可不用添加了,并且里面也可添加创建第三方 bean 的方法(但一般不这么写)。
扩展一:@Bean 返回的对象和真实 Bean 对象可能不是一个
当前工程目录如下:
在 bean 包下新建 DogFactoyBean
类,并实现 FactoryBean 接口:
import org.springframework.beans.factory.FactoryBean;public class DogFactoryBean implements FactoryBean<Dog> {@Overridepublic Dog getObject() throws Exception {return new Dog();}@Overridepublic Class<?> getObjectType() { // 返回工厂所生产对象的类型// 如果泛型是接口类型,那么当前返回其实现类的字节码return Dog.class;}@Overridepublic boolean isSingleton() { // 工厂构建的对象是否是单例return true;}
}
将 Dog 类上的注解去掉:
//@Component("dog")
public class Dog {...
}
添加返回 DogFactoryBean
类的方法,并将其返回值生成 Bean 对象:
@ComponentScan(value = {"com.axy.bean"})
public class SpringConfig {@Beanpublic DogFactoryBean dog(){ // Bean的名称是当前方法名return new DogFactoryBean(); // 返回对象的类型应该是 “DogFactoryBean”}
}
测试类:
public class App {public static void main(String[] args) {ApplicationContext ctx =new AnnotationConfigApplicationContext(SpringConfig.class);Object bean = ctx.getBean("dog");System.out.println(bean.getClass());}
}
控制台打印结果:
从结果可以看出,public DogFactoryBean dog(){…}
要返回的类型是 DogFactoryBean
,但真实返回的类型是 Dog
。
结论:@Bean 返回的对象类型和真实 Bean 对象类型可能不是一个。
扩展二:加载配置类的同时,加载配置文件(系统迁移)
场景:目前需要做一个系统的二次开发,原有系统用的是配置文件的形式声明 Bean,现准备用注解的形式配置声明 Bean。如何在注解的声明中将原有的配置文件加载进来呢?
当前工程目录如下:
添加旧配置类 applicationContext2.xml:
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="tiger" class="com.axy.bean.Tiger" />
</beans>
bean 包下添加 Tiger 类:
public class Tiger {private Integer age;public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Tiger{" +"age=" + age +'}';}
}
可以在配置类上使用 @ImportResource
注解将配置文件加载进来:
@ComponentScan("com.axy.bean")
@ImportResource("applicationContext2.xml") // 加载旧配置文件
public class SpringConfig2 {
}
测试类:
public class App {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig2.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}}
}
控制台打印结果:
扩展三:@ImportResource、@Bean、@Component 加载优先级
首先给出结论:@ImportResource、@Bean、@Component 要注入的 Bean 的名称相同时,优先级表现为:@ImportResource > @Bean > @Component。
当前工程目录如下:
修改 applicationContext2.xml,Tiger 类采用 set 方法注入,age 设置为 30:
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!--set方法注入--><bean id="tiger" class="com.axy.bean.Tiger"><property name="age" value="30"/></bean>
</beans>
新建配置类 SpringConfig3,添加生成 Bean 对象方法,其中 tiger 对象 age 设置为 20:
@ComponentScan(value = {"com.axy.bean"})
@ImportResource("applicationContext2.xml")
public class SpringConfig3 {@Beanpublic Tiger tiger(){Tiger tiger = new Tiger();tiger.setAge(20);return tiger;}
}
修改 Tiger 类,当被 @ComponentScan 扫描生成 bean 对象时,使其 age 初始化为 10:
@Component
public class Tiger {private Integer age = 10;...
}
测试类:
public class App3 {public static void main(String[] args) {ApplicationContext ctx =new AnnotationConfigApplicationContext(SpringConfig3.class);Tiger tiger = (Tiger) ctx.getBean("tiger");System.out.println(tiger);}
}
控制台打印结果:
当注释 @ImportResource(“applicationContext2.xml”)
,控制台打印结果如下:
结果表明:@ImportResource、@Bean、@Component 要注入的 Bean 的名称相同时,优先级表现为:@ImportResource > @Bean > @Component。
扩展四:@ImportResource 引入多个配置文件的优先级
首先给出结论:同名的 Bean 对象,后加载的配置会覆盖先加载的配置。
添加配置文件 applicationContext3.xml,Tiger 类采用 set 方法注入,age 设置为 40:
<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!--set方法注入--><bean id="tiger" class="com.axy.bean.Tiger"><property name="age" value="40"/></bean>
</beans>
用 @ImportResource 同时引入两个配置文件:
@ImportResource(value = {"applicationContext2.xml", "applicationContext3.xml"})
public class SpringConfig31 {
}
控制台打印结果:
如果交换引入顺序:
@ImportResource(value = {"applicationContext3.xml", "applicationContext2.xml"})
public class SpringConfig31 {
}
控制台打印结果:
因此得出结论:同名的 Bean 对象,后加载的配置会覆盖先加载的配置。
扩展五:proxyBeanMethods=true 生成代理对象
首先给出结论:proxyBeanMethods=true 可以保障当前配置类在 Spring 容器中生成的是代理对象,其里面定义的 Bean 是从容器中获取的,而不是重新创建的。
@Configuration 注解里面有一个属性 proxyBeanMethods,默认值为 true。
新建配置类 SpringConfig32,设置 proxyBeanMethods 属性
@Configuration(proxyBeanMethods = true)
public class SpringConfig32 {@Beanpublic Cat cat(){return new Cat();}
}
测试类:
public class App {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig32.class);System.out.println(ctx.getBean("springConfig32"));System.out.println("---------------------------");System.out.println(ctx.getBean("cat"));System.out.println(ctx.getBean("cat"));System.out.println(ctx.getBean("cat"));}
}
proxyBeanMethods=true
proxyBeanMethods=false


结果表明:① 设置 proxyBeanMethods=true
,生成的配置类对象是代理对象,通过其调用加载 Bean 的方法, 是从容器中获取的,而不是重新创建的。
② 设置 proxyBeanMethods=false
,生成的配置类对象是普通对象,每次执行定义 Bean 的方法都会创建一个新的对象。
总结:配置类中设置 proxyBeanMethods=true
,若其某一个方法可以得到对象,并且该对象被加载成 Bean。那么这个方法 在该配置类中 不论调用多少次,都是从容器中获取,即只会创建一次。
注:设置 proxyBeanMethods=true,里面的加载 Bean 的方法也要添加 @Bean 注解。
方式四:@Import 注解注入
当前工程目录如下:
添加配置类 SpringConfig4,使用 @Import 注解导入要注入的 bean 对应的字节码:
@Import(Dog.class) // 被导入的 bean (Dog类) 无需使用注解声明为 bean。
public class SpringConfig4 {
}
测试类:
public class App {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig4.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}}
}
控制台打印结果:
从结果可以看出与前文 Bean 名称不同的是,@Import 注解加载的 Bean 名称采用 全路径类名。
注:此形式可以有效的降低源代码与 Spring 技术的耦合度,在 spring 技术底层及诸多框架的整合中大量使用
使用 @Import 加载配置类:
使用 @Import 加载配置类 DbConfig:
@Import(value = {Dog.class, DbConfig.class})
public class SpringConfig4 {
}@Configuration
public class DbConfig {@Beanpublic DruidDataSource dataSource(){return new DruidDataSource();}
}
控制台打印结果:
结果表明:DbConfig 被加载了,并且配置类里面 Bean 的声明也会被加载。
如果去掉配置类里面的 @Configuration 注解,打印结果同上。
结论:使用 @Import 加载配置类,配置类可不用添加 @Configuration 注解。
方式五:上下文对象在容器初始化完毕后注入
上下文对象的 registerBean 方法,是 AnnotationConfigApplicationContext 独有的方法:
测试类:
public class App {public static void main(String[] args) {AnnotationConfigApplicationContext ctx =new AnnotationConfigApplicationContext(SpringConfig4.class);// 上下文容器对象已经初始化完毕后,手工加载 beanctx.registerBean("monkey", Monkey.class, 1); // 参数三代表构造函数的参数ctx.registerBean("monkey", Monkey.class, 2);ctx.registerBean("monkey", Monkey.class, 3);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}System.out.println("-------------");Monkey monkey = ctx.getBean("monkey", Monkey.class);System.out.println(monkey);}
}public class Monkey {private Integer age;public Monkey() {}public Monkey(Integer age) {this.age = age;}@Overridepublic String toString() {return "Monkey{" +"age=" + age +'}';}
}
打印结果:
结果表明:上下文容器初始化后,注册了三次 monkey 对象,但最终容器中保留的是最后一次注册的对象。
注:如果使用 registerBean 方法的时候,没有指明 bean 的名称,除非待注入的对象使用 @Component 或其衍生注解指名 bean 的名称,否则名称默认类名首字母小写。
方式六:实现 ImportSelector 接口 ★
实现 ImportSelector 接口的类,实现对导入源的编程式处理。
当前工程目录如下:
添加 MyImportSelector 类,实现 ImportSelector 接口,并重写 selectImports 方法:
public class MyImportSelector implements ImportSelector {public String[] selectImports(AnnotationMetadata metadata) {return new String[]{"com.axy.bean.Dog", "com.axy.bean.Cat"}; // 数组元素为全路径类名}
}public class Dog {}public class Cat {}
添加配置类 SpringConfig6,使用 @Import 引入 MyImportSelector 类:
@Import(MyImportSelector.class)
public class SpringConfig6 {
}
测试类:
public class App {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig6.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}}
}
控制台打印结果:
String[] selectImports(AnnotationMetadata metadata)
方法形参 metadata 代表元数据,描述的是使用该类 MyImportSelector 的对象。
public class MyImportSelector implements ImportSelector {public String[] selectImports(AnnotationMetadata metadata) {System.out.println("------------------");System.out.println("提示:" + metadata.getClassName()); // 获取使用当前该类的对象的类名System.out.println(metadata.hasAnnotation("org.springframework.context.annotation.Configuration"));System.out.println("------------------");return new String[]{"com.axy.bean.Dog", "com.axy.bean.Cat"};}
}
控制台打印结果:
结果表明:使用类 MyImportSelector 类的对象,其类名是 SpringConfig6,并且该对象没有使用 @Configuration 注解。
medata 还有很多方法可以用,如:获取 SpringConfig6 是否包含指定注解,该注解是否有某种属性等。
因此,可以用 medata 做各种条件的判定,以此来决定是否加载指定的 Bean,即动态加载 Bean。
如以下代码:
public class MyImportSelector implements ImportSelector {public String[] selectImports(AnnotationMetadata metadata) {boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");if (flag) {return new String[]{"com.axy.bean.Dog"};}return new String[]{"com.axy.bean.Cat"};}
}
注:源码中大量使用!
方式七:实现 ImportBeanDefinitionRegistrar 接口 ★
当前工程目录如下:
新建 MyRegistrar 类,实现 ImportBeanDefinitionRegistrar 接口,并重写 public void registerBeanDefinitions(…){…}
方法。
public class MyRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {// 1.使用元数据去做判定// 2.返回值是 void,不同于方式六中直接加载 Bean 的形式// 创建 beanDefinition 对象,并将该对象使用 registry 注册进容器当中BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Tiger.class).getBeanDefinition();registry.registerBeanDefinition("tiger", beanDefinition); // registerBeanDefinition 参数一:bean 名称,参数二:beanDefinition对象}
}
registerBeanDefinitions(…)
方法:
① 参数一:元数据对象,可以对使用该类 MyRegistrar 的对象各种判定;
② 参数二: Bean 注册器对象,可以实现对容器中 bean的裁定。如:例设置 bean 的单例多例等…
新建 SpringConfig7 类,并通过 @Import 注解引入 MyRegistrar 类:
@Import(MyRegistrar.class)
public class SpringConfig7 {
}
测试类:
public class App {public static void main(String[] args) {ApplicationContext ctx =new AnnotationConfigApplicationContext(SpringConfig7.class);String[] names = ctx.getBeanDefinitionNames();for (String name : names) {System.out.println(name);}}
}
控制台打印结果:
总结: 相比于方式六中实现 ImportSelector 接口,方式七实现 ImportBeanDefinitionRegistrar 接口将 bean 的管理开放了出来。
如果配置类中引入多个 ImportSelector 接口的实现类,那么对 Bean 对象的裁定由顺序决定。如以下代码:
新建 BookService 接口和实现类:
public interface BookService {public void check();
}public class BookServiceImpl1 implements BookService {@Overridepublic void check() {System.out.println("book service 1...");}
}public class BookServiceImpl2 implements BookService {@Overridepublic void check() {System.out.println("book service 2...");}
}
新建注册器,并分别注册成同名 bean:
public class MyRegistrar1 implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl1.class).getBeanDefinition();registry.registerBeanDefinition("bookService", beanDefinition);}
}public class MyRegistrar2 implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();registry.registerBeanDefinition("bookService", beanDefinition);}
}
配置类引入两个注册器实现类:
@Import({MyRegistrar1.class, MyRegistrar2.class})
public class SpringConfig71 {
}
测试类:
public class App {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig71.class);BookService bookService = ctx.getBean("bookService", BookService.class);bookService.check();}
}
控制台打印结果:
如果调换引用顺序,则 Bean 对象的最终裁定也会发生变化,即控制台将打印:book service 1…
方式八:实现 BeanDefinitionRegistryPostProcessor 接口
当前工程目录如下:
新建 MyPostProcessor 类,实现 BeanDefinitionRegistryPostProcessor 方法,并重写里面的方法:
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl3.class).getBeanDefinition();beanDefinitionRegistry.registerBeanDefinition("bookService", beanDefinition);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {}
}
方法 public void postProcessBeanDefinitionRegistry(…){…}
, 后处理并定义注册 Bean。简而言之,通过 BeanDefinition 的注册器注册实名bean,实现对容器中 bean 的最终裁定。
引荐配置类,并引用两个注册器和后处理注册器 MyPostProcessor:
@Import({MyPostProcessor.class, MyRegistrar1.class, MyRegistrar2.class})
public class SpringConfig8 {}
测试类:
public class App {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig8.class);BookService bookService = ctx.getBean("bookService", BookService.class);bookService.check();}
}
控制台打印结果:
结果表明:后处理器的实现类会在所有 Bean 都注册定义后再进行处理,如果有同名 Bean,则会覆盖前面注册的 Bean。
另外,如果配置类引用多个后处理器的实现类,则按照后处理器实现类的顺序对 Bean 进行最终的裁定。
参考链接:黑马程序员SpringBoot2全套视频教程,springboot零基础到项目实战(spring boot2完整版)
99%的人还看了
猜你感兴趣
版权申明
本文"由浅入深,详细总结 Spring 八种加载 Bean 的方式":http://eshow365.cn/6-25381-0.html 内容来自互联网,请自行判断内容的正确性。如有侵权请联系我们,立即删除!
- 上一篇: CBAM:卷积注意力机制
- 下一篇: 5.力扣c++刷题-->找出众数