经过前⾯的学习,我们已经可以实现基本的 Spring 读取和存储对象的操作了,但在操作的过程中我们发现读取和存储对象并没有想象中的那么“简单”,所以接下来我们要学习更加简单的操作 Bean 对象的⽅法。
在 Spring 中想要更简单的存储和读取对象的核⼼是使⽤注解,也就是我们接下来要学习 Spring 中的相关注解,来存储和读取 Bean 对象。
之前我们存储 Bean 时,需要在 spring-config 中添加⼀⾏ bean 注册内容才⾏,如下图所示:
⽽现在我们只需要⼀个注解就可以替代之前要写⼀⾏配置的尴尬了,不过在开始存储对象之前,我们先要来点准备⼯作。
注意:想要将对象成功的存储到 Spring 中,我们需要配置⼀下存储对象的扫描包路径,只有被配置的包下的所有类,添加了注解才能被正确的识别并保存到 Spring 中。
在 spring-config.xml 添加如下配置:
<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.wzr。service">content:component-scan>
beans>
其中标红的⼀⾏为注册扫描的包,如下图所示:
即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的。
想要将对象存储在 Spring 中,有两种注解类型可以实现:
使⽤ @Controller 存储 bean 的代码如下所示:
@Controller
public class UserController {
public void sayHello() {
System.out.println("hello");
}
}
此时我们先使⽤之前读取对象的⽅式来读取上⾯的 UserController 对象,如下代码所示:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
UserController userController = context.getBean("userController", UserController.class);
// 3.操作 Bean 对象
userController.sayHello();
}
}
使⽤ @Service 存储 bean 的代码如下所示:
@Service
public class UserService {
public void doService() {
System.out.println("Do user service.");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
// UserController userController = context.getBean("userController", UserController.class);
// // 3.操作 Bean 对象
// userController.sayHello();
UserService userService =
context.getBean("userService", UserService.class);
userService.doService();
}
}
使⽤ @Repository 存储 bean 的代码如下所示:
@Repository
public class UserRepository {
public void doRepository() {
System.out.println("Do user repository.");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
// UserController userController = context.getBean("userController", UserController.class);
// // 3.操作 Bean 对象
// userController.sayHello();
// UserService userService =
// context.getBean("userService", UserService.class);
// userService.doService();
UserRepository userRepository =
context.getBean("userRepository", UserRepository.class);
userRepository.doRepository();
}
}
使⽤ @Component 存储 bean 的代码如下所示:
@Component
public class UserComponent {
public void doComponent() {
System.out.println("Do user component.");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
// UserController userController = context.getBean("userController", UserController.class);
// // 3.操作 Bean 对象
// userController.sayHello();
// UserService userService =
// context.getBean("userService", UserService.class);
// userService.doService();
// UserRepository userRepository =
// context.getBean("userRepository", UserRepository.class);
// userRepository.doRepository();
UserComponent userComponent =
context.getBean("userComponent", UserComponent.class);
userComponent.doComponent();
}
}
使⽤ @Configuration 存储 bean 的代码如下所示:
@Configuration
public class UserConfiguration {
public void doConfiguration() {
System.out.println("Do user configuration.");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// // 2.使用 getBean 得到 Bean 对象【使用注解默认的命名规则是小驼峰】
// UserController userController = context.getBean("userController", UserController.class);
// // 3.操作 Bean 对象
// userController.sayHello();
// UserService userService =
// context.getBean("userService", UserService.class);
// userService.doService();
// UserRepository userRepository =
// context.getBean("userRepository", UserRepository.class);
// userRepository.doRepository();
// UserComponent userComponent =
// context.getBean("userComponent", UserComponent.class);
// userComponent.doComponent();
UserConfiguration userConfiguration =
context.getBean("userConfiguration", UserConfiguration.class);
userConfiguration.doConfiguration();
}
}
我们之前提到过:即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的。
那么如果我们像把不是在配置的扫描包下的类对象存储到 Spring 中,应该怎么做呢?
我们的第一想法应该是:采用之前我们所学的方法,直接注册 Bean 到 Spring 中。
那么这种方式究竟可不可行呢?
答案是:可行!
例如:
此时无法扫描到 UserConfiguration 这个 Bean,我们在使用 UserConfiguration 的时候就会报错。
但是当我们用之前学的方法,手动的 把 UserConfiguration 注册到 Spring 中:
此时就可以成功使用 UserConfiguration 了。
总结:扫描和手动注册两种方法可以混用!
类的注解其实就是将类进行分类,让程序更加精细化,让程序员看到类注解之后,就能直接了解当前类的⽤途,方便维护,⽐如:
程序的⼯程分层,调⽤流程如下:
查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:
结论:@Controller / @Service / @Repository / @Configuration 都是基于 @Component,它们的作用都是将 Bean 储存到 Spring 中。
默认情况下,使用 5 大类注解的 Bean 名称是将类首字母小写的命名规则。
ex:UserConfiguration -> userConfiguration
特殊情况:当⾸字⺟和第⼆个字⺟都是⼤写时
我们可以在 Idea 中使⽤搜索关键字“beanName”可以看到以下内容:
顺藤摸⽠,我们最后找到了 bean 对象的命名规则的⽅法:
它使⽤的是 JDK Introspector 中的 decapitalize ⽅法,ctrl+左键 得到 源码如下:
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
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);
}
我们可以用一个类来测试一下:
public class App2 {
public static void main(String[] args) {
String name = "UserController"; // 首字母小写
String name2 = "UController"; // 原类名
System.out.println("name: " + Introspector.decapitalize(name));
System.out.println("name2: " + Introspector.decapitalize(name2));
}
}
得到的结果为:
所以对于上⾯报错的代码,我们只要改为以下代码就可以正常运⾏了:
总结:当⾸字⺟和第⼆个字⺟都是⼤写时,那么 Bean 的名称为原类名!
类注解是添加到某个类上的,⽽⽅法注解是放到某个⽅法上的,如以下代码的实现:
public class UserBeans {
@Bean // 方法注解
public User user(){
// 构建数据方法
User user = new User();
user.setId(1);
user.setName("张三");
user.setAge(18);
return user;
}
}
然⽽,当我们写完以上代码,尝试获取 Bean 对象中的 user 时却发现,根本获取不到:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
User user =
context.getBean("user", User.class);
System.out.println("id: " + user.getId() + " | name: " + user.getName());
}
}
以上程序的执⾏结果如下:
这是为什么呢?
在 Spring 框架的设计中,⽅法注解 @Bean 要配合五大类注解才能将对象正常的存储到 Spring 容器中,如下代码所示:
@Component
public class UserBeans {
@Bean // 方法注解
public User user(){
// 构建数据方法
User user = new User();
user.setId(1);
user.setName("张三");
user.setAge(18);
return user;
}
}
再次执⾏以上代码,运⾏结果如下:
我们之前学习了 五大类注解 Bean 命名规则:
刚刚我们也学习到了,方法注解 要配合 类注解 使用,那么 @Bean 注解 的命名规则也和 五大类注解 相同吗?
我们用一个测试类来解决,如下所示:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
User user =
context.getBean("user", User.class);
System.out.println("id: " + user.getId() + " | name: " + user.getName());
}
}
@Component
public class UserBeans {
@Bean // 方法注解
public User user1(){
// 构建数据方法
User user = new User();
user.setId(1);
user.setName("张三");
user.setAge(18);
return user;
}
}
如果程序正确运行,则说明 @Bean 注解 的命名规则也和 五大类注解 相同;反之,则 @Bean 注解 有其独立的命名规则。
其结果为:
程序报错,说明 @Bean 注解 的命名规则也和 五大类注解 不同。
然而,我们对程序做出如下改变:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
User user =
context.getBean("user1", User.class);
System.out.println("id: " + user.getId() + " | name: " + user.getName());
}
}
其结果为:
说明 @Bean 注解的名称是方法名。
我们在使用五大类注解的时候,因为行为规范的原因,类很少有同名的情况出现;但是使用 @Bean 注解的时候会遇到一个问题 -> 不同类下方法名可能相同,例如:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
User user =
context.getBean("user1", User.class);
System.out.println("id: " + user.getId() + " | name: " + user.getName());
}
}
@Component
public class StudentBeans {
@Bean // 方法注解
public User user1(){
// 构建数据方法
User user = new User();
user.setId(1);
user.setName("StudentBeans: 小李");
user.setAge(18);
return user;
}
}
@Component
public class UserBeans {
@Bean // 方法注解
public User user1(){
// 构建数据方法
User user = new User();
user.setId(1);
user.setName("UserBeans: 张三");
user.setAge(18);
return user;
}
}
其运行结果为:
这样就会出现 数据覆盖 的问题,这里的 StudentBeans 的 user1 就被覆盖了,那我们不想让 StudentBeans 的 user1 该怎么办呢?
为了解决上述问题,我们可以通过设置 name 属性给 Bean 对象进⾏重命名操作,如下代码所示:
@Component
public class StudentBeans {
@Bean(name = "student_user1") // 方法注解
public User user1(){
// 构建数据方法
User user = new User();
user.setId(1);
user.setName("StudentBeans: 小李");
user.setAge(18);
return user;
}
}
此时我们使⽤ student_user1 就可以获取到 StudentBeans 的 user1 了,如下代码所示:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
User user =
context.getBean("student_user1", User.class);
System.out.println("id: " + user.getId() + " | name: " + user.getName());
}
}
运行结果为:
其实除了上述方法,还有其他方法进行重命名:
实际上这个重命名的 name 其实是⼀个数组,⼀个 Bean 可以有多个名字,同时 name= 可以省略。
@Component
public class StudentBeans {
// @Bean(name = "student_user1") // 方法注解
// @Bean("stu_user1")
@Bean(name = {"stu_user1", "student_user1"})
public User user1() {
// 构建数据方法
User user = new User();
user.setId(1);
user.setName("StudentBeans: 小李");
user.setAge(18);
return user;
}
}
使用 @Bean 注解并重命名,尝试使⽤原来的类名⾸字⺟⼩写是否能正确获取到对象?
没有重命名前:
public class StudentBeans {
// @Bean(name = "student_user1") // 方法注解
// @Bean("stu_user1")
// @Bean(name = {"stu_user1", "student_user1"})
@Bean
public User studentUser1() {
// 构建数据方法
User user = new User();
user.setId(1);
user.setName("StudentBeans: 小李");
user.setAge(18);
return user;
}
}
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
User user =
context.getBean("studentUser1", User.class);
System.out.println("id: " + user.getId() + " | name: " + user.getName());
}
}
运行结果为:
重命名后:
@Component
public class StudentBeans {
// @Bean(name = "student_user1") // 方法注解
// @Bean("stu_user1")
// @Bean(name = {"stu_user1", "student_user1"})
@Bean(name = "stu_user1")
public User studentUser1() {
// 构建数据方法
User user = new User();
user.setId(1);
user.setName("StudentBeans: 小李");
user.setAge(18);
return user;
}
}
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
User user =
context.getBean("studentUser1", User.class);
System.out.println("id: " + user.getId() + " | name: " + user.getName());
}
}
运行结果为:
结论:使⽤ @Bean 注解并重命名,使用方法名就不能获得 Bean 对象了。
获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注⼊。
对象装配(对象注⼊)的实现⽅法以下 3 种:
接下来,我们分别来看。下⾯我们按照实际开发中的模式,将 Service 类注⼊到 Controller 类中
属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中。
UserService 类的实现代码如下:
@Service
public class UserService {
public void doService() {
System.out.println("Do user service.");
}
}
UserController 类的实现代码如下:
@Controller
public class UserController {
// 读取 UserService[从 Spring 读取]
// 1.属性注入(Field Injection)
@Autowired // 自动装配
private UserService userService;
public void sayHello() {
System.out.println("Do User Controller.");
userService.doService();
}
}
获取 UserController 中的 sayHello ⽅法:
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserController controller =
context.getBean("userController", UserController.class);
controller.sayHello();
}
}
运行结果为:
我们可以使⽤ @Autowired 将 Service 类注⼊到 Controller 类中,那么我们做出如下修改,能否成功运行呢?
答案是:不行!
我们修改一下 Controller 类注入 Service 的对象名称,那么程序还能正确运行吗?
@Controller
public class UserController {
// 读取 UserService[从 Spring 读取]
// 1.属性注入(Field Injection)
@Autowired // 自动装配
private UserService us;
public void sayHello() {
System.out.println("Do User Controller.");
us.doService();
}
}
答案是:可以!
我们之前提到,五大类注解是根据类名进行命名,而 @Bean 注解是根据方法名进行注解;但是到了这里,由于 @Autowired 过于强大,它只需要我们二者满足其一即可!
这是因为,Spring 存储 Bean:实际上是以一个 HashMap
的形式存储。
@Bean key=方法名, value=方法返回的对象.
Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注解,如下代码所示:
@Controller
public class UserController {
// 读取 UserService[从 Spring 读取]
// // 1.属性注入(Field Injection)
// @Autowired // 自动装配【先根据类型查询,之后根据名称查询】
// private UserService us;
// 2.Setter 注入(Setter Injection)
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHello() {
System.out.println("Do User Controller.");
userService.doService();
}
}
构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所示:
@Controller
public class UserController {
// 读取 UserService[从 Spring 读取]
// // 1.属性注入(Field Injection)
// @Autowired // 自动装配【先根据类型查询,之后根据名称查询】
// private UserService us;
// // 2.Setter 注入(Setter Injection)
// private UserService userService;
//
// @Autowired
// public void setUserService(UserService userService) {
// this.userService = userService;
// }
// 3.构造方法注入(Constructor Injection)
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void sayHello() {
System.out.println("Do User Controller.");
userService.doService();
}
}
当当前类有且仅有一个构造方法时,@Autowired 可以省略!
即:当存在多个构造方法时,@Autowired 不能省略!!!!!!
案例:
@Controller
public class UserController {
// 读取 UserService[从 Spring 读取]
// // 1.属性注入(Field Injection)
// @Autowired // 自动装配【先根据类型查询,之后根据名称查询】
// private UserService us;
// // 2.Setter 注入(Setter Injection)
// private UserService userService;
//
// @Autowired
// public void setUserService(UserService userService) {
// this.userService = userService;
// }
// 3.构造方法注入(Constructor Injection)
private final UserService userService;
@Autowired
public UserController(UserService userService) {
System.out.println("----------------- 执行第1个构造方法 ------------------");
this.userService = userService;
}
public void sayHello() {
System.out.println("Do User Controller.");
System.out.println();
userService.doService();
}
}
运行结果为:
**原理:**遵循 Java 的规范。
使用 final 关键字的用法只有两种:
构造方法只会在对象创建的时候执行一次,它不会像 Setter 注入一样,执行多次,所以,不存在注入对象被修改的情况。
依赖对象在使用前一定会被完全初始化,因为依赖是在类的构造方法中执行的,而构造方法是在类创建之初就会执行的方法。
通用性更好,因为构造方法是 Java(JDK)支持【最底层的框架】,所以更换任何的框架,它都是适用的。
其实除了 @Autowired,还有一种注入关键字,也可以实现对象注入,即 @Resource。
@Controller
public class UserController2 {
// // 1.属性注入
// @Autowired
// @Resource
// private UserService userService;
// // 2.Setter注入
// private UserService userService;
//
// @Resource
// public void setUserService(UserService userService) {
// this.userService = userService;
// }
// 3.构造方法注入【不支持】
private UserService userService;
@Resource
public UserController2(UserService userService) {
this.userService = userService;
}
public void doController() {
System.out.println("Do user controller 2.");
System.out.println();
userService.doService();
}
}
这里会有一种问题:
使用 @Resource 进行属性注入 以及 Setter 注入的时候,都和 @Autowired 一样,可以成功注入;但在进行 构造方法注入 的时候,就会报错,如图:
即 @Resource 注解,不能使用在构造方法上。
我们刚刚提到 @Autowired 和 @Resource 两者的参数不同。我们先来看一个例子:
@Component
public class UserBeans {
@Bean(name = "user_user1") // 方法注解
public User user1(){
// 构建数据方法
User user = new User();
user.setId(1);
user.setName("UserBeans: 张三");
user.setAge(18);
return user;
}
@Bean // 方法注解
public User user2(){
// 构建数据方法
User user = new User();
user.setId(1);
user.setName("李四");
user.setAge(18);
return user;
}
}
@Controller
public class UserController3 {
@Autowired // type or name
private User user;
public void doController() {
System.out.println("Do user controller 3.");
System.out.println();
System.out.println("user id: " + user.getId() +
" | name:" + user.getName());
}
}
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserController3 userController3 =
context.getBean("userController3", UserController3.class);
userController3.doController();
}
}
运行结果为:
我们之前在学习 @Autowired 的时候,了解过,@Autowired 实际上是以 方法名和 返回类的类型 来进行注入的,先查询返回类的类型,然后再继续查询方法名,二者满足其一即可注入。回到本案例,这里的方法 user1 和 user2 同样都是 返回 User 对象,所以需要继续查询方法名,但是并没有一个叫 user 的方法,无法分辨到底注入那个方法,就会报错。
这个时候我们想要使用 @Autowired 来解决问题就比较困难了,就需要用到 参数更多的 @Resource,通过更多的参数来获取 Bean,代码如下:
@Controller
public class UserController3 {
// @Autowired // type or name
@Resource(name = "user_user1")
private User user;
public void doController() {
System.out.println("Do user controller 3.");
System.out.println();
System.out.println("user id: " + user.getId() +
" | name:" + user.getName());
}
}
运行结果为:
那有人就有疑问了,使用 @Autowired 比较困难,那如果一定要使用 @Autowired 注解呢?实际上还有一种方法可以解决这个问题,这个时候就要请出一个外援 @Qualifier 注解,代码如下:
@Controller
public class UserController3 {
// @Autowired // type or name
// @Resource(name = "user_user1")
@Autowired
@Qualifier(value = "user_user1")
private User user;
public void doController() {
System.out.println("Do user controller 3.");
System.out.println();
System.out.println("user id: " + user.getId() +
" | name:" + user.getName());
}
}
运行结果为:
在 Spring 项⽬中,通过 main ⽅法获取到 Controller 类,调⽤ Controller ⾥⾯通过注⼊的⽅式调⽤ Service 类,Service 再通过注⼊的⽅式获取到 Repository 类,Repository 类⾥⾯有⼀个⽅法构建⼀个 User 对象,返回给 main ⽅法。Repository ⽆需连接数据库,使⽤伪代码即可。
public class User {
private int id;
private String name;
private 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;
}
}
@Repository
public class MyRepository {
public User doRepository() {
System.out.println("Do my repository.");
User user = new User();
user.setId(1);
user.setName("MyRepository: 王五");
user.setAge(18);
return user;
}
}
@Service
public class MyService {
private MyRepository myRepository;
@Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
public User doService() {
System.out.println("Do my service.");
System.out.println();
return myRepository.doRepository();
}
}
@Controller
public class MyController {
private MyService myService;
@Autowired
public MyController(MyService myService) {
this.myService = myService;
}
public User doController() {
System.out.println("Do my controller.");
System.out.println();
return myService.doService();
}
}
public class App {
public static void main(String[] args) {
// 1.得到 Spring 上下文对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
MyController myController =
context.getBean("myController", MyController.class);
User user = myController.doController();
System.out.println("id: " + user.getId() + " | name: " + user.getName());
}
}