Spring学习日记02: IoC与依赖注入
- Spring的jar包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
</dependencies>
- Spring的配置文件
1.配置文件的放置位置:任意要求,没有硬性要求
2.配置文件的命名:没有硬性要求,一般用applicationContext.xml
思考:日后应用Spring框架时,需要进行配置文件路径的设置
IOC(Inversion of Control)
使用对象时,由主动new产生对象转换为由外部提供对象,在此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。
Spring的核心API
每个框架都有自己的核心API。
例如
- struts 中的 Action / ActionSupport / ServletActionContext。
- Mybatis 中的 SqlSessionFactory / SqlSession
Spring 容器并不是只有一个实现,而是自带了多个容器实现,可归纳为两种不同类型:bean 工厂、ApplicationContext。Spring的核心API如下所示:
Bean工厂
bean工厂:由 org.springframework.beans.factory.BeanFactory接口定义;这里不做过多介绍
ApplicationContext容器
ApplicationContext:由 org.springframework.context.ApplicationContext接口定义。
作用:用于对象的创建,可以解耦合。
ApplicationContext 是 spring 中较高级的容器。和 BeanFactory 类似,它可以加载配置文件中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。它增加了企业所需要的功能,比如,从属性文件从解析文本信息和将事件传递给所指定的监听器。
基础继承关系:
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver{...}
可见ApplicationContext是在BeanFactory基础上定义的,实际上ApplicationContext是BeanFactory的一个超全集。
Bean与ApplicationContext两类别关系:
ApplicationContext 基于 BeanFactory 构建,并提供应用框架级别的服务,即BeanFactory是基础,ApplicationContext是一个提升。
ApplicationContext的特点
特点1:单例
特点2:即时加载,是在load xml文件时就进行实例化
特点3:ApplicationContext工厂的对象占用大量内存,不会频繁创建对象:一个应用只会创建一个工厂对象
特点4:ApplicationContext工厂一定是线程安全的(多线程并发访问)
ApplicationContext 常用的接口实现类:
- ClassPathXmlApplicationContext: 该容器读取resources下的XML文件并加载已被定义的 bean。在这里,你不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为,容器会从 CLASSPATH 中搜索 bean 配置文件。
- FileSystemXmlApplicationContext: 该容器从 XML 文件中加载已被定义的 bean。在这里,你需要提供给构造器 XML 文件的完整路径(绝对路径)
- WebXmlApplicationContext:一般在web环境用,该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。
- AnnotationConfigApplicationContext: 用于Annotation
使用SpringFramework来实现工厂模式
通过上节在Java项目中自己手动设定配置文件的方式很麻烦,现在我们使用SpringFramework的自带容器来实现上节的操作。
第一种方法:直接创造Bean
直接创造一个Bean,通过工厂类创造,通过静态工厂方法创造。
要求XML配置中只有 id 和 class, 要求Bean的类必须有默认构造函数(不带参数的)
XML中Bean的写法:
<bean id="airPod" class="cn.silverCorridors.AirPod"/>
主程序中调用:
public static void main( String[] args )
{
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// 第一种写法
IEarPhone earPhone5 = (IEarPhone) ac.getBean("airPod");
// 第二种写法,不用强转
// IEarPhone earPhone5 = ac.getBean("airPod", IEarPhone.class);
System.out.println(earPhone5);
}
第二种方法:使用工厂方法
首先需要初始化工厂实例,然后调用工厂的实例方法返回Javabean实例
工厂实例:
public class BeanFactory2 {
public IEarPhone getAirPod(){
return new AirPod();//暂时写new的方式,实际上应该用反射方式实现AirPod的初始化
}
}
XML中Bean的写法:
<!-- 2.第二种方法,使用工厂方法,首先需要实例化工厂实例,然后调用工厂实例方法返回类实例 -->
<bean id="beanFactory2" class="cn.silverCorridors.BeanFactory2"/>
<bean id="airPod" factory-bean="beanFactory2" factory-method="getAirPod"/>
第三种方法:使用静态工厂的静态方法
<!-- 3.第三种方法,使用静态工厂方法,需要知道静态工厂类和静态方法 -->
<bean id="airPod" class="cn.silverCorridors.BeanFactory" factory-method="getBean">
<constructor-arg value="airPod"/>
</bean>
ApplicationContext常用方法介绍
ApplicationContext ac = new ClassPathXmlApplicationContext("xxx.xml");
// 获取Spring工厂配置文件中所有bean标签的id,没有id则是 “全类名#0”
ac.getBeanDefinitionNames();
// 获取Spring工厂配置文件中所有AirPod类型的bean对应的id
ac.getBeanNamesForType(AirPod.class);
// 用于判定是否存在指定id值对应的Bean
ac.containBeanDefinition("airPod");
Bean的作用范围
当在 Spring 中定义一个 bean 时,你必须声明该 bean 的作用域的选项。例如,为了强制 Spring 在每次需要时都产生一个新的 bean 实例,你应该声明 bean 的作用域的属性为 prototype。同理,如果你想让 Spring 在每次需要时都返回同一个bean实例,你应该声明 bean 的作用域的属性为 singleton。Spring 框架支持以下五个作用域,分别为 singleton、prototype、request、session 和 global session,5种作用域说明如下所示,注意,如果你使用 web-aware ApplicationContext 时,其中三个是可用的。
| 作用域 | 描述 |
|---|---|
| singleton(单例) | 在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值 |
| prototype(多例) | 每次从容器中调用Bean时,都返回一个新的实例(懒加载)。即每次调用getBean()时,相当于执行newXxxBean() |
| request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境。即在请求范围内是单例,跨请求是多例。 |
| session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境。即在Session范围内是单例,跨Session是多例。 |
| global-session | 一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境。在集群环境下,global-session的范围是单例,跨global-session是多例。在非集群环境下,他等同session。 |
Bean的生命周期
当一个 bean 被实例化时,它可能需要执行一些初始化使它转换成可用状态。同样,当 bean 不再需要,并且从容器中移除时,可能需要做一些清除工作。尽管还有一些在 Bean 实例化和销毁之间发生的活动,但是本章将只讨论两个重要的生命周期回调方法,它们在 bean 的初始化和销毁的时候是必需的。
Singleton模式中
- Bean初始化:即时加载,在load XML的时候进行实例化。
- Bean的存在周期:只要容器存在,Bean就一直存在。
- Bean的销毁:容器销毁的时候,Bean销毁
还是那个例子,在AirPod里新加两个方法。
public void init() {
System.out.println("init");
}
public void destroy() {
System.out.println("destroy");
}
然后在xml文件中对AirPod的bean添加两个属性:
<bean id="airPod" class="cn.silverCorridors.AirPod" scope="prototype" init-method="init" destroy-method="destroy"/>
运行类中:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IEarPhone earPhone5 = (IEarPhone) ac.getBean("airPod");
运行结果:
AirPod Constructor.
init
此时我们发现,init和构造器都被执行了,但是destroy没有执行。这是因为ApplicationContext还活着,需要调用ac.close并且需要将运行类中ac的类型改为ClassPathXmlApplicationContext
// 6.5 Bean的生命周期
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IEarPhone earPhone5 = (IEarPhone) ac.getBean("airPod");
ac.close();
运行结果:
AirPod Constructor.
init
destroy
Prototype模式中
- Bean初始化:懒加载,当要getBean的时候进行实例化。
- Bean的存在周期:由用户代码决定。
- Bean的销毁:通过JVM的垃圾回收(GC)算法释放。即当没有变量指向该Bean以后,JVM在适当的时候Gc 该Bean。
与Singleton模式中不同,当运行上面代码时,我们不会看到destroy,因为Spring在Prototype模式下根本看不到这个生成的实例了。
所以这个被销毁时,Spring也是无法知道的。
依赖注入(DI)
依赖注入:Spring在解耦合以后通过设置完成复杂类的实例化。在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入。
XML方式配置依赖注入(DI)
构造函数注入:基础类型 + Bean
- name/index(序号,构造函数参数的顺序,从0开始)/type(给出类型,按顺序) + value/ref,例如
<constructor-arg name="c" ref="c"/>
<constructor-arg index="1" value="5"/>
<constructor-arg type="java.lang.Integer" value="6"/>
也可以不用ref,嵌入Bean标签,可以不用value,嵌入value标签
<constructor-arg name="b">
<bean class="cn.sivlerCorridors.B"/>
</constructor-arg>
set函数注入:基础类型 + Bean + 集合类型
新建一个空参构造,再调用set方法
name(get属性去掉get并且首字母小写后的变量名) + value/ref
<bean id="a" class="cn.sivlerCorridors.A">
<property name="i" value="5"/>
<property name="j" value="6"/>
<property name="str" value="abc"/>
<property name="b" ref="b"/>
<property name="c" ref="c"/>
<property name="strs1">
<array>
<value>asdfqwrqwtdjhfbcinchfih</value>
<value>asdqwrqwr</value>
<value>helloworld</value>
</array>
</property>
<property name="strs2">
<list>
<value>qweqwe</value>
<value>hello</value>
<value>world</value>
</list>
</property>
<property name="str3">
<set>
<value>123</value>
<value>321</value>
<value>789</value>
</set>
</property>
<property name="map">
<map>
<entry key="123" value="cccc"></entry>
<entry key="234">
<value>dddddddddddd</value>
</entry>
</map>
</property>
<property name="props">
<props>
<prop key="123">5555555</prop>
<prop key="456">9999999</prop>
</props>
</property>
<property name="bs">
<array>
<bean class="cn.sivlerCorridors.B"/>
<bean class="cn.sivlerCorridors.B"/>
</array>
</property>
<property name="mapb">
<map>
<entry key="123">
<bean class="cn.sivlerCorridors.B"></bean>
</entry>
<entry key="456">
<bean class="cn.sivlerCorridors.B"></bean>
</entry>
</map>
</property>
</bean>
构造函数注入与set函数注入区别:
构造函数的必须和构造函数的参数一致,set可以不一致
数组:List,Set -》 array,list,set 可以混用
Map: Map,Properties -> map , props 可以混用
注解(Annotation)方式配置依赖注入(DI)
为了支持基于 java 的自动配置,Spring 提供了额外的注解。 虽然我们平时可能加过很多这种类型的注解,但是有三个最基本的注解:
@Component: 注册为由 Spring 管理的类(格式:@Component(value=”“),如果id是类名的小驼峰写法,可以不用写value) 以下三个与Component没有任何区别
@Controller -> 一般用于表示层/Web层
@Service -> 一般用于业务层/中间件/Service层
@Repository -> 一般用于持久层/数据层
@Autowired: 指示 Spring 注入一个依赖对象,如果刚好有且只有一个类型相符的bean,那么这个bean就可以被注入; 如果一个都找不到,报错@ComponentScan: 指示Spring在何处查找带有@Component注解的类,使用方式如下:
@ComponentScan(basePackages = {"cn.sivlerCorridors"})
利用配置文件来设置注入的值(value=“值”)
bean.properties中:
A.i=5
A.j=6
A.str=abc
Class A中:
@Component("a")
@PropertySource({"classpath:bean.properties"})
public class A {
@Value("${A.i}")
private Integer i;
@Value("${A.j}")
private Integer j;
@Value("${A.str}")
private String str;
...
}
执行方法:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean2.xml");
A a = (A) ac.getBean("a");
System.out.println(a);
A{i=5, j=6, str='abc', b=null, c=null, strs1=null, strs2=null, str3=null, map=null, props=null, bs=null, mapb=null}