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和注解同时存在。

本篇博客参考以下博客内容:

  1. https://bbs.huaweicloud.com/blogs/327394
  2. https://bbs.huaweicloud.com/blogs/327710
  3. 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里面添加了,如此以来无论是在LiwanLiangImpl.java还是SchoolImpl.java里面,@Value的注解默认值就可以通过${xxx}的方式,引入all.properties的值。所以此配置是全局的

再来使用注解代替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的配置。