目录
经过前面的内容, 已经可以实现基本的Spring读取和存储对象的操作了, 但是这个过程并没有那么的"简单", 所以接下来我们来看更为简单的操作Bean对象的方法.
那么在Spring中实现更简单的读取和存储对象的核心是使用注解.
存储Bean对象
之前我们在存储Bean时, 需要在spring-config.xml中配置bean, 每一个Bean是一个存储到Spring中的对象, 如下所示.
那么其实有更加简单的方式才存储Bean对象, 有两种实现方法:
- 使用类注解(五大类注解@Controller, @Service, @Repository, @Component, @Configuration)
- 使用方法注解 -> @Bean
我们先来了解这五大类注解是什么, 怎么使用, 看一下这五大类注解的存储Bean对象的能力, 再来探究为什么要用五个类注解, 这五个类注解之间有什么关系或者区别, 以及在Java编程中的标准分层.
1.1 五大类注解
五大类注解概述
- @Controller
Controller中文意思为"控制器", 控制器是属于标准分层(后文会讲)中的控制层, 它是直接和用户打交道的, 比如说广义上的普通用户输入某个URL地址, 这个有可能触发某个接口, 再比如前端程序员写的ajax请求, 我们就认为发送的ajax请求也是前端用户.
那么控制器的作用是用来验证用户请求的数据的正确性, 相当于"安保系统".
访问我们程序(比如一个商城网站程序)的有两类人:
正常用户, 正常访问网站的功能等正常行为.
非正常用户, 利用bug攻击网站等非正常行为.
对于非正常用户, 我们就会通过控制器来限制用户的行为. (对于非法用户加入黑名单...)
- @Service
Service的中文意思为"服务", 服务是属于标准分层中的服务层, 服务层的作用是用来编排和调度具体的执行方法. 那么它就相当于是"客服中心".
比如现实生活中的乘飞机, 广播会说让旅客去哪个位置登机, 但是如果旅客不知道怎么去走, 就要去客服中心询问, 不过客服中心只是提供了去登机的路线, 而具体怎么走需要旅客去自己走.
由例子可以反映出, 服务并不会直接操作数据库的, 只是说明了实现某个功能需要去调用什么接口, 它会把接口进行调用, 但是并不会具体的执行业务.
- @Repository
Repository的中文意思为"仓库", 仓库是用于放东西的, 那么程序中用来放东西的是数据库, 所以Repository是直接和数据库打交道的, 它是属于标准分层中的数据持久层, 是和数据库进行交互的. 相当于是"执行者"
也就是说, 当请求过来之后, 它会先验证(Controller), 验证完之后会走客服中心服务层(Service), 比如说有一个 添加用户操作 , 那么就需要执行两个insert()方法, 一个是User用户表的insert(), 一个是日志表的insert(), 这个时候调用这两个方法就需要在Service中操作, Service中会调用两个Repository, 一个是UserRepository, 应该是LoginRepository, 这两个就是在服务这一层调用. 而Repository数据持久层是一张表会对应一个Repository, 它就是进行Repository操作的, 它也是最后去"干活"的人.
- @Component
Component的中文意思为"组件", 是组成项目的一些工具类.
在程序中有一些特殊的选项是不可缺少的, 比如一些工具类, 那么这些工具类也想交给Spring管理. 比如一些用于加密的类, 也想交给Spring托管(因为交给Spring托管后它的生命周期就不需要程序员操心了), 那么这些工具类的东西就放到Component中.
- @Configuration
Configuration的中文意思为"配置项", 它是用于存项目中的一些配置的.
综上我们可以发现, Controller, Service, Repository是和业务操作相关联的, 而Component和Configuration和业务没有多大的关系, 是提供了程序级别的工具类.
前置工作: 配置扫描路径(重要)
注意: 不管是使用五大类注解中的哪几个, 或者用的方法注解, 都需要进行前置工作的准备, 就是要去告诉Spring框架, 接下来我们哪个包底下的类, 有可能注入到Spring当中. 只有被正确配置的包下的所有类, 添加了注解才能被正确的识别并保存到Spring中.
配置扫描路径的必要性: Spring在追求功能的同时, 也追求极致的性能.
创建spring-config.xml文件并在该文件中中添加以下配置 (注意如若读者需要复制那么需要记得修改扫描路径):
[ 注: 本篇博客以IDEA为例, 其中已完成了新建Spring项目J-0721-spring-demo2, 并且在java目录下创建了com.java.demo目录 ]
<?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
实现Bean存储
在com.java.demo下创建StudentController类, 也就是StudentController的控制器, 加入注解Controller, 这个Controller就表示将当前类存储到 Spring 中, 加了这个注解之后, 就不需要再spring-config中写bean标签了, 但是和之前写了bean标签的效果是一模一样的.
package com.java.demo;
import org.springframework.stereotype.Controller;
@Controller // 将当前类存储到 Spring 中
public class StudentController {
// 写个测试方法看能否调用成功
public void sayHi(){
System.out.println("do studentController sayHi()");
}
}
接下来我们在启动类App中取对象, 由于到这个地方还没有学习如何更简单的取对象, 所以还是使用之前的方式使用ApplicationContext获取.
import com.java.demo.StudentController;
// 启动类
public class App {
public static void main(String[] args) {
// 1. 得到 Spring 容器
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 2. 得到 Bean 对象
StudentController studentController =
context.getBean("studentController", StudentController.class);
// 3. 使用 Bean 对象
studentController.sayHi();
}
}
执行结果:
读取Bean
注意: 使用注解后, 在得到Bean对象时, getBean的s如果写了StudentController类名,
会产生如下报错,
可以看到, 当使用原类名的时候, 它的报错的信息为"No bean named 'StudentController' available", 表明没有StudentController的方法.
那么我们尝试使用小驼峰命名(即上述可复制的代码), 再次尝试,
它便正常运行了,
所以从实践中我们可以知道当我们使用五大类注解时, 只需要将原类名转换成小驼峰即可. (在这里先记住这个规则, 后续再讲特例, 然后再讲为什么是小驼峰)
小驼峰之外的特例
新建以下类:
package com.java.demo;
import org.springframework.stereotype.Controller;
@Controller
public class SController {
public void sayHi() {
System.out.println("do SController sayHi()");
}
}
于是同样的操作, 在启动类中修改代码:
public class App {
public static void main(String[] args) {
// 1. 得到 Spring 容器
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
SController sController =
context.getBean("sController", SController.class);
sController.sayHi();
}
}
运行结果报错, 报错内容为"No bean named 'sController' available", 在Spring中, 没有获取一个sController的对象.
所以, 这里面s具体的命名规则, 跟Bean的命名是有关系的, 如果名称是首字母大写, 第二个字母小写, 使用小驼峰, 就可以读取Bean对象, 但是如果首字母和第二个字母都大写, 它的名称就需要是原类名.
修改之后运行:
运行结果:
接下来我们测试其他层的使用, 只换注解, 代码类似.
@Service
package com.java.demo;
import org.springframework.stereotype.Service;
@Service // 将当前类存储到 Spring 中
public class StudentController2 {
public void sayHi() {
System.out.println("do StudentController2 sayHi()");
}
}
import com.java.demo.StudentController2;
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
StudentController2 controller2 =
context.getBean("studentController2", StudentController2.class);
controller2.sayHi();
}
}
可以看到, Service也可以更为简单的存储Bean.
那么其他的注解我们也一并测试, 那么由于此时测试代码都为新建一个"StudentControllerx"类, 代码冗余度较高, 此处直接附上运行结果, 3为Repository, 4为Component, 5为Configuration.
@Repository
@Component
@Configuration
所以只要按照规范来去操作它, 那么这五大类注解可以帮助我们更加简单的存储Bean.
常见问题
- 在spring-config.xml中能不能让<content:component-scan>和<bean>一起使用(如截图所示)? 能.
我们来测试一下, 现在com.java下新建service以便后续管理, 然后新建UserService类.
package com.java.service;
public class UserService {
public void sayHi(){
System.out.println("do UserService sayHi()");
}
}
在spring-config.xml中新增:
<bean id="userService" class="com.java.service.UserService"></bean>
public class App {
public static void main(String[] args) {
// 1. 得到 Spring 容器
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserService userService =
context.getBean("userService", UserService.class);
userService.sayHi();
}
}
可以看到, 程序成功的执行了, 所以说明<content:component-scan>和<bean>可以一起使用.
- 五大类注解可以不在component-scan包下吗?
不可以.
package com.java.service;
import org.springframework.stereotype.Service;
@Service
public class StudentService {
public void sayHi(){
System.out.println("do StudentService sayHi()");
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
StudentService service=
context.getBean("studentService", StudentService.class);
service.sayHi();
}
}
可以看到, 报了没有找到studentService的错误, 说明虽然加了五大类注解, 但是这个类没有放在要保存的路径下, 是不会被读取的.
- 如果类在com.java.demo下, 但是不加五大类注解, 能否被读取到? 不能.
注释掉StudentController类的@Controller注解, 依然是和前文中相同的代码, 再来看一下能否被读取到.
可以看到, 结果报错了, 所以即使在component-scan包下, 但是没有添加五大类注解, 一样是不能将当前对象存到Spring.
- 在demo下新建controller.bit包, 并创建UserController类, 能否读取到? 能.
package com.java.demo.controller.bit;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
public void sayHi(){
System.out.println("do UserController sayHi()");
}
}
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController =
context.getBean("userController", UserController.class);
userController.sayHi();
}
}
可以看到成功的读取到了, 所以 在component-scan下的所有子包下的类只要加了五大类注解, 同样能存储到Spring中.
- 如果是不同包的同名类? 不能.
复制一份UserController到demo包下, 如图, 那么这个时候能不能读取到?
可以看到, 运行结果报错了, 其中说明了userController有两个相同的类, 这两个类发生了不兼容的冲突. 也就是说,它这两个类在存进Spring的时候就报错了, 就不能存进Spring, 因为有两个包底下的名字都为UserController.
这个时候有两种做法, 一种是把类名改成不相同的, 一种是对注解加value.
那么这个时候要获取Bean就需要注意一下所写的类名, 如下所示,
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController =
context.getBean("userController", UserController.class);
userController.sayHi();
com.java.demo.controller.bit.UserController userController2 =
context.getBean("UserController2",
com.java.demo.controller.bit.UserController.class);
userController2.sayHi();
}
}
所以我们建议尽量不要有同名的类在不同包下.
注: 其他的注解也可以设置value.
五大类注解的关系?
要想知道五大类注解的关系, 那么需要查看它们的源码.
可以发现, 有一个非常熟悉的注解@Component, 虽然这里是Controller的实现, 但是这个实现是基于Component实现的, 这就相当于是类中的继承, Controller继承了Component中的所有方法. [注解是标识了之后当前类就拥有了某种能力], 所以又意味着这里标注了Component所以Controller就拥有了Component的能力, 所以可以认为Controller是Component的子类, 它是对于Component功能的一个升级.
于是, 使用同样的操作, 打开Service, Repository, Configuration, 会发现同样的情况, 这些都是基于Component实现的.
所以我们就找到了五大类注解之间的关系了, 我们可以认为@Controller, @Service, @Repository, @Configuration都是@Component的子类, 都是针对于@Component的一个扩展.
为什么需要五大类注解?
要搞清楚为什么需要五大类注解, 就需要搞清楚Java EE的标准分层.
首先, 我们的程序是分为三大组件:前端, 后端, 数据库.
前端是由前端程序员负责的, 然后前端程序员, 他不会去操作数据库,因为前端压根儿就没有数据库,那么前端这个时候他会去调用后端的程序来进行数据库的操作,所以咱们分为三大组件,前端,后端和数据库. 而数据库通常是由后端程序员一块儿去负责,所以我们之前在去讲项目的时候时候说,数据库非常的重要,因为程序分为两部分,一部分是程序,一部分是数据库, 这是给后端程序员去讲的,因为我们的后端一定要去操作数据库的.
在我们后端中的标准分层分为图示的这么多层, 除去那些辅助的层不说, 那么它的标准分层在JavaEE中至少分为三层: 控制层, 服务层和数据持久层.
- 控制层(验证参数)
控制层是首先用来验证前端的参数是否合格的, 如果不合格的话(非法请求), 下层代码与数据库就无法执行, 直接返回错误给前端. 如果参数符合标准, 就会往下继续调用.
- 服务层(服务调度)
那么到了服务层, 它的作用就是用于服务调度(编排)的, 即调用接口. 因为我们具体的业务, 具体的实现功能的时候, 需要调用多少个数据持久层Repository, 是不确定的. 所以服务层就像一个客服中心. 在调用编排之后就可以实现具体的业务了.
比如, 当添加用户的时候, 操作的不只是一张表而是两张表, 要给用户表insert, 也要给日志表insert, 这个时候Repository就有两个, 一个为UserRepository关于用户的增删改查, 另一个为LoginRepository关于日志增删改查. 这个时候要调用这两个方法, 就在Service进行调用, 所以 Service是用于调度的服务和编排.
- 数据持久层(直接操作数据库)
具体的业务走的就是数据持久层(数据交互层), 这一层直接和数据库打交道, 并且一张表通常会对应一个Repository.
综上, JavaEE的标准分层至少分为三层, 一般公司都是在这上面进行细化和扩展的.
那么这个标准分层和五大类注解有什么关系呢? 其实五大类注解就是根据这些分层的需求来创建的, 也就是说, 这五大类注解, 在功能层面五大类注解没有多大的区别, 但是在项目当中起了非常重要的作用.
这和为什么每个省/市都有自己的车牌号是⼀样的?比如广东的车牌号就是:粤X:XXXXXX,北京的车牌号:京X:XXXXXX,⼀样。甚至⼀个省不同的县区也是不同的,比如广州就是,粤A:XXXXX,深圳:粤B:XXXXXX,珠海,粤C:XXXXXX,⼀样。这样做的好处除了可以节约号码之外,更重要的作用是可以直观的标识一辆车的归属地。
那么程序也是一样.
所以为什么需要五大类注解, 就为为了让程序员当看到标识的注解是什么时就可以知道它所在类相对应的功能是什么.
Bean命名规则
通过上面的实例可以看出, Bean有以下的命名规则:
默认情况下, 首字母小写; 如果类名首字母和第二个字母都为大写的情况下, Bean名称为原类名.
我们可以在IDEA中搜索关键字"beanname", 可以看到以下内容:
我们发现, 有很多beanname, 然后上方标"f"的都为方法, 我们不看, 我们看以下的类, 在看类的过程中可以看到一个类为"AnnotationBeanNameGenerator"注解命名生成器, 这个时候我们点击这个类之后会看到这个类的源码, 再点击Structure(一般在IDEA的左侧), 可以看到该类的方法,
有看到一个对外的接口generateBeanName提供给别人使用, 我们一步步点击最后的return
到这里可以知道, 提供Bean命名规则的方法就是Introspector.decapitalize(), 然后再看这个方法的具体细节.
由这个源码就可以知道Bean的命名规则.
1.2 方法注解@Bean
方法注解要配合类注解使用
接下来我们还可以使用方法注解@Bean来实现Bean的存储.
我们在方法上加@Bean, 那么这个方法如何把Bean存在Spring当中?
注意: @Bean在使用的时候, 它要求当前的方法必须要有返回值, 因为@Bean是把当前方法的返回值存到Spring中.
我们来看下面这个例子:
首先创建实体类,
package com.java.demo.entity;
/**
* 普通的用户实体类
*/
public class User {
private Integer uid;
private String username;
private String password;
private Integer age;
//...
// 此处节省篇幅, 实际在IDEA中需要Alt+Insert创建 Getter and Setter, 否则在 UserBeans 中会报错
}
使用方法注解@Bean存储对象到Spring容器:
package com.java.demo.controller;
import com.java.demo.entity.User;
import org.springframework.context.annotation.Bean;
public class UserBeans {
@Bean
public User user1(){
User user = new User();
user.setUid(1);
user.setUsername("张三");
user.setPassword("123456");
user.setAge(18);
return user;
}
}
从Spring中获取User对象:
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
User user = context.getBean("user", User.class);
System.out.println(user.getUsername());
}
}
执行结果:
造成这个报错信息的原因有两个:
- @Bean 命名规则和五大类注解的命名规则不同. @Bean 命名规则, 默认情况下, @Bean 存储的对象的名称 = 方法名。
当我们将user改为user1之后, 运行发现依然报错,
User user = context.getBean("user1", User.class);
那么就有另外一个原因.
- @Bean 注解必须要配合五大类注解一起使用。
当加了五大类注解(比如Component)之后运行可以看到正常运行了.
注意, 第二个原因的来源是它处于Spring性能设计所规定的策略.
重命名Bean
因为方法名和Bean名字的命名规则不同, 所以可以通过设置 name 属性给 Bean 对象进行重命名操作,如下代码所示:
@Bean(name = {"user1", "u1"})
或
@Bean(value = {"user1", "u1"})
接下来就可以通过这两个名字获取Bean了.
注意一个问题: 当 @Bean使用了重命名之后,那么默认的方法名获取对象的方式就不能使用了.
多个Bean情况: Spring 容器中允许将同一个类型的对象,存储到容器多个 (多份)
2. 获取Bean对象(对象注入/依赖装配)
获取Bean对象也叫做对象装配, 是更加简单的读取Bean的方式, 是把对象从Spring中取出来放到某个类中, 有时候也叫做对象注入.
"装配"的意思:
我们需要这个东西, 然后叫人给我们装配一下.
程序中"装配"的意思:
比如现在有一A类, 在A类中需要B类, 这个时候就在A类中把B类 装配上.
"装配上": 从另一个地方得到这个对象, 然后放给当前的类. 这就是DI, 依赖注入.也叫做对象注入/依赖装配/查询对象. 这只是三种不同的称呼方式而已.
对象的装配分为三个方法: 属性注入, Setter注入, 构造方法注入.
属性注入
属性注入的特点是三种实现里面中最简单的, 也是日常工作中最广泛使用的.
它的实现就是使用注解@Autowired, 这个注解是作用于局部变量上的, 加这个注解的意思就是从Spring中得到这个对象, 然后把这个对象放给这个属性上, 之后这个属性就会有内容了, 那么后续就不需要new这个对象了. 这是在不new对象的情况下, 直接获得注入的对象.
比如我们我们现在的需求是, 在之前的项目中, 要在UserController中想调用UserService, 那么我们就可以使用注解@Autowired. 而不是说在UserController中再使用之前复杂的三步(得到Spring对象, 获取Bean对象, 使用Bean对象), 那么就有如下UserController类的代码 :
package com.java.demo.controller;
@Controller
public class UserController {
@Autowired // 注入对象, 更加简单的从 Spring 中读取到对象
private UserService userService;
public void sayHi(){
System.out.println("com.java.demo -> do UserController sayHi()");
userService.sayHi();
}
}
注意在App main中的关键点是验证在UserController中能否拿到UserService.
public class App {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserController userController =
context.getBean("userController", UserController.class);
userController.sayHi();
}
}
可以看到, 打印了do UserService sayHi(), 所以可以知道, 在UserController中成功拿到了UserService.
优点
实现简单, 使用简单.
缺点
- 功能性问题: 没法实现final修饰的变量注入.(无法注入一个不可变的对象)
在代码中加final, IDEA会直接报错, 显示userService无法被初始化,
所以这个是属性注入存在的问题, 也是Setter注入存在的问题.
- 兼容性不好: 只适用于IoC容器.
- 风险: 因为写法简单, 所以违背单一设计原则的概率更大.
以下是一些常见的设计原则:
1. 开闭原则 (Open-Closed Principle, OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。即在不修改现有代码的情况下,通过扩展代码来实现新的功能。
2. 里氏替换原则 (Liskov Substitution Principle, LSP):子类型必须能够替换掉它们的父类型,而不会影响程序的正确性。也就是说,派生类(子类)对象应该能够替换基类(父类)对象,而不引起意外行为。
3. 依赖倒置原则 (Dependency Inversion Principle, DIP):高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。这有助于实现松耦合和可替换性。
4. 接口隔离原则 (Interface Segregation Principle, ISP):客户端不应该依赖它不需要的接口。应该将庞大臃肿的接口拆分成更小、更具体的接口,避免客户端实现不需要的方法。
5. 合成/聚合复用原则 (Composition/Aggregation Reuse Principle, CARP):优先使用合成/聚合,而不是继承来实现代码复用。这样可以避免继承的致命缺点,降低类之间的耦合。
6. 迪米特法则 (Law of Demeter, LoD):也称为最少知识原则(Least Knowledge Principle, LKP)。一个对象应该对其他对象有尽可能少的了解,只和直接的朋友通信,不和陌生人说话。这有助于降低对象之间的耦合性。
7. 优先使用组合而不是继承 (Prefer Composition Over Inheritance):这个原则强调在设计时优先考虑使用对象组合,而不是通过继承来达到代码复用的目的。
8. 稳定依赖原则 (Stable Dependencies Principle, SDP):要依赖于比自己更稳定的模块。稳定的模块不容易发生变化,可以降低受影响的风险。
9. 稳定抽象原则 (Stable Abstractions Principle, SAP):要偏向于使用稳定的抽象。稳定的抽象不容易发生变化,可以提供稳定的接口给其他模块使用。
10. 单一设计原则 (Single Responsibility Principle, SRP):一个类应该只有一个引起它变化的原因,即一个类应该只负责一项职责。
这些设计原则在面向对象编程中是非常重要的指导原则,帮助开发者编写结构良好、易于维护和拓展的软件系统。尽量遵循这些原则可以提高代码的质量,降低代码的复杂度,并帮助团队更好地协作。
2. Setter注入
Setter注入也还是需要一个类属性的, 然后给这个类属性Alt+Insert一个Setter方法, 再给这个方法加上@Autowired注解, 再加UserService参数(以UserService为例), 然后把传递过来的UserService赋值给当前类的UserService.
// 2. Setter 注入
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
// sayHi(), App类代码同上
那么这个UserService参数就是Spring来赋值的, 赋值之后会设置到当前类的属性上, 之后就可以使用了.
优点
Setter注入只会赋值操作一个对象, 所以Setter注入是更符合单一设计原则的.
缺点
- 不能注入不可变对象(final修饰的对象)
- 使用Setter注入的对象可能会被修改.
写的setUserService()本质上还是个普通的方法, 那么这个方法是可以在任意的地方去调用的.
3. 构造方法注入(Spring官方推荐的方式)
// 3. 构造方法注入
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
可以看到, 是可以拿到UserService对象的, 说明构造方法注入成功.
那么Spring官方还在这里对这个方式单独做了处理, 如果当前类只有一个构造方法的时候, 允许我们不加@Autowired, 也能成功拿到UserService对象.
优点
- 可以注入一个不可变的对象(使用final修饰的对象)
问题: 为什么构造方法可以注入一个不可变的对象,而属性注入和 Setter 注入却不行?
答案:这是 Java 的规定,在 Java 中,被 final 对象必须满足以下两个条件中的任意一个:
a. final 修饰的对象,那么直接赋值;
b. final 修饰的对象,必须在构造方法中赋值。
2. 注入对象不会被改变(构造方法只执行一次)
3. 构造方法注入可以保证注入对象完全被初始化
4. 通用性更好(构造方法是在所有框架都支持的)
本博客部分内容摘自https://www.javacn.site
@Resource: 另一种注入关键字
在进行类注入时,除了可以使用 @Autowired 关键字之外,我们还可以使用 @Resource 进行注入.
经过探索可以发现在属性注入和Setter注入是@Resource和@Autowired效果是一样的, 但是@Resource在构造方法注入中就不能用了
当强行Run会看到
@Resource和@Autowired的区别
@Autowired是来自于Spring, 而@Resource是来自于jdk.
@Resource和@Autowired在注入上的支持是不同的. @Autowired支持三种注入, 而@Resource只支持属性输入和Setter注入, 不支持构造方法注入.
不过@Resource支持更多的参数方法设置, 而@Autowired只支持一种, 就是重命名.
@Autowired支持比较少的缺点在于如果我们同一个类型的类被注入多个在Spring中, 使用@Autowired就无法解决了, 必须使用@Resource去设置它的内部属性才行.
package com.java.demo.component;
import com.java.demo.entity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class UserBeans {
@Bean(name = {"user1", "u1"})
public User user1(){
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("123456");
user.setAge(20);
return user;
}
}
package com.java.demo.component;
import com.java.demo.entity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(10)
public class UserBeans2 {
@Bean
public User getuserbyname() {
User user = new User();
user.setUid(2);
user.setUsername("王五");
user.setPassword("123456");
user.setAge(20);
return user;
}
}
package com.java.demo.controller;
import com.java.demo.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
@Controller
public class UserController2 {
@Autowired
private User user;
public void sayHi() {
System.out.println("com.java.demo -> do userController sayHi()");
System.out.println(user.getUsername());
}
}
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserController2 controller2 =
context.getBean("userController2", UserController2.class);
controller2.sayHi();
运行结果: userController2创建报错, 原因是没有找到唯一Bean的定义, 期望找到单个Bean但是找到两个Bean, getuserbyname和user1.
那么这里就是@Autowired和@Resource的另一个区别.
首先对于 Spring在容器中找Bean有两种方式, 1根据类型查找, 2根据名称查找.
所以@Autowired和@Resource的又一个区别就是 @Autowired是先根据类型查找, 之后再根据名称查找, @Resource就相反, @Resource先根据名称查找, 再根据类型查找.
所以上例中使用@Autowired就先根据类型找, 找到了多个也就是getuserbyname和user1, 然后根据名称, 那么这个名称就是在UserController2定义的变量的名称user, 所以如果变量名为user1, 就不会报错.
那么如果依然想要变量名用user, 这个时候再尝试使用@Resource, 会发现还是会报错, 而且是和上面一样的报错.
其实@Resource的一个功能是添加值, 那么添加的这个值的意思就是在Spring中找Bean的时候找的是user1, 而不是使用默认的变量名.
@Autowired则不具备这种功能, 如果一定要用@Autowired, 那么就要在@Autowired后再加上注解@Qualifier, 在@Qualifier中就可以设置value. 也就是说可以通过@Autowired和@Qualifier的配合使用来实现和@Resource一样的效果.