SpringBoot:详解依赖注入和使用配置文件



🏡浩泽学编程:个人主页

 🔥 推荐专栏:《深入浅出SpringBoot》《java项目分享》
              《RabbitMQ》《Spring》《SpringMVC》

🛸学无止境,不骄不躁,知行合一


前言

在上一篇文章中,讲诉了SpringIoC的Bean装配,但是对于如何进行获取,也就是Bean之间的依赖还未讲诉,下面开始讲诉依赖注入(Dependency Injection,DI)以及如何使用属性文件。涉及主要注解@Autowired、@Primary、@Quelifier、@PropertySource和@ConfigurationProperties。


一、🌕依赖注入

例:人类(Person)有时候利用一些动物(Animal)去完成一些事情,比方说狗(Dog)是用来看门的,猫(Cat)是用来抓老鼠的, 鹦鹉(Paηot)是用来迎客的……于是做一些事情就依赖于那些可爱的动物。假设现在需要用狗狗来看门。

//定义人类接口
public interface Person {
    void service();

    void setAnimal(Animal animal);
}
//定义动物接口
public interface Animal {
    void user();
}
//定义狗
@Component
public class Dog implements Animal {
    @Override
    public void user() {
        System.out.println("狗【" + Dog.class.getSimpleName() + "】是用来看门的");
    }
}
//定义年轻人
@Component
public class YoungPerson implements Person {
    @Autowired
    private Animal animal = null;

    @Override
    public void service() {
        this.animal.user();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}
//定义配置类
@Configuration
@ComponentScan("com.dragon.restart")//所有的包和类都在restart下
public class AppConfig {
}

测试类:

ApplicationContext ctx =new AnnotationConfigApplicationContext(AppConfig.class) ;
		Person person= ctx.getBean(YoungPerson.class) ;
		person.service() ;


测试是成功的,这个时候SpringIoC 容器己经通过注解@Autowired成功地将Dog注入到了 YoungPerson 实例中。 但是这只是一个比较简单的例子,我们有必要继续探讨@Autowired。

🌖注解@Autowired

@Autowired 是我们使用得最多的注解之一, 因此在这里需要进一步地探讨它。 它注入的机制最基本的一条是根据类型 (bytype), 我们回顾IoC 容器的顶级接口 BeanFactory,就可以知道 IoC 容器是通过getBean 方法获取对应Bean 的,而 getBean 又支持根据类型(by type)或者根据名称(by name)。这里明显根据类型注入,将狗狗实例注入Animal对象。
再回到上面的例子,我们只是创建了一个动物一一狗,而实际上动物还可以有猫 (Cat),猫可以为我们抓老鼠, 于是我们又创建了一个猫的类。

@Component
public class Cat implements Animal{
    @Override
    public void user() {
        System.out.println("猫【" + Cat.class.getSimpleName() + "】是抓老鼠的");
    }
}

好了,如果我们还使用着YoungPerson类,那么麻烦来了,因为这个类只是定义了一个动物属性(Animal),而我们却有两个动物,一个狗, 一个猫, SpringIoC 如何注入呢?
运行测试,可以看到IoC容器抛出异常,如下:

Description:
Field animal in com.dragon.restart.pojo.impl.YoungPerson required a single bean, but 2 were found:
	- cat: defined in file [E:\IDEA_projects\restart\target\classes\com\dragon\restart\pojo\Cat.class]
	- dog: defined in file [E:\IDEA_projects\restart\target\classes\com\dragon\restart\pojo\impl\Dog.class]

那么使用@Autowired 能处理这个问题吗?答案是肯定的。假设我们目前需要的是狗提供服务,那么可以把属性名称转化为dog,也就是原来YoungPerson的private Animal animal = null; 改为private An工mal dog = null;

@Component
public class YoungPerson implements Person {
    @Autowired
    private Animal dog = null;

    @Override
    public void service() {
        this.dog.user();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.dog = animal;
    }
}

这里, 我们只是将属性的名称从animal 修改为了 dog,那么我们再测试的时候,你可以看到是采用狗来提供服务的。那是因为@Autowired提供这样的规则:首先它会根据类型找到对应的Bean,如果对应类型的 Bean 不是唯一的,那么它会根据其属性名称和 Bean 的名称进行匹配。如果匹配得上,就会使用该Bean:如果还无法匹配,就会抛出异常。这里还要注意的是@Autowired 是一个默认必须找到对应 Bean 的注解,如果不能确定其标注属性一定会存在并且允许这个被标注的属性为null, 那么你可以配置@Autowired属性 required 为 false@Autowired(required = false)

@Autowired除了可以标注属性外,还可以标注方法, 如setAnimal方法,如下所示:

@Override
@Autowired 
public void setAnimal (Animal animal) { 
this.animal = animal;

这样它也会使用setAnimal 方法从 IoC 容器中找到对应的动物进行注入,甚至我们还可以使用在方法的参数上,后续文章会再谈到它。

🌗消除歧义性——@Quelifier和@Primary

在上面我们发现有猫有狗的时候, 为了使@Autowired 能够继续使用,我们做了一个决定,将YoungPerson 的属性名称从 animal 修改为 dog。显然这是一个憋屈的做法,好好的一个动物,却被我们定义为了狗,毕竟不能每次换个对象就改一次,这样太麻烦了。产生注入失败的问题根本是按类型(bytype) 查找, 正如动物可以有多种类型,这样会造成 Spring IoC 容器注入的困扰,我们把这样的一个问题称为歧义性。知道这个原因后, 那么这两个注解是从哪个角度去解决这些问题的呢?

首先是一个注解@Primary,它是一个修改优先权的注解,当我们有猫有狗的时候,假设这次需要使用猫, 那么只需要在猫类的定义上加入@Primarγ就可以了,如下:

@Component
@Primary
public class Dog implements Animal {
    @Override
    public void user() {
        System.out.println("狗【" + Dog.class.getSimpleName() + "】是用来看门的");
    }
}

这里的@Primary 的含义告诉 Spring IoC 容器, 当发现有多个同样类型的 Bean 时,请优先使用我进行注入,于是再进行测试时会发现,系统将用狗狗为你提供服务。 因为当 Spring 进行注入的时候,虽然它发现存在多个动物, 但因为Dog被标注为了@Primarγ,所以优先采用Dog的实例进行了注入,这样就通过优先级的变换使得IoC容器知道注入哪个具体的实例来满足依赖注入。然后,有时候@Primary 也可以使用在多个类上,也许无论是猫还是狗狗都可能带上@Primary 注解, 其结果是IoC容器还是无法区分采用哪个Bean的实例进行注入, 又或者说我们需要更加灵活的机制来实现注入,那么**@Quelifier** 可以满足你的这个愿望。

它将与@Autowired 组合在一起,通过类型和名称一起找到Bean。我们知道Bean 名称在 Spring IoC 容器中是唯一的标识,通过这个就可以消除歧义性了。此时你是否想起了BeanFactory接口中的这个方法呢?<T> T getBean(String name, Class<T> requiredType) throws BeansException;

代码:

@Component
public class YoungPerson implements Person {
    @Autowired
    @Qualifier("dog")
    private Animal animal = null;

    @Override
    public void service() {
        this.animal.user();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}

一旦这样声明, Spring IoC 将会以类型和名称去寻找对应的Bean进行注入。根据类型和名称,显然也只能找到狗狗为我们服务了。

🌑带有参数的构造方法类装配

在上面,我们都基于一个默认的情况,那就是不带参数的构造方法下实现依赖注入。但事实上,有些类只有带有参数的构造方法,于是上述的方法都不能再使用了。为了满足这个功能,我们可以使用@Autowired 注解对构造方法的参数进行注入,例如,修改类YoungPerson来满足这个功能。

@Component
public class YoungPerson implements Person {
    private Animal animal = null;

    public YoungPerson(@Autowired @Qualifier("dog") Animal animal) {
        this.animal = animal;
    }

    @Override
    public void service() {
        this.animal.user();
    }

    @Override
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }
}

@Autowired 和@Qualifier 注解,使其注入进来。这里使用@Qualifier 是为了避免歧义性。当然如果你的环境中不是有猫有狗,则可以完全不使用@Qualifier,而单单使用@Autowired就可以了。

二、📝使用属性文件

Java 开发使用属性文件已经十分普遍,所以这里谈谈这方面的内容。在Spring Boot 中使用属性文件,可以采用其默认为我们准备的application.properties,也可以使用自定义的配置文件。 应该说读取配置文件的方法很多, 这里没有必要面面俱到地介绍每一个细节,只是介绍那些最常用的方法。

在SpringBoot项目的application.properties:

database.driverName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/my
database.username=root
database.password=root

创建个DataBaseProperties类:

/**
 * @Version: 1.0.0
 * @Author: Dragon_王
 * @ClassName: DataBaseProperties
 * @Description: TODO描述
 * @Date: 2024/1/16 16:07
 */
@Component
public class DataBaseProperties {
    @Value("${database.driverName}")
    private String driverName = null;
    @Value("${database.url}")
    private String url = null;
    private String username = null;
    private String password = null;

    public void setDriverName(String driverName) {
        System.out.println(driverName);
        this.driverName = driverName;
    }

    public void setUrl(String url) {
        System.out.println(url);
        this.url = url;
    }

    @Value("${database.username}")
    public void setUsername(String username) {
        System.out.println(username);
        this.username = username;
    }

    @Value("${database.password}")
    public void setPassword(String password) {
        System.out.println(password);
        this.password = password;
    }

    public String getDriverName() {
        return driverName;
    }

    public String getUrl() {
        return url;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }
}

通过@Value注解, 使用${… }这样的占位符读取配置在属性文件的内容。这里的@Value 注解,既可以加载属性, 也可以加在方法上。
这样就能成功将属性文件内容成功注入了。但是我们可以使用过注解@ConfigurationProperties来简化一下,同样能实现注入,如下:

/**
 * @Version: 1.0.0
 * @Author: Dragon_王
 * @ClassName: DataBaseProperties
 * @Description: TODO描述
 * @Date: 2024/1/16 16:07
 */
@Component
@ConfigurationProperties("database")
public class DataBaseProperties {
    private String driverName = null;
    private String url = null;
    private String username = null;
    private String password = null;

    public void setDriverName(String driverName) {
        System.out.println(driverName);
        this.driverName = driverName;
    }

    public void setUrl(String url) {
        System.out.println(url);
        this.url = url;
    }

    public void setUsername(String username) {
        System.out.println(username);
        this.username = username;
    }

    public void setPassword(String password) {
        System.out.println(password);
        this.password = password;
    }

    public String getDriverName() {
        return driverName;
    }

    public String getUrl() {
        return url;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }
}

这里@ConfigurationProperties注解内的database将会与属性名组成属性的全限定名去配置文件里查找,如属性driveName就会和database组成database.driverName去查找,这里注意一下不需要管字母的大小写,不影响。

如果我不用默认的application.properties文件怎么办呢?现在我们重新创建一个jdbc.properties文件,将原配置文件内容移入其中。
DataBaseProperties 类不变如上,启动类配置如下:

@SpringBootApplication
@PropertySource(value = {"classpath:jdbc.properties"},ignoreResourceNotFound = true)
public class RestartApplication {
	public static void main(String[] args) {
		SpringApplication.run(RestartApplication.class, args);
	}

}


value 可以配置多个配置文件。使用 classpath 前缀, 意味着去类文件路径下找到属性文件;ignoreResourceNotFound 则是是否忽略配置文件找不到的问题。 ignoreResourceNotFound 的默认值为false,也就是没有找到属性文件, 就会报错;这里配置为true,也就是找不到就忽略掉,不会报错。通过运行日志,可以看出成功注入。


总结

以上就是依赖注入和使用配置配置文件的讲解,欢迎大家一起讨论。

转载请说明出处内容投诉
CSS教程_站长资源网 » SpringBoot:详解依赖注入和使用配置文件

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买