前言
IoC是Spring的核心思想之一,常用的实现IoC的技术就是依赖注入
Spring 依赖注入简单实现
Beer:
package pojo;
public class Beer {
public void toDrink(){
System.out.println("喝啤酒");
}
}
Person(注入Beer对象):
package pojo;
public class Person {
private Beer beer;
public void setBeer(Beer beer) {
this.beer = beer;
}
public void drink(){
beer.toDrink();
}
}
application.xml(Spring配置文件):
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="pojo.Person">
<property name="beer" ref="beer"/>
</bean>
<bean id="beer" class="pojo.Beer"/>
</beans>
MyTest:
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Person;
public class MyTest {
@Test
public void test(){
//ClassPathXmlApplicationContext解析配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
Person person = (Person) applicationContext.getBean("person");
person.drink();
}
}
运行:
这就是一个简单的Spring依赖注入的案例
依赖注入:由容器将对象注入到需要的地方,我们仅需要被动接受即可(案例中将Beer对象注入到Person类中)
这是常用的IoC的实现方法
Spring中的依赖注入方式
Spring官网:Core详细介绍了依赖注入
其中,有3种配置方式
- XML Schema-based configuration:基于XML模式的配置,最基础的通过xml配置bean
- Annotation-based Container Configuration:基于注解的配置
- Java-based Container Configuration:通过Java程序配置(完全摆脱XML文件,是SpringBoot的核心之一)
支持两种注入方式:
- Constructor-based Dependency Injection:构造器注入
- Setter-based Dependency Injection:基于Setter方法注入
后续一一解析
XML Schema-based configuration
基于XML文件的依赖注入是最基础的注入方式
我们上面的案例就是基于XML文件的注入
需要了解Spring对应的XML配置文件:
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="pojo.Person">
<property name="beer" ref="beer"/>
</bean>
<bean id="beer" class="pojo.Beer"/>
</beans>
- 约束:spirng采用的是Schema约束,约束文件是xsd关于XML约束
schema约束比dtd约束更强,可以约束文本内容
我们点入该xsd约束的bean标签(ctrl+鼠标左键点击标签)
约束了很多东西:
总之,bean标签表示的就是我们的对象
通过<property name="beer" ref="beer"/>
将id=beer的bean注入到了person中
property标签中,name对应的是Person类的成员变量名,ref表示引用其他bean
如果只是一个简单属性可以通过Value值<property name="beer" value=""/>
这里的property标签就3个属性
这是一种setter注入
关于Spring.xml配置文件,有很多值得注意的细节,后续深入
Setter注入和构造器注入
通过阅读官网,知道
构造器注入:
通过constructor-arg
子标签构造器注入
Setter注入:
通过property
子标签Setter注入
我们可以通过一个案例简单查看:
修改前面的Person类:
package pojo;
public class Person {
private Beer beer;
public Person() {
}
public Person(Beer beer) {
this.beer = beer;
System.out.println("有参构造方法");
}
public void setBeer(Beer beer) {
this.beer = beer;
System.out.println("Setter注入");
}
public void drink(){
beer.toDrink();
}
}
再运行
确实是Setter方法注入
然后修改一下xml中的配置:
<bean id="person" class="pojo.Person">
<constructor-arg name="beer" ref="beer"/>
</bean>
<bean id="beer" class="pojo.Beer"/>
ok,验证了上面的结论正确
property
标签是Setter方法注入constructor-arg
标签是有参构造方法注入
XML注入的自动装配
从上面的案例的XML文件的配置,可以清晰的看出两个Bean之间的关系(不管是property
还是constructior-arg
)
表现了父子的依赖关系,显然,这很麻烦,类结构表现了依赖关系,xml中又要表现一次,而且当新增属性,又要多加子标签
XML提供了全局自动装配和局部自动装配,自动装配就是Spring自己根据类结构将表现bean的依赖关系
- 全局自动装配:XML根标签
<beans>
末尾加default-autowire=""
配置
这样,整个beans标签内的bean都会自动装配
- 局部自动装配:在对应的父bean标签添加属性
autowire=""
装配属性:
-
no:默认值,不开启自动装配
-
default:默认采用上一级标签的自动装配的取值。如果存在多个配置文件的话,那么每一个配置文件的自动装配方式都是独立的
-
byName:Spring容器查看XML配置文件中autowire属性设置为byName的bean(全局或局部)。然后,它尝试将其属性与配置文件中相同名称定义的bean进行匹配并连接。如果找到匹配,它将注入这些bean。否则,bean将不会被注入
如上面案例中,byName可以注入,但当修改属性名就无法注入
原因是属性名不对,Setter方法名不对public void setBee(Beer bee)
,自然无法注入,可以通过在成员变量上加上标签@Qualifier自定义别名解决
- byType:通过参数的数据类型自动装配,如果一个bean的数据类型和另外一个bean的property属性的数据类型兼容,就自动装配
byType主要涉及到继承关系
Car继承体系:
package pojo.bytype;
interface Car {
}
class FirstCar implements Car{}
class SecondCar implements Car{}
Person类:
package pojo.bytype;
public class Person {
//自动注入,但是以byType方法,变量名需要对齐类名
private Car car;
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Person{" +
"car=" + car +
'}';
}
}
在xml配置文件中:
<bean id="person" class="pojo.bytype.Person" autowire="byType"/>
<bean id="firstCar" class="pojo.bytype.FirstCar"/>
这样就可以运行成功,使用byType,不用关心实体类成员变量name与bean的id
但如果有两个同类型的bean就无法识别
<bean id="person" class="pojo.bytype.Person" autowire="byType"/>
<bean id="firstCar" class="pojo.bytype.FirstCar"/>
<bean id="secondCar" class="pojo.bytype.SecondCar"/>
因为我们在person类中的属性是Car car
,有两个指向car类型的bean,无法识别
- constructor:通过构造器自动装配
很明显,是通过有参构造方法自动装配的
小结:
- 全局自动装配是在
<beans>
标签添加属性default-autowire=""
- 局部自动装配是在想要注入的
<bean>
标签添加属性autowire
- 自动装配有5个类型,no是spring默认的,无自动装配,default是采用上一节标签的自动装配方案
- byName是通过bean的id与要注入的实体类属性名对应,通过Setter注入
- byType是通过bean类型与要注入的实体类属性类型对应,所以一个注入的类型只能有一个bean,通过Setter注入
- constructor通过有参构造器注入
Annotation-based Container Configuration
基于注解的依赖注入,Spring提供了注解的方式简便XML配置
我们前面需要<bean>
指定特定的实体类,而通过在实体类上注解,Spring自动扫描装配即可代替<bean>
- 标记实体类
Spring提供了一些标签:@Component、@Service、@Controller
等等,最基础的是@Component,其他的标记注解都是基于这个基础注解,这个注解表示类是Spring的组件bean,即会被Spring扫描成bean
package pojo.bytype;
import org.springframework.stereotype.Component;
interface Car {
}
@Component
class FirstCar implements Car{}
class SecondCar implements Car{}
- 注入属性
@Autowired
,类似与前面的自动装配,这个是自动注入
当然还有@Resource(JSR-250定义),@Inject(JSR-330定义)
属性名、构造方法、Setter方法都可以添加该注解,即可以切换注入方式(添加在属性名、构造方法上就是有参构造器注入,添加在Setter方法上就是Setter注入)
- 告诉Spring需要自动扫描
即然实体类都标记好了,就需要告诉Spring开启扫描
<context:component-scan base-package="pojo.bytype"/>
从官方的注释看:
<context:component-scan>的使用隐式启用<context:annotation-config>的功能。使用<context:component-scan>时,通常无需包含<context:annotation-config>元素。
在使用注解时,需要在Spring中配置AnnotationBeanPostProcessor,如autowired注解需要<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor "/>
,当然,这是一种完整的做法
而<context:annotation-config/>
是隐式地向Spring容器注册AutowiredAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor以及PersistenceAnnotationBeanPostProcessor这4个BeanPostProcessor,也就是可以直接使用注解
运行:
@Test
public void test3(){
//ClassPathXmlApplicationContext解析配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("annotation.xml");
pojo.bytype.Person person = (pojo.bytype.Person) applicationContext.getBean("person");
System.out.println(person.toString());
}
那么是使用什么自动装配方式呢?byName、byType、constructor?
修改一下Car的名字private Car myCar;
测试:
很明显,不是byName
在两个Car子类上都添加@Component
再运行:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'pojo.bytype.Car' available: expected single matching bean but found 2: firstCar,secondCar
报错,提醒我们有两个Car类
明显,就是byType自动注入方式,但当我们把属性名该为对应的注入类的类名
可以注入,注入的确实是FirstCar
当byType失败时,会尝试byName,都失败就报错
如果不想改属性名,Spring还提供了别名注解@Qualifier("firstCar")
Java-based Container Configuration
基于Java类的依赖注入
上面还是需要在XML配置文件中配置标签,可不可以完全抛弃XML文件呢?
基于Java类+注解可以实现(SpringBoot就是这种方式)
需要两个注解:
@Configuration
:表示这个Java类是Spring配置类,替代XML@ComponentScan(value = "pojo.bytype")
表示该配置类扫描哪个包,替代XML中的<context:component-scan base-package="pojo.bytype"/>
基于Java的依赖注入步骤:
- 设置一个配置类:
package pojo.bytype;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(value = "pojo.bytype")
public class Config {
}
- 修改测试类:由于我们已经没有了XML配置文件,就不能使用
ClassPathXmlApplicationContext
类解析了,Spring提供了AnnotationConfigApplicationContext
解析注解配置文件
@Test
public void test4(){
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
pojo.bytype.Person person = (pojo.bytype.Person) context.getBean("person");
System.out.println(person.toString());
}
这样,就完全不需要XML配置文件了
当然,如果不使用自动扫描,也可以在JavaConfig里配置Bean
package pojo.bytype;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
@Bean
public Car car(){
return new FirstCar();
}
@Bean
public Person person(){
return new Person();
}
}
一样可以注入
总结
- 依赖注入有3种模式:基于XML配置文件、基于注解,基于Java配置类(这三种可以混搭),有2种注入方式:Setter和有参构造
- 基于XML配置文件的依赖注入比较麻烦,需要在XML配置文件设置bean及依赖关系,可以通过自动装配简化
- 全局自动装配
default-autowire=""
,局部自动装配autowire=""
- 基于注解,可以在需要Spring容器获得的类上添加
@Component
注解,需要注入的地方添加@Autowire
(属性名、构造方法、Setter方法都可以),在XML配置文件中只需要标记需要扫描的包 - 基于Java配置类
@Configuration
表示该Java类为配置类,代替XML@ComponentScan(value = "pojo.bytype")
表示扫描的包,测试类需要AnnotationConfigApplicationContext
类解析注解配置类
学海无涯苦作舟
都看到这了,点个赞呗(^_−)☆