Spring创建bean的几种方式及使用场景
作者:IT人的天地 发布时间:2021-06-20 14:16:35
本篇我们讲解下使用spring创建bean的几种方式,创建bean,也可以叫组件注册,就是把单例bean放到spring容器中。我们定义如下工程结构:
sping
--src
----main
java
com.xk.spring (包路径)
--bean (普通类所在的包名)
--config (用来写各种配置类)
--service (用来编写服务层)
resources
--(本目录下用于存放各类资源配置文件,后面测试会用到)
----test
--pom.xml
在bean包下面定义Person类,用于演示
package com.xk.spring.bean;
/**
* @author xk
* @since 2023.04.28 8:44
*/
public class Person {
private Integer id;
private String name;
private Integer age;
public Person(){}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Override
public String toString() {
return "name:"+this.name+" age:"+this.age+" id:"+this.id;
}
}
另外再定义一个打印spring容器中bean名称的方法,在单元测试中会使用。
void printBeanNames(ApplicationContext applicationContext){
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames();
for (String definitionName : beanDefinitionNames) {
System.out.println("----"+definitionName);
}
}
1、@Configuration注解
首先我们先说下@Configuration这个注解,在springboot应用中,经常可以看到引用的各类jar包中定义的类中用到了这个注解。该注解只能作用于类上,在一个类上面加@Configuration注解,就说明该类是一个配置类,该配置类也会作为一个bean被存放到spring容器中,bean的名称就是类的名称(首字母小写)。如下所示,MyConfig是个配置类,spring容器中就拥有了一个bean名称为myConfig的bean。
package com.xk.spring.config;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
}
我们编写一个单元测试,来打印下spring容器中bean的名称。
@Test
public void testMyConfig(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MyConfig.class);
printBeanNames(applicationContext);
}
执行testMyConfig单元测试,得到结果如下:
----org.springframework.context.annotation.internalConfigurationAnnotationProcessor ----org.springframework.context.annotation.internalAutowiredAnnotationProcessor ----org.springframework.context.annotation.internalCommonAnnotationProcessor ----org.springframework.context.event.internalEventListenerProcessor ----org.springframework.context.event.internalEventListenerFactory ----myConfig
前面几个bean,是spring内置的bean,最后一个bean就是我们定义的这个配置类,bean名称为myConfig.
2、@Bean注解
@Bean作用于配置类的方法上,要求该方法有返回值,返回的值就是spring容器中的bean,bean名称默认就是取方法的名称。比如下面的代码说明创建一个名称为onePerson的bean。
package com.xk.spring.config;
import com.xk.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@Bean
public Person onePerson(){
return new Person("xx",12);
}
}
我们再次执行上面的testMyConfig单元测试,会得到如下结果:
----org.springframework.context.annotation.internalConfigurationAnnotationProcessor ----org.springframework.context.annotation.internalAutowiredAnnotationProcessor ----org.springframework.context.annotation.internalCommonAnnotationProcessor ----org.springframework.context.event.internalEventListenerProcessor ----org.springframework.context.event.internalEventListenerFactory ----myConfig ----onePerson
可以看到,相比于第1步中的结果,spring容器中多了一个叫onePerson的bean,而onePerson也就是方法onePerson的方法名称。
3、@Import注解
@Import注解只能作用于类上面,可以导入标记有@Configuration的配置类、ImportSelector和ImportBeanDefinitionRegistrar的实现类,还能导入普通的类(不含spring注解),所谓导入,就是将类的实例注册到spring容器中。该注解必须和@Configuration注解结合使用,而且正常情况下,只有当某个组件不会被spring自动注册时(比如当我们使用ComponentScan指定了某个包扫描路径),才会使用该注解。spring-boot-autoconfigure.jar包大量使用了该注解,感兴趣的可以去参考下。
3.1、导入标记有@Configuration的配置类
通过@Import注解导入配置类,会把该配置类中定义的bean都注册到spring容器中,同时spring也会该该配置类创建一个bean,bean的名称就是该类的全路径名。
我们定义一个ImportConfig配置类,内容如下:
package com.xk.spring.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author xk
* @since 2023.04.28 20:03
*/
@Import({MyConfig.class})
@Configuration
public class ImportConfig {
}
我们在配置类上通过@Import注解导入另外的配置类MyConfig,会把MyConfig配置类以及在MyConfig类中定义(注册)的bean都导入到spring容器中。
我们编写一个单元测试,来打印下spring容器中bean的名称。
----org.springframework.context.annotation.internalConfigurationAnnotationProcessor ----org.springframework.context.annotation.internalAutowiredAnnotationProcessor ----org.springframework.context.annotation.internalCommonAnnotationProcessor ----org.springframework.context.event.internalEventListenerProcessor ----org.springframework.context.event.internalEventListenerFactory ----importConfig ----com.xk.spring.config.MyConfig ----onePerson
注意,此时容器中不仅有importConfig这个bean,也有MyConfig配置类中定义的onePerson这个bean。而导入的MyConfig配置类在spring容器中对应的bean名称是MyConfig类的全路径名com.xk.spring.config.MyConfig。特别提醒,由于MyConfig本身是个配置类,如果spring本身能自动扫描注册该类(比如配置了@ComponentScan包含config包路径),那么我们再通过@Import这种方式导入MyConfig,MyConfig在spring容器中对应的名称就变成了myConfig,也就是bean的名称是类名(首字母小写)。当然,正如前面所说,如果我们的配置类能被spring自动扫描注册,我们就不会使用@Import这种方式导入配置类。
另外,需要格外注意的是,我们通过@Import导入的类本身会被当做配置类,所以即使此处MyConfig类没有加@Configuration注解,通过配置@Import({Myconfig.class}),也会把MyConfig类里面定义的bean注册到spring容器,此时MyConfig类对应的bean名称就是类的全路径名。但我们一般不这么做,如果一个类中定义了bean,我们很自然会为该类加上@Configuration注解。
3.2、导入ImportSelector的实现类
ImportSelector是个接口,它的定义如下:
public interface ImportSelector {
/**
* 返回需要导入的类的字符串数组,数组中的元素就是类的全路径名。
* @param importingClassMetadata 当前标记@Import注解的类的元数据信息
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
/**
* 定义一个断言,用来对上面selectImports返回的类名数组进行过滤,如果某个元素被断言判定为true,则该元素不会被spring注册为bean。此处我们先不关注该方法。
*/
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
我们定义一个MyImportSelector类,实现该接口,内容如下:
package com.xk.spring.importselector;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author xk
* @since 2023.04.28 21:32
*/
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.xk.spring.bean.Person"};
}
}
然后我们修改下我们的ImportConfig类,通过@Import导入MyImportSelector类,内容如下:
package com.xk.spring.config;
import com.xk.spring.importselector.MyImportSelector;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author xk
* @since 2023.04.28 20:03
*/
@Import({MyImportSelector.class})
@Configuration
public class ImportConfig {
}
最后我们执行上面的testImportConfig单元测试,可以得到下面的结果:
----org.springframework.context.annotation.internalConfigurationAnnotationProcessor ----org.springframework.context.annotation.internalAutowiredAnnotationProcessor ----org.springframework.context.annotation.internalCommonAnnotationProcessor ----org.springframework.context.event.internalEventListenerProcessor ----org.springframework.context.event.internalEventListenerFactory ----importConfig ----com.xk.spring.bean.Person
可以看到,通过导入MyImportSelector类,spring自动帮我们生成了Person的bean实例,而Person对应的bean的名称就是Person类的全路径名。
3.3、导入ImportBeanDefinitionRegistrar的实现类
ImportBeanDefinitionRegistrar也是个接口,它的定义如下:
public interface ImportBeanDefinitionRegistrar {
/**
* 基于标记@Import注解的类的元数据信息来注册bean定义信息(最终spring会根据这些bean定义信息生成bean)。注意:由于spring bean生命周期的约束,BeanDefinitionRegistryPostProcessor类型的bean不会在此处被注册。后面我们再来讨论bean的生命周期。
* @param importingClassMetadata 当前标记@Import注解的类的元数据信息
* @param registry bean定义注册中心,所有的bean定义信息都会被注册到这里
* @param importBeanNameGenerator 被导入的bean名称的生成策略
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
/**
* 基于标记@Import注解的类的元数据信息来注册bean定义信息(最终spring会根据这些bean定义信息生成bean)。注意:由于spring bean生命周期的约束,BeanDefinitionRegistryPostProcessor类型的bean不会在此处被注册。后面我们再来讨论bean的生命周期。
* @param importingClassMetadata 当前标记@Import注解的类的元数据信息
* @param registry bean定义注册中心,所有的bean定义信息都会被注册到这里
*/
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
我们定义一个MyImportBeanDefinitionRegistrar类,实现ImportBeanDefinitionRegistrar接口,定义如下:
package com.xk.spring.importbeandefinitionregistrar;
import com.xk.spring.bean.Person;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author xk
* @since 2023.04.28 21:45
*/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean flag = registry.containsBeanDefinition("com.xk.spring.bean.Person");
if(!flag){
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Person.class);
//注册person,并且自定义bean的名称为myPerson
registry.registerBeanDefinition("myPerson",rootBeanDefinition);
}
}
}
然后我们修改下我们的ImportConfig配置类,使用@Import导入MyImportBeanDefinitionRegistrar类,内容如下:
package com.xk.spring.config;
import com.xk.spring.importbeandefinitionregistrar.MyImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author xk
* @since 2023.04.28 20:03
*/
@Import({MyImportBeanDefinitionRegistrar.class})
@Configuration
public class ImportConfig {
}
最后我们执行testImportConfig单元测试,会得到如下的结果:
----org.springframework.context.annotation.internalConfigurationAnnotationProcessor ----org.springframework.context.annotation.internalAutowiredAnnotationProcessor ----org.springframework.context.annotation.internalCommonAnnotationProcessor ----org.springframework.context.event.internalEventListenerProcessor ----org.springframework.context.event.internalEventListenerFactory ----importConfig ----myPerson
可以看到,我们通过这种方式成功的导入Person类对应的bean,而且bean的名称就是我们自定义的myPerson。
3.4、导入普通的类
我们还可以通过@Import导入普通类到spring容器,导入之后的bean的名称就是类名的全路径。这些普通类可以什么注解都不加,比如没有@Component注解,也没有@Configuration注解。就拿我们的Person类来说,我们可以通过@Import({Person.class})的方式直接将其导入spring容器,。
修改ImportConfig配置类,内容如下:
package com.xk.spring.config;
import com.xk.spring.bean.Person;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author xk
* @since 2023.04.28 20:03
*/
@Import({Person.class})
@Configuration
public class ImportConfig {
}
最后执行testImportConfig单元测试,会得出如下结果:
----org.springframework.context.annotation.internalConfigurationAnnotationProcessor ----org.springframework.context.annotation.internalAutowiredAnnotationProcessor ----org.springframework.context.annotation.internalCommonAnnotationProcessor ----org.springframework.context.event.internalEventListenerProcessor ----org.springframework.context.event.internalEventListenerFactory ----importConfig ----com.xk.spring.bean.Person
可以看到,Person类被导入到spring容器,并且对应的bean名称是Person类的全路径名。
4、@ImportResource注解
传统的spring项目通过使用xml配置文件来定义bean信息,然后在web.xml中声明我们的spring的xml配置文件。现在我们换种方式,我们可以通过使用@ImportResource注解,将整个的xml配置文件导入到配置类,进而由spring替我们生成对应的bean。
我们在工程的resources资源目录下创建application-beans.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--定义person-->
<bean id="person" class="com.xk.spring.bean.Person">
<!-- collaborators and configuration for this bean go here -->
</bean>
</beans>
然后我们修改ImportConfig配置类,内容如下:
package com.xk.spring.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
/**
* @author xk
* @since 2023.04.28 20:03
*/
@ImportResource({"classpath:/application-beans.xml"})
@Configuration
public class ImportConfig {
}
最后执行testImportResource单元测试,得出结果如下:
----org.springframework.context.annotation.internalConfigurationAnnotationProcessor ----org.springframework.context.annotation.internalAutowiredAnnotationProcessor ----org.springframework.context.annotation.internalCommonAnnotationProcessor ----org.springframework.context.event.internalEventListenerProcessor ----org.springframework.context.event.internalEventListenerFactory ----importConfig ----person
可以看到,通过@ImportResource的方式,我们把在xml配置文件中定义的person这个bean注册到了spring容器中。
5、@ComponentScan注解
@ComponentScan注解可以让spring方便地识别标记了@Component注解的组件,需要和@Configuration注解一起使用。我们在web开发时,经常会使用@Service、@Repository、@Controller、@Configuration、@Component等注解,用来表示MVC不同层次的组件bean。其实前面四种注解的元注解上面都标记了@Component,说明被这四种注解标记的类通过配置@ComponentScan指令,就可以被注册到spring容器中,而它们对应的bean的名称就是类名(首字母小写)。
我们拿@Service为例,在service包下面创建PersonService类,内容如下:
package com.xk.spring.service;
import org.springframework.stereotype.Service;
/**
* 人员服务层
* @author xk
* @since 2023.04.29 7:25
*/
@Service
public class PersonService {
}
内容很简单,就简单的通过@Service将该类标记为一个组件。接下来我们修改MyConfig类,在它上面加上@ComponentScan注解,内容如下:
package com.xk.spring.config;
import com.xk.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan("com.xk.spring.service")
@Configuration
public class MyConfig {
@Bean
public Person onePerson(){
return new Person("xx",18);
}
}
最后我们执行下先前定义的testMyConfig单元测试,得到结果如下:
----org.springframework.context.annotation.internalConfigurationAnnotationProcessor ----org.springframework.context.annotation.internalAutowiredAnnotationProcessor ----org.springframework.context.annotation.internalCommonAnnotationProcessor ----org.springframework.context.event.internalEventListenerProcessor ----org.springframework.context.event.internalEventListenerFactory ----myConfig ----personService ----onePerson
可以看到,容器中多了一个personService的bean,而该bean就是对应被我们标记了@Service注解的PersonService。虽然我们config包下面的ImportConfig类也加了@Configuration注解,但由于它不在包扫描的范围,所以spring无法将其自动注册到容器中。
6、使用总结
(1)如果一个类不是ImportSelector或ImportBeanDefinitionRegistrar的子类,当它被@Import导入的时候,它就会被当成一个配置类存在,可以简单理解为它被加了@Configuration注解,那么原本在这个类上面的注解(比如@ComponentScan、@Import、@ImportResource等等)就也会生效。
(2)我们通过springboot开发客户端jar包,提供给其他团队使用时,如果里面涉及到多个配置类,并且需要实现自动配置的功能,那么可以在配置类A上面通过@Import({B.class})将配置类B导入,然后再根据springboot自动配置类编写规范,将配置类A写到类路径下面的META-INF/spring.factories文件中。
(3)@ComponentScan注解可以让我们指定扫描的包路径,假如我们公司里面用的项目都以com.xk开头,我们的项目的包名以com.xk.spring开始,其他的团队的项目包名以com.xk.mybatis开始。如果com.xk.mybatis中也配置了一些组件,但是我们并不想将它们注册到spring容器中,就可以通过配置@ComponentScan({"com.xk.spring"})的方式只扫描我们项目下的包,也相当于排除掉其他路径的包。
结束语
来源:https://juejin.cn/post/7227139379105480760


猜你喜欢
- 本文实例为大家分享了Android实现竖直跑马灯效果的具体代码,供大家参考,具体内容如下首先给出跑马灯效果图中间的色块是因为视频转成GIF造
- 最近在开发的过程中,一个列表的查询,涉及到了多表的关联查询,由于持久层使用的是mongodb,对这个非关系型数据使用的不是很多,所以在实现此
- 概述MerkleTree被广泛的应用在比特币技术中,本文旨在通过代码实现一个简单的MerkleTree,并计算出Merkle tree的 T
- 介绍该系统有三个角色,分别是:普通用户、房屋中介、管理员。普通用户的功能:浏览房屋信息、预约看房、和中介聊天、申请成为中介等等。房屋中介的功
- 如何解决某个节点故障的问题?如何解决数据一致性的问题?如何解决数据倾斜的问题?CAP理论先从定义开始:C(Consistence):一致性所
- Java中对象创建,内存分配,垃圾回收的权力交给了虚拟机,这其中有利也有弊,程序员也减轻了负担,但是如果不熟悉Java的内存区域划分,一旦出
- 什么是适配器模式? 适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不
- 这篇文章算是对整个引导界面开发专题的一个终结了吧,个人觉得大部分的引导界面基本上都是千篇一律的,只要熟练掌握了一个,基本上也就没什么好说的了
- 本文大纲本文章将要介绍的内容有以下几点,读者朋友也可先自行思考一下相关问题:线程中断 interrupt 方法怎么理解,意思就是线程中断了吗
- 现在很多app一打开就是一个ViewPager,然后可以用手指滑,每滑一次就换一张图,底下还会有圈圈表示说现在滑到第几章~通常这些图片都是放
- 本文实例讲述了Java模拟QQ桌面截图功能实现方法。分享给大家供大家参考。具体如下:QQ的桌面截图功能非常方便,去年曾用Java模拟过一个,
- 一、什么是重量级锁当有大量的线程都在竞争同一把锁的时候,这个时候加的锁,就是重量级锁。这个重量级锁其实指的就是JVM内部的ObjectMon
- Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsof
- 选取单个元素直觉来说选取单个元素肯定会比选取多个要简单得多,不过这里也存在一些问题。我们先看下一般的做法的问题是什么,然后再看下如何用lam
- 本文实现了C#隐式运行CMD命令的功能。下图是实例程序的主画面。在命令文本框输入DOS命令,点击“Run”按钮,在下面的文本框中输出运行结果
- 本文实例讲述了Java接口继承和使用接口操作。分享给大家供大家参考,具体如下:一 接口的继承1 点睛接口支持多继承,一个接口可以有多个父接口
- 本文实例讲述了C#获取两个时间的时间差并去除周末的方法。分享给大家供大家参考。具体分析如下:一般来说取时间差的代码很多,但是能够只取工作日的
- 从SD卡中获取图片资源,或者拍一张新的图片。 先贴代码 获取图片: 注释:拍照获取的话,可以指定图片的保存地址,在此不说明。 CharSeq
- 1. 问题所示编译ssm的项目的时候出现了这个错误导致一直运行不起来SLF4J: Failed to load class "or
- 概述在 Linux 平台下使用搜狗输入法在 IDEA 中输入中文时,输入法候选框总是静止在 IDEA 的左下角,而不能跟随光标进行移动。虽然