spring控制反转
2022-07-23 # 学习笔记 # Spring # Java

理解IoC

bean 是一个被实例化,组装,并通过 Spring IoC 容器所管理的对象。

Spring通过IoC的方式将用户管理bean转变为框架来管理bean

不仅接管Bean的生成。同时接管其整个生命周期

管理的bean存放在IoC Container中,应用程序代码从Ioc Container中获取依赖的bean,注入到应用程序中,这个过程叫 依赖注入(DI)

IoC是设计思想,DI是实现方式

在传统的Java SE程序设计中,对象的创建和它所需要依赖对象的创建和注入都是由我们来完成的,例如

1
2
3
UserServiceImpl userService = new UserServiceImpl();//service层对象的创建
UserDaoImpl userDao = new UserDaoImpl();//其所需的依赖对象的创建
userService.setUserDao(userDao);//其所需的依赖对象的注入

当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,而是直接从IoC容器中获取

1
UserServiceImpl service = (UserServiceImpl)context.getBean("userService");

IoC配置

xml配置

此种配置方式,将bean的创建和依赖注入方式写入了xml文件。然后将xml配置文件传给spring的特定类。从而根据这些配置创建出bean并且注入相关的依赖。

xml文件格式

1
2
3
4
5
6
7
8
9
10
<?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="userDaoImplBean" class="com.shijivk.dao.impl.UserDaoImpl"/>
<bean id="userServiceImplBean" class="com.shijivk.service.impl.UserServiceImpl">
<property name="userDao" ref="userDaoImplBean"/>
</bean>
</beans>

在xml文件的beans标签中完成bean的创建和依赖的配置。

bean的创建

1
2
3
4
<bean id="userDaoImplBean" class="com.shijivk.dao.impl.UserDaoImpl"/>
<bean id="userServiceImplBean" class="com.shijivk.service.impl.UserServiceImpl">
<property name="userDao" ref="userDaoImplBean"/>
</bean>

对于没有依赖类的bean,使用自结束标签创建,而有依赖类的则需使用一对标签来创建。

id属性表示要创建的bean的名称,id是唯一的,用于标识容器中的一个bean。

bean默认采用单例的方式,也就是对于多次getBean只会在容器中创建一个对象。每次返回的对象相同

可以看到上图中两次的地址相同。将bean标签中的scope属性设置为prototype可以切换为多例。

name属性可以同时创建多个同类型的对象,例如

1
<bean id = "bean1" name="bean2 bean3" class="com.shijivk.dao.impl.UserDaoImpl"

就会同时创建三个名字分别为bean1,bean2,bean3的bean

class属性表示所要创建的bean对应的实现类

依赖注入

依赖注入即让spring给类中的基本类型或是一些类的指针赋值,我们通常通过setter或者是构造函数向一个类中传入数据。因此便有了setter注入和构造器注入。

setter注入

主要做两件事:

告知spring要将哪个bean赋值给类里面的哪个对象

提供setter方法

1
2
3
<bean id="userServiceImplBean" class="com.shijivk.service.impl.UserServiceImpl">
<property name="userDao" ref="userDaoImplBean"/>
</bean>

通过property标签,告诉spring将ref属性指定的Bean赋值给类里面名叫userDao的变量。

1
2
3
<bean id="userServiceImplBean" class="com.shijivk.service.impl.UserServiceImpl">
<property name="userName" value="ZhangSan"/>
</bean>

对于基本类型的注入,则使用value属性

构造器注入

1
2
3
4
<bean id="userServiceImplBean" class="com.shijivk.service.impl.UserServiceImpl">
<constructor-arg name="userDao" ref="userDaoBean"/>
<constructor-arg name="adminDao" ref="adminDaoBean"/>
</bean>
1
2
3
4
5
6
public class UserServiceImpl {
public UserServiceImpl(UserDao userDao,AdminDao adminDao){
this.userDao = userDao;
this.adminDao = adminDao;
}
}

将ref中的bean,赋值给name对应的形参(注意对应的是形参名而不是类内的变量名)。同时通过调用构造函数

这一构造函数可以为public也可以为private。spring采用反射的方式。即使为private也可正确调用。

1
<constructor-arg name="userName" value="ZhangSan"/>

对于基本类型可以采用如上方式。

但这样的方式还是会有一定的耦合。因为name对应着的为类内的形参,二者的名字要保持一致。因此可以采用如下的方式来降低耦合

采用类型来匹配(要求各形参类型不同)

1
2
<constructor-arg type="int" value="10"/>
<constructor-arg type="java.lang.String" value="ZhangSan"/>

采用位置来匹配

1
2
<constructor-arg index="0" value="10"/>
<constructor-arg index="1" value="ZhangSan"/>

自动装配

在满足一定的条件时可以让spring自动的完成依赖的注入

主要有两种方式:

按类型(byType)

1
2
<bean id="userDaoImplBean" class="com.shijivk.dao.impl.UserDaoImpl"/>
<bean id="userServiceImplBean" class="com.shijivk.service.impl.UserServiceImpl" autowire="byType"/>

当依赖的类只有一个bean的时候。可以让spring自动的完成注入。但如果有多个类型相同的bean时则会报错

按名称(byName)

1
2
<bean id="userDaoImplBean" class="com.shijivk.dao.impl.UserDaoImpl"/>
<bean id="userServiceImplBean" class="com.shijivk.service.impl.UserServiceImpl" autowire="byName"/>

此时spring会自动寻找UserServiceImpl类中的名为userDaoImplBean的变量。并自动的将这个bean赋值给它。

即id的值应该与类中对应类型的变量的名字相同。

补充:Spring默认查找的不是原变量,而是对应Setter的名字。例如userDaoImplBean,如果我们根据规范来命名setter,应该为setUserDaoImplBean,Spring会截取后半部分,然后判断是否和name中的值相同。如果相同就使用这个setter来注入。

byName方式一般不推荐使用。因为会提高耦合度。

注解配置

使用注解的方式配置bean,并使用配置类来替代xml文件

配置类

我们希望改变使用xml配置的方式,用java配置类来代替xml

首先创建一个配置类。在其上方加@Configuration注解即可将其指定为配置类

1
2
3
@Configuration
public class SpringConfig {
}

bean的创建

使用注解定义bean有两种方式,分别针对不同的使用场景

方案1:针对第三方类(不修改使用类的源代码)

对于第三方类,我们无法修改其源代码,例如在其中添加@Component注解,所以我们直接在配置类中定义bean

1
2
3
4
5
6
7
@Configuration
public class SpringConfig {
@Bean("userDao")
public UserDao userDao(){
return new UserDaoImpl();
}
}

在配置类中。我们需要提供一个方法。返回值即为bean所对应的对象。需要在方法上方加上@Bean注解,其中参数可以忽略。若忽略,bean的id即为方法名(首字母小写),若未忽略,则bean的id即为注解的参数。

依赖注入:

对于此种配置方式,只需要在形参中写上需要的类型,Spring就会默认采用byType的方式来为该形参赋值,例如

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class SpringConfig {
@Bean
public UserDao userDao(){
return new UserDaoImpl();
}

@Bean("userService")
public UserService userService(UserDao userDao){
return new UserServiceImpl(userDao);
}
}

Spring在查找到UserDao这个类型的bean之后,将其赋值给我们下面userService函数的形参,然后我们再手动的将其注入到bean中。

注意:Spring在此处仅仅是找到对应类型的bean,然后赋值给形参。其并不会完成依赖的注入。剩余对bean的赋值操作需要我们手动来完成。

方案2:在可以修改引用类的源代码时

在需要创建对应bean的类上方加上@Component注解,例如

1
2
3
4
5
6
7
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("getUser");
}
}

这样便定义好了一个id为userDao的bean,相当于如下配置

1
<bean id="userDaoImplBean" class="com.shijivk.dao.impl.UserDaoImpl"/>

此外@Component还有三个衍生注解

@Controller:用于表现层bean的定义

@Service:用于业务层bean的定义

@Repository:用于数据层bean的定义

这三个衍生注解的效果与@Component相同,只是更便于看出所定义的bean属于哪一个层。

对于此种方案,在定义好了bean之后,我们还需要让Spring来查找到我们到底定义了哪些bean。需要告知Spring扫描的位置。

若采用xml的方式。我们可以在beans标签中填上如下标签(此时不需要配置类,因为配置类是代替xml的)

1
<context:component-scan base-package="com.shijivk"/>

这时Spring便会扫描com.shijivk下的所有类。找出有@Component及其衍生注解的类。并创建bean

同时我们还需要一个注解来代替我们上面的标签。让Spring去扫描。因此加上@ComponentScan注解,如下:

1
@ComponentScan("com.shijivk")

但如果相加多个扫描路径。用数组的格式,例如

1
@ComponentScan({"com.shijivk.service","com.shijivk.dao"})

这样,便完成了采用注解定义和创建bean

依赖注入

在纯注解的开发模式下,采用自动装配的方式。在需要被依赖注入的类中加入@Autowired注解,Spring会自动采取byType的匹配方式

1
2
3
4
5
6
7
8
9
10
@Component("userService")
public class UserServiceImpl implements UserService {
@Autowired
public UserDao userDao;
@Override
public void getAll() {
userDao.getUser();
System.out.println("serve user");
}
}

而如果同一个类型有多个bean,则可以在@Autowired注解下方加上@Qualifier注解。指定对应的bean

对于基本类型的注入。使用@value注解

1
2
3
4
5
@Component("userService")
public class UserServiceImpl implements UserService {
@Value("ZhangSan")
public String name;
}

这一注解内的内容,还可以移入一个properties文件中,以减少代码中的信息

在java配置类的上方加上@PropertySource注解,属性为配置文件类路径。例如config.properties,多文件要使用数组的形式。

在配置文件中写上对应的值,例如name=ZhangSan

然后将原value注解的内容改为

1
@Value("${name}")

使用容器

创建容器

通过xml文件配置的容器创建方式如下

1
2
3
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\Projects\\Spring\\src\\main\\resources\\applicationContext.xml")

ClassPathXmlApplicationContext内的路径为类路径

FileSystemXmlApplicationContext内的路径为文件系统路径

通过注解配置的容器创建方式如下

1
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

AnnotationConfigApplicationContext内的为java配置类

获取bean

1
2
BookDao bookDao = (BookDao)ctx.getBean("bookDao");
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);