Spring依赖注入
1. 前言
本篇博客主要通过代码练习Spring框架的依赖注入。
- 基于XML配置文件的依赖注入
- 基于注解的依赖注入
依赖,值得是类A的功能实现,需要类B的相关属性和方法。那么类A对类B就有依赖。
通过Spring的控制反转【IOC】功能,就能够把所有的bean(java类)注册到Spring的容器,通过容器的管理功能,来实现类B注入到类A中,通过解耦的方式实现类A的依赖注入。
Spring可以解析XML配置文件来得知如何注册bean A和bean B,也能够知道如何把bean B注入到bean A,比如通过set设值注入 ,或者构造方法注入等。而注入的过程已经不是开发者需要关注的了,对开发者透明的,开发者这需要指定如何注入就可以了,注入的过程由Spring bean容器进行完成和管理。
为了应对大型软件架构的复杂性,尤其是配置的复杂性,通过注解实现依赖注入的方式成为必然。当然也可以XML和注解同时存在。
本篇博客参考以下博客内容:
- https://bbs.huaweicloud.com/blogs/327394
- https://bbs.huaweicloud.com/blogs/327710
- https://bbs.huaweicloud.com/blogs/329482
2 开发环境
本篇在虚拟机node99里面安装了JDK开发环境。
[root@node99][/opt]
# cat /etc/redhat-release
CentOS Linux release 8.5.2111
[root@node99][/opt]
# java -version
java version "17" 2021-09-14 LTS
Java(TM) SE Runtime Environment (build 17+35-LTS-2724)
Java HotSpot(TM) 64-Bit Server VM (build 17+35-LTS-2724, mixed mode, sharing)
[root@node99][/opt]
# mvn --version
Apache Maven 3.8.2 (ea98e05a04480131370aa0c110b8c54cf726c06f)
Maven home: /opt/apache/maven/3.8.2
Java version: 17, vendor: Oracle Corporation, runtime: /opt/jdk/jdk-17
Default locale: zh_CN, platform encoding: UTF-8
OS name: "linux", version: "4.18.0-348.el8.x86_64", arch: "amd64", family: "unix"
[root@node99][/opt]
# catalina.sh --version
Using CATALINA_BASE: /opt/apache/tomcat/8.5.70
Using CATALINA_HOME: /opt/apache/tomcat/8.5.70
Using CATALINA_TMPDIR: /opt/apache/tomcat/8.5.70/temp
Using JRE_HOME: /opt/jdk/jdk-17
Using CLASSPATH: /opt/apache/tomcat/8.5.70/bin/bootstrap.jar:/opt/apache/tomcat/8.5.70/bin/tomcat-juli.jar
Using CATALINA_OPTS:
Usage: catalina.sh ( commands ... )
commands:
debug Start Catalina in a debugger
debug -security Debug Catalina with a security manager
jpda start Start Catalina under JPDA debugger
run Start Catalina in the current window
run -security Start in the current window with security manager
start Start Catalina in a separate window
start -security Start in a separate window with security manager
stop Stop Catalina, waiting up to 5 seconds for the process to end
stop n Stop Catalina, waiting up to n seconds for the process to end
stop -force Stop Catalina, wait up to 5 seconds and then use kill -KILL if still running
stop n -force Stop Catalina, wait up to n seconds and then use kill -KILL if still running
configtest Run a basic syntax check on server.xml - check exit code for result
version What version of tomcat are you running?
Note: Waiting for the process to end and use of the -force option require that $CATALINA_PID is defined
创建项目放置目录:
mkdir /root/liwl_dev/java/web/spring
使用mvn创建项目:
mvn archetype:generate -DgroupId=com.liwl.dev -DartifactId=SpringDI -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
使用vscode连接node99,然后打开项目文件。
在项目管理配置文件pom.xml里面,添加以下内容
4.0.0
com.liwl.dev
SrpingDI
jar
1.0-SNAPSHOT
LiwlSrping5Dev
http://maven.apache.org
5.3.15
junit
junit
3.8.1
test
org.springframework
spring-core
${spring.version}
org.springframework
spring-context
${spring.version}
org.apache.maven.plugins
maven-compiler-plugin
3.8.0
17
17
UTF8
在/root/liwl_dev/java/web/spring/SpringDI/src/main目录下,创建resources目录。在该目录下创建用来配置bean的XML文件springdi.xml
3. 使用XML配置文件进行依赖注入
3.1 set设值注入
3.1.1 基本数据类型注入
在目录/root/liwl_dev/java/web/spring/SpringDI/src/main/java/com/liwl/dev下创建源码文件LiwanLiang.java
package com.liwl.dev;
public interface LiwanLiang {
}
同一目录下创建接口LiwanLiang的实现类LiwanLiangImpl.java
package com.liwl.dev;
public class LiwanLiangImpl implements LiwanLiang {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "学生信息:"+"name:"+name+","+"age:"+age;
}
}
修改/root/liwl_dev/java/web/spring/SpringDI/src/main/resources目录下的srpingdi.xml内容
在目录/root/liwl_dev/java/web/spring/SpringDI/src/test/java/com/liwl/dev下创建测试代码LiwlTest.java
package com.liwl.dev;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class LiwlTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("springdi.xml");
LiwanLiangImpl liwl = (LiwanLiangImpl) context.getBean("liwl");
System.out.println(liwl);
context.close();
}
}
执行测试代码,结果
liwanliang信息:name:liwanliang,age:30
3.1.2 引用数据类型注入
以LiwanLiangImpl依赖SchoolImpl为例子。
首先创建接口源文件School.java和实现类源文件SchoolImpl.java
package com.liwl.dev;
public interface School {
}
package com.liwl.dev;
public class SchoolImpl implements School {
private String name;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "学校信息:"+"名称:"+name+","+"地址:"+address;
}
}
修改LiwanLiangImpl.java,加入school属性
package com.liwl.dev;
public class LiwanLiangImpl implements LiwanLiang {
private String name;
private int age;
private School school;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
}
修改srpingdi.xml文件
修改LiwanLiangImpl.java里面toString方法内容
@Override
public String toString() {
return "学生信息:"+"name:"+name+","+"age:"+age+"学校信息:"+school;
}
运行测试源码文件LiwlTest.java
学生信息:name:liwanliang,age:30学校信息:学校信息:名称:江南大学,地址:江苏省无锡市滨湖区蠡湖大道1800号
3.1.3 其他引用类型注入
a. List集合注入基本类型
修改LiwanLiangImpl.java,增加属性stringList
package com.liwl.dev;
import java.util.List;
public class LiwanLiangImpl implements LiwanLiang {
private String name;
private int age;
private School school;
private List course;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
public void setCourse(List course) {
this.course = course;
}
public List getCourse() {
return course;
}
@Override
public String toString() {
return "学生信息:"+"name:"+name+","+"age:"+age+","+".学校信息:"+school+".其他:"+course;
}
}
修改springdi.xml
"高等数学"
"线性代数"
"工程制图"
运行结果
学生信息:name:liwanliang,age:30,.学校信息:学校信息:名称:江南大学,地址:江苏省无锡市滨湖区蠡湖大道1800号.其他:["高等数学", "线性代数", "工程制图"]
b. List集合注入引用类型
这里把school对象增加一个
修改LiwanLiangImpl.java
package com.liwl.dev;
import java.util.List;
public class LiwanLiangImpl implements LiwanLiang {
private String name;
private int age;
private List schoolList;
private List course;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List getSchoolList() {
return schoolList;
}
public void setSchoolList(List schoolList) {
this.schoolList = schoolList;
}
public void setCourse(List course) {
this.course = course;
}
public List getCourse() {
return course;
}
@Override
public String toString() {
return "学生信息:"+"name:"+name+","+"age:"+age+","+".学校信息:"+schoolList+".其他:"+course;
}
}
修改springdi.xml
"高等数学"
"线性代数"
"工程制图"
运行结果
学生信息:name:liwanliang,age:30,.学校信息:[学校信息:名称:江南大学01,地址:江苏省无锡市滨湖区蠡湖大道1800号, 学校信息:名称:江南大学02,地址:江苏省无锡市滨湖区蠡湖大道1800号].其他:["高等数学", "线性代数", "工程制图"]
c. Map集合注入
修改LiwanLiangImpl.java
package com.liwl.dev;
import java.util.List;
import java.util.Map;
public class LiwanLiangImpl implements LiwanLiang {
private String name;
private int age;
private Map schoolMap;
private List course;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Map getSchoolMap() {
return schoolMap;
}
public void setSchoolMap(Map schoolMap) {
this.schoolMap = schoolMap;
}
public void setCourse(List course) {
this.course = course;
}
public List getCourse() {
return course;
}
@Override
public String toString() {
return "学生信息:"+"name:"+name+","+"age:"+age+","+".学校信息:"+schoolMap+".其他:"+course;
}
}
修改springdi.xml
"高等数学"
"线性代数"
"工程制图"
d. Properties类型注入
待补充
3.1.4 其他配置文件引入
在实际开发过程中,当把所有配置都放在同一个配置文件中时,会导致Spring的配置繁多,复杂,体积庞大。所以根据项目规划,在Spring主要配置文件中,通过import标签引入其他配置文件。这样可以是配置文件得到简化,易于配置修改和维护。
在目录/root/liwl_dev/java/web/spring/SpringDI/src/main/resources下创建liwanliang.xml和school.xml
修改springdi.xml
修改liwanliang.xml
修改school.xml
修改LiwanLiangImpl.java
package com.liwl.dev;
import java.util.List;
public class LiwanLiangImpl implements LiwanLiang {
private String name;
private int age;
private School school;
private List course;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
public void setCourse(List course) {
this.course = course;
}
public List getCourse() {
return course;
}
@Override
public String toString() {
return
"name:"+name
+","+
"age:"+age
+ ","+
"school"+school
;
}
}
运行结果
name:liwanliang,age:30,schoolname:江南大学01,address:江苏省无锡市滨湖区蠡湖大道1800号
3.2 构造方法注入
spring按照无参构造函数来创建对象。如果一个类创建了有参构造函数,需要在XML里面配置构造函数。
修改liwanliang.xml
修改LiwanLiangImpl.java
package com.liwl.dev;
public class LiwanLiangImpl implements LiwanLiang {
private String name;
private int age;
private School school;
public LiwanLiangImpl(String name,int age,School school){
this.name = name;
this.age = age;
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return
"name:"+name
+","+
"age:"+age
+ ","+
"school"+school
;
}
}
运行结果
name:liwanliang,age:30,schoolname:江南大学01,address:江苏省无锡市滨湖区蠡湖大道1800号
3.3 自动注入
手动配置要注入的bean,数量变多时,会经常出错。Spring提供了自动注入的方式。
配置以后,不需要为要配置的bean指定其他需要注入的bean,Spring会在指定路径下扫描全部的bean,发现了需要自动注入的bean以后,会为要配置的bean自动注入。
3.3.1 按照名称注入
修改liwanliang.xml
当使用autowire=byName后,Spring会扫描容器里面全部注册的bean,然后发现LiwanLiangImpl需要School的bean,且School的bean具有get/set属性,因此就把School的bean自动注入到LiwanLiangImpl里面,不需要在XML里面手动为LiwanLiangImpl来指定bean
修改liwanliang.xml的配置,运行程序得到结果
name:liwanliang,age:30,schoolname:江南大学01,address:江苏省无锡市滨湖区蠡湖大道1800号
修改LiwanLiangImpl.java
package com.liwl.dev;
public class LiwanLiangImpl implements LiwanLiang {
private String name;
private int age;
private School school;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return
"name:"+name
+","+
"age:"+age
+ ","+
"school"+school
;
}
}
3.3.2 按照类型注入
待补充
4. 半注解方式注入
半注解的方式,是在XML配置文件中,通过标签来设置Spring要扫描和注册的bean。然后在源码中通过注解就可以免除配置XML了。
4.1 注解类型
- 第一组注解:关于对象实例化创建
- @Component 使用在类上用于实例化Bean
- @Controller 使用在web层类上用于实例化Bean
- @Service 使用在service层类上用于实例化Bean
- @Repository 使用在dao层类上用于实例化Bean
- 第二组注解:对象属性的注入
- @Autowired 用在引用数据类型属性的注入
- @Resource
- @Qulifier 与@Autowired一起使用
- 第三组注解:字符串注入
- @Value 基本数据类型的注入
- @ProertySource 引入外部properties文件
- @Bean 将方法的返回注入spring容器
4.2 使用实例
本部分通过XML来注册扫描器,通过注解注册LiwanLiangImpl的bean和School的bean。
修改springdi.xml
修改LiwanLiangImpl.java
package com.liwl.dev;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "liwl")
public class LiwanLiangImpl implements LiwanLiang {
@Value(value = "liwanliang")
private String name;
@Value(value = "30")
private int age;
@Autowired
private School school;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return
"name:"+name
+","+
"age:"+age
+","+
"school"+school
;
}
}
修改SchoolImpl.java
package com.liwl.dev;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "school")
public class SchoolImpl implements School {
@Value(value = "江南大学")
private String name;
@Value(value = "江苏省无锡市滨湖区蠡湖大道1800号")
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return
"name:"+name
+","+
"address:"+address
;
}
}
name:liwanliang,age:30,schoolname:江南大学,address:江苏省无锡市滨湖区蠡湖大道1800号
上面代码中,通过@Component注解了LiwanLiangImpl和SchoolImpl,也就是向Spring容器注册bean:LiwanLiangImpl和SchoolImpl,其bean的id是"liwl"和"school",而LiwanLiangImpl和SchoolImpl的属性值name和age,通过@Value来注解。在LiwanLiangimpl里面的School属性,则是通过注解@Autowire+@Qualifiler实现按照名称自动注入
4.3 引入外部属性文件
假如我们是通过外部配置文件来设置数据库,我们没有必要把数据库相关配置数据写入源码,从而解耦数据库的相关信息。那我们可以通过以下方式来解耦。
以外部配置文件来配置实现类的初始值
修改springdi.xml
在目录下创建all.properties文件,内容
liwl_name="liwanliang001"
liwl_age=30
school_name="江南大学001"
school_address="江苏省无锡市滨湖区蠡湖大道1800号001"
修改LiwanLiangImpl.java
package com.liwl.dev;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "liwl")
public class LiwanLiangImpl implements LiwanLiang {
@Value(value = "${liwl_name}")
private String name;
@Value(value = "${liwl_age}")
private int age;
@Autowired
private School school;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return
"name:"+name
+","+
"age:"+age
+","+
"school"+school
;
}
}
修改SchoolImpl.java
package com.liwl.dev;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component(value = "school")
public class SchoolImpl implements School {
@Value(value = "${school_name}")
private String name;
@Value(value = "${school_address}")
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return
"name:"+name
+","+
"address:"+address
;
}
}
运行结果
name:"liwanliang001",age:30,schoolname:"江南大学001",address:"江苏省无锡市滨湖区蠡湖大道1800号001"
总结一下:
在springdi.xml里面添加了
再来使用注解代替XML配置
修改spring.xml
修改LiwanLiangImpl.java
package com.liwl.dev;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@PropertySource(value = {"classpath:all.properties"},encoding = "utf-8")
@Component(value = "liwl")
public class LiwanLiangImpl implements LiwanLiang {
@Value(value = "${liwl_name}")
private String name;
@Value(value = "${liwl_age}")
private int age;
@Autowired
private School school;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
@Override
public String toString() {
return
"name:"+name
+","+
"age:"+age
+","+
"school"+school
;
}
}
运行结果
name:"liwanliang003",age:30,schoolname:"江南大学003",address:"江苏省无锡市滨湖区蠡湖大道1800号003"
注:SchoolImpl.java不需要添加@PropertySource注解,它通过Component注册到Spring容器
到目前发现spring.xml已经只剩下标签
接下来就是把该标签也变成注解
5. 全注解方式注入
在目录/root/liwl_dev/java/web/spring/SpringDI/src/main/java/com/liwl/dev下,创建配置类源文件SpringDIConfig.java
package com.liwl.dev;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Configuration
@ComponentScan(value={"com.liwl.dev"})
public class SpringDIConfig {
private String info = "我是spring配置类";
public void showMe(){
System.out.println(info);
}
}
修改MyTest.java文件
package com.liwl.dev;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class LiwlTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringDIConfig.class);
SpringDIConfig config = context.getBean(SpringDIConfig.class);
config.showMe();
((ConfigurableApplicationContext) context).close();
}
}
运行结果
我是spring配置类
说明Spring已经加载了配置类,实例化配置类以后,调用了它的方法
修改MyTest.java
package com.liwl.dev;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class LiwlTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringDIConfig.class);
LiwanLiangImpl liwl = (LiwanLiangImpl) context.getBean("liwl");
System.out.println(liwl);
((ConfigurableApplicationContext) context).close();
}
}
运行结果
name:"liwanliang003",age:30,schoolname:"江南大学003",address:"江苏省无锡市滨湖区蠡湖大道1800号003"
6. 总结
本篇主要介绍了使用Spring框架开发时,如何通过XML配置bean,然后注册到Spring容器里。XML通过不同的标签来设置对象的属性以及注入方式(set设值注入,构造方法注入),也可以通过标签来引入其他模块的XML配置。当不在XML里面配置bean时,通过
指定要扫描的包以及属性配置文件,然后Spring就能够通过注解的方式代替XML拿到bean配置的属性值,同时通过注解来自动加载需要注入的bean。
最后可以创建配置类,通过注解完全代替XML的配置。