目录
前言
属性注入(@Autowired)
Setter 注入
构造方法注入
@Resource
@Autowired 和 @Resource 的区别
@Autowired 和 @Resource 查找 Bean 对象的区别
前言
配置文件
<?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:content="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--base-package 表示要扫描的路径--> <content:component-scan base-package="com.java.demo"></content:component-scan> </beans>
启动类
import com.java.demo.controller.StudentController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { // 得到 Spring 上下文对象 ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); // 获取 Bean 对象 StudentController studentController = context.getBean("studentController",StudentController.class); studentController.sayHi(); } }
- 在启动类中,不能使用下述三种注入方式来实现对象的注入
- 运行启动类时,需要执行 main 方法,然而 main 方法被 static 关键字所修饰的,即 main 方法为静态方法
- 静态方法的加载顺序是高于 Spring 容器初始化的
- 所以在该静态方法中,我们无法使用以下三种依赖注入的方式来获取 Bean 对象
- 所以在下文 举例实现这三种依赖注入的方式 时,我们还是会通过 启动类 来获取 StudentController 的 Bean 对象
- 但是会在 StudentController 类中使用以下三种依赖注入的方式,来实现在StudentController 类中成功注入 UserService 类的 Bean 对象
属性注入(@Autowired)
- 日常开发中,属性注入是我们最常用的一种注入方式
实例
import com.java.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; // 使用 @Controller 注解将当前类存储到 spring 容器中 @Controller public class StudentController { @Autowired // 注入 UserService 的 Bean 对象 private UserService userService; public void sayHi() { System.out.println("student controller say hi"); // 直接调用执行 userService 的成员方法 userService.sayHi(); } }
运行启动类的结果:
重点理解
当使用属性注入时:
- 首先 ApplicationContext 在 Spring 容器启动时就会被实例化和初始化,并它会在启动过程中创建和管理所有的 Bean 对象
- 随后容器在创建 StudentController 类的 Bean 对象时,会自动检测 StudentController 类的依赖关系并注入相应的依赖对象
- UserService 的 Bean 对象,便在此时由 Spring 容器自动注入给了 StudentController 类
- 即 StudentController 类中的 userService 对象被 Spring 容器直接赋值
优点
- 实现简单、使用简单
- 只需给变量上添加一个注解(@Autowired),即可获得注入的 Bean 对象
缺点
1、无法实现 final 修饰的变量注入
- 在 Java 中被 final 关键字修饰的变量被称为常量
- 常量的声明和初始化需要在同一时间完成,且只能被赋值一次
- 方式一:直接赋值
- 方式二:通过构造方法赋值
2、只适用于 IoC 容器(兼容问题)
- 如果将属性注入的代码移植到其他非 IoC 的框架中,该代码就无效了,所以其兼容性有限
3、因为写法简单,所以违背单一职责原则的概率更大(存在风险)
- 单一职责原则 是面向对象设计中的一个重要原则,它指出一个类应该有且只有一个引起它变化的原因
- 此处强调的是违背单一职责原则的可能性,而不是一定会违背单一职责原则,这与程序员自己的代码强相关
Setter 注入
实例
import com.java.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; // 使用 @Controller 注解将当前类存储到 spring 容器中 @Controller public class StudentController { // 使用 Setter 注入 private UserService userService; @Autowired public void setUserService(UserService userService) { this.userService = userService; } public void sayHi() { System.out.println("student controller say hi"); // 直接调用执行 userService 的成员方法 userService.sayHi(); } }
- 此处需要使用 @Autowired 添加到 Setter 方法上
- 代表着 Spring 会针对该 Setter 方法 的参数进行相应的赋值
运行启动类执行结果:
重点理解
当使用 Setter 注入时:
- 首先 ApplicationContext 在 Spring 容器启动时就会被实例化和初始化,并它会在启动过程中创建和管理所有的 Bean 对象
- 随后容器在创建 StudentController 类的 Bean 对象时,会自动检测 StudentController 类的依赖关系并注入相应的依赖对象
- UserService 的 Bean 对象,便在此时由 Spring 容器自动注入给了 StudentController 类
- 即 StudentController 类中的 setUserService 方法的参数直接被 Spring 容器给赋值
- 然后通过方法中的 this.userService = userService 语句
- 将被 Spring 容器赋值的 userService 对象,传递赋值给 StudentController 类中userService 变量
- 此刻 StudentController 类便可以成功使用 UserService 类的 Bean 对象了
缺点
1、同样无法实现 final 修饰的变量注入
2、注入对象可被修改
import com.java.demo.controller.StudentController; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class App { public static void main(String[] args) { // 得到 Spring 上下文对象 ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); // 获取 Bean 对象 StudentController studentController = context.getBean("studentController",StudentController.class); // setUserService 可以被随便调用,然后改变原先已经被 Spring 容器注入好的 userService Bean 对象 // 此处直接将 studentController 对象中的 userService 变量的值改为 null studentController.setUserService(null); // 执行到此处时便会发生报错 studentController.sayHi(); } }
运行结果:
图解原因:
构造方法注入
- 构造方法注入是 Spring 官方推荐的注入方式
- 但是日常开发用的最多的还是属性注入
特点:
- 如果当前类中只有一个构造方法的话, @Autowired 注解可以省略
实例
import com.java.demo.service.UserService; import org.springframework.stereotype.Controller; // 使用 @Controller 注解将当前类存储到 spring 容器中 @Controller public class StudentController { // 使用 构造方法 注入不可变对象 private final UserService userService; // @Autowired 因为当前类仅一个构造方法,所以此处的 @Autowired 可以省略 public StudentController (UserService userService) { this.userService = userService; } public void sayHi() { System.out.println("student controller say hi"); userService.sayHi(); } }
运行结果:
优点
1、可以注入一个不可变对象(即用 final 关键字修饰的对象)
- 被 final 关键字修饰的对象必须满足以下两个条件中的任意一个
- final 修饰的对象,要么需直接赋值
- final 修饰的对象,要么需通过构造方法赋值
2、注入对象后不会被修改
- 构造方法随着类加载仅执行一次
3、构造方法注入可以保证对象完全被完全初始化
- 当创建一个对象时,一定会调用该类的构造方法来初始化该对象
- 所以通过构造方法注入时,将必然保证该对象已经是被 Spring 容器所注入了 Bean 对象的
4、相比属性注入,构造方法注入的兼容性更好
- 构造方法注入可适用于任何环境,无论是 IoC 框架还是非 IoC 框架
@Resource
- @Resource 注解由 JDK 所提供
@Autowired 和 @Resource 的区别
- 出身不同:@Autowired 来自于 Spring,而 @Resource 来自于 JDK
- @Autowired 可用于 属性注入、Settter 注入、构造函数注入,而 @Resource 只能用于 属性注入、Setter 注入
- 使用时设置的参数不同:相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 重命名
@Autowired 和 @Resource 查找 Bean 对象的区别
- @Autowired 先会根据类型查找,之后再根据名称查找
- 而 @Resource 会先根据名称来查,再根据类型查找
实例理解
- 我们创建一个实体类
// 普通的用户实体类 public class User { public Integer uid; public String username; public String password; public Integer age; public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
- 再创建一个 UserBean 类,通过该类向 Spring 容器中注入 User 的 Bean 对象
import com.java.demo.entity.User; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; @Component public class UserBeans { @Bean public User getUserByName() { User user = new User(); user.setUid(1); user.setUsername("张三"); user.setPassword("123456"); user.setAge(18); return user; } @Bean(name = {"user1","u1"}) public User getUserById() { User user = new User(); user.setUid(1); user.setUsername("李四"); user.setPassword("123456"); user.setAge(18); return user; } }
- 将 User 的 Bean 对象通过 @Autowired 注入到 StudentController 类中
import com.java.demo.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; // 使用 @Controller 注解将当前类存储到 spring 容器中 @Controller public class StudentController { // 使用 属性 注入 @Autowired private User user; public void sayHi() { System.out.println("student controller say hi"); user.getUsername(); } }
运行结果:
报错分析:
- 正因为我们在 UserBeans 类中向 Spring 容器注入了两个同为 User 类型的 Bean 对象
- 所以当我们使用 @Autowired 注解来注入 Bean 对象的时候,它会先根据类型来查找
- 那此时便在 Spring 容器中查找到了 两个同为 User 类型的 Bean 对象
- 从而 将会根据绿框名称来查找,若 Spring 容器中有 id 为 user 的 Bean 对象,便会直接注入
- 但是我们存入 Spring 容器中的 User 类型的 Bean 对象,其一 id 为 getUserByName,其二 id 为 user1 或 u1
- 所以 @Autowired 找不到 id 为 user 的 Bean 对象,从而发生报错
解决方法:
方案一:
- 修改绿框名称,通过 id 来指定注入一个 Bean 对象
方案二:
- 使用 @Resource 注解,通过设置参数来查找
- 当然本应将参数名称设置为 user1 来实现依赖注入的
- 使用该方式,可以实现 参数名称 的重命名
方案三:
- 组合使用 @Autowired 和 @Qualifier 注解
- @Qualifier 注解起到筛选的作用,筛选出 id 为 user1 的 Bean 对象,并将其注入
- 该方式所呈现的效果 与 方式二 相同