由浅入深,详细总结 Spring 八种加载 Bean 的方式 您所在的位置:网站首页 bean是 由浅入深,详细总结 Spring 八种加载 Bean 的方式

由浅入深,详细总结 Spring 八种加载 Bean 的方式

2024-07-10 01:06| 来源: 网络整理| 查看: 265

文章目录 方式一:XML 方式声明 bean方式二:XML + 注解方式声明 bean方式三:注解方式声明配置类扩展一:@Bean 返回的对象和真实 Bean 对象可能不是一个扩展二:加载配置类的同时,加载配置文件(系统迁移)扩展三:@ImportResource、@Bean、@Component 加载优先级扩展四:@ImportResource 引入多个配置文件的优先级扩展五:proxyBeanMethods=true 生成代理对象 方式四:@Import 注解注入方式五:上下文对象在容器初始化完毕后注入方式六:实现 ImportSelector 接口 ★方式七:实现 ImportBeanDefinitionRegistrar 接口 ★方式八:实现 BeanDefinitionRegistryPostProcessor 接口

方式一:XML 方式声明 bean

目录初始化: 在这里插入图片描述

pom.xml:

4.0.0 com.axy springboot_bean_init 1.0-SNAPSHOT 8 8 UTF-8 org.springframework spring-context 5.3.9

applicationContext.xml:

待注入对象:

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 中添加如下坐标:

com.alibaba druid 1.1.16

applicationContext.xml:

那么这样会使用 DruidDataSource 的默认构造函数来创建 Bean 对象。控制台打印结果如下: 在这里插入图片描述

相关链接:Spring 从入门到精通系列 05 —— Spring 依赖注入的三种方式

方式二:XML + 注解方式声明 bean

applicationContext.xml:

待注入对象:

@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 { @Bean public DruidDataSource dataSource(){ // 方法的名称代表了当前 bean 的名称 return new DruidDataSource(); } }

控制台打印结果如下: 在这里插入图片描述 注:@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 { @Override public Dog getObject() throws Exception { return new Dog(); } @Override public Class getObjectType() { // 返回工厂所生产对象的类型 // 如果泛型是接口类型,那么当前返回其实现类的字节码 return Dog.class; } @Override public boolean isSingleton() { // 工厂构建的对象是否是单例 return true; } }

将 Dog 类上的注解去掉:

//@Component("dog") public class Dog { ... }

添加返回 DogFactoryBean 类的方法,并将其返回值生成 Bean 对象:

@ComponentScan(value = {"com.axy.bean"}) public class SpringConfig { @Bean public 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:

bean 包下添加 Tiger 类:

public class Tiger { private Integer age; public void setAge(Integer age) { this.age = age; } @Override public 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:

新建配置类 SpringConfig3,添加生成 Bean 对象方法,其中 tiger 对象 age 设置为 20:

@ComponentScan(value = {"com.axy.bean"}) @ImportResource("applicationContext2.xml") public class SpringConfig3 { @Bean public 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:

用 @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 { @Bean public Cat cat(){ return new Cat(); } }

测试类:

public class App { public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig32.class); SpringConfig32 springConfig32 = ctx.getBean("springConfig32", SpringConfig32.class); System.out.println(springConfig32.cat()); System.out.println(springConfig32.cat()); System.out.println(springConfig32.cat()); } } proxyBeanMethods=trueproxyBeanMethods=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 { @Bean public 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); // 上下文容器对象已经初始化完毕后,手工加载 bean ctx.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; } @Override public 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 { @Override public 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 { @Override public void check() { System.out.println("book service 1..."); } } public class BookServiceImpl2 implements BookService { @Override public void check() { System.out.println("book service 2..."); } }

新建注册器,并分别注册成同名 bean:

public class MyRegistrar1 implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl1.class).getBeanDefinition(); registry.registerBeanDefinition("bookService", beanDefinition); } } public class MyRegistrar2 implements ImportBeanDefinitionRegistrar { @Override public 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 { @Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl3.class).getBeanDefinition(); beanDefinitionRegistry.registerBeanDefinition("bookService", beanDefinition); } @Override public 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完整版)



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有