文章目录

方式一:XML 方式声明 bean方式二:XML + 注解方式声明 bean方式三:注解方式声明配置类扩展一:@Bean 返回的对象和真实 Bean 对象可能不是一个扩展二:加载配置类的同时,加载配置文件(系统迁移)扩展三:@ImportResource、@Bean、@Component 加载优先级扩展四:@ImportResource 引入多个配置文件的优先级扩展五:proxyBeanMethods=true 生成代理对象

方式四:@Import 注解注入方式五:上下文对象在容器初始化完毕后注入方式六:实现 ImportSelector 接口 ★方式七:实现 ImportBeanDefinitionRegistrar 接口 ★方式八:实现 BeanDefinitionRegistryPostProcessor 接口

方式一:XML 方式声明 bean

目录初始化:

pom.xml:

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">

4.0.0

com.axy

springboot_bean_init

1.0-SNAPSHOT

8

8

UTF-8

org.springframework

spring-context

5.3.9

applicationContext.xml:

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">

待注入对象:

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:

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">

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

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

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

applicationContext.xml:

xmlns:context="http://www.springframework.org/schema/context"

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

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context.xsd

">

待注入对象:

@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:

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 包下添加 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:

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

">

新建配置类 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:

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

">

用 @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完整版)

acfun打不开(ACFUN打不开的原因及解决方法)
《圆桌派8》温暖回归!窦文涛开新话题聊遍人世间,一方圆桌窥见生活万花筒