大家家好,我是一名网络怪咖,北漂五年。相信大家和我一样,都有一个大厂梦,作为一名资深Java选手,深知Spring重要性,现在普遍都使用SpringBoot来开发,面试的时候SpringBoot原理也是经常会问到,SpringBoot是为了简化Spring开发,但是底层仍然是Spring。如果不了解Spring源码,那就更别提SpringBoot源码了,接下来我准备用两个月时间,从基础到源码彻彻底底的学习一遍Spring。
学习框架一定要踏踏实实一步一个脚印的学,很多人都比较喜欢急功近利,选择跳着学,包括我有时候也是,最终会发现你不但节约不了时间,反而会更耗时,而且学着学着很容易放弃。
目录
*
– 一、依赖注入的概念
– 二、三种注入方式
–
+ 2.1.构造器注入的4种方式
+
* 2.1.1.根据构造器参数索引
注入
* 2.1.2.根据构造器参数类型
注入
* 2.1.3.根据构造器参数名称
注入
* 2.1.4.根据bean的名称注入
+ 2.2.setter注入
+ 2.3.使用 p 名称空间注入数据
– 三、注入容器中的bean
–
+ 3.1.ref属性方式
+ 3.2.内置bean的方式
– 四、注入集合属性
一、依赖注入的概念
依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。我们的程序在编写时,通过控制反转,把 对象的创建
交给了 spring
,但是代码中不可能出现没有依赖的情况。例如:我们的 controller
层仍会调用 service
层的方法。那这种 controller
和 service
层的依赖关系,在使用 spring
之后,就让 spring
来维护了。简单的说,就是坐等框架把 service
层对象传入 controller
层,而不用我们自己去获取。
public class TestController {
private TestService testService;
public void method1(){
testService.method1();
}
public void method2(){
testService.method2();
}
}
TestController当中需要通过
TestService
来完成某些操作,这就叫TestController
依赖于TestService
。
上面的 TestController
其实是存在一个问题的, testService
并没有赋值,我们要给成员变量赋值只有两个途径, 构造器赋值
和 set方法赋值
。那也就意味着创建 TestController
对象之前,需要先将被依赖对象通过 new
的方式创建好,然后通过两种方式当中的任意一种将其传递给 TestController
,这些工作都是 TestController
的使用者自己去做的,所有对象的创建都是由使用者自己去控制的。
上一篇我们刚刚接触了IOC容器,也就是 TestController
我们肯定是要通过容器来创建的,那么他依赖的对象当然也要交给 spring
来给我们注入。所谓的 注入其实就是给依赖的成员变量赋值
。
二、三种注入方式
spring中依赖注入主要分为 手动注入
和 自动注入
,本文我们主要说一下 手动注入
,手动注入需要我们明确配置需要注入的对象。
刚才上面我们回顾了,将被依赖方注入到依赖方,通常有2种方式:构造函数的方式和set属性的方式,spring中也是通过这两种方式实现注入的,下面详解2种方式。
2.1.构造器注入的4种方式
顾名思义,就是 使用类中的构造函数,给成员变量赋值
。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。
构造器的参数就是被依赖的对象,构造器注入又分为4种注入方式:
- 根据构造器参数
索引
注入 - 根据构造器参数
类型
注入 - 根据构造器参数
名称
注入 - 根据bean的名称注入
如下类是我们要测试的类:
public class TestController {
private String name;
private Integer age;
public TestController(String name,Integer age) {
this.name = name;
this.age = age;
System.out.println("TestController初始化了");
}
@Override
public String toString() {
return "TestController{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
之前无参构造的话是:
<bean id="testController" class="com.gzl.cn.controller.TestController"></bean>
bean标签是单标签,现在因为要使用<bean></bean>
标签当中的子标签<constructor-arg></constructor-arg>
来注入构造器参数,所以使用了<bean></bean>
双标签。
使用构造函数的方式,给 service 中的属性传值,要求: 类中需要提供一个对应参数列表的构造函数。
可以存在多个构造器,不影响的。
涉及的标签:constructor-arg
属性:
- index: 指定参数在构造函数参数列表的索引位置
- type: 指定参数在构造函数中的数据类型
- name: 指定参数在构造函数中的名称
- value: 它能赋的值是基本数据类型和 String 类型
- ref: ref 是 reference 的缩写形式,翻译为:涉及,参考的意思,用于引用其他Bean的id。
依赖注入的数据类型有如下三种:
- 普通数据类型,例如:String、int、boolean等,通过value属性指定。
- 引用数据类型,例如:UserDaoImpl、DataSource等,通过ref属性指定。
- 集合数据类型,例如:List、Map、Properties等。
2.1.1.根据构造器参数 索引
注入
<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">
<bean id="testController" class="com.gzl.cn.controller.TestController">
<constructor-arg index="0" value="张三">constructor-arg>
<constructor-arg index="1" value="11">constructor-arg>
bean>
beans>
public class Client {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("classpath:bean.xml");
TestController testController = (TestController)classPathXmlApplicationContext.getBean("testController");
System.out.println(testController);
}
}
运行结果:
优缺点:
- 参数位置的注入对参数顺序有很强的依赖性,若构造函数参数位置被人调整过,会导致注入出错。
- 不过通常情况下,不建议去在代码中修改构造函数,如果需要新增参数的,可以新增一个构造函数来实现,这算是一种扩展,不会影响目前已有的功能。
2.1.2.根据构造器参数 类型
注入
<bean id="testController" class="com.gzl.cn.controller.TestController">
<constructor-arg type="java.lang.String" value="张三">constructor-arg>
<constructor-arg type="java.lang.Integer" value="11">constructor-arg>
bean>
假如有多个String类型,那么按照类型他还能注入吗?
其实是不影响的,只要我们写 <constructor-arg></constructor-arg>
的时候,参数顺序和构造器当中参数的顺序对应上即可。
优缺点:
实际上按照参数位置或者按照参数的类型注入,都有一个问题,很难通过bean的配置文件,知道这个参数是对应UserModel中的那个属性的,代码的可读性不好,比如我想知道这每个参数对应UserModel中的那个属性,必须要去看UserModel的源码,下面要介绍按照参数名称注入的方式比上面这2种更优秀一些。
2.1.3.根据构造器参数 名称
注入
<bean id="testController" class="com.gzl.cn.controller.TestController">
<constructor-arg name="name" value="张三">constructor-arg>
<constructor-arg name="age" value="11">constructor-arg>
bean>
关于方法参数名称的问题
java通过反射的方式可以获取到方法的参数名称,不过源码中的参数通过编译之后会变成class对象,通常情况下源码变成class文件之后,参数的真实名称会丢失,关于这一点我们可以通过 javac 类名.class
命令来讲java编译成class文件,参数的名称会变成paramString,paramInteger或者arg1 arg2再或者var1 var2等等,总之和实际参数名称不一样了,如下:
如果需要将源码中的参数名称保留在编译之后的class文件中,编译的时候需要用下面的命令: 主要是加一个 parameters
javac -parameters java源码.class
如果使用ider的话,我们正常编译后的class会到target目录当中,可能是编译器处理了这个问题,并没有做任何配置,然后测试了一下方法名称是保留下来的。
参数名称可能不稳定的问题,spring提供了解决方案,通过ConstructorProperties注解来定义参数的名称,将这个注解加在构造方法上面,注意只能在构造器上使用,如下:
@ConstructorProperties({"第一个参数名称", "第二个参数的名称",..."第n个参数的名称"})
public 类名(String p1, String p2...,参数n) {
}
public class TestController {
private String name;
private Integer age;
@ConstructorProperties({"name", "age"})
public TestController(String p1, Integer p2) {
this.name = p1;
this.age = p2;
System.out.println("TestController初始化了");
}
@Override
public String toString() {
return "TestController{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
xml当中可能会报红,因为他找不到对应的构造器参数,实际我们通过@ConstructorProperties注解来指定了,所以报红并不影响正常运行!
<bean id="testController" class="com.gzl.cn.controller.TestController">
<constructor-arg name="name" value="张三">constructor-arg>
<constructor-arg name="age" value="11">constructor-arg>
bean>
运行结果:
说直白点其实就是类似于一个参数别名一样,他并不会改变参数名称的编译结果,但是spring会通过这个注解所指定的名称,找到当前参数。
2.1.4.根据bean的名称注入
public class TestController {
private TestService testService;
public TestController(TestService testService) {
this.testService = testService;
}
@Override
public String toString() {
return "TestController{" +
"testService=" + testService +
'}';
}
}
<bean id="testService" class="com.gzl.cn.service.TestService">bean>
<bean id="testController" class="com.gzl.cn.controller.TestController">
<constructor-arg name="testService" ref="testService">constructor-arg>
bean>
运行结果:
2.2.setter注入
通常情况下,我们的类都是标准的javabean,javabean类的特点:
- 属性都是private访问级别的
- 属性通常情况下通过一组setter(修改器)和getter(访问器)方法来访问
- setter方法,以set开头,后跟首字母大写的属性名,如:setUserName,简单属性一般只有一个方法参数,方法返回值通常为void;
- getter方法,一般属性以get开头,对于boolean类型一般以is开头,后跟首字母大写的属性名,如:getUserName,isOk;
所谓的setter注入顾名思义就是通过构造器创建好对象之后,通过指定属性的set方法,将值赋值给成员变量。
涉及的标签:property,set注入同构造器注入一样,都是可以注入普通类型和bean类型
属性:
name:找的是类中 set 方法后面的部分
ref:指定bean的名称
value:给属性赋值是基本数据类型和 string 类型的
public class TestController {
private String name;
private Integer age;
private TestService testService;
public TestController() {
System.out.println("无参构造执行了");
}
public void setName(String name) {
System.out.println("name的set方法执行了");
this.name = name;
}
public void setAge(Integer age) {
System.out.println("age的set方法执行了");
this.age = age;
}
public void setTestService(TestService testService) {
System.out.println("testService的set方法执行了");
this.testService = testService;
}
@Override
public String toString() {
return "TestController{" +
"name='" + name + '\'' +
", age=" + age +
", testService=" + testService +
'}';
}
}
public class TestService {
public TestService() {
System.out.println("TestService初始化了");
}
}
<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">
<bean id="testService" class="com.gzl.cn.service.TestService">bean>
<bean id="testController" class="com.gzl.cn.controller.TestController">
<property name="age" value="11"/>
<property name="name" value="张三"/>
<property name="testService" ref="testService">property>
bean>
beans>
运行结果:
2.3.使用 p 名称空间注入数据
此种方式是通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的set 方法实现注入功能。
使用p标签得需要引入: xmlns:p="http://www.springframework.org/schema/p"
否则他无法识别p标签!
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
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">
<bean id="testService" class="com.gzl.cn.service.TestService">bean>
<bean id="testController" class="com.gzl.cn.controller.TestController" p:age="11" p:name="李四"
p:testService-ref="testService"/>
beans>
运行结果:
三、注入容器中的bean
注入容器中的bean有两种写法:
- ref属性方式
- 内置bean的方式
3.1.ref属性方式
将上面介绍的constructor-arg或者property元素的value属性名称替换为ref,ref属性的值为容器中其他bean的名称,如:构造器方式,将value替换为ref:
<constructor-arg ref="需要注入的bean的名称"/>
setter方式,将value替换为ref:
<property name="属性名称" ref="需要注入的bean的名称" />
3.2.内置bean的方式
构造器的方式:
<constructor-arg>
<bean class=""/>
constructor-arg>
setter方式:
<property name="属性名称">
<bean class=""/>
property>
四、注入集合属性
注入集合属性同样是可以用set方式注入,也可以用构造器方式注入,只不过集合得需要使用集合的标签。
public class TestController {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public TestController(String[] myStrs, List<String> myList, Set<String> mySet, Map<String, String> myMap, Properties myProps) {
this.myStrs = myStrs;
this.myList = myList;
this.mySet = mySet;
this.myMap = myMap;
this.myProps = myProps;
}
@Override
public String toString() {
return "TestController{" +
"myStrs=" + Arrays.toString(myStrs) +
", myList=" + myList +
", mySet=" + mySet +
", myMap=" + myMap +
", myProps=" + myProps +
'}';
}
}
其中有set、array、list、props、map等标签,可以用在constructor-arg标签下,或者property标签下!
<bean id="testController" class="com.gzl.cn.controller.TestController">
<constructor-arg name="myStrs">
<set>
<value>AAAvalue>
<value>BBBvalue>
<value>CCCvalue>
set>
constructor-arg>
<constructor-arg name="myList">
<array>
<value>AAAvalue>
<value>BBBvalue>
<value>CCCvalue>
array>
constructor-arg>
<constructor-arg name="mySet">
<list>
<value>AAAvalue>
<value>BBBvalue>
<value>CCCvalue>
list>
constructor-arg>
<constructor-arg name="myMap">
<props>
<prop key="testA">aaaprop>
<prop key="testB">bbbprop>
props>
constructor-arg>
<constructor-arg name="myProps">
<map>
<entry key="testA" value="aaa">entry>
<entry key="testB">
<value>bbbvalue>
entry>
map>
constructor-arg>
bean>
运行结果:
Original: https://blog.csdn.net/weixin_43888891/article/details/127675128
Author: 怪 咖@
Title: Spring学习第4篇:Spring 的依赖注入
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/660672/
转载文章受原作者版权保护。转载请注明原作者出处!