1. IOC与DI
IOC(Inversion of Control):控制反转
DI(Dependency Injection):依赖注入
“控制反转”和“依赖注入”其实是同一个概念:
改变由调用者创建被调用者的过程。被调用者依赖Spring容器注入到调用者中。
之所以出现两个名词是因为“控制反转”比较难懂,听者不明所以,所以有了更形象的“依赖注入”。
2. IOC的发展历程
3. Spring容器
简单说,就是Spring中生成“Bean”的工厂,并管理“Bean”的生命周期。Spring的配置文件说到底就是“Bean”的配置。
“Bean”是Spring管理的基本单位,不仅仅指简单的POJO类,组件也是“Bean”,例如数据源、事务管理器,Hibernate的SessionFactory。
Spring中内置了两种基础DI容器:BeanFactory
和ApplicationContext
。
ApplicationContext
继承了BeanFactory
的所有特性。关系如下图:
BeanFactory
应用与内存、CPU资源受限的场合,一般情况下优先使用ApplicationContext
,因为它除了拥有BeanFactory
支持的所有功能,还可以:
- 载入多个配置文件
- 提供国际化支持
- 事件机制
- 默认初始化所有的singleton Bean(
BeanFactory
则不会)
常用的ApplicationContext
的子类如下图:
- XmlWebApplicationContext:面向Web应用
- AnnotationConfigApplicationContext:基于注解存储DI元数据
- XmlPortlerApplicationContext:面向Portal应用
- ClassPathXmlApplicationContext、FileSystemXmlApplicationContext:适用于各种场景
4. 注入方式
Spring的注入方式有三种:设值注入、构造器注入、工厂注入。
设置注入和构造器注入
实体类:
public class Person{
public String name;
private Axe axe;
public Person(Axe axe, String name) {
System.out.println("Person 的带参构造方法");
this.axe = axe;
this.name = name;
}
/**
* 砍柴
*/
public void choopWood() {
if(axe!=null){
axe.useAxe();
}
}
public void info() {
System.out.println("This person's name is " + name);
}
public void setName(String name) {
this.name = name;
}
public void setAxe(Axe axe) {
this.axe = axe;
}
}
注入类:
public interface Axe {
public void useAxe();
}
public class StoneAxe implements Axe {
public void useAxe() {
System.out.println(test + "石斧砍柴很慢");
}
}
public class IronAxe implements Axe{
public void useAxe() {
System.out.println("铁斧砍柴很快");
}
}
Spring配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!--spring配置文件的根元素,使用spring-beans-3.0.xsd语义约束(.xsd都是语言约束文件)
xmlns:p配置可使用p标签更方便的配置属性 -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- id 表示你这个组件的名字,class表示组件类;autowire为是否自动加载; lazy-init为是否懒加载,即applicationContext容器启动不自动加载 -->
<bean id="person" class="test.wsz.spring.bean.Person" lazy-init="true" autowire="byType" >
<!--自定义set注入,name 为成员变量名称,value为自定义的值 -->
<property name="name" value="wsz"></property>
<!--引用set注入,name 为成员变量名称,ref为注入bean的id -->
<property name="axe" ref="ironaxe"></property>
<!--构造注入,index为形参的位置,从0开始,ref为注入bean的id -->
<constructor-arg ref="stoneaxe" index="0" />
<constructor-arg value="wsz" index="1" />
</bean>
<!--其余实体Bean-->
<bean id="ironaxe" class="test.wsz.spring.bean.IronAxe" />
<bean id="stoneaxe" class="test.wsz.spring.bean.StoneAxe" />
</beans>
从配置的注释可以知道“设置注入”和“构造器注入”的配置方法。
- “设置注入”会调用成员变量的
setXXX
方法。 - “构造器注入”会调用类对应的构造方法。
测试代码:
ApplicationContext context=new ClassPathXmlApplicationContext("config/spring-config.xml");
Person person=context.getBean("person",Person.class);
person.info();
person.choopWood();
工厂方式注入
工厂方式注入有好几种,这里只举“静态工厂注入”和“工厂Bean的例子”
- 静态工厂注入
先创建一个工厂类:
/**
* 静态简单工程类
*/
public class AxeFactory {
/**
* 获取产品的方法,根据形参返回不同的产品
*/
public static Axe getAxe(String type) {
if("stone".equals(type))
return new StoneAxe();
else if("iron".equals(type))
return new IronAxe();
return new StoneAxe();
}
}
再修改之前的类当成产品类:
public class StoneAxe implements Axe {
private String test;
public StoneAxe() {
System.out.println("调用StoneAxe的无参构造方法");
}
public void useAxe() {
System.out.println(test + "石斧砍柴很慢");
}
public void setTest(String test) {
this.test = test;
}
}
再在xml文件中进行对应的配置:
<!--静态工厂类获取Bean,class为工厂类;factory-method为工厂获取方法-->
<bean id="stone" class="test.wsz.spring.factory.AxeFactory" factory-method="getAxe">
<!--设置传入工厂类获取产品方法形参的值-->
<constructor-arg value="stone"/>
<!--设置注入产品Bean的属性-->
<property name="test" value="哈哈,"></property>
</bean>
根据配置我们可以知道,现在设置的工厂类返回StoneAxe
,我们进行测试:
Axe axe=context.getBean("stone",Axe.class);
axe.useAxe();
输出:
哈哈,石斧砍柴很慢
- 工厂Bean注入
这种方式配置比较简单。只需要创建一个实现了Spring工厂接口的工厂类,再将这个工厂类配置在<Beans />
中即可。
工厂类,需实现接口的三个方法:
public class AxeBeanFactory implements FactoryBean<Axe>{
Axe axe=null;
/**
* 返回产品
*/
public Axe getObject() throws Exception {
if(axe==null){
axe=new StoneAxe();
}
return axe;
}
/**
* 返回产品的类型
*/
public Class<?> getObjectType() {
return Axe.class;
}
/**
* 是否单例
*/
public boolean isSingleton() {
return true;
}
}
xml配置:
<!--容器中的工厂Bean-->
<bean id="stonefactory" class="test.wsz.spring.factory.AxeBeanFactory" />
值得注意的是,虽然<Bean />
中配置的好像是工厂类,但实现了Spring工厂接口的该类,最终getBean()
时返回的是对应的产品类。
测试代码:
Axe axe=context.getBean("stonefactory",Axe.class);
axe.useAxe();
输出:
null石斧砍柴很慢
5. 配置细节
Spring的配置的参数太多,这里只总结一些常用的。复杂的等用到时再看书就行。
<bean />
中的常用配置
- scope:
Bean的作用域,有5种:
- singleton:单例模式,即整个容器里面该Bean实例只有一个,默认
- prototype:原型模式,每次
getBean
获取的是一个新创建的对象 - request:每个HTTP请求,产生一个实例
- session:每个HTTP session,产生一个实例
- global session:每个全局的HTTP session,产生一个实例
- lazy-init:
懒加载,当使用使用ApplicationContext
时,会自动初始化所有的singleton
的“Bean”(“Bean”默认是singleton
)。设置懒加载即不自动初始化这个类。 - autowire:
自动注入。即你不用在配置中设定成员变量的注入。设置为byType
时,会自动匹配依赖类型相同的Bean,若有多个类型相同的会抛出异常。设置为byName
时,会查找id名与属性名相等的Bean。 - init-method:
指定Bean依赖关系设置完毕后执行的方法。另外一种实现方式是让Bean类实现InitializingBean
接口 - destory-method:
指定Bean销毁之前执行的方法。另外一种实现方式是让Bean类实现DisposableBean
接口 - p名称空间:
可以看作一个语法糖,在<beans />
中添加:
xmlns:p="http://www.springframework.org/schema/p"
即可更简单的设置注入:
<bean id="person" class="test.wsz.spring.bean.Person"
p:name="name" />
等效于
<bean id="person" class="test.wsz.spring.bean.Person" >
<property name="name" value="wsz"></property>
</bean>
6. 免XML的JAVA类配置
这个其实应用场景比较少,毕竟单纯用Java类完成复杂的Spring配置还是比较困难的。下面简单给个例子:
@Configuration
public class Config {
@Value("大树") String personName; //注入Bean的属性中的值
@Bean(name="person")
public Person person(){
Person p=new Person();
p.setName(personName);
p.setAxe(stoneAxe());
return p;
}
@Bean(name="stoneAxe")
public Axe stoneAxe() {
Axe axe=new StoneAxe();
return axe;
}
}
除了纯粹的Java类配置,还可以在xml中引用Java类配置,或者在Java类配置中引用xml配置。有兴趣的朋友可以自行百度。
本文总结于
《疯狂JavaEE》的第七章《Spring的基本用法》