依赖注入与对象装配
依赖注入(Dependency Injection) 是Spring框架的一大特色。咱们可以把它想象成是一种“自动传递东西”的方式,就像你坐电梯不需要自己操作按钮,电梯会帮你到达目的地一样。在程序中,当一个类需要用到另一个类的功能时,不需要自己去创建,而是由Spring自动帮你注入所需的对象。这样做有点像你在做披萨,需要的配料自己不用操心,厨师会给你加好。
对象装配(Object Assembly) 则是Spring中的一个步骤,就像你把积木拼在一起一样。它是通过配置文件或者注解来告诉Spring如何把不同的类组合在一起,形成一个完整的程序。就像你组装一辆汽车,需要把引擎、轮子、座位等组件组合在一起,Spring帮你把各个类组装成一个强大的系统。
首先,我们需要创建一些类,就像你在搭积木,每个类都有自己的功能。然后,在Spring的配置中,你会告诉Spring这些类之间的关系,以及谁需要谁的功能。Spring会在需要的时候,自动把这些类创建并组装起来,就像你玩乐高积木,把各种小块搭建成一个酷炫的模型。
举个例子吧!假设你要开发一个图书管理系统。你有一个 Book
类表示图书,还有一个 Library
类表示图书馆。在Spring中,你可以通过依赖注入,让图书馆知道它需要用到图书,然后Spring会自动给它注入图书的实例。就像你在图书馆借书,不需要自己找,图书管理员会把书递给你一样。
配置这些关系可以通过XML配置文件,也可以使用注解。你可以告诉Spring哪些类需要注入,哪些属性需要赋值,就像你在指挥乐团,把每个乐器的音符安排得和谐动听。
Spring的依赖注入和对象装配让你的代码更加整洁、灵活,让你可以更专注于解决实际问题,而不用纠结于类与类之间的繁琐连接。
依赖注入的常见方式
Spring提供了多种方法来帮助你将不同的类连接在一起,实现依赖注入。以下是一些常见的依赖注入方式:
属性注入(Property Injection)
属性注入是我们最熟悉,也是日常开发中使用最多的一种注入方式。通过类的属性来注入依赖。这通常需要提供一个设置方法(setter)来设置依赖。就像你在乐高积木上插入一个小块一样,将依赖的类通过属性赋值进来。
@Controller
public class UserController {
// 从 spring 容器中获取 UserService 对象
/**
* 1.属性注入@Autowired 自动装配
*/
@Autowired
private UserService userService;
public void sayHello() {
System.out.println("Hello ");
userService.doService();
}
}
这里的UserService对象就像属性一样直接能被UserController调用,所有叫属性注入。
属性注入的优缺点
优点
属性注入最大的优点就是实现简单、使用简单,只需要给变量上添加一个注解(@Autowired),直接获得注入的对象了,所以它的优点就是使用简单。
缺点
功能缺陷: 不能注入一个final修饰的属性,在JavaSE中final修饰的变量要么使用时直接赋值,要么构造方法赋值,在这里两者都不满足,所以报错。
通用性问题: 只适用于IoC容器。,如果将属性注入的代码移植到其他非 IoC 的框架中,那么代码就无效了,所以属性注入的通用性不是很好。
- 设计原则问题: 使用属性注入的方式,因为使用起来很简单,所以开发者很容易在一个类中同时注入多个对象,而这些对象的注入是否有必要?是否符合程序设计中的单一职责原则?就变成了一个问题。但可以肯定的是,注入实现越简单,那么滥用它的概率也越大,所以出现违背单一职责原则的概率也越大。
注意:这里强调的是违背设计原则(单一职责)的可能性,而不是一定会违背设计原则,二者有着本质的区别。
Setter 注入(Setter Injection)
Setter 注入 是通过设置方法(setter)来实现依赖注入的方式。这种方法允许你在类中定义一个或多个设置方法,用于注入依赖对象。
/**
* 2.setter注入
*/
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHello() {
System.out.println("Hello ");
userService.doService();
}
Setter注入优缺点
优点
完全符合单一职责的设计原则,每一个 Setter 只针对一个对象
缺点
不能注入不可变对象
使用 Setter 注入依然不能注入不可变对象,比如以下注入会报错:注入对象可被修改
Setter 注入提供了 setXXX 的方法,意味着你可以在任何时候、在任何地方,通过调用 setXXX 的方法来改变注入对象,所以 Setter 注入的问题是,被注入的对象可能随时被修改。构造函数注入(Constructor Injection)
通过构造函数,你可以在创建一个类的实例时传入它所依赖的其他类的实例。就像你在组装一个模型,每个积木块都是构造函数的一个参数,将它们组合在一起就形成了完整的模型。
/**
* 3.构造器注入
*/
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHello() {
System.out.println("Hello ");
userService.doService();
}
- 如果当前的类中只有一个构造方法,那么 @Autowired 可以省略
构造函数注入优缺点
优点
构造方法注入相比于前两种注入方法,它可以注入不可变对象,并且它只会执行一次,也不存在像 Setter 注入那样,被注入的对象随时被修改的情况,它的优点有以下 4 个:
注入不可变对象
使用构造方法注入可以注入不可变对象,并不会报错注入对象不会被修改
构造方法注入不会像 Setter 注入那样,构造方法在对象创建时只会执行一次,因此它不存在注入对象被随时(调用)修改的情况。- 完全初始化
因为依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,会被完全初始化,这也是构造方法注入的优点之一。 - 通用性更好
构造方法和属性注入不同,构造方法注入可适用于任何环境,无论是 IoC 框架还是非 IoC 框架,构造方法注入的代码都是通用的,所以它的通用性更好。
缺点
构造函数可以有多个参数,当有多个参数时,不符合单一设计原则。
依赖注入推荐用法
Spring 4.2 之前推荐的注入用法是 Setter,Spring 4.2 之后推荐使用构造方法注入
@Resource:另一种注入关键字
在进⾏类注⼊时,除了可以使⽤ @Autowired 关键字之外,我们还可以使⽤ @Resource 进⾏注⼊。
- 实现属性注入
- 实现setter注入
- 不能用于构造函数注入
与@Autowired的区别
- @Autowired 来⾃于 Spring,⽽ @Resource 来⾃于 JDK 的注解
- 相⽐于 @Autowired 来说,@Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean
同⼀类型多个 @Bean 报错
当出现以下多个 Bean,返回同⼀对象类型时程序会报错,如下:
@Component
public class Users {
@Bean
public User user1() {
User user = new User();
user.setName("fyd");
user.setAge(18);
return user;
}
@Bean
public User user2() {
User user = new User();
user.setName("fyd2");
user.setAge(19);
return user;
}
}
获取User对象,如下:
@Controller
public class UserController {
@Resource
private User user;
public User getUser() {
return user;
}
}
程序报错如下:
没有 "com.fyd.User "类型的符合条件的 Bean:预期只有一个匹配的 Bean,但发现了 2 个:user1,user2
这里我们有两个一样类型的Bean对象,所以我们直接注入程序分不清是要注入哪一个。
使⽤ @Resource(name="user1") 解决
使用 @Qualifier 注解解决
什么是单一设计原则
当谈论软件设计时,"单一设计原则"(Single Responsibility Principle,简称 SRP)是面向对象编程中的一个重要原则之一。它强调一个类应该只有一个引起它变化的原因,也就是说,一个类应该只负责一项职责。
这个原则的核心思想是,将一个类的功能限制在一个明确的范围内,使得该类的修改只会因为这个范围内的需求变化,而不会受到其他无关功能的影响。这有助于代码的可维护性、可读性和可扩展性,因为当一个类只关注一项职责时,它的代码更加清晰,容易理解和修改。
举个例子来解释单一设计原则:假设你正在开发一个图书馆管理系统,你可能会有一个 Book 类负责表示图书的信息,另一个 Library 类负责管理图书的借阅和归还。按照单一设计原则,Book 类只需要关注图书本身的属性和方法,而 Library 类只需要关注图书的管理,这样两个类的职责得到了明确的划分。
然而,当一个类承担了过多的职责时,代码可能变得混乱,难以维护。如果在上述例子中,你让 Book 类既负责图书信息又负责图书管理,那么当图书信息或图书管理的需求变化时,就可能需要同时修改 Book 类的多个部分,增加了代码变更的风险和复杂性。
单一设计原则的目标是使每个类都变得更加聚焦和专注,减少类之间的耦合,提高代码的可维护性和灵活性。通过将不同的职责分开,你可以更容易地修改、测试和扩展每个类,从而更好地满足软件的需求。
总结
在这篇博客中,我们学习到了spring依赖注入的三种常见的方式,并且了解了它们的优缺点,还了解了依赖注入的两个重要的关键字:@Autowired、@Resource,并了解了两者的区别。还知道了如何解决同一类型Bean注入的报错问题和了解了软件设计中的单一设计原则。