前言:
作者简介:热爱编程的小七,致力于C、Java、Python等多编程语言,热爱编程和长板的运动少年!
相关专栏Java基础语法,JavaEE初阶,数据库,数据结构和算法系列等,大家有兴趣的可以看一看。
有兴趣的话关注博主一起学习,一起进步吧!
想要将对象存储在 Spring 中,有两种注解类型可以实现:✍️
1. 类注解:
@Controller:【控制器】校验参数的合法性(安检系统)
@Service:【服务】业务组装(客服中心)
@Repository:【数据持久层】实际业务处理(实际办理的业务)
@Component:【组件】工具类层
@Configuration:【配置层】配置
(可同时使用注解和XML存储Bean)
2. 方法注解:@Bean。
//将对象存储到Spring容器中
@Controller
public class UserController {
public void sayHi(){
System.out.println("Hi,UserController!");
}
}
此时我们先使用之前读取对象的方式来读取上面的 UserController 对象,如下代码所示:
public class App {
public static void main(String[] args) {
//1.得到Spring上下文
ApplicationContext contest=new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到bean对象
UserController userController=contest.getBean("userController",UserController.class);
//3.使用bean对象
userController.sayHi();
}
}
//将对象存储到Spring容器中
@Service
public class UserService {
public void sayHi(){
System.out.println("Hi,UserService!");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
//1.得到Spring上下文
ApplicationContext contest=new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到bean对象
UserService userService=contest.getBean("userService",UserService.class);
//3.使用bean对象
userService.sayHi();
}
}
@Repository
public class UserRepository {
public void sayHi(){
System.out.println("Hi,UserRepository!");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
//1.得到Spring上下文
ApplicationContext contest=new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到bean对象
UserRepository userRepository=contest.getBean("userRepository",UserRepository.class);
//3.使用bean对象
userRepository.sayHi();
}
}
@Component
public class User {
public void sayHi(){
System.out.println("Hi User!");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
//1.得到Spring上下文
ApplicationContext contest=new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到bean对象
User user=contest.getBean("user",User.class);
//3.使用bean对象
user.sayHi();
}
}
@Configuration
public class User {
public void sayHi(){
System.out.println("Hi User!");
}
}
读取 bean 的代码:
public class App {
public static void main(String[] args) {
//1.得到Spring上下文
ApplicationContext contest=new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到bean对象
User user=contest.getBean("user",User.class);
//3.使用bean对象
user.sayHi();
}
}
既然功能是一样的,为什么需要这么多的类注解呢?
原因就是让程序员看到类注解之后,就能直接了解当前类的用途,比如:
程序的工程分层,调用流程如下:
查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:
其实这些注解 都有 个注解 @Component,说明它们本身就是属于 @Component 的“ 类”。
通过上述示例,我们可以看出,通常我们 bean 使用的都是标准的大驼峰命名, 读取的时候小写就可以获取到 bean 了,如下图所示:
然而 ,当我们首字母和第二个字母都是大写时,就不能正常读取到 bean 了,如下图所示:
这个时候,我们就要查询 Spring 关于 bean 存储时生成的命名规则了。
在IDEA中按两次 shift 键可以查询关键字
最后找到了 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);
}
所以对于上面报错的代码,我们只要改为以下代码就可以正常运行了:
总结:
如果首字母是大写,第二个字母是小写,那么Bean的名称就是类名小写。
如果不满足首字母大写和第二个字母小写的情况,那么Bean的名称就是原类名。
类注解是添加到某个类上的,而方法注解是放到某个方法上的,如以下代码的实现:
public class User {
private int id;
private String name;
public void setId(int id){
this.id=id;
}
public void setName(String name){
this.name=name;
}
public String toString(){
return "{id="+id+",name="+name+"]";
}
public void sayHi(){
System.out.println("Hi User!");
}
}
public class Users {
@Bean
public User users(){
User user=new User();
user.setId(1);
user.setName("张三");
return user;
}
}
然而,当我们写完以上代码,尝试获取 bean 对象中的 user1 时却发现,根本获取不到:
public class App {
public static void main(String[] args) {
//1.得到Spring上下文
ApplicationContext contest=new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到bean对象
User user=contest.getBean("user",User.class);
//3.使用bean对象
System.out.println(user);
}
}
这是为什么呢?
在 Spring 框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到 Spring 容器中,如下代码所示:
@Component
public class Users {
@Bean
public User user1(){
User user=new User();
user.setId(1);
user.setName("张三");
return user;
}
}
public class App {
public static void main(String[] args) {
//1.得到Spring上下文
ApplicationContext contest=new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到bean对象
User user=(User)contest.getBean("user1");
//3.使用bean对象
System.out.println(user.toString());
}
}
@Bean获取时的注意事项:@Bean的默认命名 = 方法名
可以通过设置 name 属性给 Bean 对象进行重命名操作,如下代码所示:
@Component
public class Users {
@Bean(name = "u1")
public User user1(){
User user=new User();
user.setId(1);
user.setName("张三");
return user;
}
}
此时使用原来的类名首字母小写是否能正确获取到对象呢?
默认命名注意事项:当@Bean重命名之后,那么默认的使用方法名获取Bean对象的方式就不能使用了。
同样我们可以重命名多个值,如下代码所示:
@Component
public class Users {
//通过花括号包起来
@Bean(name = {"u1", "u2"})
public User user1(){
User user=new User();
user.setId(1);
user.setName("张三");
return user;
}
}
并且 name={} 可以省略,如下代码所示:
@Component
public class Users {
//通过花括号包起来
@Bean({"u1", "u2"})
public User user1(){
User user=new User();
user.setId(1);
user.setName("张三");
return user;
}
}
注意事项:如果多个Bean使用相同的名称,那么程序执行时不会报错,但是第一个Bean之后的对象不会被存放到容器中,也就是只有在第一次创建Bean的时候会将对应的Bean名称关联起来,后续再有相同的名称的Bean存储的时候,容器会自动忽略。
获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注入。
对象装配(对象注入)的实现方法以下 3 种:
1. 属性注入
2. 构造方法注入
3. Setter 注入
属性注入是使用 @Autowired 实现的,将 Service 类注入到 Controller 类中。
//将对象存储到Spring容器中
@Service
public class UserService {
public User getUser(){
//伪代码的实现
User user=new User();
user.setId(1);
user.setName("张三");
return user;
}
}
//将对象存储到Spring容器中
@Controller
public class UserController {
@Autowired
private UserService userService;
public User getUser(){
return userService.getUser();
}
}
public class App {
public static void main(String[] args) {
//1.得到Spring上下文
ApplicationContext contest=new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到bean对象
UserController userController=contest.getBean("userController",UserController.class);
//3.使用bean对象
System.out.println(userController.getUser().toString());
}
}
属性注入最大的优点就是实现简单、使用简单,只需要给变量上添加一个注解(@Autowired),就可以在不 new 对象的情况下,直接获得注入的对象了(这就是 DI 的功能和魅力所在),所以它的优点就是使用简单。
然而,属性注入虽然使用简单,但也存在着很多问题,甚至编译器 Idea 都会提醒你“不建议使用此注入方式”,Idea 的提示信息如下:
属性注入的缺点主要包含以下 3 个:
使用属性注入无法注入一个不可变的对象(final 修饰的对象),如下图所示:
原因也很简单:在 Java 中 final 对象(不可变)要么直接赋值,要么在构造方法中赋值,所以当使用属性注入 final 对象时,它不符合 Java 中 final 的使用规范,所以就不能注入成功了。
PS:如果要注入一个不可变的对象,要怎么实现呢?使用下面的构造方法注入即可。
使用属性注入的方式只适用于 IoC 框架(容器),如果将属性注入的代码移植到其他非 IoC 的框架中,那么代码就无效了,所以属性注入的通用性不是很好。
使用属性注入的方式,因为使用起来很简单,所以开发者很容易在一个类中同时注入多个对象,而这些对象的注入是否有必要?是否符合程序设计中的单一职责原则?就变成了一个问题。 但可以肯定的是,注入实现越简单,那么滥用它的概率也越大,所以出现违背单一职责原则的概率也越大。 注意:这里强调的是违背设计原则(单一职责)的可能性,而不是一定会违背设计原则,二者有着本质的区别。
构造方法注入是在类的构造方法中实现注入,如下代码所示:
//将对象存储到Spring容器中
@Controller
public class UserController {
private UserService userService;
public User getUser(){
return userService.getUser();
}
@Autowired
public UserController(UserService userService){
this.userService=userService;
}
}
注意:如果只有一个构造方法,那么 @Autowired 注解可以省略,如下图所示:
但是如果类中有多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法,否则程序会报错,如下图所示:
使用构造方法注入可以注入不可变对象,如下代码所示:
构造方法注入不会像 Setter 注入那样,构造方法在对象创建时只会执行一次,因此它不存在注入对象被随时(调用)修改的情况。
因为依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,会被完全初始化,这也是构造方法注入的优点之一。
构造方法和属性注入不同,构造方法注入可适用于任何环境,无论是 IoC 框架还是非 IoC 框架,构造方法注入的代码都是通用的,所以它的通用性更好。
Setter 注入和属性的 Setter 方法实现类似,只不过在设置 set 方法的时候需要加上 @Autowired 注解,如下代码所示:
@Controller
public class UserController {
private UserService userService;
public User getUser(){
return userService.getUser();
}
/* public UserController(UserService userService){
this.userService=userService;
}*/
@Autowired
public void setUserService(UserService userService){
this.userService=userService;
}
}
注意:若不加 @Autowired 注解会报错
使用 Setter 注入依然不能注入不可变对象,比如以下注入会报错:
Setter 注入提供了 setXXX 的方法,意味着你可以在任何时候、在任何地方,通过调用 setXXX 的方法来改变注入对象,所以 Setter 注入的问题是,被注入的对象可能随时被修改。
在进行类注入时,除了可以使用 @Autowired 关键字之外,我们还可以使用 @Resource 进行注入,如下代码所示:
//将对象存储到Spring容器中
@Controller
public class UserController {
@Resource
private UserService userService;
public User getUser(){
return userService.getUser();
}
}
@Autowired 和 @Resource 来自不同的“父类”,其中 @Autowired 是 Spring 定义的注解,而 @Resource 是 Java 定义的注解,它来自于 JSR-250(Java 250 规范提案)。
小知识:JSR 是 Java Specification Requests 的缩写,意思是“Java 规范提案”。任何人都可以提交 JSR 给 Java 官方,但只有最终确定的 JSR,才会以 JSR-XXX 的格式发布,如 JSR-250,而被发布的 JSR 就可以看作是 Java 语言的规范或标准。
依赖注入的功能,是通过先在 Spring IoC 容器中查找对象,再将对象注入引入到当前类中。而查找有分为两种实现:按名称(byName)查找或按类型(byType)查找,其中 @Autowired 和 @Resource 都是既使用了名称查找又使用了类型查找,但二者进行查找的顺序却截然相反。
@Autowired 是先根据类型(byType)查找,如果存在多个 Bean 再根据名称(byName)进行查找,它的具体查找流程如下:
@Resource 是先根据名称查找,如果(根据名称)查找不到,再根据类型进行查找,它的具体流程如下图所示:
@Autowired 和 @Resource 在使用时都可以设置参数,比如给 @Resource 注解设置 name 和 type 参数,实现代码如下:
@Resource(name = "userinfo", type = UserInfo.class)
private UserInfo user;
但二者支持的参数以及参数的个数完全不同,其中 @Autowired 只支持设置一个 required 的参数,而 @Resource 支持 7 个参数,支持的参数如下图所示:
其中,@Autowired 支持属性注入、构造方法注入和 Setter 注入,而 @Resource 只支持属性注入和 Setter 注入,当使用 @Resource 实现构造方法注入时就会提示以下错误:
当使用 IDEA 专业版在编写依赖注入的代码时,如果注入的是 Mapper 对象,那么使用 @Autowired 编译器会提示报错信息,报错内容如下图所示:
虽然 IDEA 会出现报错信息,但程序是可以正常执行的。 然后,我们再将依赖注入的注解更改为 @Resource 就不会出现报错信息了,具体实现如下:
当出现以下多个 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;
}
}
//将对象存储到Spring容器中
@Controller
public class UserController {
// 注入
@Resource
private User user;
public User getUser() {
return user;
}
}
public class App {
public static void main(String[] args) {
//1.得到Spring上下文
ApplicationContext contest=new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到bean对象
UserController userController=contest.getBean("userController",UserController.class);
//3.使用bean对象
System.out.println(userController.getUser().toString());
}
}
报错的原因是,非唯一的 Bean 对象。
解决同一个类型,多个 bean 的解决方案有以下两个:
1.将属性的名字和Bean的名字对应上
2.使用 @Qualifier 注解定义名称,配合@Autowired一起使用。
//将对象存储到Spring容器中
@Controller
public class UserController {
// 注入
@Resource(name="user1")
private User user;
public User getUser() {
return user;
}
}
使用 @Qualifier:
//将对象存储到Spring容器中
@Controller
public class UserController {
// 注入
@Resource
@Qualifier(value="user1")
private User user;
public User getUser() {
return user;
}
}