目录
一、前言:
二、储存Bean对象和使用Bean对象
1. 添加存储对象的注解 (这一步完成Spring容器已经有了Bean对象)
(1)类注解
为什么需要五大类注解
(2)方法注解
(3) 类注解和方法注解的区别
2. 对象装配/对象注入 (你的代码可以使用被注入的对象来完成各种任务)
(1) 属性注入 (日常开发中使用最多的一种注入方式)
(2) Setter方法注入
(3) 构造方法注入 ( Spring 官方从 4.x 之后推荐的注入方式)
(4) @Autowired与@Resource区别
(5) 注入异常问题
一、前言:
上篇博客,我们讲了一个spring core项目的大致流程:
创建项目——》将对象储存到Spring容器中 ——》从Spring容器中取出Bean对象
上篇博客的这种Spring对象的存储和读取方式虽然能够满足我们的需求,但是这也只是站在小项目的层面上来讲的。如果一个项目中的类多起来了,我们如果采用上篇博客这种面向xml配置配置Spring存储Bean对象,那得在spring的配置文件中添加多少bean标签,并且id还不能够重复!所以这种操作Spring存储和读取Bean对象的做法太过复杂.
接下来我们来看如何更简单的实现Spring中存储Bean对象的存取操作!!!
首先,Spring项目的创建——这个没有什么好说的!就按我们上篇博客的步骤来进行。
但注意:与上篇博客相比,spring的配置文件发生了改变——为了更简单的实现Bean对象的存入(把对象存到spring中)
更改后的spring配置文件(resources
目录下的spring-config.xml)
<?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">
<content:component-scan base-package="在对象储存中,要扫描的路径"></content:component-scan>
</beans>
同时pom.xml中添加Spring的框架支持,xml配置如下:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.27</version>
</dependency>
</dependencies>
二、储存Bean对象和使用Bean对象
首先我们回忆下,我们执行储存Bean对象的方式。
之前我们存储 Bean 时,需要在 spring-config 中添加⼀⾏ bean 注册内容才⾏,如下图所示:这种存入 Bean 的方式,并不是很好!
1、需要手动添加 bean 对象 到 配置文件中
2、如果 是配置文件中出现了问题,不好调试。
而现在我们不需要一个一个的在spring配置文件中添加我们要储存的Bean对象。我们直接:
你以为把扫描路径添加到配置文件中就行了吗?
不过你还需要再类中添加注解——再扫描的过程中,只有添加了注解的类才能成功存储到spring中
这就引起了我们注解的概念
1. 添加存储对象的注解 (这一步完成Spring容器已经有了Bean对象)
(1)类注解
- @Controller,控制层(前端参数校验)
- @Service,服务层(数据组装和接口调用)
- @Repository,数据持久层(负责和数据库进行交互)
- @Component,组件(非其他四类)
- @Configuration,配置层(系统运行前,提前加载一些资源)
@Controller
public class UserController {
public void sayHello(){
System.out.println("hello Controller");
}
}
- 使用注解注入Bean对象时,bean的 id 默认用
类名的小驼峰
的表示- 如果原类名的第一个字母和第二个字母都是大写,那么此时的bean
id为原类名
不变bean对象 为什么这样命名?让我们一起来翻看一下源码>> (五大类注解时的命名规则)
下面是添加注解后获取bean对象的示例: (其他四个注解与此用法相同)
public class Application {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//bean id为类名改写为小驼峰方式
UserController userController = context.getBean("userController",UserController.class);
userController.sayHello();
}
}
为什么需要五大类注解
既然这5大类注解的用法这么相似,那为啥还要分成5个不同的注解?统一弄成一个注解不好吗?
这就与每个省市都有自己限定名称的车牌号一样,不仅能够节约号码,同时让别人看到能够大致清楚这是哪里的车牌号。我们的类注解也是这样,在项目开发中,一个项目中可能有特别多的类,并且不同的类可能归属于不同的层级,我们这几种类注解就是让程序员看到之后,能够直接了解该类的用途层级。如下图所示:
更详细一点:
- @Controller——控制器存储
存储逻辑控制层的Bean对象。归属于程序的逻辑控制层。前端发送的数据通常会先经过逻辑控制层的控制器来判断传递的参数是否合法 - @Service——服务存储
存储服务层的Bean对象。归属于程序的服务层。前端数据在经过控制器校验合法后将数据传递到服务层,而一个客户服务可能需要操控多张表,我们的服务层就负责调度底层的数据存储层逻辑来进行对应请求服务的处理。 - @Repository——仓库存储
存储数据处理层的Bean对象。归属于持久层。通常情况下每一个表都会对应一个@Repository对象。该对象位于数据处理层,接受服务层的调度,完成对应的业务处理逻辑中涉及到的数据操作,这一层将直接与数据库打交道 - @Component——组件存储
存储具有通用性质的工具类对象,归属于公共工具类,这些工具类提供了一些公共方法。 - @Configuration——配置存储
归属于配置层,用来配置当前项目中的一些信息
(2)方法注解
@Bean注解加到方法上,将方法的返回值添加到容器中
注意: @Bean注解必须结合五大类注解来使用,并且方法必须有返回值
@Component
public class Students {
@Bean
public Student s1(){
Student student = new Student();
student.setName("小诗诗");
student.setAge(18);
return student;
}
}
public class App1 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Student student = context.getBean("s1",Student.class);
System.out.println(student);
}
}
@Bean命名规则:(使用方法注解配合类注解时的命名规则)
- 当没有设置name属性时,方法名为bean名称
- 当设置了name属性,只能通过name属性对应的值来获取,使用方法名就获取不到
@Component
public class Students {
@Bean(name = "student1")
public Student s1(){
Student student = new Student();
student.setName("小诗诗");
student.setAge(18);
return student;
}
}
public class App1 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Student student = context.getBean("student1",Student.class);
System.out.println(student);
}
}
@Bean注解在起名字的时候,也可以起多个名字:
@Bean(name = {"user1","user2","user3"})
public User getUser(){
User user = new User();
user.setUsername("张三");
user.setAge(10);
return user;
}
(3) 类注解和方法注解的区别
- 类注解(如
@Component
、@Service
、@Repository
或@Controller
):这种方式告诉 Spring 框架,需要为这个类创建一个 bean 实例,并将其注册到 Spring 容器中。这个类的构造方法(如果有的话)会被自动调用,以创建新的实例。但是,你不能直接在注解中指定任何参数。Spring 将尝试使用容器中可用的 bean 作为参数,进行自动装配。如果需要更复杂的初始化过程,可以使用@PostConstruct
注解的方法或者实现InitializingBean
接口。 - 方法注解(
@Bean
):这种方式更加灵活。它允许你在一个方法中定义 bean 的创建逻辑,可以包括任意的代码。这个方法可以接收参数,Spring 将尝试使用容器中可用的 bean 作为参数,进行自动装配。这个方法返回的对象将被注册到 Spring 容器中。这种方式特别适合于创建你无法修改的类的实例(例如,来自第三方库的类),或者当你需要在创建 bean 时执行一些自定义逻辑。
2. 对象装配/对象注入 (你的代码可以使用被注入的对象来完成各种任务)
在Spring中,@Autowired
和@Resource
注解用于自动装配(自动注入)Bean。装配方式主要有属性注入、构造方法注入和Setter方法注入。
其中,@Autowired注解可以用于标注 属性注入、Setter注入以及构造方法注入;而@Resource只能用于标注 属性注入和Setter注入,不能用于标注构造方法注入!
(1) 属性注入 (日常开发中使用最多的一种注入方式)
这是最常见的注入方式。在类的字段上直接使用@Autowired
或@Resource
注解,Spring将自动查找并注入匹配的Bean。
@RestController
public class UserController {
// 属性对象
@Autowired
private UserService userService;
@RequestMapping("/add")
public UserInfo add(String username, String password) {
return userService.add(username, password);
}
}
优点:
- 属性注入最大的优点就是实现简单、使用简单
缺点:
- 功能性问题:无法注入一个不可变的对象(final 修饰的对象);
- 通用性问题:只能适应于 IoC 容器;
- 设计原则问题:更容易违背单一设计原则。
(2) Setter方法注入
在类的setter方法上使用@Autowired
或@Resource
注解,Spring将自动查找并注入匹配的Bean。
@RestController
public class UserController {
// Setter 注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@RequestMapping("/add")
public UserInfo add(String username, String password) {
return userService.add(username, password);
}
}
优点:
- 要说 Setter 注入有什么优点的话,那么首当其冲的就是它完全符合单一职责的设计原则,因为每一个 Setter 只针对一个对象。
缺点:
- 不能注入不可变对象(final 修饰的对象);
- 注入的对象可被修改。(通过提供的set方法修改)
原因:final修饰的变量必须直接赋值
(3) 构造方法注入 ( Spring 官方从 4.x 之后推荐的注入方式)
在类的构造方法上使用@Autowired
或注解,Spring将自动查找并注入匹配的Bean。
如果当前类中只存在一个构造方法,那么@Autowired可以省略,多个构造方法时则不可以省略
@RestController
public class UserController {
// 构造方法注入
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@RequestMapping("/add")
public UserInfo add(String username, String password) {
return userService.add(username, password);
}
}
构造方法注入相比于前两种注入方法,它可以注入不可变对象,并且它只会执行一次,也不存在像 Setter 注入那样,被注入的对象随时被修改的情况,它的优点有以下 4 个:
1. 可注入不可变对象
2. 注入对象不会被修改:
构造方法注入不会像 Setter 注入那样,构造方法在对象创建时只会执行一次,因此它不存在注入对象被随时(调用)修改的情况。
3. 注入对象会被完全初始化:
因为依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,会被完全初始化,这也是构造方法注入的优点之一。
4. 通用性更好
构造方法和属性注入不同,构造方法注入可适用于任何环境,无论是 IoC 框架还是非 IoC 框架,构造方法注入的代码都是通用的,所以它的通用性更好。
(4) @Autowired与@Resource区别
- 出身不同:@Resource来自于jdk,@Autowired时Spring框架提供的
- 用法不同:@Autowried可以用于属性注入,构造方法注入,Setter注入,@Resource不支持构造方法注入
- 参数不同:@Resource支持更多的参数设置,比如name,type,而@Autowried只支持required参数
关于@Autowired
和@Resource
的具体区别,以下是一些主要的点:
@Autowired
是Spring框架的注解,它默认通过类型进行注入,然后是按照字段名称进行匹配(类型相同时)。如果Spring上下文中存在多个相同类型的Bean,你需要与@Qualifier
注解一起使用来指定Bean的名称。如果没有找到匹配的Bean,它将抛出异常。如果你希望允许为null的值,你可以设置其required
属性为false
。@Resource
是Java EE的注解,它默认通过名称进行注入,然后是按照类型进行匹配(名称相同时)。它有两个重要的属性:name和type。如果你使用name属性,那么你使用的是按名称自动注入策略,如果你使用type属性,那么你使用的是按类型自动注入策略。如果既没有指定name也没有指定type,那么默认使用按名称的自动注入策略。如果没有找到匹配的Bean,那么会回退到按类型匹配,并自动注入如果找到匹配的Bean.@Resource
的装配顺序是:如果指定了name和type,那么只有在Spring上下文中找到唯一匹配的Bean才会装配,如果没有找到,将抛出异常。如果指定了name,将装配上下文中匹配name(ID)的Bean,如果没有找到,将抛出异常。如果指定了type,当在上下文中找到唯一匹配的Bean类型时进行装配,如果没有找到或找到多个,将抛出异常。如果既没有指定name也没有指定type,装配将自动按名称进行,如果没有找到匹配,回退是按原始类型匹配,如果找到匹配,将自动装配。
(5) 注入异常问题
当我们的ioc容器中存在了多个相同类型bean对象,再通过上方的注入方式来获取的话,程序就会报异常!
在spring容器中找bean有两种方式:
- 根据bean的名称也就是bean id
- 根据bean的类型也就是bean class
先创建一个Cat类,属性有name和color,并提供get,set,toString方法
往Spring容器中存入多个Cat对象 (方法注入)
@Controller
public class Cats {
@Bean
public Cat cat1(){
Cat cat = new Cat();
cat.setName("糯米");
cat.setColor("白色");
return cat;
}
@Bean
public Cat cat2(){
Cat cat = new Cat();
cat.setName("汤圆");
cat.setColor("黑色");
return cat;
}
}
在另一个类中获取Cat对象 (对象注入,你的代码可以使用被注入的对象来完成各种任务)
@Controller
public class CatController {
@Autowired
private Cat cat;
public void getCat(){
System.out.println(cat);
}
}
创建启动类
public class App3 {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
CatController catController = context.getBean(CatController.class);
catController.getCat();
}
}
发现报错了,因为Spring中已经注入了两个Cat对象,所以此时不知道获取哪个对象
它默认通过类型进行注入,但是在匹配过程中发现了两个相同的bean对象,然后按照字段名称进行匹配,发现找不到所以报错了!!!
三种解决方案
- 精确的描述bean的名称(将注入的名称写对)
- 使用
@Rsource设置name
方式来重命名注入对象 - 使用
@Autowired+@Qualifier
来筛选bean对象
方案一:精确的描述bean的名称(将注入的名称写对)
@Controller
public class CatController {
@Autowired
private Cat cat1; //精确描述bean名称
public void getCat(){
System.out.println(cat1);
}
}
方案二: 使用@Rsource设置name方式来重命名注入对象
@Controller
public class CatController {
@Resource(name = "cat2")
private Cat cat;
public void getCat(){
System.out.println(cat);
}
}
方案三:使用@Autowired+@Qualifier来筛选bean对象
@Controller
public class CatController {
@Autowired
@Qualifier(value = "cat1")
private Cat cat;
public void getCat(){
System.out.println(cat);
}
}