之前我们存储获取 bean 的操作很繁琐,需要将 bean 放入 xml 文件中,获取上下文对象还得 new 操作,后面还要加上 xml 的路径,就很麻烦。
其实这些都可以简化,在 Spring 中想要更简单的存储和读取对象,核⼼是使⽤注解。
想要将对象存储在 Spring 中,有两种注解类型可以实现:
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration。
- ⽅法注解:@Bean
获取对象的实现⽅法有以下 3 种:
- 属性注⼊
- 构造⽅法注⼊
- Setter 注⼊
五大类注解有:
@Controller(控制器)校验参数的合法性,相当于安检系统
@Service(服务)业务组装,相当于客服中心
@Repository(数据持久层)实际业务处理,就是实际办理的业务
@Component(组件)工具类层,基础工具
@Configuration(配置层)配置
首先,我们得配置一下 xml 文件:
注意:使用类注解存储 bean,和使用 xml 存储 bean 是可以混用的。
User 类:
//使用类注解
@Controller
public class User {
public void sayHi(String name) {
System.out.println("hello " + name);
}
}
public class App {
public static void main(String[] args) {
//得到 spring 上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//得到 bean 对象
User user = (User) context.getBean("user");
user.sayHi("张三");
}
}
关于 getBean 后面应该填什么?
对于不同的类名有不同的写法,如果首字母大写,第二个字母小写,那么 bean 的名称就是类名首字母小写;如果不满足首字母大写第二字母小写,则使用原类名。
当然了,我们也可以在类注解后面设置 id,通过 id 来得到 bean 对象。
//id 可以随便取, 两种方式都可
//@Controller(value = "666")
@Controller("666")
public class User {
public void sayHi(String name) {
System.out.println("hello " + name);
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
User user = (User) context.getBean("666");
user.sayHi("张三");
}
}
来看看五大类注解间的关系:
这些注解⾥⾯都有⼀个注解 @Component,说明它们本身就是属于 @Component 的“⼦类”。
所以它们在使用上都差不多,那为什么还要分出 5 大类呢?
直接一个 @Component 就可以 存储 bean 对象了,还要分这么多有什么用呢?
其实是让程序员看到类注解之后,就能直接了解当前类的⽤途,比如我们看到一辆车的车牌号就知道它的归属地是哪,这个也一样。
类注解是添加到某个类上的,⽽⽅法注解是放到某个⽅法上的:
Student 类:
//普通类
public class Student {
private String name;
private int id;
private int score;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
", score=" + score +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
class 类:
//注意: 方法注解要配合五大类注解一起使用
// 该类也存储在 IoC 容器中
@Controller
public class Class {
//使用方法注解, 将方法的返回值存储到 IoC 容器中
@Bean
public Student student() {
Student student = new Student();
student.setId(6);
student.setName("张三");
student.setScore(150);
return student;
}
}
获取 bean 对象:
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//注意:@Bean 默认命名是方法名
// 也就是添加方法注解的那个方法名
Student student = context.getBean("student", Student.class);
System.out.println(student.toString());
}
}
@Bean("aaa")
@Bean(value = "sss")
@Bean(name = "nnn")
点入 @Bean :
除了取单个名称外,还可以取多个名称:
@Bean(value = {"sss","888"})
@Bean(name = {"sss","888"})
@Bean({"sss","888"})
注意:
- 如果重命名了,那么获取 bean 对象时,默认的方法获取 bean 对象的方式就不能用了。
- 添加了 @Bean 注解的方法无法传参(方法的调用是程序在控制),也就不能重载。
- 如果多个 Bean 使用相同的名称,那么程序执行不会报错,但是第一个 Bean 后的对象不会被放到容器中,也就是只有第一次创建 Bean 的时候会将对应的 Bean 名称关联起来,后续再有相同名称的 Bean 存储的时候,容器会自动忽略。
获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注⼊。
属性注⼊是使⽤ @Autowired 实现的:
普通类 User:
public class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
添加类注解的 UserService:
@Service
public class UserService {
public User getUser(int id, String name) {
User user = new User();
user.setId(id);
user.setName(name);
return user;
}
}
添加类注解的 UserController:
@Controller
public class UserController {
//使用属性注入,将IoC容器中的UserService类型的对象
//注入变量 userService中
@Autowired
private UserService userService;
public User getUser(int id, String name) {
return userService.getUser(id,name);
}
}
注意:下面我们不使用 @Autowired 来进行依赖注入,因为 main 函数是静态方法,@Autowired 又不能给局部变量注入,那就只能放在类里面方法外面了,再加上 static 修饰 才能在 main 里访问,但可惜的是静态类属于方法的一部分,static 成员是不能使用 @Autowired 来注入。
public class Test {
public static void main(String[] args) {
//依赖查找
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
UserController userController = context.getBean("userController", UserController.class);
//检验是否得到了 UserController 类型对象
System.out.println(userController.getUser(3,"张三").toString());
}
}
依赖查找 VS 依赖注入
依赖查找:依赖 Bean 的名称来获取 Bean 对象。
依赖注入:通过注解 @Autowired 根据对象类型进行依赖注入,首先根据 getType 从容器中获取对象,如果 IoC 容器中只有一个该类型对象,则直接注入到当前属性上;如果容器中有多个该类型对象,则会使用 getName (根据名称)进行匹配。(具体体现如下)
问题:如若有多个同类型的 Bean 对象存储进 IoC 容器中,那么我们该如何准确获取该类型对象?
问题场景:
Users 类:
@Service
public class Users {
//通过方法注解,添加两个同类型 bean
@Bean("user1")
public User user1() {
User user = new User();
user.setName("李四");
user.setId(22);
return user;
}
@Bean("user2")
public User user2() {
User user = new User();
user.setName("王五");
user.setId(88);
return user;
}
}
UserService 类:
@Service
public class UserService {
//对 user 进行属性注入
@Autowired
User user;
public void add() {
//拿到 user 后打印内容,判断是哪个 user
System.out.println(user.toString());
}
}
Test 类:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
看结果:
它是说期望有一个 bean 进行匹配,但出现了两个:user1、user2
解决问题:
就比如我要得到的是 王五 这个对象,那我只需要将下面两点的名称对应上即可:
属性注入分析:
优点: 使用简单
缺点:
- 无法注入 final 修饰的变量
- 只适用于 IoC 容器
- 容易违背单一设计原则
我们写类属性时,可以对这些属性生成相应的 get 和 set 方法,Setter 注入就是针对 set 方法,进行的注入:
User 类:
@Service
public class Users {
@Bean("user")
public User getUser() {
User user = new User();
user.setName("老王");
user.setId(26);
return user;
}
}
UserService 类:
@Service
public class UserService {
private User user;
//对类属性 user 进行 Setter 注入
@Autowired
public void setUser(User user) {
this.user = user;
}
public void add() {
System.out.println(user.toString());
}
}
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
注意:@Autowired 不能省略
Setter 注入分析:
优点:通常 Setter 只是注入一个属性,所以 Setter 更符合单一设计原则。
缺点:
- 无法注入一个 final 修饰的变量
- Setter 注入的对象可以被修改。(setter 是一个方法,既然是方法就可能被多次调用和修改)
构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所示:
只对 UserService 进行修改:
@Service
public class UserService {
private User user;
//构造方法注入
@Autowired
public UserService(User user) {
this.user = user;
}
public void add() {
System.out.println(user.toString());
}
}
注意:如果只有一个构造方法,不写 @Autowired 也可以,但若是有多个构造方法,就得加上 @Autowired,表明是哪个构造方法需要注入。
构造方法注入分析:
优点:
- 可以注入一个 final 修饰的变量
- 注入的变量不会被修改,因为构造方法只加载一次
- 构造方法注入可以保证注入对象完全初始化
- 构造方法注入通用性更好
缺点:
- 写法比属性注入更复杂
- 使用构造方法注入,无法解决循环依赖的问题
其实 @Resource 的功能和 @Autowired 差不多,那它俩有啥区别呢?
@Autowired 与 @Resource 的区别:
- 出生不同:@Resource 来自 JDK,@Autowired 来自 Spring 框架
- 支持参数不同:@Resource 支持很多参数设置,@Autowired 只支持一个参数设置
- 使用上的区别:@Resource 不支持构造方法注入,@Autowired 支持构造方法注入
- idea 兼容性支持不同:使用 @Autowired 在 idea 专业版下可能会误报,@Resource 不存在误报问题(@Resource 相当于是亲儿子了)
关于第四点:因为@Autowired 来自 Spring 框架,@Resource 来自 JDK,所以执行顺序有差异,Spring 框架的执行是在 java 程序执行之后的,当我们使用 @Autowired 时,它不能检测到 IoC 容器中是否有这个类型的 Bean,所以就报错(运行起来不影响结果);使用 @Resource 的话,执行顺序是靠前的,它知道这个 Bean 是否存在。