目录
在 Spring 中想要更简单的存储和读取对象的核⼼是使⽤注解,也就是我们接下来要学习 Spring 中的相关注解,来存储和读取 Bean 对象。
1.存储 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="org.example"></content:component-scan> </beans>
即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的。
1.2 添加注解存储 Bean 对象
想要将对象存储在 Spring 中,有两种注解类型可以实现:
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration。
- ⽅法注解:@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 App { 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("zhangsan"); } }
1.2.2 @Service(服务存储)
使⽤ @Service 存储 bean 的代码如下所示:
@Service public class UserService { public void sayHi(String name) { System.out.println("Hi," + name); } }
读取 bean 的代码:
public 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("lisi"); } }
1.2.3 @Repository(仓库存储)
使⽤ @Repository 存储 bean 的代码如下所示:
@Repository public class UserRepository { public void sayHi(String name) { System.out.println("Hi," + name); } }
读取 bean 的代码:
public 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("zhangsan"); } }
1.2.4 @Component(组件存储)
使⽤ @Component 存储 bean 的代码如下所示:
@Component public class UserComponent { public void sayHi(String name) { System.out.println("Hi," + name); } }
读取 bean 的代码:
public 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("lisi"); } }
1.2.5 @Configuration(配置存储)
使⽤ @Configuration 存储 bean 的代码如下所示:
@Configuration public class UserConfiguration { public void sayHi(String name) { System.out.println("Hi," + name); } }
读取 bean 的代码:
public 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 App2 { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml"); User user = (User) context.getBean("user1"); System.out.println(user.toString()); } }
然⽽,当获取 bean 对象中的 user1 时却发现,根本获取不到:
编辑
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 可以有多个名字;
并且 name={} 可以省略。
2.获取 Bean 对象(对象装配)
获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注⼊。
对象装配(对象注⼊)的实现⽅法以下 3 种:
- 属性注⼊
- 构造⽅法注⼊
- Setter 注⼊
下⾯我们按照实际开发中的模式,将 Service 类注⼊到 Controller 类中。
2.1 属性注入
属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中。
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 类的实现代码如下:
@Controller public class UserController { // 注⼊⽅法1:属性注⼊ @Autowired private UserService userService; public User getUser(Integer id) { return userService.getUser(id); } }
获取 Controller 中的 getUser ⽅法:
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(空指针异常)。不能注入一个final修饰的属性。
构造⽅法注⼊
- 优点是可以注入final修饰的属性,注入的对象不会被修改,依赖对象在使用前一定会被完全初始化,通用性好
- 缺点是如果有多个注⼊会显得⽐较臃肿,但出现这种情况你应该考虑⼀下当前类是否符合程序的单⼀职责的设计模式了
Setter 注入
- 优点是方便再类实例之后,重新对该对象进行配置或注入
- 不能注入一个final修饰的属性;注入对象可能会被改变,因为setter方法可能会被多次调用,就有被修改的风险
补充:
final修饰的属性,需要具备下列两个条件之一:
编辑
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。
- @Autowired 可⽤于 Setter 注⼊、构造函数注⼊和属性注⼊,⽽ @Resource 只能⽤于 Setter 注⼊和属性注⼊,不能⽤于构造函数注⼊。
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; } }
总结
1. 将对象存储到 Spring 中:
a. 使⽤类注解:@Controller、@Service、@Repository、@Configuration、@Component【它们之间的关系】
b. 使⽤⽅法注解:@Bean【注意事项:必须配合类注解⼀起使⽤】
2. Bean 的命名规则:⾸字⺟和第⼆个字⺟都⾮⼤写,⾸字⺟⼩写来获取 Bean,如果⾸字⺟和第⼆个字⺟都是⼤写,那么直接使⽤原 Bean 名来获取 Bean。
3. 从 Spring 中获取对象:
a. 属性注⼊
b. Setter 注⼊
c. 构造函数注⼊(推荐)
4. 注⼊的关键字有:
a. @Autowired
b. @Resource
5. @Autowired 和 @Resource 区别:出身不同; 使⽤时设置参数不同 @Resource ⽀持更多的参
数,⽐如 name。
6. 解决同⼀类型多个 Bean 的报错:
a. 使⽤ @Resource(name="")
b. 使⽤ @Qualifier(value="")