目录
前一章我们学习了Spring如何读取和存储对象,我们是在配置文件中,通过添加bean标签的方式,来将我们的bean注册到Spring容器当中.当我们的项目中bean对象很多的时候,这个方法就很麻烦了,所以接下来我们要学习更加简单的读取和存储bean对象.
在Spring中更简单的存储和读取对象的核心就是使用注解,包括类注解和方法注解.因此我们先来了解一下,Spring当中更简单的存Bean对象的注解.
常用注解
五大类注解
- @Controller:控制器,用来验证用户请求的数据的正确性.
- @Service:服务,用来编排和调度具体的执行方法.
- @Repository:持久层,和数据库进行交互.
- @Component:组件,工具类.
- @Configuration:配置项,项目中的一些配置.
方法注解 @Bean
前置工作
我们在使用注解之前,必须要完成一个前置工作:配置扫描路径.
只有被配置的包下的所有类,添加了注解才能被正确识别并保存到Spring中.
只有在这个包下的类,才会去扫描,是否添加了五大类注解.
<?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.java.demo"></content:component-scan> </beans>
五大类注解
使用Controller注解
在demo的包下创建一个StudentController类,并使用@Controller注解.
在我们的启动类中去获取并调用,看是否能正确打印.
打印结果:
由此我们可以看到,加了@Controller类注解,不需要在去配置文件中添加baen标签,在加载Spring配置文件的时候,Spring会自动的帮我们将带有类注解的对象存储到Spring容器当中去.
这里我们要注意一个问题,bean对象的id是怎么确定的?
五大类注解bean对象的命名规则
如果原类名的第一个字符和第二个字符是大写的,那么bean名称就是原类名;如果不满足上述条件,bean名称就是使用原类名并且首字母小写.
使用其他类注解
只要会用了@Controller,其他的就很简单.我们来依次演示一下:
打印结果:
注意事项
演示完了用法就有几个问题需要明确一下.
<bean>标签能否和component-scan一起使用??
我们在com.java.service包下创建一个UserService类,将它使用bean标签的方式注册到Spring容器中,在启动类中看看是否可以获取到它.
由此我们可以看到,这两个是可以一起使用的.
五大类注解可以不在component-scan的包下吗?
答案:不可以,只有在component-scan包下的才会去扫描是否添加了五大类注解.
注意:即使在conponent-scan下,但是如果没有加五大类注解,一样是不能将当前对象存储到Spring.
在component-scan的所有子包下的类,只要加了五大类注解,同样能存储到Spring中.
五大类注解的关系
可以认为@Controller/@Service/@Repository/@Configuration 都是@Component 的"子类",都是针对@Component的一个扩展.
为什么需要五大注解
让程序员看到注解之后就知道当前类的作用.
JavaEE标准分层:
至少3层:控制层,服务层,数据持久层.
这么多注解其实就是为了满足标准分层的需求.
方法注解
使用方法注解@Bean 存储对象到Spring容器.
首先我们先创建一个实体类User:
package com.java.demo.entity; /** * 普通用户的实体类 */ public class User { private Integer uid; private String username; private String password; private Integer age; public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
然后在创建一个UserBeans类,在这个类里使用@Bean方法注解
用Bean对象来接受当前这个方法的返回值.
最后在启动类里获取bean对象:
由此可以看到我们通过方法注解的方式将当前方法的返回值存到容器中,再从容器里获取是可行的.
注意事项
使用@Bean方法注解需要注意两个点:
1.@Bean的命名规则和五大类注解的命名规则不同.@Bean的命名规则,默认情况下@Bean存储的对象名称是方法名.
2.@Bean注解必须要配合五大类注解一起使用.这是基于Spring的性能所设计的一种策略.
@Bean重命名
底层是一个数组,可以起多个名字.
但是需要注意的是:当@Bean使用了重命名之后,默认的方法名获取对象的方式已经不能使用了,重命名了就只能用重命名的名字.
更简单的获取Bean对象(对象装配)
获取Bean对象也叫做对象装配,是把对象取出来放到某个类中,也叫做对象注入.
直白一点讲就是A类中需要B类,把B类从Spring中装配到A类当中,这叫做对象装配,也叫做对象注入,也是DI,名字有很多,但是实现的功能都是一致的.(更加简单的读取Bean,是从Spring容器中读取某个对象,放到当前类里面)
对象装配的实现方法有三种:属性注入,Setter注入,构造方法注入.
注入依赖的注解是:@Autowired
属性注入(Field Injectoon)
属性注入是最简单的,也是日常开发中使用最多的一种注入方式.
实现代码:
目标:从UserController里面通过简单的方式获取到UserService.
验证:在启动类里获取到UserController,调用它的sayHi方法.
从打印结果可以看到,我们从UserController里面是获取到了UserService对象的.
优点:简单
缺点:1.没办法实现final修饰的变量注入.
2.兼容不好,只适用于IoC容器.
3.风险:因为写法简单,所以违背单一设计原则的概率更大.
Setter注入(Setter Injection)
实现代码
优点:符合单一设计原则(每个方法只传递一个对象)
缺点:1.不能注入不可变对象(final修饰的对象)
2.使用setter注入的对象可能会被修改.
构造方法注入(Constructor Injection)
优点:
1.可以注入一个不可变的对象(使用final修饰的对象)
为什么构造方法可以注入一个不可变的对象,而属性注入和setter注入却不行?
这是Java的规定,在Java中被final修饰的对象必须满足以下两个条件中的任意一个:
- a.final 修饰i的对象,直接赋值.
- b.final修饰的对象,必须在构造方法中赋值.
2.注入的对象不会被改变(构造方法只执行一次)
3.构造方法注入可以保证注入对象完全被初始化
4.通用性更好
另一种注入关键字:@Resource
在进行对象注入的时候,除了可以使用@Autowired关键字外,还可以使用@Resource.
@Autowired和@Resource的区别
- 出身不同:@Autowired来自于Spring,@Resource来自于JDK的注解.
- 使用时设置的参数不同:相对于@Autowired来说,@Resource支持更多的参数设置.
- @Autowired可以用于属性注入,Setter注入,构造方法注入,而@Resource只能用于属性注入和Setter注入,不能用于构造方法注入.
- 在Spring容器中找Bean有两种方式,1是根据类型查找2是根据名称查找.@Autowired先根据类型查找,之后在根据名称查找;@Resource先根据名称查找,在根据类型查找.
同一类型多个@Bean报错
User类型的对象在Spring容器中存了多个:
package com.java.demo.component; import com.java.demo.entity.User; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; @Component public class UserBeans { @Bean(name = {"u1","user1"}) public User getUserById(){ User user = new User(); user.setUid(1); user.setUsername("张三"); user.setPassword("123456"); user.setAge(18); return user; } @Bean public User getUserByName(){ User user = new User(); user.setUid(2); user.setUsername("李四"); user.setPassword("1234"); user.setAge(20); return user; } }
在UserController2中获取User对象:
package com.java.demo.controller; import com.java.demo.entity.User; import com.java.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @Controller public class UserController2 { @Autowired //默认是把属性名字当作Bean对象名称去容器里查找 private User user; public void sayHi(){ System.out.println("do UserController sayHi()"); System.out.println(user.getUsername()); } }
在启动类中测试:
发现报错了:
报错的大体意思是:没有找到唯一的Bean异常,找到了两个u1,和getUserByName.
因为我们是使用了@Autowired,先根据类型查找,发现有两个,在根据名称去查找,还是不能确定,所以就报错了.同理使用@Resource也会报错.
解决上述问题:
解决方案有两个,使用@Resource设置name属性或使用@Autowired搭配@Qualifier(筛选).