在上一篇文章中,讲诉了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 是我们使用得最多的注解之一, 因此在这里需要进一步地探讨它。 它注入的机制最基本的一条是根据类型 (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 容器中找到对应的动物进行注入,甚至我们还可以使用在方法的参数上,后续文章会再谈到它。
在上面我们发现有猫有狗的时候, 为了使@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,也就是找不到就忽略掉,不会报错。通过运行日志,可以看出成功注入。
以上就是依赖注入和使用配置配置文件的讲解,欢迎大家一起讨论。