经过上一篇博客,我们已经可以实现基本的
Spring
读取和存储对象的操作了,但在操作的过程中我们发 现读取和存储对象并没有想象中的那么“
简单
”
,所以接下来我们要学习更加简单的操作
Bean
对象的方法。
在
Spring
中想要
更简单的存储和读取对象的核心是使用注解
,也就是我们接下来要学习
Spring
中的相关注解,来存储和读取 Bean
对象。
一、存储 Bean 对象
之前我们存储
Bean
时,需要在
spring-config
中添加一行
bean
注册内容才行,如下图所示:
而现在我们只需要一个注解就可以替代之前要写一行配置的尴尬了,不过在开始存储对象之前,我们先要来点准备工作。
1.1 前置工作:配置扫描路径(重要)
注意:想要将对象成功的存储到
Spring
中,我们需要配置一下存储对象的扫描包路径,只有被配置的包下的所有类,添加了注解才能被正确的识别并保存到 Spring
中。
在
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="com.bit.service"></content:componentscan>
</beans>
其中标红的一行为注册扫描的包,如下图所示:
也就是说,即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到
Spring
中
的。
1.2 添加注解存储 Bean 对象
想要将对象存储在
Spring
中,有两种注解类型可以实现:
1. 类注解: @Controller 、 @Service 、 @Repository 、 @Component 、 @Configuration 。2. 方法注解: @Bean 。
接下来我们分别来看。
1.2.1 @Controller(控制器存储)
使用
@Controller
存储
bean
的代码如下所示:
@Controller // 将对象存储到 Spring 中
public class UserController {
public void sayHi(String name) {
System.out.println("Hi," + name);
}
}
此时我们先使用之前读取对象的方式来读取上面的
UserController
对象,如下代码所示:
public class Application {
public static void main(String[] args) {
// 1.得到 spring 上下文
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到 bean
UserController userController = (UserController)
context.getBean("userController");
// 3.调用 bean 方法
userController.sayHi("Bit");
}
}
1.2.2 @Service(服务存储)
使用
@Service
存储
bean
的代码如下所示:
@Service
public class UserService {
public void sayHi(String name) {
System.out.println("Hi," + name);
}
}
读取 bean 的代码:
class App {
public static void main(String[] args) {
// 1.得到 spring 上下文
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到 bean
UserService userService = (UserService) context.getBean("userService");
// 3.调用 bean 方法
userService.sayHi("Bit");
}
}
1.2.3 @Repository(仓库存储)
使用
@Repository
存储
bean
的代码如下所示:
@Repository
public class UserRepository {
public void sayHi(String name) {
System.out.println("Hi," + name);
}
}
读取
bean
的代码:
class App {
public static void main(String[] args) {
// 1.得到 spring 上下文
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到某个 bean
UserRepository userRepository = (UserRepository)
context.getBean("userRepository");
// 3.调用 bean 方法
userRepository.sayHi("Bit");
}
}
1.2.4 @Component(组件存储)
使用
@Component
存储
bean
的代码如下所示:
@Component
public class UserComponent {
public void sayHi(String name) {
System.out.println("Hi," + name);
}
}
读取
bean
的代码:
class App {
public static void main(String[] args) {
// 1.得到 spring 上下文
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到某个 bean
UserComponent userComponent = (UserComponent)
context.getBean("userComponent");
// 3.调用 bean 方法
userComponent.sayHi("Bit");
}
}
1.2.5 @Configuration(配置存储)
使用
@Configuration
存储
bean
的代码如下所示:
@Configuration
public class UserConfiguration {
public void sayHi(String name) {
System.out.println("Hi," + name);
}
}
读取
bean
的代码:
class App {
public static void main(String[] args) {
// 1.得到 spring 上下文
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到某个 bean
UserConfiguration userConfiguration = (UserConfiguration)
context.getBean("userConfiguration");
// 3.调用 bean 方法
userConfiguration.sayHi("Bit");
}
}
1.3 为什么要这么多类注解?
既然功能是一样的,为什么需要这么多的类注解呢?
这和为什么每个省
/
市都有自己的车牌号是一样的?比如陕西的车牌号就是:陕
X
:
XXXXXX
,北京的车牌号:京X
:
XXXXXX
,一样。甚至一个省不同的县区也是不同的,比如西安就是,陕
A
:
XXXXX
,咸阳:陕B:
XXXXXX
,宝鸡,陕
C
:
XXXXXX
,一样。这样做的好处除了可以节约号码之外,更重要的作用是可以直观的标识一辆车的归属地。
那么为什么需要怎么多的类注解也是相同的原因,就是让程序员看到类注解之后,就能直接了解当前类的用途,比如:
- @Controller:表示的是业务逻辑层;
- @Servie:服务层;
- @Repository:持久层;
- @Configuration:配置层。
程序的工程分层,调用流程如下:
1.3.1
类注解之间的关系
查看
@Controller / @Service / @Repository / @Configuration
等注解的源码发现:
其实这些注解里面都有一个注解 @Component,说明它们本身就是属于 @Component 的“子类”。
1.3.2 注意 Bean 的命名
通过上面示例,我们可以看出,通常我们
bean
使用的都是标准的大驼峰命名,而读取的时候首字母小写就可以获取到 bean
了,如下图所示:
然而,当我们首字母和第二个字母都是大写时,就不能正常读取到 bean 了,如下图所示:
这个时候,我们就要查询 Spring 关于 bean 存储时生成的命名规则了。
我们可以在 Idea 中使用搜索关键字“beanName”可以看到以下内容:
顺藤摸瓜,我们最后找到了 bean 对象的命名规则的方法
它使用的是 JDK Introspector 中的 decapitalize 方法,源码如下:
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
// 如果第一个字母和第二个字母都为大写的情况,是把 bean 的首字母也大写存储了
if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) &&
Character.isUpperCase(name.charAt(0))){
return name;
}
// 否则就将首字母小写
char chars[] = name.toCharArray();
chars[0] = Character.toLowerCase(chars[0]);
return new String(chars);
}
所以对于上面报错的代码,我们只要改为以下代码就可以正常运行了:
1.4 方法注解 @Bean
类注解是添加到某个类上的,而方法注解是放到某个方法上的,如以下代码的实现:
public class Users {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
}
然而,当我们写完以上代码,尝试获取
bean
对象中的
user1
时却发现,根本获取不到:
public class Application {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
User user = (User) context.getBean("user1");
System.out.println(user.toString());
}
}
以上程序的执行结果如下:
这是为什么嘞?
1.4.1 方法注解要配合类注解使用
在
Spring
框架的设计中,方法注解
@Bean
要配合类注解才能将对象正常的存储到
Spring
容器中,如下代码所示:
@Component
public class Users {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
}
再次执行以上代码,运行结果如下:
1.4.2 重命名 Bean
可以通过设置
name
属性给
Bean
对象进行重命名操作,如下代码所示:
@Component
public class Users {
@Bean(name = {"u1"})
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
}
此时我们使用
u1
就可以获取到
User
对象了,如下代码所示:
class App {
public static void main(String[] args) {
// 1.得到 spring 上下文
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 2.得到某个 bean
User user = (User) context.getBean("u1");
// 3.调用 bean 方法
System.out.println(user);
}
}
这个重命名的
name
其实是一个数组,一个
bean
可以有多个名字:
@Bean(name = {"u1", "us1"})
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
并且
name={}
可以省略,如下代码所示:
@Bean({"u1", "us1"})
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
二、获取 Bean 对象(对象装配)
获取
bean
对象也叫做
对象装配
,是把对象取出来放到某个类中,有时候也叫
对象注入
。
对象装配(对象注入)的实现方法以下
3
种:
1. 属性注入2. 构造方法注入3. Setter 注入
接下来,我们分别来看。
下面我们按照实际开发中的模式,将
Service
类注入到
Controller
类中。
2.1 属性注入
属性注入是使用
@Autowired
实现的,将
Service
类注入到
Controller
类中。
Service
类的实现代码如下:
import org.springframework.stereotype.Service;
@Service
public class UserService {
/**
* 根据 ID 获取用户数据
*
* @param id
* @return
*/
public User getUser(Integer id) {
// 伪代码,不连接数据库
User user = new User();
user.setId(id);
user.setName("Java-" + id);
return user;
}
}
Controller
类的实现代码如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
// 注入方法1:属性注入
@Autowired
private UserService userService;
public User getUser(Integer id) {
return userService.getUser(id);
}
}
获取
Controller
中的
getUser
方法
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserControllerTest {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController = context.getBean(UserController.class);
System.out.println(userController.getUser(1).toString());
}
}
最终结果如下:
属性注入的核心实现如下:
2.2 构造方法注入
构造方法注入是在类的构造方法中实现注入,如下代码所示:
@Controller
public class UserController2 {
// 注入方法2:构造方法注入
private UserService userService;
@Autowired
public UserController2(UserService userService) {
this.userService = userService;
}
public User getUser(Integer id) {
return userService.getUser(id);
}
}
注意事项:如果类只有一个构造方法,那么
@Autowired
注解可以省略;如果类中有多个构造方
法,那么需要添加上
@Autowired
来明确指定到底使用哪个构造方法。
2.3 Setter 注入
Setter
注入和属性的
Setter
方法实现类似,只不过
在设置
set
方法的时候需要加上
@Autowired
注
解
,如下代码所示:
@Controller
public class UserController3 {
// 注入方法3:Setter注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public User getUser(Integer id) {
return userService.getUser(id);
}
}
2.4 三种注入优缺点分析
- 属性注入的优点是简洁,使用方便;缺点是只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)。
- 构造方法注入是 Spring 推荐的注入方式,它的缺点是如果有多个注入会显得比较臃肿,但出现这种情况你应该考虑一下当前类是否符合程序的单一职责的设计模式了,它的优点是通用性,在使用之前一定能把保证注入的类不为空。
- Setter 方式是 Spring 前期版本推荐的注入方式,但通用性不如构造方法,所有 Spring 现版本已经推荐使用构造方法注入的方式来进行类注入了。
2.5 @Resource:另一种注入关键字
在进行类注入时,除了可以使用
@Autowired
关键字之外,我们还可以使用
@Resource
进行注入,如下代码所示:
@Controller
public class UserController {
// 注入
@Resource
private UserService userService;
public User getUser(Integer id) {
return userService.getUser(id);
}
}
@Autowired
和
@Resource
的区别
- 出身不同:@Autowired 来自于 Spring,而 @Resource 来自于 JDK 的注解;
- 使用时设置的参数不同:相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如:name 设置,根据名称获取 Bean。
2.6 同一类型多个 @Bean 报错
当出现以下多个
Bean
,返回同一对象类型时程序会报错,如下代码所示:
@Component
public class Users {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
@Bean
public User user2() {
User user = new User();
user.setId(2);
user.setName("MySQL");
return user;
}
}
在另一个类中获取
User
对象,如下代码如下:
@Controller
public class UserController4 {
// 注入
@Resource
private User user;
public User getUser() {
return user;
}
}
以上程序的执行结果如下:
报错的原因是,非唯一的
Bean
对象。
同一类型多个
Bean
报错处理
解决同一个类型,多个
bean
的解决方案有以下两个:
- 使用 @Resource(name="user1") 定义。
- 使用 @Qualifier 注解定义名称。
① 使用 @Resource(name="XXX")
@Controller
class UserController4 {
// 注入
@Resource(name = "user1")
private User user;
public User getUser() {
return user;
}
}
② 使用 @Qualifier
@Controller
public class UserController5 {
// 注入
@Autowired
@Qualifier(value = "user2")
private User user;
public User getUser() {
return user;
}
}