详解Spring中bean的几种注入方式
作者:Kevin.ZhangCG 发布时间:2023-02-12 20:25:07
首先,要学习Spring中的Bean的注入方式,就要先了解什么是依赖注入。依赖注入是指:让调用类对某一接口的实现类的实现类的依赖关系由第三方注入,以此来消除调用类对某一接口实现类的依赖。
Spring容器中支持的依赖注入方式主要有属性注入、构造函数注入、工厂方法注入。接下来将为大家详细介绍这三种依赖注入的方式以及它们的具体配置方法。
1.属性注入
属性注入即通过setXXX( )方法注入bean的属性值或依赖对象。由于属性注入方式具有可选择性和灵活性高的特点,因此它也是实际开发中最常用的注入方式。
Spring首先会调用bean的默认构造函数实例化bean对象,然后再通过反射的方法调用set方法来注入属性值。
属性注入要求bean提供一个 默认的构造函数 ,并且得为需要注入的属性提供 set方法 。
TIps:所谓默认的构造函数,即不带参数的构造函数。如果类中没有自定义任何构造函数,则系统(JVM)会自动生成一个不带参的默认构造函数,如果类中显式的自定义了有参数的构造函数,则系统就不会在自动生成默认构造函数,需要自己手动再加一个无参的构造函数。
下面通过一个实例来演示Spring中bean的属性注入方式:
编写一个user类:
package com.Kevin.bean;
/**
* 创建一个类测试bean的属性注入方式
* @author Kevin
*
*/
public class User {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" 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"> <!-- bean definitions here -->
<!-- 配置对象 -->
<bean id="user" class="com.Kevin.bean.User">
<property name="username">
<value>Kevin</value>
</property>
</bean>
</beans>
其中,每个属性值对应一个property标签,name属性值为类中属性的名称。在bean实现类中拥有与其对应的实现方法setUsername( )。
Tips:Spring只会检查bean中是否含有setter方法,而对是否有对应的属性变量则不作具体要求,但按照约定俗成的规则我们最好为其设定相应的属性变量。
Spring中<property>标签的命名规范:
Spring的<property>标签所指定的属性名称和bean实现类的setter方法满足Sun JavaBean的属性命名规范,即XXX的属性对应setXXX( )的方法。
一般情况下,java的属性变量名都以小写字母开头,但考虑到一些特殊意义的英文缩略词,java bean也允许一些大写字母开头的变量名。但必须满足以下两点:
变量的前两个字母要么全部大写,要么全部小写;
但以一般编程习惯来说,属性名最好全部使用小写字母,方便编程和阅读。
对于属性注入方式来说,只能人为的在配置文件中提供保证,而无法在语法级别提供保证。此时就需要使用构造函数注入这种方式,以此来更好的满足要求。
2.构造函数注入
构造函数注入是除属性注入之外的另一种常用的注入方式,它可以保证一些必要的属性在bean实例化时就得到了设置,并在实例化后就可以使用。
使用构造函数注入的前提是: bean必须提供带参的构造函数。
对于构造函数的注入,配置文件可以有以下几种方式:
按类型匹配入参
按索引匹配入参
联合使用类型和索引匹配入参
通过自身类型反射匹配入参
【按类型匹配入参方式】
编写bean代码:
package com.Kevin.bean;
/**
* 编写bean测试按类型匹配入参方式
* @author Kevin
*
*/
public class Person {
private String name;
private Integer 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;
}
}
编写配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" 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"> <!-- bean definitions here -->
<!-- 配置对象 -->
<bean id="person" class="com.Kevin.bean.Person">
<constructor-arg type="String">
<value>Kevin</value>
</constructor-arg>
<constructor-arg type="Integer">
<value>20</value>
</constructor-arg>
</bean>
</beans>
Spring的配置文件采用和元素标签顺序无关的配置策略,因此可以在一定程度上保证配置信息的确定性。
那么当bean中的构造函数的 多个类型参数一样时 ,按照类型匹配入参的这种方式容易产生混淆,此时就需要使用另一种方式:按照索引匹配入参。
【按照索引匹配入参】
编写bean代码:
package com.Kevin.bean;
/**
* 编写bean测试按照索引方式入参
* @author Kevin
*
*/
public class Student {
private String name;
private String gender;
private Double score;
public Student(String name, String gender, Double score) {
super();
this.name = name;
this.gender = gender;
this.score = score;
}
}
配置文件编写如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" 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"> <!-- bean definitions here -->
<!-- 配置对象 -->
<bean id="student" class="com.Kevin.bean.Student">
<constructor-arg index="0" value="Kevin"></constructor-arg>
<constructor-arg index="1" value="Male"></constructor-arg>
<constructor-arg index="2" value="66"></constructor-arg>
</bean>
</beans>
Tips: 在属性注入时,Spring按java bean的规范确定配置 属性 和对应的 setter方法 ,并使用java反射机制调用属性的setter方法完成属性注入。但java反射机制并 不会记住构造函数的入参名 ,因此我们不能通过制定构造函数的入参名称来进行构造函数的配置,所以我们只能通过 入参的类型及索引 来间接完成构造函数的属性注入。
【联合使用类型和索引匹配入参】
在某些复杂的配置文件当中,需要使用type和index同时出马才能完成构造函数的参数注入。下面使用一个实例来演示。
编写bean:
package com.Kevin.bean;
/**
* 编写bean测试联合使用类型和索引匹配入参
* @author Kevin
*
*/
public class Teacher {
private String name;
private String address;
private double salary;
private int age;
public Teacher(String name, String address, double salary) {
super();
this.name = name;
this.address = address;
this.salary = salary;
}
public Teacher(String name, String address, int age) {
super();
this.name = name;
this.address = address;
this.age = age;
}
}
在这个类中,有两个重载的构造函数,他们都有三个参数,在这种情况下使用type和index的方法都不能完成要求,这时候就需要他们两个属性同时使用了。
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" 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"> <!-- bean definitions here -->
<!-- 配置对象 -->
<bean id="teacher" class="com.Kevin.bean.Teacher">
<constructor-arg index="0" type="String">
<value>Kevin</value>
</constructor-arg>
<constructor-arg index="1" type="String">
<value>China</value>
</constructor-arg>
<constructor-arg index="2" type="int">
<value>20</value>
</constructor-arg>
</bean>
</beans>
可以看到其实重点在于第三个入参的类型,所以我们在配置文件中指定了索引和类型,这样便可以使得Spring知道对哪个构造函数进行参数注入了。
Tips: 加入我们得配置文件中存在歧义问题,Spring容器是可以正常启动的,并不会报错,它将随机采用一个匹配的构造函数实例化bean。而随机选择的构造函数可能并不是用户所需要的,所以我们在编程时要小心避免出现这种歧义情况。
【通过自身类型反射匹配入参】
如果bean构造函数入参的类型是可辨别的,由于java反射机制可以获取构造函数入参的类型,即使构造函数的注入不提供类型和索引的信息,Spring依旧可以完成构造函数信息的注入。例如下面实例中Manager类的构造函数的入参类型就是可以辨别的。
编写Manager类:
package com.Kevin.bean;
/**
* 编写Bean测试通过自身类型反射匹配入参方式
* @author Kevin
*
*/
public class Manager {
private String name;
private Double salary;
private Person person;
public Manager(String name, Double salary, Person person) {
super();
this.name = name;
this.salary = salary;
this.person = person;
}
}
编写配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" 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"> <!-- bean definitions here -->
<!-- 配置对象 -->
<bean id="manager" class="com.Kevin.bean.Manager">
<constructor-arg>
<value>Kevin</value>
</constructor-arg>
<constructor-arg>
<ref bean="user"/>
</constructor-arg>
<constructor-arg>
<ref bean="person"/>
</constructor-arg>
</bean>
</beans>
以上几种方法都可以实现 构造函数参数的注入 ,但是为了避免问题的发生,还是建议使用 显式的index和type 来配置构造函数的入参信息。
3.工厂方法注入
工厂方法是应用中被经常使用的设计模式,也是 控制反转 和 单实例设计思想 的主要实现方法。工厂类负责创建一个或多个工厂类实例,工厂类方法一般以接口或抽象类变量的形式返回目标类实例。
工厂类对外屏蔽了目标类的实例化步骤,调用者甚至根本不用指定具体的目标类是什么。由于Spring容器以框架的方法提供工厂方法的功能,并以透明的方式开放给开发者。因此很少需要手工编写工程方法。但在一些遗留系统或第三方类库中还是会碰到工程方法,此时便可以使用Spring工厂注入的方法来进行Spring的注入。
Spring工厂注入的方法可以分为 静态 和 非静态 两种。
【非静态工厂方法】
有些工厂方法是非静态的,必须实例化工厂类之后才能调用工厂方法。下面通过一个实例来演示。
编写工厂类:
package com.Kevin.factorybean;
/**
* 编写工厂类测试非静态工厂方法注入
* @author Kevin
*
*/
public class BookFactory {
public Book buyBook(){
Book book = new Book();
book.setName("Think in Java");
return book;
}
}
配置文件编写:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" 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"> <!-- bean definitions here -->
<!-- 配置对象 -->
<bean id="bookFactory" class="com.Kevin.factorybean.BookFactory"></bean>
<bean id="book" factory-bean="bookFactory" factory-method="buyBook"></bean>
</beans>
由于bookFactory的工厂方法不是静态的,因此需要先定义一个工厂类的bean,然后通过 factory-bean 属性来引用工厂bean实例。再通过属性 factory-method 来指定对应的工厂方法。
【静态工厂方法】
很多工厂类方法都是静态的,这意味着无需创建工厂类实例的情况下就可以调用工厂类方法。因此静态工程方法比非静态工厂方法的调用更加方便简洁。下面通过一个实例来演示静态工厂方法。
编写factory类:
package com.Kevin.factorybean;
/**
* 编写工厂类测试静态工厂方法注入
* @author Kevin
*
*/
public class CarFactory {
public static Car createCar(){
Car car = new Car();
car.setBrand("Lamborghini");
return car;
}
}
编写配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" 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"> <!-- bean definitions here -->
<!-- 配置对象 -->
<bean id="car" class="com.Kevin.factorybean.Car" factory-method="createCar"></bean>
</beans>
总结
Spring提供了三种可供选择的注入方式,但在实际应用中,我们究竟该选择哪种注入方式,并没有统一的标准,如下是一些可以参考的理由:
构造函数注入理由:
构造函数保证重要属性预先设置;
无需提供每个属性的setter方法,减少类的方法个数;
可以更好地封装类变量,避免外部错误调用。
属性注入理由:
属性过多时,构造函数变的臃肿;
构造函数注入灵活性不强,有时需要为属性注入null值;
多个构造函数时,配置上产生歧义,复杂度升高;
构造函数不利于类的继承和扩展;
构造函数注入会引起循环依赖的问题。
其实Spring为我们注入参数提供了这么多方法,那么这些方法必然有他们存在的道理,每个方法在某一问题上会有独特的优势,我们只需要按照我们具体的使用需求选择适合的方法来使用就好了,但一般不太推荐工厂方法注入。
来源:http://www.cnblogs.com/Kevin-ZhangCG/p/9080183.html


猜你喜欢
- 查了下网上的一些资料,感觉比较复杂,这里,我这几使用两种很简单的办法解决了中文乱码问题。Spring版本:3.2.2.RELEASEJack
- 一、启动android默认浏览器这样子,android就可以调用起手机默认的浏览器访问。二、指定相应的浏览器访问1、指定android自带的
- 项目运行:环境配置:Jdk1.8 + Tomcat8.5 + mysql + Eclispe(IntelliJ IDEA,Eclispe,M
- 本篇博客要分享的一个UI效果——实现底部切换标签,想必大家在一些应用上面遇到过这种效果了,最典型的就是微信了,可以左右滑动切换页面,也可以点
- 效果图如下所述:布局<?xml version="1.0" encoding="utf-8"?
- 在windows环境下,我们通常在IDE如VS的工程中开发C++项目,对于生成和使用静态库(*.lib)与动态库(*.dll)可能都已经比较
- 前文传送门:NioEventLoop处理IO事件执行任务队列继续回到NioEventLoop的run()方法:protected void
- 前言此系统为博主大一上学期C语言课程设计的大作业,由于当时初步接触C语言,现在来看程序写的太烂了,简直不忍直视……但是还是想通过博客的形式记
- 枚举是 C# 中最有意思的一部分,大部分开发人员只了解其中的一小部分,甚至网上绝大多数的教程也只讲解了枚举的一部分。那么,我将通过这篇文章向
- 用过新版本android 360手机助手都人都对 360中只在桌面显示一个小小悬浮窗口羡慕不已吧? 其实实现这种功能,主要有两步: 1.判断
- 在使用Android Studio开发的时候,如遇到多个项目引用同一个library的情况时,会遇到在每个项目中都要有一套library的代
- 一、方法这里我们用两种方法来实现跑马灯效果,虽然实质上是一种实质就是:1、TextView调出跑马灯效果2、TextView获取焦点&nbs
- 前言前面我们已经分析Dubbo SPI相关的源码,看过的小伙伴相信已经知晓整个加载过程,我们也留下两个问题,今天我们先来处理下其中关于注解A
- 桶排序桶排序是计数排序的升级,计数排序可以看成每个桶只存储相同元素,而桶排序每个桶存储一定范围的元素,通过函数的某种映射关系,将待排序数组中
- @Profiles和@PropertySource根据环境切换配置文件使用@PropertySource注解加载配置文件,并制定解析配置文件
- 1、Handle,MessageQueue,Message类图Handle: 处理消息,并提供一系列函数帮忙我们创建消息和插入消息到消息队列
- 一、跨域认证的问题互联网服务离不开用户认证。一般流程是下面这样。1、用户向服务器发送用户名和密码。2、服务器验证通过后,在当前对话(sess
- 本文实例讲述了java动态绑定和静态绑定用法。分享给大家供大家参考,具体如下:背景1.当子类和父类存在同一个方法,子类重写了父类的方法,程序
- 一、简介现在的Android应用程序中,不可避免的都会使用到图片,如果每次加载图片的时候都要从网络重新拉取,这样不但很耗费用户的流量,而且图
- 前言:我们每天都在编写Java代码,编译,执行。很多人已经知道Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.cl