本小节源码:Spring-DI
1. 依赖查找
前面两个小节我们学习了如何将 Bean 对象分别以配置文件与注解的方式存入 Spring 中,然而通过 Bean 从 Spring 容器中取出的过程其实就是依赖查找,这里我简单归纳一下各种依赖查找的方式。
1.1 通过beanId查找
将ByName
类配置进xml文件以存入Spring容器:
<bean id="test1" class="com.chenshu.beans.ByName"></bean>
测试代码:
BeanFactory factory = new ClassPathXmlApplicationContext("spring-conf.xml");
ByName test1 = (ByName) factory.getBean("test1");
System.out.println(test1);
成功打印出全限定类名:
com.chenshu.beans.ByName@548e7350
1.2 通过beanClass查找
这次我们直接通过class
存入Spring容器,不再声明Bean的id
:
<bean class="com.chenshu.beans.ByType"></bean>
由于我们通过传类型来查找 Bean 因此不需要强转:
ByType byType = factory.getBean(ByType.class);
System.out.println(byType);
成功打印出全限定类名:
com.chenshu.beans.ByType@754ba872
1.3 通过interface查找
创建下面的DemoDao
接口和DemoDaoImpl
实现类:
将DemoDaoImpl
实现类配置入Spring的xml文件
<bean class="com.chenshu.dao.impl.DemoDaoImpl"></bean>
测试代码:
DemoDao demoDao = factory.getBean(DemoDao.class);
System.out.println(demoDao);
通过传接口的class对象,发现可以成功取出它的实例(前提是该接口只有一个对应的bean实例):
com.chenshu.dao.impl.DemoDaoImpl@6eebc39e
2. 依赖注入
前面创建的 Bean 都是没有预设属性的,如果需要预设的属性就得运用到 IOC 的实现方式——“依赖注入”了。这里我将使用xml文件配置以及注解配置两种方式分别演示一遍如何依赖注入,xml文件配置的方式作为简单的了解即可,注解的方式需要掌握。
2.1 xml文件配置
在xml文件配置的依赖注入方式有以下两种:
- Setter注入
- Constructor注入
2.1.1 Setter注入
Setter注入,简单来说就是Spring通过Setter方法将属性值注入,因此Bean对象必须实现Setter()
方法:
public class User {
private int age;
private String name;
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + ''' +
'}';
}
}
简单类型
在bean的配置中新增property
标签,name
的值对应的就是需要引入依赖的字段名,value
的值对应的就是字段值:
<bean id="user" class="com.chenshu.beans.User">
<property name="age" value="19"></property>
<property name="name" value="zhangsan"></property>
</bean>
测试代码:
User user = (User) factory.getBean("user");
System.out.println(user);
成功打印出刚刚我们所配置的字段值:
User{age=19, name='zhangsan'}
引用类型
在Spring框架中,如果一个Bean需要依赖另一个对象(即引用另一个对象),那么被引用的对象通常也应该是一个Spring容器管理的Bean。这是因为Spring的依赖注入机制是基于容器的,它负责创建、配置和管理Bean的生命周期。
新建一个service
包,并在包中创建一个UserService
的类:
public class UserService {
private DemoDao demoDao;
public void setDemoDao(DemoDao demoDao) {
this.demoDao = demoDao;
}
public DemoDao getDemoDao() {
return demoDao;
}
}
并且该类想要依赖下面这个前面已经配置过的Bean:
<bean class="com.chenshu.dao.impl.DemoDaoImpl"></bean>
由于前面给大家演示的时候并没有给这个 Bean 取一个name
,因此我们这里修改一下配置:
<bean id="demoDaoImpl" class="com.chenshu.dao.impl.DemoDaoImpl"></bean>
将UserService添加至xml文件,并添加property
标签,其中name
的值就是UserService需要依赖注入的字段名,ref
的值对应的是需要引入的依赖的beanId
,这就是为什么要给需要依赖的Bean取name
的原因
<bean class="com.chenshu.service.UserService">
<property name="demoDao" ref="demoDaoImpl"></property>
</bean>
测试代码:
UserService userService = factory.getBean(UserService.class);
System.out.println(userService.getDemoDao());
成功打印出我们所配置的值的全限定类名:
com.chenshu.dao.impl.DemoDaoImpl@67784306
2.1.2 Constructor注入
既然是构造器注入,那自然免不了编写它的构造器,你需要注入几个字段就编写其对应的构造器就好,我这里选择把两个字段都注入:
public User(int age, String name) {
this.age = age;
this.name = name;
}
简单类型
除了标签名,其他都和Setter大体一致,这里直接给出代码:
<bean id="user" class="com.chenshu.beans.User">
<constructor-arg name="age" value="20"></constructor-arg>
<constructor-arg name="name" value="lisi"></constructor-arg>
</bean>
运行结果:
User{age=20, name='lisi'}
引用类型
编写UserService
的构造器:
public UserService(DemoDao demoDao) {
this.demoDao = demoDao;
}
配置文件:
<bean class="com.chenshu.service.UserService">
<constructor-arg name="demoDao" ref="demoDaoImpl"></constructor-arg>
</bean>
运行结果:
com.chenshu.dao.impl.DemoDaoImpl@65e2dbf3
2.2 注解配置
注解配置更加简单、方便。人总是趋利的,在企业开发的时候绝大多数还是使用注解的方式进行配置,注解配置实现依赖注入的方式有以下三种:
- Field注入
- Setter注入
- Construcor注入
依赖注入需要使用@Autowired
或@Resource
注解,这里我先演示前者,下一节会讲它们的区别。
这里我将使用纯注解的方式注册和动态注入Bean。
前置操作:
- 在xml文件中添加扫描路径
<content:component-scan base-package="com.chenshu"></content:component-scan>
- 通过
@Controller、@Service、@Repository
这三个注解分别将UserController、UserService、UserDaoImpl
存入 IOC 容器。
2.2.1 Field(属性)注入
所谓field注入就是属性注入,这种方式很简单,直接在需要依赖注入的属性上加上@Autowired
注解就好了,它也是日常开发中最常使用的一种注入方式
UserController
需要依赖UserSerice
:
@Controller
public class UserController {
@Autowired
private UserService userService;
public void doController() {
userService.doService();
System.out.println("Do User Controller.");
}
}
UserService
需要依赖UserDaoImpl
,由于UserDao
是UserDaoImpl
的接口,我们直接使用UserDao
来接收:
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void doService() {
userDao.selectAll();
System.out.println("Do User Service");
}
}
UserDaoImpl
没有需要依赖的对象:
@Repository
public class UserDaoImpl implements DemoDao {
@Override
public void selectAll() {
System.out.println("select * from user");
}
}
通过依赖查找找到UserController
,并调用doController()
:
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("spring-conf.xml");
UserController userController = factory.getBean("userController", UserController.class);
userController.doController();
}
依赖成功注入,并分别成功调用了三个 Bean 的方法:
select * from user
Do User Service
Do User Controller.
看到这里是否觉得很眼熟,我们可以回顾一下前面所学章节:1.Spring的核心思想 —— IOC和DI,再次理解一下何为IOC和DI
Field注入优缺点总结
优点:写法简单
缺点:
- 不能注入
final
修饰的属性:final
修饰的变量只能在使用时直接赋值或者在构造方法赋值,而属性注入的方式,是在构造出对象之后Spring帮你赋值,不符合jdk的规范; - 通用性问题:只能适用于IOC容器;
- 更容易违背单一设计原则:单一设计原则指的是一个类应只负责一项职责,由于这种写法过于简单,违背单一设计原则的概率就提升了;
2.2.2 Setter注入
所谓Setter注入,就是Spring通过setter方法来注入依赖的Bean,这种方式需要构造一个Setter方法,再在Setter上面加上一个@Autowired
注解:
@Controller
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void doController() {
userService.doService();
System.out.println("Do User Controller.");
}
}
依赖成功注入,并分别成功调用了三个 Bean 的方法:
select * from user
Do User Service
Do User Controller.
Setter注入优缺点总结
优点: 符合单一设计原则
缺点:
- 不能注入
final
修饰的属性,原因同属性注入; - 【重要】注入对象可能被改变:由于setter方法可能会被多次调用,所以就有被修改的风险;
2.2.3 Constructor注入
Constructor注入(Spring官方推荐的依赖注入方式),也就是Spring通过Bean提供的构造器,将依赖注入Bean,跟Setter大同小异,只要在构造方法中将需要添加的依赖作为构造器的参数,再在构造器上添加一个@Autowired
注解就可以了:
@Controller
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void doController() {
userService.doService();
System.out.println("Do User Controller.");
}
}
依赖成功注入,并分别成功调用了三个 Bean 的方法:
select * from user
Do User Service
Do User Controller.
与Setter的小区别: 当这个Bean的构造方法只有一个时,可以省略@Autowired参数
Constructor注入优点总结
- 可以注入
final
修饰的对象; - 注入对象不会被修改:不像setter注入那样,构造方法在对象创建时只会执行一次,因此不存在依赖Bean被修改的情况;
- 完全初始化:因为依赖在类的构造方法中执行的,而构造方法是在对象创建之初执行的方法;
- 通用性更好,因为构造方法是 JDK 支持,所以更换任何框架,它都是适用的。