三、Spring 获取 bean 对象
获取 bean 对象也叫 “对象装配”,“对象注入”, 意思就是把对象取出来放到某个类中。
对象装配(对象注入)的实现有以下三种方法:
1. 属性注入 2. 构造方法注入 3. Setter 注入
接下来博主将围绕着3种方式展开来给大家讲述Spring 如何实现动态注入 bean 对象(运行时动态的将 bean 对象注入到某个类中)。
3.1 属性注入
属性注入需要使用到 @Autowired 注解实现。接下来给大家举一个例子:
有一个学生类(学生信息的组织),假设对应数据库中的学生表,有一个Service 层的 StuService类,这类可以从数据库中读取学生数据(增删查改),实例成一个完整的学生对象,有一个 StuController 类,他可以针对学生数据进行实际的使用(调用处理),所以 StuController 类在运行中需要获取到 StuService 类的实例对象来操作数据库中的学生表。当然这里不会给大家真的用数据库,写伪代码来模拟一下真实的环境。
@Service
public class StuService {
/**
* 根据学生id从数据库中获取学生信息
* @param id
* @return
*/
public Student getStu(String id) {
//伪代码,链接数据库,查找学生信息
Student stu = new Student();
stu.setId(id);
stu.setName("张三");
stu.setAge(18);
stu.setSex("男");
return stu;
}
}
@Controller
public class StuController {
//1. 属性注入,从Spring 中取出 StuService 类型的bean 对象并注入到类引用中
@Autowired
private StuService stuService;
//2. 调用注入的 bean 对象执行查询操作
public Student getStu(String id) {
return stuService.getStu(id);
}
}
获取 Controller 中的 getStu 的方法:正常情况下我们就将 Student 学生的信息转换为 json 格式由前端发请求,服务器做出响应,将数据(http协议)给到前端,前端利用数据构建页面。
package school.student2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import school.student.Student;
public class App {
public static void main(String[] args) {
//1. 得到 Spring 的上下文对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-test.xml");
//2. 调用 getBean() 方法获取指定的 Bean 对象
StuController stuController = context.getBean("stuController", StuController.class);
//3. 使用bean 对象
Student student = stuController.getStu("1");
System.out.println(student.toString());
}
}
属性注入的核心:
3.2 构造方法注入
构造方法注入是在类的构造方法中实现注入:同样使用 @Autowired 注解
如果只有一个构造方法,那么 @Autowired 注解可以省略不写:
如果类中出现多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法,否则程序会报错。
3.3 Setter 注入
Setter 本质上就是使用自定义方法来代替构造方法注入,也是需要在 set 方法上添加 @Autowired 注解:
注意:使用 Setter 注入一定要添加@Autowired 注解
3.4 三种注入优缺点分析
3.4.1 属性注入
优点:实现起来比较简单,只需要一个@Autowired 注解
缺点:
1. 不能注入不可变的(final)的对象
final 修饰的引用,只能在创建的时候进行初始化赋值操作,或在构造方法中初始化。
2. 只能适用于 loC 容器
属性注入通常与IoC(控制反转)容器紧密相关,并且在大多数情况下,它更常用于IoC容器中。IoC容器通过管理对象的生命周期、依赖关系和对象创建等任务,将控制权交给框架,以实现解耦和可扩展性。总体而言,属性注入与IoC容器密切相关,并且在通过IoC容器管理对象时,属性注入是一种常用的依赖注入方式,能够帮助实现代码的解耦和可配置性。然而,在非IoC场景下,属性注入的使用可能会受到一些限制,并且可能不如其他依赖注入方式适用。
3. 更容易违背单一设计原则(针对类)
属性注入在某些情况下可能会违背单一设计原则(Single Responsibility Principle,SRP)。SRP的目标是确保一个类对于变化的原因只有一个,以便提高代码的可维护性和灵活性。它强调职责的划分和分离,使每个类专注于单一的功能或关注点。
以下是属性注入可能导致违背SRP的一些情况:
依赖过多:属性注入可能导致一个类具有过多的依赖关系。如果一个类依赖过多的其他类或组件,那么它可能承担过多的职责。这样的类可能会变得庞大而难以理解和维护。
责任扩散:属性注入可能导致一个类承担不应该由它承担的额外职责。当一个类注入了许多属性时,特别是来自不同领域的属性,它可能参与多个责任和业务逻辑,从而违反SRP。
耦合度增加:属性注入可能导致类之间的耦合度增加。如果一个类对其依赖项的实例化和配置有太多控制权,它与这些依赖项之间的耦合度会增加。这使得类的扩展和修改变得更加困难。
虽然属性注入在一些情况下可能会违背SRP,但这并不意味着属性注入本身是错误的或应该完全避免的。合理使用属性注入,可以通过分割类的职责、解耦依赖关系和保持代码的可维护性来避免违背SRP。在设计和实现过程中,应该谨慎选择注入的属性和依赖项,确保它们与类的职责和关注点保持一致,避免过度复杂化和耦合。
3.4.2 构造方法注入
优点:
1. 可以注入一个不可变(final修饰)对象,构造方法可以为其赋值。
2. 注入的对象不会被修改
- 可以被 final 的修饰(只有一个实例)
- 构造方法伴随着类加载只执行一次
3. 通用性更好
通过构造方法注入,类的依赖关系可以明确地表达出来,构造方法注入允许依赖关系在类的实例化阶段就被传递进来。这意味着在类的实例化过程中,可以清楚地看到类所依赖的对象是哪些,而不是在方法调用时才进行依赖注入。
缺点:
没有属性注入实现简单。
需要注意的是,构造方法注入并不适用于所有情况,特别是当所依赖的对象较多时,会出现构造方法参数列表冗长的问题。在这种情况下,可以考虑使用其他形式的依赖注入方式,如属性注入或方法注入,来提供更灵活的注入方式。选择合适的依赖注入方式,取决于具体的情况和项目需求。
构造方法注入是 Spring 官方推荐的注入方法。
3.4.3 Setter 方式注入(普通方法注入)
Setter 方法是 Spring 早期版本推荐的注入方式,。
优点:比较符合单一设计原则(SRP),方法嘛,针对的是方法级别的,虽然在通用性上没有构造方法强,Setter方法注入也有其适用的场景,比如可选的依赖项或动态变化的依赖关系。在这些情况下,setter方法注入可能更为灵活和方便。选择合适的依赖注入方式,需要根据具体需求和设计原则进行权衡和判断。
缺点:
1. 不能注入不可变(final)的对象;
final 修饰的引用,只能在创建的时候进行初始化赋值操作,或在构造方法中初始化。
2. 注入对象可以被修改
set 方法是普通的方法,可以被重复调用,在调用时就存在被修改的风险。Spring 现版本已经推荐使用构造方法注入的方式来进行类注入。
3.5 @Resource 注入
在 Java 里面在进行类注入时,除了使用 @Autowired 注解,还可以使用 @Resource 注解:
@Autowired
和 @Resource
都是在 Java 中用于进行依赖注入的注解,但它们有一些不同之处。
3.5.1 @Autowired 和 @Resource 的区别
- 出生不同:@Autowired 注解出自于Spring框架(非官方), @Resource 注解出自于 JDK(官方)。
- 功能支持不同:@Autowired 可用于属性注入,构造方法注入,和 Setter(普通方法) 注入.而 @Resoure 只能用于 Setter 注入和属性注入, 不能用于构造方法注入(意味着不能注入不可变对象).
- 参数支持不同: @Resource 支持更多的参数设置, 例如: 对Bean 取一个名字(name), 而 @Autowired 只能设置
required 参数。required
是@Autowired
注解的一个可选属性,默认值为true
。它用于指定依赖注入是否是必需的。当required
属性设置为true
时,如果找不到匹配的依赖对象,则会抛出NoSuchBeanDefinitionException
异常。示例:
@Autowired(required = true) private MyDependency myDependency;
当
required
属性设置为false
时,如果找不到匹配的依赖对象,则会将依赖对象设置为null
,即允许依赖注入的可选性。示例:
@Autowired(required = false) private MyDependency myDependency;
尽管
required
默认值为true
,但是在某些情况下,我们可能使用@Autowired(required = false)
来标记可选的依赖注入,以便在没有匹配的依赖对象时不抛出异常。这在需要可选依赖或动态注入的场景下非常有用。
3.6 @Bean 注入多个同类型的 bean 对象报错处理
当我们使用 @Bean 方法注解往 Spring 中注入多个同类型的 Bean 对象时,就会报错。
注意:以往的例子我们需要在 Spring 配置文件中将路径注释一下,不然启动的时候Spring 容器中会注册多个同类型的 bean对象(stu)。
@Component
public class StudentDemo {
StudentDemo() {
System.out.println("StudentDemo 执行");
}
@Bean
public Student getStu() {
//1. 创建对象
Student student = new Student();
//2. 初始化对象
student.setId("1");
student.setName("李小四");
student.setSex("男");
student.setAge(18);
//3. 返回对象
return student;
}
@Bean
public Student getStu2() {
//1. 创建对象
Student student = new Student();
//2. 初始化对象
student.setId("2");
student.setName("王小五");
student.setSex("男");
student.setAge(20);
//3. 返回对象
return student;
}
}
在另一个类中获取 Student 对象:
public class StudentApp {
public static void main(String[] args) {
//1. 得到 Spring 的上下文对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-test.xml");
//2. 调用 getBean() 方法获取指定的 Bean 对象
StudentController stuController = context.getBean(StudentController.class);
//3. 利用 stuController 对象的 getStudent()获取 bean 对象
Student student = stuController.getStudent();
System.out.println(student.toString());
}
}
@Controller
class StudentController {
//1. 注入 student 对象,在StudentDemo 类中我们使用 @Bean 注入了两个stu 对象
@Autowired
private Student student;
//2. 返回
public Student getStudent() {
return student;
}
}
以上程序执行结果:
同一类型多个 Bean 报错处理:
- 使用Resoure(name = "FunctionName") 进行类注入的时候只能指定获取@Bean注解修饰方法的名字,如此一来,问题迎刃而解~
- 使用@Qualifier 注解定义名称,可以跟@Autowired 注解配合使用(Autowired 注解不可以指定name 注入)。
1. 使用 @Resource(name = "XXXXX") 定义
2. 使用 @Qualifier
@Qualifier注解 用于帮助指定特定的依赖注入或自动装配。它通常与依赖注入框架(如Spring)一起使用,以解决多个候选对象的歧义性。在依赖注入中,当存在多个相同类型的bean时,可以使用@Qualifier注解来标识要使用的具体bean。通过在@Qualifier注解中指定一个唯一的标识符(name),可以告诉依赖注入框架,注入哪个具体的bean。
四、总结
1. 将对象存储到 Spring 中:
- 古老一点的做法是在 Spring 配置文件的 <Beans> </Beans> 标签下直接添加注解。
不好的一点是比较麻烦,每个 Bean 对象都需要我们手动的添加,对此我们有了注解的方式。
- 使用类注解:@Controller 、@Service、@Repository、@Configuration、@Component这些其他注解的内部都有一个 @Component注解, 说明这些注解可以认为是 @Component 的子类.
使用类注解:需要在Spring 配置文件中设置包扫描路径,当Spring 启动时,会根据包路径下的类注解将修饰的类实例到 Spring 当中,类直接需要配合扫描路径使用,不然无法成功注入(此处是单例模式,一个类只有一份实例)。
- 使用方法注解 @Bean 【注意事项:必须配合类注解一起使用】
2. Bean 对象的命名规则
类首字母大写和类首字母小写及第二个字母小写,都是默认都是通过首字母小写作为name 来获取 Bean ;如果类首字母和第二个字母都是大写,那么直接使用原类名作为name 来获取 Bean 。
3. 从Spring 中获取bean 对象:
- 属性注入
- 构造方法注入(官方推荐)
- Setter 注入
4. 注入的关键字:
@Autowired 和 @Resource
5. @Autowired 与 @Resource 的区别:
- 出生不同:Autowired 出自Spring ,Resource 出自 JDK
- 参数不同:@Resource 支持更多的参数,例如可以在获取的时候给 Bean 对象取名字
- 功能的不同:@Autowired 支持属性注入,构造方法注入和 Setter 方法注入,@Resource 只支持属性注入和 Setter 方法注入。
6. 解决@Bean 方法注解注入多个同类型 Bean 对象的报错:
- 使用 @Resource(name= " 方法名")
- 使用 @Qualifier (value = "方法名")
人生的旅途中,前面还有无数个日子需要别无选择地去面对。