本章介绍了控制反转(IoC)原理的Spring框架实现。IoC也被称为依赖注入(DI)。在此过程中,对象仅通过构造函数参数、工厂方法的参数或在对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义它们的依赖关系(即它们工作的其他对象)。然后容器在创建bean时注入这些依赖项。这个过程从根本上说是bean本身的逆过程(因此得名“控制反转”)
Spring使用控制反转容器IOC实现依赖注入,相比于传统依赖注入很大程度上解耦合程序。
Spring 依赖注入的方法大致有以下几种:
一.基于xml的spring配置: setter-based 依赖注入,constructor-based依赖注入,静态工厂方法注入(以后补充)
1.引入maven依赖
2.编写需要管理的类
3.编写核心配置文件
bean元素声明对象实例
property指定要赋值的属性名
value赋值的内容
二.基于注解:扫包依赖注入
参考:
Core Technologieshttps://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans
有关spring xml配置请看这篇文章
目录
场景说明:
假设现有三个java bean,A,B,C。 A依赖于一个B对象,一个由C对象组成的list对象,使用spring IOC容器完成依赖注入。
准备工作:
Maven依赖
<dependencies>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--快速java bean-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
id属性是标识各个bean定义的字符串。
class属性定义bean的类型,并使用完全限定的类名。
1.setter-based 依赖注入(设值依赖注入)
基于setter的DI是由容器在调用无参数构造函数或无参数静态工厂方法来实例化bean之后调用bean上的setter方法来完成的。
A,B,C的java bean如下:
package com.example.demo;
import java.util.List;
public class A {
String Aname;
B b;
List<C> cs;
@Override
public String toString() {
return "A{" +
"Aname='" + Aname + '\'' +
", b=" + b +
", cs=" + cs +
'}';
}
public String getAname() {
return Aname;
}
public void setAname(String aname) {
Aname = aname;
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
public List<C> getCs() {
return cs;
}
public void setCs(List<C> cs) {
this.cs = cs;
}
public A(String aname, B b, List<C> cs) {
Aname = aname;
this.b = b;
this.cs = cs;
}
public A() {
}
}
package com.example.demo;
public class B {
String Bname;
public B(String bname) {
Bname = bname;
}
public B() {
}
@Override
public String toString() {
return "B{" +
"Bname='" + Bname + '\'' +
'}';
}
public String getBname() {
return Bname;
}
public void setBname(String bname) {
Bname = bname;
}
}
package com.example.demo;
public class C {
String Cname;
public C() {
}
public C(String cname) {
Cname = cname;
}
@Override
public String toString() {
return "C{" +
"Cname='" + Cname + '\'' +
'}';
}
public String getCname() {
return Cname;
}
public void setCname(String cname) {
Cname = cname;
}
}
spring.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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1.配置实体类 -->
<bean id="A" class="com.example.demo.A">
<property name="Aname" value="Sb"></property>
<property name="b" ref="B"></property>
<property name="cs" >
<list>
<ref bean="C" ></ref>
<ref bean="C" ></ref>
</list>
</property>
</bean>
<bean id="B" class="com.example.demo.B">
<property name="Bname" value="Jack"></property>
</bean>
<bean id="C" class="com.example.demo.C">
<property name="Cname" value="John"></property>
</bean>
</beans>
很明显这里是通过设值的方式去实现依赖注入的初始化,要么用的name-value赋值,要么用的是name-ref赋值,要么用的是name-list赋值。
单元测试
@Test
public void junitTest1() {
//指定spring配置文件的位置和名称
String resource = "spring.xml";
//创建spring容器对象
ApplicationContext container = new ClassPathXmlApplicationContext(resource);
//通过id从spring容器中获取对象
A a = (A) container.getBean(A.class);
System.out.println(a);
}
结果
2.constructor-based依赖注入
基于构造函数的DI是由容器调用带有许多参数的构造函数来完成的,每个参数代表一个依赖项。调用带有特定参数的静态工厂方法来构造bean几乎是等价的,本文将以类似的方式处理构造函数和静态工厂方法的参数。
构造函数参数解析匹配通过使用参数的类型发生。如果bean定义的构造函数参数中不存在潜在的歧义,那么在bean定义中定义构造函数参数的顺序就是在实例化bean时将这些参数提供给适当的构造函数的顺序。但是如果你定义了name属性那么name属性的值一定要与构造函数的参数列表对应。
这里值得注意的是java bean的构造函数要实现全
这里对之前的A,B,C 3个java bean,单元测试代码都不做修改
spring.xml 配置实体类
<bean id="A" class="com.example.demo.A">
<constructor-arg name="aname" value="Sb"></constructor-arg>
<constructor-arg name="b" ref="B"></constructor-arg>
<constructor-arg name="cs" >
<list>
<ref bean="C" ></ref>
<ref bean="C" ></ref>
</list>
</constructor-arg>
</bean>
<bean id="B" class="com.example.demo.B">
<constructor-arg name="bname" value="Jack"></constructor-arg >
</bean>
<bean id="C" class="com.example.demo.C">
<constructor-arg name="cname" value="John"></constructor-arg>
</bean>
结果
关于基于构造函数依赖补充:
你可以使用index属性来显式指定构造函数参数的索引,也可以使用type属性显式指定构造函数参数的类型,容器可以使用简单类型匹配。这里可以去参考官方文档。
3.扫包依赖注入(个人比较偏好的一种方式)
XML设置的另一种选择是基于注释的配置,它依赖字节码元数据来连接组件,而不是尖括号声明。开发人员不使用XML来描述bean连接,而是通过使用相关类、方法或字段声明上的注释,将配置转移到组件类本身。
如果使用注解的方式进行依赖注入则通常需要使用如下注解:
@Component
标注一个类为Spring容器的Bean,(把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>)
value 指定java bean的 bean id。
@Qualifier
使用 @Autowired
注解是 Spring 依赖注入的绝好方法。但是有些场景下仅仅靠这个注解不足以让Spring知道到底要注入哪个 bean。默认情况下,@Autowired
按类型装配 Spring Bean。如果容器中有多个相同类型的 bean,则框架将抛出 NoUniqueBeanDefinitionException
, 以提示有多个满足条件的 bean 进行自动装配。(其实就是解决接口有多个实现类,框架不知道如何选择实现类的情况)
value 指定 注入的java bean 的 bean id。
Spring 注解 @Qualifier 详细解析 - 知乎
@Autowired
自动注入依赖类
自动匹配 属性名 类型 id
required=false 没找到bean返回null
required=true (默认值)没找到bean报异常
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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example"/>
</beans>
<context:component-scan>的使用隐式启用了<context:annotation-config>的功能。当使用<context:component-scan>时,通常不需要包含<context:annotation-config>元素。同时base-package应改为需要扫描的java bean所在的全限定包名。
实例中除了单元测试代码不需要更改,其他都需要改
spring.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example.demo"/>
</beans>
A,B,C的java bean如下:
值得注意的是,以为是通过扫包依赖注入,默认是优先调取对应的 java bean的无参构造函数,所以这里我修改了无参构造函数,使得依赖注入时有默认值。
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class A {
String Aname;
@Autowired
B b;
@Autowired
List<C> cs;
@Override
public String toString() {
return "A{" +
"Aname='" + Aname + '\'' +
", b=" + b +
", cs=" + cs +
'}';
}
public String getAname() {
return Aname;
}
public void setAname(String aname) {
Aname = aname;
}
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
public List<C> getCs() {
return cs;
}
public void setCs(List<C> cs) {
this.cs = cs;
}
public A(String aname, B b, List<C> cs) {
Aname = aname;
this.b = b;
this.cs = cs;
}
public A() {
this.Aname="Sb";
this.b=new B();
this.cs=new ArrayList<C>();
this.cs.add(new C());
}
}
package com.example.demo;
import org.springframework.stereotype.Component;
@Component
public class B {
String Bname;
public B(String bname) {
Bname = bname;
}
public B() {
this.Bname="Jack";
}
@Override
public String toString() {
return "B{" +
"Bname='" + Bname + '\'' +
'}';
}
public String getBname() {
return Bname;
}
public void setBname(String bname) {
Bname = bname;
}
}
package com.example.demo;
import org.springframework.stereotype.Component;
@Component
public class C {
String Cname;
public C() {
this.Cname="John";
}
public C(String cname) {
Cname = cname;
}
@Override
public String toString() {
return "C{" +
"Cname='" + Cname + '\'' +
'}';
}
public String getCname() {
return Cname;
}
public void setCname(String cname) {
Cname = cname;
}
}
结果