在Spring中,依赖注入是IOC的主要实现方式,也是在开发过程中最常用的方式,那么你有没有考虑过Spring中到底提供了多少中依赖注入的方式呢?对于这些依赖注入的方式又有那些需要注意的地方呢?我们能不能通过对Spring框架进行扩展来对依赖注入进行扩展呢?带着上边这些问题,我们来开启今天的学习之旅🦾
本文主要介绍Spring的依赖注入,列举了几种依赖注入的方式,包括基于注解的注入、基于XML文件的注入,基于Aware接口回调的注入、基于Bean DefinitionAPI的注入、以及分组注入,对于这些依赖注入的方式分别通过代码示例展示了它们的用法。通过这篇文章的内容可以加深对Spring依赖注入的印象,同时在开发过程中能够更灵活的实现相关的功能。
众所周知,Spring作为一个IOC容器,它的控制反转主要通过DI(依赖注入实现),那么在Spring中我们可以通过那些方式实现DI呢,这里对Spring的依赖注入方式做了简单整理,包括三类常用方式、三类扩展方式,每种类型有包括几种不同的具体实现方法,其中三种常用方式包括
通过xml文件注入(setter注入、构造器注入、自动绑定)
基于@Bean
注解的自动注入(setter注入、构造器注入)
使用依赖注入注解(字段注入、方法注入)
三种扩展方式包括
基于 Aware
接口回调注入
通过 BeanDefinition
类注入
依赖注入的高级用法(@Qualifier
注解限定注入、自定义注解注入)
为了方便后续测试,先定义三个类作为后续待注入的Bean,代码如下
/**
* 人员类
*/
public class PersonEntity {
//姓名
private String name;
//年龄
private int age;
//性别
private Gender gender;
public static PersonEntity bornMale(String name){
PersonEntity res = new PersonEntity();
res.setGender(Gender.MALE);
res.setAge(1);
res.setName(name);
return res;
}
public static PersonEntity bornFemale(String name){
PersonEntity res = new PersonEntity();
res.setGender(Gender.FEMALE);
res.setAge(1);
res.setName(name);
return res;
}
//省略getter、setter、toString方法
}
/**
* 性别类
*/
public enum Gender {
MALE,FEMALE;
}
/**
* 城池类
*/
public class Fortresses {
//名称
private String name;
//守将
private PersonEntity guard;
//兵力
private Long troops;
public Fortresses() {
}
public Fortresses(String name, PersonEntity guard,Long troops) {
this.name = name;
this.guard = guard;
this.troops=troops;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Fortresses{");
sb.append("name='").append(name).append('\'');
sb.append(", guard=").append(guard.getName());
sb.append(", troops=").append(troops);
sb.append('}');
return sb.toString();
}
//省略getter、setter方法
}
在Spring配置文件中存在如下配置
<?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">
<bean id="caocao" class="top.sunyog.spring.notes.entity.PersonEntity">
<property name="name" value="曹操"/>
<property name="age" value="28"/>
<property name="gender" value="MALE"/>
</bean>
</beans>
基于xml配置文件的构造函数注入写法如下
<bean id="yanzhou" class="top.sunyog.spring.notes.entity.Fortresses">
<constructor-arg name="name" value="兖州"/>
<!--ref指向bean的名称,自动注入bean-->
<constructor-arg name="guard" ref="caocao"/>
<constructor-arg name="troops" value="50000"/>
</bean>
基于xml配置文件的setter注入写法如下
<bean id="xuchang" class="top.sunyog.spring.notes.entity.Fortresses" >
<property name="name" value="许昌"/>
<property name="guard" ref="caocao"/>
<property name="troops" value="3000"/>
</bean>
spring提供自动注入的功能,自动注入通过bean标签内的 autowire
属性实现,常用的方式有 byName、byType
,分别是按Bean名称匹配和按类型匹配,在本例种适合使用按类型匹配
<bean id="xuchang" class="top.sunyog.spring.notes.entity.Fortresses" autowire="byType">
<property name="name" value="许昌"/>
<property name="troops" value="3000"/>
</bean>
但是按类型匹配存在一个问题,当同类型的Bean存在多个时,是无法匹配成功的,如增加一个 PersonEntity
类型的Bean
<bean id="yuanshao" class="top.sunyog.spring.notes.entity.PersonEntity">
<property name="name" value="袁绍"/>
<property name="age" value="30"/>
<property name="gender" value="MALE"/>
</bean>
这是启动程序会提示如下信息(发现2个同类型的bean)
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'xuchang' defined in class path resource [config/beans-config.xml]: Unsatisfied dependency expressed through bean property 'guard'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'top.sunyog.spring.notes.entity.PersonEntity' available: expected single matching bean but found 2: caocao,yuanshao
一个简单的解决方式是,为其中一个bean添加 primary
属性,如下所示
<bean id="caocao" class="top.sunyog.spring.notes.entity.PersonEntity" primary="true">
<property name="name" value="曹操"/>
<property name="age" value="28"/>
<property name="gender" value="MALE"/>
</bean>
这样,所有PersonEntity
类型的自动注入都会首先注入 caocao
,而其他同类型的bean需要按名称获取。注意:Spring官方是不推荐这种用法的
在Spring中事先提供如下配置
@Bean
public PersonEntity guanyu(){
PersonEntity res = PersonEntity.bornMale("关羽");
res.setAge(48);
return res;
}
基于@Bean注解的setter注入写法如下
@Bean
public Fortresses xiangyang(PersonEntity guanyu){
Fortresses res = new Fortresses();
res.setName("襄阳");
res.setGuard(guanyu);
res.setTroops(5000L);
return res;
}
基于@Bean注解的构造函数注入写法
@Bean
public Fortresses jingzhou(PersonEntity guanyu){
return new Fortresses("荆州",guanyu,80000L);
}
注意:这里的方法参数名称是固定的,Spring默认按照方法名称通过 getBean(name)
方法获取到对应的Bean,如果这时 xiangyang
这个bean改成这样是会报错的
@Bean
public Fortresses xianigyang(PersonEntity caoren){
Fortresses res = new Fortresses();
res.setName("襄阳");
res.setGuard(caoren);
res.setTroops(5000L);
return res;
}
需要明确的定义名称为 caoren
的bean才能修正
@Bean
public PersonEntity caoren(){
PersonEntity res = PersonEntity.bornMale("曹仁");
res.setAge(45);
return res;
}
这时通过如下代码测试
@Component
public class BeanDIComponent implements ApplicationRunner, BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void run(ApplicationArguments args) throws Exception {
this.beanFactory.getBeanProvider(Fortresses.class).forEach(o-> System.out.println(o));
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory=beanFactory;
}
}
打印结果如下
Fortresses{name='荆州', guard=关羽, troops=80000}
Fortresses{name='襄阳', guard=曹仁, troops=5000}
在Spring中,允许通过注解标注的方式将需要的Bean注入到对应的属性中,可实现属性注入的注解有以下几种
@Autowired
@Resource
@Inject
,使用此注解需要引入 javax.inject
依赖包,maven的写法如下<!--jsr330-->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
以上三个注解的简单用法为(其中属性名称即是bean的名称)
@Autowired
private PersonEntity guanyu;
@Resource
private PersonEntity caoren;
@Inject
private Fortresses jingzhou;
如果属性名称与bean的名称不对应,可以通过指定Bean名称的方式实现注入
@Autowired
@Qualifier(value = "guanyu")
private PersonEntity person_1;
@Resource(name = "caoren")
private PersonEntity person_2;
@Inject
@Qualifier(value = "jingzhou")
private Fortresses city_1;
针对同类型的bean,可以使用这三个注解获取到所有bean的集合
@Autowired
//@Inject
//@Resource
private Collection<PersonEntity> persons;
除此之外,这三个注解还支持在方法上实现自动注入,使用方法类似于 @Bean
,需要注意的是,这里自动注入的是方法的入参。
//自动注入
//@Inject
//@Resource
@Autowired
private void initCity2(Fortresses xiangyang){
this.city_2=xiangyang;
}
//按自定义名称注入
//@Autowired
//@Inject
//@Qualifier(value = "jingzhou")
@Resource(name = "xiangyang")
private void iniitCity3(Fortresses city){
this.city_3=xiangyang;
}
基于Aware接口回调函数也可以实现依赖注入,只是这种方法只能实现特定类型的依赖注入,本文中 BeanDIComponent
类的代码中实现的 BeanFactoryAware
接口就是其中一个,Spring中能够使用的Aware
接口包括:
接口名称 | 说明 |
---|---|
BeanFactoryAware | 获取Spring容器BeanFactory对象 |
ApplicationContextAware | 获取Spring应用上下文 |
EnvironmentAware | 获取Environment对象 |
ResourceLoaderAware | 获取资源加载器对象,ResourceLoader |
BeanClassLoaderAware | 获取加载当前Bean Class的ClassLoader |
BeanNameAware | 获取当前Bean的名称 |
MessageSourceAware | 获取MessageSource对象 |
ApplicationEventPublisherAware | 用于处理Spring事件 |
EmbeddedValueResolverAware | 获取StringValueResoulver对象,用于处理占位符 |
这里以 BeanFactoryAware
和 ApplicationContextAware
为例展示Aware接口的用法
@Component
public class BeanDIComponent implements ApplicationRunner, BeanFactoryAware, ApplicationContextAware {
private BeanFactory beanFactory;
private ApplicationContext context;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(this.beanFactory.getClass());
System.out.println(this.context.getClass());
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory=beanFactory;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context=applicationContext;
}
}
启动程序后的打印结果为
class org.springframework.beans.factory.support.DefaultListableBeanFactory
class org.springframework.context.annotation.AnnotationConfigApplicationContext
注意:这里的打印结果是依赖于所使用的Spring容器的,这里启动的是Springboot项目
基于上例可知,在Spring中我们可以获取到 AnnotationConfigApplicationContext
这个容器类,这个类是Spring中注解配置的主要容器类,它的内部提供了Bean和BeanDefinition注册的方法,利用这两个方法也可以实现Bean的配置,可配置也就可注入
private void apiDependencyInject(){
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(Fortresses.class);
//注入名称为zhangliao的bean
builder.addPropertyReference("guard","zhangliao");
builder.addPropertyValue("name","合肥");
builder.addPropertyValue("troops",3000L);
if (context instanceof AnnotationConfigApplicationContext){
((AnnotationConfigApplicationContext) context).registerBeanDefinition("hefei",builder.getBeanDefinition());
}
}
@Override
public void run(ApplicationArguments args) throws Exception {
this.apiDependencyInject();
this.beanFactory.getBeanProvider(Fortresses.class).forEach(o-> System.out.println(o));
}
/**
* 配置Bean
*/
@Bean
public PersonEntity zhangliao(){
PersonEntity res = PersonEntity.bornMale("张辽");
res.setAge(40);
return res;
}
输出结果为
Fortresses{name='合肥', guard=张辽, troops=3000}
在 ApplicationContext
中可以通过 register
和 registerBean
方法注册Bean,本文主要介绍Bean的注入,这里就不过多介绍了
上文中介绍了 @Qualifier
注解的使用方法,实际上,@Qualifier
注解有将容器中的bean分组的功能,使用 @Qualifier
注解对下列bean进行分组
@Bean
@Qualifier("cao")
public PersonEntity zhangliao(){
PersonEntity res = PersonEntity.bornMale("张辽");
res.setAge(40);
return res;
}
@Bean
@Qualifier("liu")
public PersonEntity guanyu(){
PersonEntity res = PersonEntity.bornMale("关羽");
res.setAge(48);
return res;
}
@Bean
@Qualifier("cao")
public PersonEntity caoren(){
PersonEntity res = PersonEntity.bornMale("曹仁");
res.setAge(45);
return res;
}
对于按类型获取bean的集合的功能,可以实现按分组获取,代码如下
@Autowired
@Qualifier("cao")
private Collection<PersonEntity> persons;
@Override
public void run(ApplicationArguments args) throws Exception {
this.persons.forEach(o-> System.out.println(o));
}
输出结果为
PersonEntity{name='张辽', age=40, gender=MALE}
PersonEntity{name='曹仁', age=45, gender=MALE}
修改代码 @Qualifier("liu")
后的输出结果为
PersonEntity{name='关羽', age=48, gender=MALE}
除了通过 @Qualifier
注解按名称分组之外,还支持自定义注解分组,只需要在自定义注解上使用 @Qualifier
标注即可,之类新增三个自定义注解说明
@Qualifier
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Wei {
}
@Qualifier
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Shu {
}
@Qualifier
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Wu {
}
@Bean
@Qualifier("cao")
@Wei
public PersonEntity zhangliao(){
PersonEntity res = PersonEntity.bornMale("张辽");
res.setAge(40);
return res;
}
@Bean
@Shu
@Qualifier("liu")
public PersonEntity guanyu(){
PersonEntity res = PersonEntity.bornMale("关羽");
res.setAge(48);
return res;
}
@Bean
@Qualifier("cao")
@Wei
public PersonEntity caoren(){
PersonEntity res = PersonEntity.bornMale("曹仁");
res.setAge(45);
return res;
}
@Bean
@Wu
public PersonEntity sunjian(){
PersonEntity res = PersonEntity.bornMale("孙坚");
res.setAge(30);
return res;
}
@Autowired
@Wu//这里只列出@Wu注解的bean,@Wei和@Shu标记的bean的行为类似
private Collection<PersonEntity> persons;
@Override
public void run(ApplicationArguments args) throws Exception {
this.persons.forEach(o-> System.out.println(o));
}
输出结果为
PersonEntity{name='孙坚', age=30, gender=MALE}
在上文中我们列出了Spring中常用的三个依赖注入注解,除此之外,Spring还支持自定义依赖注入注解。由于依赖注入注解的功能是在 AutowiredAnnotationBeanPostProcessor
这个类中实现的,只要修改了这个类的功能(向其中添加我们自定义的注解)即可实现通过自定义注解实现依赖注入的功能
自定义依赖注入注解的步骤如下
AutowiredAnnotationBeanPostProcessor
这个bean自定义注解
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MagicalInject {
}
配置
@Configuration
public class AnnoAutowiredConfig {
@Bean
public AutowiredAnnotationBeanPostProcessor myInjectAnnotationBeanPostProcessor() {
AutowiredAnnotationBeanPostProcessor processor = new AutowiredAnnotationBeanPostProcessor();
processor.setAutowiredAnnotationType(MagicalInject.class);
return processor;
}
}
使用自定义注解
@MagicalInject
@Qualifier("sunjian")
private PersonEntity person_3;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(person_3);
}
自定义注解也支持按名称注入
@MagicalInject
private PersonEntity sunjian;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(sunjian);
}
本文介绍了Spring支持的几种依赖注入方式,其中三种常用方式,三种扩展方式。除了依赖注入之外,结合 @Qualifier
注解还可以实现对spring bean进行分组,实现更加自由、多样的依赖注入。文中有两个重点内容需要关注
📩 联系方式
邮箱: qijilaoli@foxmail.com
掘金: 我的掘金
CSDN: 我的CSDN
微信公众号:奇迹老李
?版权声明
本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问我的博客首页