动手实现简易Spring Ioc和AOP、Spring Bean的生命周期、循环依赖问题
一、简易 Ioc 实现最简单的 IOC 容器只需4步即可实现,如下:加载 xml 配置文件,遍历其中的标签获取标签中的 id 和 class 属性,加载 class 属性对应的类,并创建 bean遍历标签中的标签,获取属性值,并将属性值填充到 bean 中将 bean 注册到 bean 容器中下面就是实现的代码,其中包含的文件作用分别是:SimpleIOC:IOC 的实现类,实现了上面所说的4个步
一、简易 Ioc 实现
最简单的 IOC 容器只需4步即可实现,如下:
- 加载 xml 配置文件,遍历其中的标签
- 获取标签中的 id 和 class 属性,加载 class 属性对应的类,并创建 bean
- 遍历标签中的标签,获取属性值,并将属性值填充到 bean 中
- 将 bean 注册到 bean 容器中
下面就是实现的代码,其中包含的文件作用分别是:
- SimpleIOC:IOC 的实现类,实现了上面所说的4个步骤
- SimpleIOCTest: IOC 的测试类
- Car: IOC 测试使用的 bean
- Wheel:同上
- ioc.xml:配置文件
容器实现类 SimpleIOC 的代码:
/**
* 模仿Spring Ioc实现的简单Ioc类
*/
public class SimpleIOC {
//用来实际存储bean对象
private Map<String, Object> beanMap = new HashMap<String, Object>();
public SimpleIOC(String location) throws Exception {
loadBean(location);
}
/**
* 通过name获取对象
* @param name
* @return
*/
public Object getBean(String name) throws IllegalAccessException {
Object bean = beanMap.get(name);
if (bean == null) {
throw new IllegalAccessException("there is no bean with name " + name);
}
return bean;
}
/**
* 通过 location 指定的配置文件加载bean
* @param location
*/
private void loadBean(String location) throws Exception {
//加载 xml 配置文件
InputStream inputStream = new FileInputStream(location);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = factory.newDocumentBuilder();
Document document = documentBuilder.parse(location);
Element root = document.getDocumentElement();
NodeList nodes = root.getChildNodes();
//遍历 <bean> 标签
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
String id = ele.getAttribute("id");
String className = ele.getAttribute("class");
//加载 beanClass
Class beanClass = null;
try {
beanClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return;
}
//创建bean
Object bean = beanClass.newInstance();
//遍历 <property> 标签
NodeList propertys = ele.getElementsByTagName("property");
for (int j = 0; j < propertys.getLength(); j++) {
Node propertyNode = propertys.item(j);
if (propertyNode instanceof Element) {
Element propertyEle = (Element) propertyNode;
String name = propertyEle.getAttribute("name");
String value = propertyEle.getAttribute("value");
//利用反射将bean相关的字段访问限权设为可访问
Field declaredField = bean.getClass().getDeclaredField(name);
declaredField.setAccessible(true);
if (value != null && value.length() > 0) {
//将属性值填充到相关字段中
declaredField.set(bean, value);
} else {
String ref = propertyEle.getAttribute("ref");
if (ref == null || ref.length() == 0) {
throw new IllegalAccessException("ref config error");
}
//将引用类型填充到·相关的字段中
declaredField.set(bean, getBean(ref));
}
//将bean注册到bean容器中
registerBean(id, bean);
}
}
}
}
}
private void registerBean(String id, Object bean) {
beanMap.put(id, bean);
}
}
容器测试使用的 bean 代码:
public class Car {
private String name;
private String length;
private String width;
private String height;
private Wheel wheel;
// setter和getter以及toString
}
public class Wheel {
private String brand;
private String specification ;
// setter和getter以及toString
}
bean 配置文件 ioc.xml 内容:
<beans>
<bean id = 'wheel' class = "cn.hanzhuang42.simplespring.Wheel">
<property name="brand" value="Michelin"/>
<property name="specification" value="265/60 R18"/>
</bean>
<bean id = "car" class="cn.hanzhuang42.simplespring.Car">
<property name="name" value="Mercedes Benz G 500"/>
<property name="length" value="4717mm"/>
<property name="width" value="1855mm"/>
<property name="height" value="1949mm"/>
<property name="wheel" ref="wheel"/>
</bean>
</beans>
IOC 测试类 SimpleIOCTest:
public class SimpleIOCTest {
@Test
public void getBean() throws Exception {
String location = SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile();
SimpleIOC beanFactory = new SimpleIOC(location);
Wheel wheel = (Wheel) beanFactory.getBean("wheel");
System.out.println(wheel);
Car car = (Car) beanFactory.getBean("car");
System.out.println(car);
}
}
结果:
以上是简单 IOC 实现的全部内容
二、简易 AOP 实现
Spring AOP默认情况下采用动态代理机制实现AOP。动态代理(Dynamic Proxy)机制,可以在运行期间,为相应的接口(Interface)动态生成对应的代理对象。使用动态代理可以将横切关注点逻辑封装到动态代理的InvocationHandler中,然后在系统运行期间,根据横切关注点需要织入的模块位置,将横切逻辑织入到相应的代理类中。
在介绍 AOP 的实现步骤之前,先引入 Spring AOP 中的一些概念,接下来我们会用到这些概念。
-
连接点(Joinpoint):AOP的功能模块都需要织入到OOP的功能模块中。而这些将要在其之上进行织入操作的系统执行点就称之为Joinpoint。例如:方法调用、方法执行等
-
通知(Advice):Advice是单一横切关注点逻辑的载体,它代表将会织入到Joinpoint的横切逻辑。按照执行时机不同分为:
- 前置通知(Before):在目标方法执行前,执行通知
- 后置通知(After):在目标方法执行后,执行通知,此时不关系目标方法返回的结果是什么
- 返回通知(After-returning):在目标方法执行后,执行通知
- 异常通知(After-throwing):在目标方法抛出异常后执行通知
- 环绕通知(Around): 目标方法被通知包裹,通知在目标方法执行前和执行后都被会调用
-
切点(Pointcut):Pointcut是指Joinpoint的表述方式。Pointcut的表述方式有下面几种:
- 直接指定Joinpoint所在方法名称
- 正则表达式
- 使用特定的Pointcut表述语言
-
切面(Aspect):切面包含了通知和切点,通知和切点共同定义了切面是什么,在何时,何处执行切面逻辑。
说完概念,接下来我们来说说简单 AOP 实现的步骤。这里 AOP 是基于 JDK 动态代理实现的,只需3步即可完成:
- 定义一个包含切面(Aspect)逻辑的对象,这里假设叫 logMethodInvocation
- 定义一个 Advice 对象(实现了 InvocationHandler 接口),并将上面的 logMethodInvocation 和 目标对象传入
- 将上面的 Adivce 对象和目标对象传给 JDK 动态代理方法,为目标对象生成代理
简单 AOP 的代码结构如下所示:
- MethodInvocation 接口:实现类包含了切面(Aspect)逻辑,如上面的 logMethodInvocation
- Advice 接口:继承了 InvocationHandler 接口
- BeforeAdvice 类:实现了 Advice 接口,是一个前置通知
- SimpleAOP 类:生成代理类
- SimpleAOPTest:SimpleAOP 从测试类
- HelloService 接口:目标对象接口
- HelloServiceImpl:目标对象
下面是具体的实现:
MethodInvocation 接口代码:
public interface MethodInvocation {
void invoke();
}
Advice 接口代码:
public interface Advice extends InvocationHandler {}
BeforeAdvice 实现代码:
public class BeforeAdvice implements Advice{
private Object bean;
private MethodInvocation methodInvocation;
public BeforeAdvice(Object bean, MethodInvocation methodInvocation) {
this.bean = bean;
this.methodInvocation = methodInvocation;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在目标方法执行前调用通知
methodInvocation.invoke();
return method.invoke(bean, args);
}
}
SimpleAOP 实现代码:
public class SimpleAOP {
public static Object getProxy(Object bean, Advice advice) {
return Proxy.newProxyInstance(SimpleAOP.class.getClassLoader(),
bean.getClass().getInterfaces(), advice);
}
}
HelloService 接口,及其实现类代码:
public interface HelloService {
void sayHelloWorld();
}
public class HelloServiceImpl implements HelloService {
@Override
public void sayHelloWorld() {
System.out.println("hello world!");
}
}
SimpleAOPTest 代码:
public class SimpleAOPTest {
@Test
public void getProxy() throws Exception {
// 1. 创建一个 MethodInvocation 实现类
MethodInvocation logTask = () -> System.out.println("log task start");
HelloServiceImpl helloServiceImpl = new HelloServiceImpl();
// 2. 创建一个 Advice
Advice beforeAdvice = new BeforeAdvice(helloServiceImpl, logTask);
// 3. 为目标对象生成代理
HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloServiceImpl,beforeAdvice);
helloServiceImplProxy.sayHelloWorld();
}
}
运行结果:
以上实现了简单的 IOC 和 AOP,不过实现的 IOC 和 AOP 还很简单,且只能独立运行。
三、稍复杂的IoC和AOP实现
四、Spring Bean的生命周期
接下来我们就从宏观层面上,来看看 Spring 中的 bean 由实例化到销毁的过程。在详细讨论 bean 生命周期前,先上一张图,后面也会围绕这张图展开讨论。
接下来对照上图,一步一步对 singleton 类型 bean 的生命周期进行解析:
- 实例化 bean 对象,类似于 new XXObject()
- 将配置文件中配置的属性填充到刚刚创建的 bean 对象中。
- 检查 bean 对象是否实现了 Aware 一类的接口,如果实现了则把相应的依赖设置到 bean 对象中。比如如果 bean 实现了 BeanFactoryAware 接口,Spring 容器在实例化bean的过程中,会将 BeanFactory 容器注入到 bean 中。
- 调用 BeanPostProcessor 前置处理方法,即 postProcessBeforeInitialization(Object bean, String beanName)。
- 检查 bean 对象是否实现了 InitializingBean 接口,如果实现,则调用 afterPropertiesSet 方法。或者检查配置文件中是否配置了 init-method 属性,如果配置了,则去调用 init-method 属性配置的方法。
- 调用 BeanPostProcessor 后置处理方法,即 postProcessAfterInitialization(Object bean, String beanName)。我们所熟知的 AOP 就是在这里将 Adivce 逻辑织入到 bean 中的。
- 注册 Destruction 相关回调方法。
- bean 对象处于就绪状态,可以使用了。
- 应用上下文被销毁,调用注册的 Destruction 相关方法。如果 bean 实现了 DispostbleBean 接口,Spring 容器会调用 destroy 方法。如果在配置文件中配置了 destroy 属性,Spring 容器则会调用 destroy 属性对应的方法。
五、循环依赖问题
现在考虑这样一种情况,BeanA 依赖 BeanB,BeanB 依赖 BeanC,BeanC 又依赖 BeanA。三者形成了循环依赖,如下所示:
对于这样的循环依赖,根据依赖注入方式的不同,Spring 处理方式也不同。**如果依赖靠构造器方式注入,则无法处理,Spring 直接会报循环依赖异常。**因为就算在使用下面要介绍的三级缓存解决循环依赖时也要先使用构造函数创建对象
但是其他的注入方式,Spring仍然可以解决
Spring 如何解决的
- Spring 为了解决单例的循环依赖问题,使用了 三级缓存 ,递归调用时发现 Bean 还在创建中即为循环依赖
- 单例模式的 Bean 保存在如下的数据结构中:
/** 一级缓存:用于存放完全初始化好的 bean **/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** 二级缓存:存放原始的 bean 对象(尚未填充属性),用于解决循环依赖 */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** 三级级缓存:存放 bean 工厂对象,用于解决循环依赖 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/**
bean的获取过程:先从一级获取,失败再从二级、三级里面获取
创建中状态:是指对象已经new出来了,但是所有属性均为null,等待init
*/
假设现在两个单例bean:A和B存在循环依赖问题,检测循环以来的过程如下:
- A创建过程中需要B,于是A将自己放入三级缓存中,去实例化B
- B实例化的时候发现需要A,于是B先查一级缓存,如果没有在查二级缓存,还是没有,就查三级缓存,找到了!
- 然后把三级缓存中的这个A放到二级缓存里面,并删除三级缓存中的A
- B将此时的A设置为自己的属性,顺利初始化完毕,将自己(B)放到一级缓存中(此时B里面的A依然是创建中状态)
- 然后回来接着创建A,此时B已经创建结束,直接从一级缓存中拿到B,然后完成创建,并将自己放到一级缓存中
- 如此依赖就解决了循环依赖问题
// 以上叙述的源代码
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
一句话总结就是:先让最底层(严格来说是自己选的)的对象完成初始化,通过三级缓存与二级缓存提前曝光创建中的Bean,让其他 Bean 率先完成初始化。
更多推荐
所有评论(0)