目录
3.4 byName自动注入@AutoWired和@Qualifier
前言
声明:本专栏文章均为观看动力节点王鹤老师三大框架的视频所撰写的笔记,笔者实力有限,内容如有错误欢迎各位小伙伴在评论区指出。
视频链接:SSM-Spring
👉上文说到利用控制反转的思想可以将创建对象的任务交给Spring容器去执行,对象创建好了以后,自然就要去考虑如何给我们的对象赋值的问题,这同样是Spring容器应该考虑并解决的问题。那么Spring是如何做的呢?首先,Spring使用依赖注入(Dependency Injection),简称DI,实现了控制反转,在此基础上,分别基于xml文件和注解解决了给对象赋值的问题,这就相当于给类的私有属性暴露出两种公共访问方法供人进行属性设置一样,具体如何操作,闲话少说,大家往下看!
1、Bean的装配
Bean的概念:
Bean的英文含义是小豆子的意思,Spring非常形象的将我们需要用到的各种Java对象,比如实体类对象、容器对象、操作接口实现类对象等,都比喻成一颗颗小豆子,然后存放在自己的容器中。
1.1 默认的装配方式
所谓默认装配方式就是指当我们使用ApplicationContext容器创建对象的时候,他会读取配置文件中<bean>并默认调用的是该对象的无参构造方法进行实例化。
1.2 Bean的作用域
除了对象的创建以及对象属性赋值意外,关于类还有一个重要的属性需要关注,那就是对象的作用范围。java基础当中,利用四种不同的访问修饰符可以在类中的使控制其方法变量的使用范围,类似的,关于容器中bean的使用范围,Spring在<bean>标签中提供了一种属性,Scope属性,进行使用范围也就是作用域的指定。
scope属性值 | 作用域 |
singleton(单例模式) | 作用于整个 Spring 容器中,使用 singleton 定义的 Bean 将是单例的,叫这个名称的对象只有一个实例 |
prototype(原型模式) | 作用域每一个调用它的方法中,每次使用 getBean 方法获取的同一个的实例都是一个 新的实例 |
request | 作用于每一个请求中,对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例;该作用域仅适用于web的Spring WebApplicationContext环境 |
session | 对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例; |
2、基于xml的依赖注入
bean实例在调用无参构造器创建对象后,就要对bean对象的属性进行初始化。初始化是由容器自动完成的,称为注入。 根据注入方式的不同,常用的有两类:set注入、构造注入
2.1 set注入(也叫设值注入)
set注入也叫设值注入是指,通过setter方法传入被调用者的实例。这种注入方式简单、直观,因而在Spring的依赖注入中大量使用。
2.1.1 简单类型set注入
Spring中规定java中的基本数据类型和String类型都是简单类型。
创建学生类:
package com.bjpowernode.ba01;
public class Student {
private String name;
private int age;
public Student() {
System.out.println("spring会调用类的无参数构造方法创建对象");
}
public void setName(String name) {
System.out.println("setName:"+name);
this.name = name.toUpperCase();
}
public void setAge(int age) {
System.out.println("setAge:"+age);
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
spring配置文件spring.xml中声明Student类的bean:
<bean id="myStudent" class="com.bjpowernode.ba01.Student" >
<property name="name" value="李四" /><!--setName("李四")-->
<property name="age" value="22" /><!--setAge(21)-->
</bean>
单元测试:
@Test
public void test01(){
System.out.println("=====test01========");
String config="ba01/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//从容器中获取Student对象
Student myStudent = (Student) ac.getBean("myStudent");
System.out.println("student对象="+myStudent);
}
2.1.2 引用类型set注入
当指定bean的某属性值为另一bean的实例时,属性值不是设置value而是通过ref指定它们间的引用关系。ref的值必须为某bean的id值。 例如上述学生类的属性中增加一个学校信息。
创建学校类:
package com.bjpowernode.ba02;
public class School {
private String name;
private String address;
public void setName(String name) {
this.name = name;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
Student类中添加School属性,修改如下:
package com.bjpowernode.ba02;
public class Student {
private String name;
private int age;
//声明一个引用类型
private School school;
public Student() {
System.out.println("spring会调用类的无参数构造方法创建对象");
}
// 包名.类名.方法名称
// com.bjpowernode.ba02.Student.setName()
public void setName(String name) {
System.out.println("setName:"+name);
this.name = name;
}
public void setAge(int age) {
System.out.println("setAge:"+age);
this.age = age;
}
public void setSchool(School school) {
System.out.println("setSchool:"+school);
this.school = school;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
单元测试:
@Test
public void test02(){
System.out.println("===test02===");
Student student = new Student();
student.setName("lisi");
student.setAge(20);
School school = new School();
school.setName("门头沟");
school.setAddress("北京");
student.setSchool(school);
// setSchool(mySchool)
System.out.println("student==="+student);
}
2.2 构造注入
上面两种赋值情况都是基于无参构造方法的,而当对象的构造方法重写为有参构造方法后,怎么在bean中配置呢?那就是利用<constructor-arg/>标签配置参数。
首先,重写Student类中的构造方法:
package com.bjpowernode.ba03;
public class Student {
private String name;
private int age;
//声明一个引用类型
private School school;
public Student() {
System.out.println("spring会调用类的无参数构造方法创建对象");
}
/**
* 创建有参数构造方法
*/
public Student(String myname,int myage, School mySchool){
System.out.println("=====Student有参数构造方法======");
//属性赋值
this.name = myname;
this.age = myage;
this.school = mySchool;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
其次,spring配置文件spring.xml中配置使用<constructor-arg/>标签配置bean:
<constructor-arg/>设置参数有三种方法:
第一种:根据参数名设置,name--->value;
第二种:根据参数位置,index--->value,index从0开始;
第三种:位置缺省,按照构造方法中默认的参数位置序号。
<!--使用name属性实现构造注入-->
<bean id="myStudent" class="com.bjpowernode.ba03.Student" >
<constructor-arg name="myage" value="20" />
<constructor-arg name="mySchool" ref="myXueXiao" />
<constructor-arg name="myname" value="周良"/>
</bean>
<!--使用index属性-->
<bean id="myStudent2" class="com.bjpowernode.ba03.Student">
<constructor-arg index="1" value="22" />
<constructor-arg index="0" value="李四" />
<constructor-arg index="2" ref="myXueXiao" />
</bean>
<!--省略index-->
<bean id="myStudent3" class="com.bjpowernode.ba03.Student">
<constructor-arg value="张强强" />
<constructor-arg value="22" />
<constructor-arg ref="myXueXiao" />
</bean>
<!--声明School对象-->
<bean id="myXueXiao" class="com.bjpowernode.ba03.School">
<property name="name" value="清华大学"/>
<property name="address" value="北京的海淀区" />
</bean>
单元测试:
@Test
public void test03(){
System.out.println("=====test03========");
String config="ba03/applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(config);
//从容器中获取Student对象
Student myStudent = (Student) ac.getBean("myStudent");
System.out.println("student对象="+myStudent);
File myFile = (File) ac.getBean("myfile");
System.out.println("myFile=="+myFile.getName());
}
2.3 引用类型的自动注入
关于上面引用类型的注入,spring提供了自动注入的方式,通过为<bean/>标签设置autowire属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。根据自动注入判断标准的不同,可以分为两种:
🍉byName:根据名称自动注入
🍊 byType: 根据类型自动注入
2.3.1 ByName 自动注入
容器是通过调用者的bean类的属性名与配置文件的被调用者bean的id进行比较而实现自动注入的。例如下面的Student类,只有当School对象的bean的id属性设置为和java代码中Student类里面的学校参数名一样,就可以使用byName属性值进行自动注入。
spring配置文件spring.xml中Student类的bean设置autowire如下:
<!--byName-->
<bean id="myStudent" class="com.bjpowernode.ba04.Student" autowire="byName">
<property name="name" value="李四" />
<property name="age" value="26" />
<!--引用类型-->
<!--<property name="school" ref="mySchool" />-->
</bean>
<!--声明School对象-->
<bean id="school" class="com.bjpowernode.ba04.School">
<property name="name" value="清华大学"/>
<property name="address" value="北京的海淀区" />
</bean>
2.3.2 ByType 自动注入
除了上述的可以使用参数名进行自动注入外,还可以利用引用对象的类型进行注入,此时对bean配置中School的id属性值不再要求保持一致。但是,配置文件中只能注册一个School类型的对象,无论子类还是父类,像下面的配置中如果把School的子类primarySchool也注册到容器中,由于子类父类同源的关系,spring容器就不知道应该注入哪一个了。
spring配置文件spring.xml中Student类的bean设置autowire如下:
<!--byType-->
<bean id="myStudent" class="com.bjpowernode.ba05.Student" autowire="byType">
<property name="name" value="张飒" />
<property name="age" value="26" />
<!--引用类型-->
<!--<property name="school" ref="mySchool" />-->
</bean>
<!--声明School对象-->
<bean id="mySchool" class="com.bjpowernode.ba05.School">
<property name="name" value="人民大学"/>
<property name="address" value="北京的海淀区" />
</bean>
<!--声明School的子类-->
<!--<bean id="primarySchool" class="com.bjpowernode.ba05.PrimarySchool">
<property name="name" value="北京小学" />
<property name="address" value="北京的大兴区" />
</bean>-->
2.4 Spring多配置文件
在大型项目开发中,往往都是模块化,其中创建的对象不仅数量庞大,而且不同模块之间负责的业务也是互不相同的,所以,如果把所有的模块中的对象全部配置到一个spring文件中,不仅使得配置文件非常臃肿,而且在后续的开发和使用中也非常不方便。因此,spring提供了类似文件夹的方式来区分不同模块中的bean配置。实现多配置文件的步骤如下:
第一步:创建一个总配置文件,例如spring-total.xml;
第二步:创建各个子配置文件,例如spring-pojo.xml,spring-dao.xml,spring-service.xml;
第三步:总配置文件中使用<import>将各个子配置文件引入。
spring-total.xml:
注意:这里的resources的属性值必须设置成各个子配置文件的类路径
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--加载的是文件列表-->
<import resource="classpath:ba06/spring-school.xml" />
<import resource="classpath:ba06/spring-student.xml" />
</beans>
如上面所示,仅仅只有两个子配置文件,当开发的模块比较多,此时就要写很多的子配置文件引入,那么有没有可以节省的写法呢?答案是有的,观察上面两个子配置文件的格式可以发现他们具有很多相同的部分,于是spring提供了通配符的写法可以非常快速的引入各个子配置文件,但是要求我们的总配置文件不能和各个子配置文件格式相同,否则陷入循环依赖中,即自己循环加载自己, 例如将总配置文件spring-total更名为total.xml。
total.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--
在包含关系的配置文件中,可以通配符(*:表示任意字符)
注意: 主的配置文件名称不能包含在通配符的范围内(不能叫做spring-total.xml)
-->
<import resource="classpath:ba06/spring-*.xml" />
</beans>
3、基于注解的依赖注入
基于xml的依赖注入方式最重要也是最麻烦的部分就是各种bean的配置,数量少的时候还可以忍受,但是数量非常多的时候,显然就会很麻烦。因此,spring提供了另一种实现依赖注入的方式,也就是基于注解的依赖注入。但是,使用注解的前提是要给各个类开启支持注解,这就需要在spring的配置文件中配置一个组件扫描器,告诉spring给哪些类支持注解。
spring配置文件spring.xml中添加组件扫描器:
设置component-scan属性声明组件扫描:base-package:指定注解的项目包名。
<context:component-scan base-package="com.bjpowernode.ba02" />
3.1 定义Bean的注解@Component
有了组件扫描器后,各个包下的类就可以使用注解进行bean的声明、赋值操作了。针对我们上面用到的Student实体类,只需要在类的上面添加@component注解即可,
注意到前面用到了实体类这个词,聪明的各位想必已经猜到针对不同类,声明bean用到的注解也是不一样的。这里的不同主要是根据开发时不同的接口来区分的,如下表:
注解 | 类 | 示例 |
@component | 实体类 | Student |
@Repository | dao接口的实现类 | StudentDaoImpl |
@Service | service接口的实现类 | StudentServiceImpl |
@Controller | controller接口的实现类 | StudentController |
3.2 简单类型属性注入@value
对于简单类型注入,需要在属性上使用注解@Value,该注解的value属性用于指定要注入的值。 使用该注解完成属性注入时,类中无需setter。当然,若属性有setter,则也可将其加到setter上。
Student类注解:
@Component("myStudent")
public class Student {
/**
* @Value: 简单类型的属性赋值
* 属性: value 是String类型的,表示简单类型的属性值
* 位置: 1.在属性定义的上面,无需set方法,推荐使用。
* 2.在set方法的上面
*/
@Value("lisi") //使用属性配置文件中的数据
private String name;
@Value("20") //使用属性配置文件中的数据
private Integer age;
public Student() {
System.out.println("==student无参数构造方法===");
}
}
利用@value注解实现属性值的注入,确实非常方便,不需要再去配置bean,写一个个的property了,然而当我们想要更改属性值的时候,却必须进到java代码中修改,这貌似有亿点点不友好。熟悉jdbc的朋友们相比都是用过jdbc属性配置文件, 这里,spring针对value注解同样提供了属性配置文件的方式,然后我们就可以像jdbc的属性配置文件一样通过${属性名}的方式设置value的值,下面就看一下怎么操作😁
👉第一步:和spring的配置文件同级的目录下创建一个属性配置文件,test.properties
👉第二步:在spring配置文件中加载属性配置文件,类似于mybatis配置中加载jdbc属性配置文件
👉第三步:@value注解用${myname},${myage}设置属性值
2.3.3 byType自动注入@AutoWired
需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配Bean的方式。 使用该注解完成属性注入时,类中无需setter。当然,若属性有setter,则也可将其加到setter上。
School类添加注解:
@Component("mySchool")
public class School {
@Value("北京大学")
private String name;
@Value("北京的海淀区")
private String address;
public void setName(String name) {
this.name = name;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "School{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
Student类添加注解:
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
private Integer age;
@Autowired
private School school;
public Student() {
System.out.println("==student无参数构造方法===");
}
public void setName(String name) {
this.name = name;
}
@Value("30")
public void setAge(Integer age) {
System.out.println("setAge:"+age);
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
3.4 byName自动注入@AutoWired和@Qualifier
byName自动注入的方式比byType稍微复杂一点,需要使用两个注解。@AutoWired表示自动注入,@Qualifier通过别名表示注入的是哪一个引用。
修改Student类注解:
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
private Integer age;
//byName自动注入
@Autowired
@Qualifier("mySchool")
private School school;
public Student() {
System.out.println("==student无参数构造方法===");
}
public void setName(String name) {
this.name = name;
}
@Value("30")
public void setAge(Integer age) {
System.out.println("setAge:"+age);
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", school=" + school +
'}';
}
}
个人感觉byName这种虽然会麻烦一点,但是对School类没有同源的限制,且用且体会吧。
3.5 JDK注解@Resource自动注入
Spring提供了对jdk中@Resource注解的支持。@Resource注解既可以按名称匹配Bean,也可以按类型匹配Bean。默认是按名称注入。使用该注解,要求JDK必须是6及以上版本。@Resource可在属性上,也可在set方法上。
@Resource注解默认支持的是byType型的,如果给@Resource注解设置属性值的话,自动转换为byName型的,听到这脑海里就一句话,"前面的又白学了😵"。
byType👉修改Student类注解:
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
private Integer age;
@Resource
private School school;
//...
}
byname👉修改Student类注解:
@Component("myStudent")
public class Student {
@Value("李四" )
private String name;
private Integer age;
//只使用byName
@Resource(name = "mySchool")
private School school;
//...
}
4、XML和注解两种方式的对比
两种依赖注入的实现方式各有长短,对比如下:
注解优点是:
👉方便
👉 直观
👉 高效(代码少,没有配置文件的书写那么复杂)。
其弊端也显而易见:以硬编码的方式写入到Java代码中,修改是需要重新编译代码的。
XML方式优点是:
👉配置和代码是分离的
👉 在xml中做修改,无需编译代码,只需重启服务器即可将新的配置加载。
其弊端也显而易见:编写麻烦,效率低,大型项目过于复杂。
具体怎么选择相信根据项目的大小、场景甚至个人喜好,大家都会找到最合适自己选择。
5、小结
了解了spring中实现依赖注入的两种方式,不仅能够体会到控制反转的妙处,也能大大加快我们的开发效率。关于⭐set注入⭐、⭐多文件配置⭐、⭐属性配置文件⭐以及⭐自动注入的两种类型byName和byType⭐都是后续过程中经常使用的,建议重点掌握。感谢大家的观看,一起加油,一起进步!