什么是依赖注入与控制反转
依赖注入:当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。然而采用依赖注入的方式,创建被调用者的工作不再由调用者来完成,创建被调用者的实例的工作由 IOC
容器来完成。然后注入调用者,称为依赖注入
控制反转:当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。然而采用控制反转的方式,创建被调用者的工作不再由调用者来完成,创建被调用者的实例的工作由 IOC
容器来完成。也就是把对象的创建,初始化,销毁的工作交给 spring ioc
容器来做。由 spring ioc
容器来管理对象的生命周期
依赖注入与自动装配的关系
依赖注入的本质就是装配,装配是依赖注入的具体行为
在传统的使用 xml
文件装配 bean
是一件很繁琐的事情,而且还需要找到对应类型的 bean
才能装配,一旦 bean
很多,就不好维护了。为了解决这种问题,spring
使用注解来进行自动装配。自动装配就是开发人员不必知道具体要装配哪个 bean
的引用,这个识别的工作会由 spring
来完成。与自动装配配合的还有“自动检测”,这个动作会自动识别哪些类需要被配置成 bean
,进而来进行装配
因此也可以这样理解:自动装配是为了将依赖注入自动化的一个简化配置的操作
例如不进行自动装配配置如下
<bean id="userDefault" class="cn.zhuoqianmingyue.ioc.di.autowire.UserDefault">
<property name="country" ref="country"></property>
</bean>
<bean id="country" class="cn.zhuoqianmingyue.ioc.di.autowire.Country"></bean>
通过属性名称自动装配配置如下
<bean id="userByName" class="cn.zhuoqianmingyue.ioc.di.autowire.UserByName" autowire="byName"></bean>
<bean id="country" class="cn.zhuoqianmingyue.ioc.di.autowire.Country"></bean>
而 spring 在 3.X
之后,已推荐使用 JavaConfig
的形式来配置搭建项目,在此 spring
会使用注解 @Autowire
来自动装配。这也就是:自动装配是为了将依赖注入自动化的一个简化配置的操作
spring
基于 xml
文件形式的依赖注入的方式
- 构造函数注入(常用)
setter
方法注入(常用)p
命名空间注入(其实和setter
注入原理一样,只是写法不一样)spel
表达式注入- 复杂类型的注入(
List,Map,Set
等集合的注入):<List>
: 该标签用来装配可重复的list
值;<Set>
:该标签用来装配没有重复的set
值;<Map>
:该标签可用来注入键和值可以为任何类型的键值对;<props>
:该标签支持注入键和值都是字符串类型的键值对
构造函数注入(实体类不需要 setter
方法)
实体类
public class User {
private int id;
private String username;
private String password;
public User() {
System.out.println("User 类的无参构造函数被调用了");
}
public User(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
System.out.println("User 类的有参构造函数被调用了");
}
@Override
public String toString() {
return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +'}';
}
}
默认的构造函数是不带参数的构造函数。 Java
语言规定,如果类中没有定义任何构造函数,JVM
会自动为其生成一个默认的构造函数;反之,如果类中显式的定义了构造函数,JVM
则不会为其生成默认的构造函数。如果类中显式的声明了其他构造函数,如果未提供一个默认的构造函数,则属性注入时,会抛出异常
配置文件(通过 constructor-arg
标签 value
属性)
<?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="user" class="com.atguigu.pojo.User">
<constructor-arg value="1001"></constructor-arg>
<constructor-arg value="张三"></constructor-arg>
<constructor-arg value="123456"></constructor-arg>
</bean>
</beans>
需要注意的是这里的标签的顺序要和构造方法中的参数顺序一致
测试类
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user);
}
}
测试结果
疑问(重点)
从测试结果来看,并没有打印
public User() {
System.out.println("User 类的无参构造函数被调用了");
}
也就是说在 bean
实例化时,它的无参构造器没有被调用。为什么呢?
答案且看: spring源码之实例化 createBeanInstance 方法解读
配置文件 constructor-arg
标签属性另外几种写法
指定角标方式进行注入
<bean id="userIndex" class="cn.zhuoqianmingyue.ioc.di.constructor.User">
<constructor-arg index="1" value="30"/>
<constructor-arg index="0" value="zhouqianmingyue"/>
</bean>
指定类型方式进行注入
<bean id="userType" class="cn.zhuoqianmingyue.ioc.di.constructor.User">
<constructor-arg type="java.lang.Integer" value="30"/>
<constructor-arg type="java.lang.String" value="zhouqianmingyue"/>
</bean>
指定引用类型进行注入
<bean id="userHasClass" class="cn.zhuoqianmingyue.ioc.di.constructor.User">
<constructor-arg type="java.lang.Integer" value="30"/>
<constructor-arg type="java.lang.String" value="zhouqianmingyue"/>
<constructor-arg type="cn.zhuoqianmingyue.ioc.di.constructor.Country" ref="country"/>
</bean>
<bean id="country" class="cn.zhuoqianmingyue.ioc.di.constructor.Country">
<property name="name" value="CHINA"/>
</bean>
setter
方法注入(实体类必须要 setter
方法)
实体
public class User {
private int id;
private String username;
private String password;
public User() {
System.out.println("User 类的无参构造函数被调用了");
}
public void setId(int id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +'}';
}
}
配置文件
<?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">
<!-- 第二种方式:set 方法注入 -->
<bean id="user1" class="com.atguigu.pojo.User">
<property name="id" value="1002"></property>
<property name="password" value="123123"></property>
<property name="username" value="李四"></property>
</bean>
<beans>
测试类
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user1");
System.out.println(user);
}
}
测试结果
疑问(重点)
从 User
实体类中只有一个无参构造器,在 bean
实例化时,无参构造器被调用了
结合第一个疑问,可以得出以下结论
spring
在实例化bean
时,如果同时存在无参与有参构造器,那么有参构造器会被调用,无参构造器不会调用- 在使用构造器进行依赖注入时,必须要有参构造器,无参构造器可有可无;否则,在属性注入时,会抛异常
- 在使用
setter
方法进行依赖注入时,必须要有无参构造器,有参构造器可有可无;否则,在属性注入时,会抛异常;如果什么构造器都没有,bean
是无法完成实例化的,进而无法完成依赖注入
为什么会得出这样的结论,请查看: spring源码之实例化 createBeanInstance 方法解读
P
命名空间注入
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 第三种注入方式:p 命名空间注入 -->
<bean id="user2" class="com.atguigu.pojo.User"
p:id="1003" p:username="王五" p:password="@123">
</bean>
</beans>
spel
表达式注入
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 第四种注入方式:spel 表达式注入 -->
<bean id="user3" class="com.atguigu.pojo.User">
<property name="id" value="#{1004}"></property>
<property name="username" value="#{'李欢'}"></property>
<property name="password" value="#{'jffdkj'}"></property>
</bean>
</beans>
复杂类型注入
list
类型
<bean id="user" class="cn.zhuoqianmingyue.ioc.di.User">
<property name="address">
<list>
<value>北京</value>
<value>河北</value>
</list>
</property>
</bean>
set
类型
<bean id="user" class="cn.zhuoqianmingyue.ioc.di.User">
<property name="address2">
<set>
<value>北京</value>
<value>河北</value>
</set>
</property>
</bean>
map
类型
<bean id="user" class="cn.zhuoqianmingyue.ioc.di.User">
<property name="addressMap">
<map>
<entry key="BEIJING" value="北京"/>
<entry key="HEBEI" value="河北"/>
</map>
</property>
</bean>
静态工厂方法注入
public class UserFactory {
// 静态方法
public static User getUser1() {
return new User();
}
}
xml
配置文件
<!-- 使用静态工厂实例化 user -->
<bean id="user1" class="ioc.service.UserFactory" factory-method="getUser1"></bean>
实例工厂方法注入
public class UserFactory {
// 普通方法
public User getUser2() {
return new User();
}
}
xml
配置文件
<!-- 使用实例工厂实例化 user -->
<bean id="userFactory" class="ioc.service.UserFactory"></bean>
<bean id="user2" factory-bean="userFactory" factory-method="getUser2"></bean>
使用 autowire
标签注入
public class Teacher {
private Student student;
public Teacher() {
System.out.println("Teacher 的无参构造器被调用了");
}
public Teacher(Student student) {
System.out.println("Teacher 的有参构造器被调用了");
this.student = student;
}
public void echo() {
System.out.println("I'm a student : " + student);
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
@Override
public String toString() {
return "Teacher{" +
"student=" + student +
'}';
}
}
public class Student {
private String name;
public Student() {
System.out.println("Student 的无参构造器被调用了");
}
public Student(String name) {
System.out.println("Student 的有参构造器被调用了");
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
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">
<bean id="student" class="com.atguigu.pojo.Student">
<property name="name" value="小吉"></property>
</bean>
<bean id="teacher" class="com.atguigu.pojo.Teacher" autowire="byType"></bean>
</beans>
spring 基于注解形式的依赖注入方式
@Bean
注解
@Configuration
public class RabbitmqConfig {
@Bean
public Queue queue() {
return new Queue(Constant.ES_QUEUE);
}
@Bean
public DirectExchange directExchange() {
return new DirectExchange(Constant.ES_EXCHAGE);
}
@Bean
public Binding binding(Queue queue, DirectExchange directExchange) {
return BindingBuilder.bind(queue).to(directExchange).with(Constant.ES_BIND_KEY);
}
}
@Autowired
注解
用在构造器上
@Controller
public class SoakController extends BaseController {
private SoakService soakService;
@Autowired
public SoakController(SoakService soakService) {
this.soakService = soakService;
}
}
用在方法上
@Controller
public class SoakController extends BaseController {
private SoakService soakService;
@Autowired
public void setSoakService(SoakService soakService) {
this.soakService = soakService;
}
}
用在参数上
@Controller
public class SoakController extends BaseController {
private SoakService soakService;
public SoakController(@Autowired SoakService soakService) {
this.soakService = soakService;
}
}
用在字段上(较为常用)
@Controller
public class SoakController extends BaseController {
@Autowired
private SoakService soakService;
}