IoC,Inversion of Control 控制反转,是一个过程。仅通过构造函数、工厂方法或在对象实例化后在对象实例上设置属性来定义其依赖关系。容器负责这些工作,这个过程从本质上来说是bean本身的反向,因此称为反向控制。
1 容器
负责实例化、配置及装配bean。容器从配置元数据那获取该怎么实例化、配置及装配bean。而配置来源主要有三个:1)XML;2)java注释;3)java代码。xml比较常用。
@Configuration // 将一个普通类标志为IoC容器
public class CustomConfig {
@Bean // 定义一个bean
public User customUser() {
return new User();
}
}
private static void test1() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CustomConfig.class);
User user = context.getBean(User.class);
System.out.println(user);
}
上面代码展示了java代码配置bean的方式。通过AnnotationConfigApplicationContext来获取容器。
图 ApplicationContext的方法及实现类
BeanFactory 接口提供了管理Bean的方法,而ApplicationContext继承了该接口,并有以下扩展:
1)更容易与Spring 的aop集成。
2)消息资源处理。
3)事件发布。
4)针对web项目,提供了特定的子类。
图 ApplicationContext的方法及实现类
2 Bean
构成程序主干并由IoC容器管理的对象称为bean。
在模块化开发中,不同模块的容器可能存在依赖了同一bean的bean。有时,我们考虑到扩展或者在某个模块中有特定的命名规范,所依赖的这个bean的命名可能会不同。 比如模块A、B依赖同一个数据源配置bean。 在模块A中该bean命名为datasourceA,在模块B中命名为datasourceB。这时候需要用到别名。
<!--数据源,common.xml-->
<bean id="datasource" class="dao.BaseDao"/>
<!--模块A a.xml-->
<import resource="dao.xml"/>
<!—别名-->
<alias name="datasource" alias="datasourceA"/>
<bean id="teacherService" class="service.TeacherService">
<property name="baseDao" ref="baseDao"/>
</bean>
2.1 实例化
xml配置中,实例化bean有三种方式:1)构造函数;2)静态工厂方法;3)bean的工厂方法。
public class GoodsDao {
}
public class GoodsService {
public GoodsDao makeGoodsDao() {
return new GoodsDao();
}
}
public class StaticFactory {
public static GoodsService makeGoodsService() {
return new GoodsService();
}
}
<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" default-lazy-init="true">
<!--构造函数实例化-->
<bean id="goodsService" class="instantiating.GoodsService"/>
<!--静态工厂实例化,class为该工厂的类-->
<bean id="goodsService2" class="instantiating.StaticFactory" factory-method="makeGoodsService"/>
<!--bean的工厂实例化-->
<bean id="goodsDao" factory-bean="goodsService" factory-method="makeGoodsDao"/>
</beans>
3 依赖
依赖是指对象在运行中需要用到的其他对象。在IoC中由容器负责注入。注入方式有两种:1)构造函数;2)set方法。
public class OtherDao {
}
public class ReportDao {
}
public class ReportService {
private ReportDao reportDao;
private String name;
private Double num;
public ReportService(ReportDao reportDao, String name, Double num) {
this.reportDao = reportDao;
this.name = name;
this.num = num;
}
private Properties dataProperties;
private Map<String, String> stockInfoMap;
private List<String> links;
private OtherDao otherDao;
public void setDataProperties(Properties dataProperties) {
this.dataProperties = dataProperties;
}
public void setStockInfoMap(Map<String, String> stockInfoMap) {
this.stockInfoMap = stockInfoMap;
}
public void setLinks(List<String> links) {
this.links = links;
}
public void setOtherDao(OtherDao otherDao) {
this.otherDao = otherDao;
}
@Override
public String toString() {
return "ReportService{" +
"reportDao=" + reportDao +
", name='" + name + '\'' +
", num=" + num +
", dataProperties=" + dataProperties +
", stockInfoMap=" + stockInfoMap +
", links=" + links +
", otherDao=" + otherDao +
'}';
}
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("dependency.xml");
ReportService reportService = applicationContext.getBean(ReportService.class);
System.out.println(reportService);
}
}
<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" default-lazy-init="true">
<bean id="reportDao" class="dependency.ReportDao"/>
<bean id="reportService" class="dependency.ReportService">
<!-- constructor-arg 为构造函数参数,参数可以依赖其他bean,也可以是基本类似-->
<constructor-arg name="reportDao" ref="reportDao"/>
<constructor-arg name="name" value="reportService层"/>
<constructor-arg name="num" value="103.4"/>
<!-- set方法,来注入参数-->
<!-- Properties类型-->
<property name="dataProperties">
<props>
<prop key="host">localhost</prop>
<prop key="username">admin</prop>
<prop key="password">123</prop>
</props>
</property>
<!-- Map类型-->
<property name="stockInfoMap">
<map>
<entry key="name" value="中国平安"/>
<entry key="stock" value="601318.sh"/>
</map>
</property>
<!-- List类型-->
<property name="links">
<!-- 空值-->
<null />
</property>
<property name="otherDao">
<!-- 内部bean,可以不要id或者name,不会被其他bean依赖-->
<bean class="dependency.OtherDao"/>
</property>
</bean>
</beans>
3.1 depends-on与懒加载
容器创建一个bean时,会先创建起依赖的bean。但是有时两个bean直接没有直接依赖,但是希望在创建这个bean之前,先创建其他的bean。可用depends-on来完成这个需求:
<bean id="teacherDao" class="dao.TeacherDao" depends-on="userDao,baseDao"/>
在创建teacherDao这个bean之前,会先创建userDao及baseDao这两个bean。
在容器中,默认会在项目加载时把所有的bean都创建完成,这样做的好处是某个bean的配置错误能在运行时被发现。但是有时不希望创建所有的bean,希望当要使用这个bean时再来创建,default-lazy-init 懒加载属性可以作用于全局的beans,也可以作用于单个的beans。当值为true时,bean在第一次使用时才会被创建。
3.2 方法注入
假如bean1的某个方法,在每次调用时都需要一个特定的bean2(不是bean1的直接依赖,即非bean1的字段)。传统方法是,可以在该方法中通过ApplicationContext获取bean2。这是这样加大了耦合度,容器提供了Lookup标签来实现此类需求:
public class Bean2 {
}
public class Bean1 {
private final static ApplicationContext applicationContext = new ClassPathXmlApplicationContext("method.xml");
/**
* 传统方式
*/
public void showBean2OfTradition() {
Object bean2 = applicationContext.getBean("bean2");
System.out.println(bean2);
}
/**
* Lookup 方法
*/
public Bean2 getBean2() {
return null;
}
public static void main(String[] args) {
Bean1 bean1 = applicationContext.getBean(Bean1.class);
bean1.showBean2OfTradition();
System.out.println(bean1.getBean2());
}
}
<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" default-lazy-init="true">
<bean id="bean1" class="method.Bean1">
<!-- 会覆盖原getBean2方法,直接返回bean2-->
<lookup-method name="getBean2" bean="bean2"/>
</bean>
<bean id="bean2" class="method.Bean2"/>
</beans>
4 作用域
bean 也可以指定作用域(生命周期),Spring支持六种作用域。bean的scope属性来指定该bean的作用域。
singleton | 默认作用域。不同容器生成的bean不同。 |
prototype | 在同一容器中,不同bean依赖的bean被创建的实例不同。 |
request | 每次请求都会创建不同的bean。 |
session | 每个session都会创建不同 bean。 |
application | 每个servletContext生成不同的bean。 |
websocket | 每个websocket连接生成不同的bean。 |
表 spring 的六种bean的作用域