目录
一. 存储 Bean 对象
1. 前置工作:配置扫描路径
2. 使用五大类注解存储Bean对象
@Controller
命名规则
@Service
@Repository
@Compoent
@Configuration
五大类注解之间的关系
3. 使用方法注解存储Bean对象
二. 获取 Bean 对象
1. 属性注入
优点分析
缺点分析
2. Setter 注入
优点分析
缺点分析
3. 构造方法注入
优点分析
通过前面的学习,我们学习实现了基本的 Spring 读取和存储对象,但在日常的开发中,往往采用更简单的方式,也就是利用注解来进行读取和存储Bean对象。
一. 存储 Bean 对象
1. 使用五大类注解:
- @Controller
- @Service
- @Repository
- @Component
- @Configuration
2. 使用方法注解 @Bean
1. 前置工作:配置扫描路径
为了让对象成功的存储在Spring中,就必须配置一下存储对象的扫描路径,通过扫描路径来决定是要对哪个路径下的对象进行存储到Spring容器中。同时,并不是在扫描路径下的所有类都会被保存到Spring中。只有在扫描路径下,同时添加了注解,才能被正确的识别并保存到Spring中。 也就是说,即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的。
配置扫描路径,就需要在spring配置文件中,添加语句:
<content:component-scan base-package=""></content:component-scan>
base-package字段就表示要扫描的路径。添加扫描路径后,扫描的范围不止该路径本身,还会包括该路径的所有子路径。
2. 使用五大类注解存储Bean对象
添加注解后,扫描路径内的类就会作为Bean对象存储于Spring容器中了。
@Controller
这个注解可以理解为是一个 "控制器",用来验证用户请求的数据正确性。
这个注解也可以传入参数,这个参数可以作为这个Bean对象的名称。
// @Controller("stu")
@Controller // 将当前类存储到 spring 中
public class StudentController {
public static void say(){
System.out.println(" do student controller ");
}
}
用代码进行演示:
命名规则
当注解里没有带有参数的时候,那么会有默认的命名规则,此处的名称指的是 getBean()方法中的第一个参数id。这里先总结一下再进行分析:
Bean 命名规则:
1. 默认情况下是首字母变为小写。例如:Student 这个类就对应为 student,StudentName 就对应为 studentName;
2. 如果类名首字母和第二个字母都为大写的情况下,那么对应的 Bean名称就为原类名。例如:CStudent 就对应为 CStudent,SController 就对应为 SController;
从源码分析:
也可以对这个方法进行运行一下:
@Service
这个注解可以理解为是 "服务层",用于调度具体的执行方法。
@Repository
这个注解可以理解为是"数据持久层",用于与数据库进行交互。
@Compoent
这个注解可以理解为是"组件层",用于存放一些工具类。
@Configuration
这个注解可以理解为是"配置层",用于存放项目中的一些配置。
五大类注解之间的关系
实际上,五大类注解的作用都是一样的,之所以需要分五大注解,是基于软件开发模型的,为了让程序员看到类注解后,就能直接了解到当前类的用途。这种作用就类似于广东省内的车牌分为粤A,粤B,粤C等,A,B,C...的加入可以让我们一看车牌就知道是广州,深圳,珠海的车。
在 JavaEE 中,标准分层至少分为三层:
通过观察 @Controller,@Service,@Repository,@Configuration等注解的源码可以看出,他们都属于 @Component 的子类。
3. 使用方法注解存储Bean对象
类注解是添加到某个类上的,⽽⽅法注解是放到某个⽅法上的。
1. 先新建一个User类:
public class User {
public int id;
public String name;
public int age;
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;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
2. 给方法添加注解
注意:
1. 此方法必须有返回值,方法的返回值就作为Bean对象存储到Spring容器中。
2. 添加方法注解 @Bean 必须是在五大类注解的基础上添加!!!
这样是为了提高效率,只有被五大类注解作用的时候,并且是在扫描路径范围内,才会去将对应被修饰 @Bean 的方法进行扫描,从而将返回的Bean对象存储至Spring容器中。
3. 通过方法注解 @Bean 存储到Spring中的Bean对象默认名称为方法名。
@Component
public class UserBeans {
@Bean
public User func(){
User user = new User();
user.setAge(20);
user.setId(1);
user.setName("张三");
return user;
}
}
此外,还可以在 @Bean 注解当中指定参数 name 或者 value,来作为存储Bean对象的名称。可以指定一个名称,也可以指定多个。
@Bean(name = {"hello","helloUser"})
注意: 当 @Bean 使用了重命名之后,那么默认的使用方法名获取对象的方式就不能再使用了,也就是说 getBean() 参数中的 id 就不能为方法名了,只能为重命名后的名称。
3. 使用getBean获取对象
getBean() 方法的名称,在不加参数 name 的情况下,默认为方法名。加了参数 name 之后,就以参数 name 的指定名称为准。
补充:在方法注入的时候,还可以通过注解 @order(int i) 来控制注入的顺序,i 的值越小,权重就越高,就越早注入。如果是Bean对象名称相同的情况,就有可能会发生覆盖的问题。
二. 获取 Bean 对象
在上述我们讲解了如何去利用 五大类注解 和 方法注解 的方式来更加简单的存储Bean对象,那么接下来就讲解如何来更加简单的从 Spring 容器中来获取 Bean 对象。主要有三种方式:
- 属性注入
- Setter 注入
- 构造方法注入
1. 属性注入
通过 @Autowired 注解进行实现:
通过 @Autowired 对一个属性进行注解,就会从 Spring 中去查找是否有跟这个属性相对应的Bean对象,如果有,就将这个Bean对象赋值给对应的属性。
例如:先在Spring容器中存储一个Bean对象:
@Service
public class UserService {
public int age = 10;
public void sayHi() {
System.out.println("do userService sayHi() -> " + age);
}
}
然后再在 UserController 类中,对 UserService 进行注入,并存储在Spring容器中:
@Controller
public class UserController {
// 1. 属性注入
@Autowired
private UserService userService;
public void sayHi(){
System.out.println(userService.age);
}
}
最后在main方法中运行得到结果:
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController = context.getBean("userController",UserController.class);
userController.sayHi();
}
可以得出 在 UserController 对象中已经成功注入了 UserService 对象。
优点分析
属性注入使用起来简单方便,只需要给对应的属性添加注解 @Autowired ,就可以注入从Spring获得的Bean对象。
缺点分析
1. 属性注解存在一个问题:使用 @Autowired 注解给属性注入对象的时候, 要求 Spring 容器中有且仅有一个和 @Autowired 注入的属性相同的 Bean 对象。
例如,给Spring容器中存储两个 User对象,此时用属性注入来获取对象,就会报错。
2. 属性注入无法注入一个不可变对象,也就是被 final 修饰过的对象。而这也跟Java的语法知识是相关联的,Java中的 final 对象,要么直接赋值,要么通过构造方法赋值,所以当使用属性注入 final 对象时,它不符合 Java 中 final 的使用规范,所以就不能注入成功了。
3. 兼容性问题:使用属性注入的方式只适用于 IoC 框架。
4. 由于使用方法的简单,所以可能会违背了单一设计原则。
2. Setter 注入
@Controller
public class UserController {
// 2. Setter注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println(userService.age);
}
}
优点分析
通过Setter注入的方式,相比于属性注入,它更符合于单一设计原则,因为每一个Setter只针对一个对象。
缺点分析
1. 依旧是无法注入一个被 final 修饰的对象;
2. 由于Setter注入是通过set方法实现的,那么就有可能 set 方法被执行多次从而改变注入的对象。
3. 构造方法注入
构造方法注入是官方最推荐的一种使用方式。 将 @Autowired注解放于构造方法上。
@Controller
public class UserController {
// 3. 构造方法注入
private UserService userService;
@Autowired
public UserController(UserService userService){
this.userService = userService;
}
public void sayHi(){
System.out.println(userService.age);
}
}
当只有一个构造方法的时候,作用在构造方法上的 @Autowired 注解是可以省略的,但是如果类中有多个构造⽅法,那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法,否则程序会报错。
优点分析
1. 可注入不可变对象:使用构造方法注入,就可以注入一个被 final 修饰的对象了;
2. 注入对象不会被修改:与 Setter注入相比,构造方法只会执行一次,也就不会再存在像 Setter注入中 set方法被调用多次从而改变注入对象;
3. 完全初始化:由于注入对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,就会被完全初始化。
4. 通用性更好:构造方法注入可适用于任何环境,无论是 IoC 框架还是非 IoC 框架,构造方法注入的代码都是通用的,所以它的通用性更好。
补充说明:静态类的加载顺序是高于Spring的,因此在启动类 main 方法中获取对象注入,就还是采用老方法,而不采用注解。