SpringIOC 机制的详解
IOC概述及设计思想
SpringIOC:IOC 是 Inversion of Control 的缩写,多数书籍翻译成“控制反转”,还有些书籍翻译成为“控制反向”或者“控制倒置”。
1996 年,Michael Mattson 在一篇有关探讨面向对象框架的文章中,首先提出了 IOC 这个概念。简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且可以灵活地被重用和扩展。
IOC 理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦 如下图
大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,使得A、B、C、D这4个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了, 全部对象的控制权全部上缴给“第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合 在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来。
我们再来做个试验:把上图中间的IOC容器拿掉,然后再来看看这套系统:
我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D这4个对象之间已经没有了耦合关系,彼此毫无联系,这样的话, 当你在实现A的时候,根本无须再去考虑B、C和D了,对象之间的依赖关系已经降低到了最低程度。所以,如果真能实现IOC容器,对于系统开发而言,这将是 一件多么美好的事情,参与开发的每一成员只要实现自己的类就可以了,跟别人没有任何关系!
IOC作用
IOC本质上就是一个大工程,大容器。主要作用就是创建和管理对象的依赖关系,削减计算机程序的耦合(解除我们代码中的依赖关系),提高程序的可扩展性和可维护性。
Spring基于XML的IOC细节
1 、IOC配置文件详解:配置标签书写规范,配置标签的属性
bean标签: 用于配置对象交给Spring 来创建。
默认情况下他会调用类中无参数的构造器,如果没有无参数构造器则不能成功创建
基本属性:
id : Bean实例对象在Spring容器当中的唯一标识
class: Bean 的全限定类名
2、 SpringIOC机制源码解析
IOC容器解析
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂,Spring 中工厂的类结构图如下:
1)BeanFactory :IOC容器的基本实现,是Spring内部使用的接口,不提供开发人员使用,加载配置文件时,不 会创建对象,在获得(使用)对象时才采取创建对象。
2)HierarchicalBeanFactory:这个工厂接口非常简单,实现了Bean工厂的分层。 工厂接口也是继承自BeanFacotory,也是一个二级接口,相对于父接口,它只扩展了一个重要的功能——工厂分层
3)AutowireCapableBeanFactory:该接口有自动装配能力,需要注意的是,ApplicationContext接口并没有实现此接口,因为应用代码很少用到此功能,如果确实需要的话,可以调用ApplicationContext的getAutowireCapableBeanFactory方法,来获取此接口的实例。
4)ListableBeanFactory:获取bean时,Spring 鼓励使用这个接口定义的api,如查看Bean的个数、获取某一类型Bean的配置名、查看容器中是否包括某一Bean等方法。
5)ApplicationContext: BeanFactory接口的子接口,提供更多强大的功能,一般由开发人员使用.接口提供了bean基础性操作同时,扩展了国际化等功能。ApplicationContext接口在加载配置文件时候就会配置文件当中的对象进行创建,存放在IOC容器当中
6)AnnotationConfigApplicationContext:
当使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
7)ClassPathXmlApplicationContext
它是从类的根路径下加载配置文件 推荐使用这种
8)FileSystemXmlApplicationContext
它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
IOC容器底层bean初始化过程
1)BeanFactoryPostProcessor
作用:定义了在bean工厂对象创建后,bean对象创建前执行的动作,用于对工厂进行创建后业务处理
运行时机: 操作用于对工厂进行处理,仅运行一次
2)BeanPostProcessor
作用: 定义了所有bean初始化前后进行的统一动作,用于对bean进行创建前业务处理与创建后业务处理
运行时机:当前操作伴随着每个bean的创建过程,每次创建bean均运行该操作
3)InitializingBean
作用:定义了每个bean的初始化前进行的动作,属于非统一性动作,用于对bean进行创建前业务处理。类似于init-method。
运行时机:当前操作伴随着任意一个bean的创建过程,保障其个性化业务处理
手动实现自己的IOC容器
分析IOC 实现思路
实现步骤
构建maven项目,引入依赖
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<!--导入spring的context坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
设计接口和类以及编写配置文件
//设定UserDao接口
public interface UserDao {
public void save();
}
//设定UserDao接口实现类
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("userDao save method running...... ");
}
}
<!--配置userDaoImpl-->
<bean id="userDao" class="com.ujiuye.dao.impl.UserDaoImpl"></bean>
使用xml技术解析配置文件
<!--引入dom4J-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
/**
* 创建自己的工厂类
*/
public class MyBeanFactory {
//创建一个map集合,模拟IOC容器
private static Map<String,Object> map = new HashMap<>();
static{
try {
//使用dom4J 解析xml文件:
//第一步:获得一个解析器:
SAXReader reader = new SAXReader();
//第二: 读取外部的配置文件:
String path = "src/main/resources/applicationContext.xml";
//第三: 读取了整个文档对象
Document document = reader.read(path);
//第四: 获得根节点:
Element rootElement = document.getRootElement();//beans
//第五: 获得根节点下的所有的bean 标签对应的节点:
List<Element> bean = rootElement.elements("bean");// bean
for (Element element : bean) {
//获得id属性对应的值:
String id1 = element.attributeValue("id");
//获得class属性对应的值:【全限定类名】
String aClass = element.attributeValue("class");//获得class对应的值: 全限定类名。
//通过反射创建对象:
Class clz = Class.forName(aClass);
Object object = clz.newInstance();
//存容器 id做key,创建出来的对象value
map.put(id1,object);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根据id从容器当中获得对象
* @param id id的名称
* @return 返回Object类型对象
*/
public static Object getBean(String id){
Object o = map.get(id);
return o;
}
}
编写测试文件,展示测试结果
@Test
public void testMyFactory(){
//1:创建工厂对象
MyBeanFactory factory =new MyBeanFactory();
//2:从容器当中根据id获得对象
UserDao userDao = (UserDao) factory.getBean("userDao");
System.out.println(userDao);
userDao.save();
}
运行结果
bean的作用域
所谓Bean的作用域其实就是指Spring给我们创建出的对象的存活范围,在配置文件中通过bean的scope属性指定
scope:指对象的作用范围,取值如下:
取值范围 | 说明 |
---|---|
singleton | 默认值,单例的 |
prototype | 多例的 |
request | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中 |
session | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中 |
global session | WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么globalSession 相当于 session |
bean作用域的解析
当scope的取值为singleton时
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
当scope的取值为prototype时
Bean的实例化个数:多个
Bean的实例化时机:当调用getBean()方法时实例化Bean
当Scope的取值为其他值
scope指定为其他值,需要在特定的环境下使用, 只需要作为一个了解知识,面试能够回答出来即可
<!--
bean中scope属性:
singleton 【默认】 单例————表示始终用的都是一个实例对象
prototype 原型、多例————表示每次都是new一个新的实例对象
request 在web环境中,会把对象放到 request中
session 在web环境中,会把对象放到session中
global session 在web项目中,应用在Protlet环境,如果没有protlet环境,那么global session 相当于session
-->
<!-- 第一种方式,使用bean标签,使用无参的构造方法 -->
<bean id="personDao" class="com.com.chenshuang.dao.impl.PersonDaoImpl" ></bean>
<!-- 第二种方式 使用工厂的静态方法实例化对象,并交给spring容器管理 -->
<bean id="personDaoFactoryStatic" class="com.com.chenshuang.factory.PersonDaoFactory" factory-method="getPersonDao"></bean>
<!-- 第三种方式 使用工厂的非静态方法实例化对象,交给spring容器管理 -->
<bean id="personDaoFactory" class="com.com.chenshuang.factory.PersonDaoFactory"></bean>
<bean id="personDao2" factory-bean="personDaoFactory" factory-method="getPersonDao1"></bean>
Spring中依赖注入方式
构造函数注入
顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置
public class Account {
private String name;
private Integer age;
private Date birthday;
public Account(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
}
使用构造方法注入依赖数据
<!--使用构造函数的方式:给account中的属性赋值
要求:
类中需要提供一个对应参数列表的构造器函数
涉及的标签:
constructor-arg:
属性:
name: 执行参数在构造器中的名称
value:它能赋的值是基本数据类型和 String 类型
ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
-->
<bean id="now" class="java.util.Date"></bean>
<bean id="account" class="com.ujiuye.pojo.Account">
<constructor-arg name="name" value="王达"></constructor-arg>
<constructor-arg name="age" value="20"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
setter注入
顾名思义,就是在类中提供需要注入成员的 set 方法。
public class Account {
private String name;
private Integer age;
private Date birthday;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
利用setter方法注入依赖数据
<!--使用set方法的方式给属性赋值
涉及的标签:
property
属性:
name:找的是类中set方法后面的部分
ref: 给属性赋值是其他bean类型的
value:给属性赋值是基本数据类型和 string 类型的
实际开发当中, 此种方式用的比较多,推荐使用
-->
<bean id="account" class="com.ujiuye.pojo.Account">
<property name="name" value="张三丰"></property>
<property name="age" value="31"></property>
</bean>
注入集合数据
顾名思义,就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。
我们这里介绍注入数组,List,Set,Map,Properties。
public class Account {
//注入数组,List集合,Set集合,Map集合,Properties集合属性
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public Account() {
}
public String[] getMyStrs() {
return myStrs;
}
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public List<String> getMyList() {
return myList;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public Set<String> getMySet() {
return mySet;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public Map<String, String> getMyMap() {
return myMap;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public Properties getMyProps() {
return myProps;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
}
利用setter注入依赖集合数据
<!--注入集合类型数据:
涉及到标签:
List结构: array list set
Map结构: map entry props prop
-->
<bean id="account" class="com.ujiuye.pojo.Account">
<!--注意:在注入集合数据时,只要是结构相同,标签可以互换-->
<!--注入数组数据-->
<property name="myStrs">
<array>
<value>array-AAA</value>
<value>array-BBB</value>
<value>array-CCC</value>
</array>
</property>
<!--注入List集合数据-->
<property name="myList">
<list>
<value>list-AAA</value>
<value>list-BBB</value>
<value>list-CCC</value>
</list>
</property>
<!--注入Set集合数据-->
<property name="mySet">
<list>
<value>set-AAA</value>
<value>set-BBB</value>
<value>set-CCC</value>
</list>
</property>
<!--注入Map集合-->
<property name="myMap">
<map>
<entry key="map-a" value="AAA"></entry>
<entry key="map-b">
<value>BBB</value>
</entry>
</map>
</property>
<!--注入Properties集合-->
<property name="myProps">
<props>
<prop key="pro-a">AAA</prop>
<prop key="pro-b">BBB</prop>
</props>
</property>
</bean>
模板设计模式解析
模板设计模式介绍
模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式
模板方法模式的静态结构图如下:
模板设计模式的应用场景
在多个子类中拥有相同的方法,而且逻辑相同时,可以将这些方法抽出来放到一个模板抽象类中
程序主框架相同,细节不同的情况下,也可以使用模板方法
举例说明:
a:使用过Servlet的人都清楚,除了要在web.xml做相应的配置外,还需继承一个叫HttpServlet的抽象类。HttpSevlet类提供了一个service()方法,这个方法调用七个do方法中的一个或几个,完成对客户端调用的响应。这些do方法需要由HttpServlet的具体子类提供,因此这是典型的模板方法模式。