说明:进阶目录下,会对 IOC、AOP 的应用做一次相对全的介绍,同时包括部分源码等的说明,因此,重点在于分析每个部分/组件,因此不会一步一步创建工程等,重点还是理解 Spring 设计的精妙和思想。刚接触的小伙伴,推荐阅读基础部分后,直接跳转 SpringMvc 继续学习。
1. 传统代码
三层结构:client / servlet / controller <==> service(doGet/doPost)<==> dao(mapper)
public class UserServiceImpl implements UserService {
/* 直接 new 实例化 */
private UserDao userDao = new UserDaoImpl();
@Override
public List<User> listAllUser() {
return userDao.listAllUser();
}
}
存在的问题:
- 调用方强依赖于被调用方,即 Service 强依赖于 Dao 层实现。一旦 UserDaoImpl 出现丢失或者不可读的情况,则会无法通过编译。
2. Spring IOC 的思路
这只是对其思路的仿真,并不是源码,重点是理解思路。2.1 核心实现
创建 bean.properties
创建工厂(核心) ```java package com.ideal.factory;userDao=com.ideal.dao.impl.UserDaoImpl
userService=com.ideal.service.impl.UserServiceImpl
import lombok.extern.slf4j.Slf4j;
import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Properties;
/**
- Bean 工厂 *
- @author baiwenhuang
@date 2022-03-08 5:38 下午 / @Slf4j public class BeanFactory { /
properties 文件 */ private static Properties properties;
/**
缓存 */ private static Map
beanCatchMap = new HashMap<>(); static { properties = new Properties(); try {
properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"));
} catch (IOException e) {
throw new ExceptionInInitializerError("load bean.properties error " + e.getMessage());
} }
public static Object getBean(String beanName) { if (!beanCatchMap.containsKey(beanName)) {
synchronized (BeanFactory.class) { if (!beanCatchMap.containsKey(beanName)) { try { Class<?> beanClass = Class.forName(properties.getProperty(beanName)); Object beanObject = beanClass.newInstance(); beanCatchMap.put(beanName, beanObject); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { throw new RuntimeException("getBean error", e); } } }
} return beanCatchMap.get(beanName); } }
通过工厂获取实例对象 ```java private UserDao userDao = (UserDao) BeanFactory.getBean("userDao");
2.2 解释
2.2.1 解决强依赖/高耦合问题
我们应该努力让程序在编译期不依赖,而运行时才可以有一些必要的依赖(依赖是不可能完全消除的),因为 UserDaoImpl 这个类丢失或者不可读,就相当于代码编译时就缺少了这个类,这也是强依赖的体现。而我们可以通过其类的全限定类名,进行反射,这样也可以构造对象。
而反射是在代码中运行时才会执行,虽然代码仍然不可以运行,但是对于这个 UserDaoImpl 丢失的报错已经延迟到了运行时。即强依赖变成了若依赖。
关键代码:
- properties.getProperty(beanName) 先别管,下面会介绍,这里当做类的全限定名 ```java Class<?> beanClass = Class.forName(properties.getProperty(beanName));
// 上线代码实际执行的样子类似如下(只不过换成动态的了) Class<?> beanClass = Class.forName(“com.ideal.dao.impl.UserDao);
<a name="RZUtE"></a>
### 2.2.2 优化硬编码问题
我们为什么要直接把数据写固定呢,直接通过 Properties 获取不香嘛。<br />在 resources 下创建了 key-value 格式的内容,key 是 bean 的 name,value 是这个 bean 的全限定名
关键代码:
- 通过静态代码块,起到在初始化阶段,加载一次 properties 文件的效果。
- 如果 Factory init 失败了,那么直接抛出异常吧,已经没继续走的必要了。
```java
static {
properties = new Properties();
try {
properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"));
} catch (IOException e) {
throw new ExceptionInInitializerError("load bean.properties error " + e.getMessage());
}
}
这也对应了前面提到的反射中的 properties.getProperty(beanName)
调用者传什么,我就再 properties 中找什么,非常灵活。
Class<?> beanClass = Class.forName(properties.getProperty(beanName));
2.2.3 多例到单例
在调用处,打印多次 BeanFactory.getBean("userDao");
看下结果:特别显眼的十次输出,我们的问题也就出来了,我所创建的10个对象是不同的,也就是说,每一次调用,都会实例化一个新的对象,这也叫做多例
com.ideal.dao.impl.UserDaoImpl@53ca01a2
com.ideal.dao.impl.UserDaoImpl@358c99f5
com.ideal.dao.impl.UserDaoImpl@3ee0fea4
com.ideal.dao.impl.UserDaoImpl@48524010
com.ideal.dao.impl.UserDaoImpl@4b168fa9
com.ideal.dao.impl.UserDaoImpl@1a84f40f
com.ideal.dao.impl.UserDaoImpl@23282c25
com.ideal.dao.impl.UserDaoImpl@7920ba90
com.ideal.dao.impl.UserDaoImpl@6b419da
com.ideal.dao.impl.UserDaoImpl@3b2da18f
这有什么问题吗?
- 多次创建对象的代价就是消耗性能,导致效率会低一些
- 相比较单例,jvm会回收较多的垃圾
- 获取速度比单例慢,因为单例除了第一次,其后都是从缓存中获取
所以,我们要试着将它改成单例的,单例从表现上来看,我们查询到的对象都应该是一个。
这里大家回头看前面的代码,我们通过引入了 Map 作为缓存,配合单例模式的双重校验锁模式,实现了线程安全的单例。
补充:单例也不是就全是好处,单例模式下在并发情况下,可能会出现线程安全问题。请规避在类
3. IOC 的思想和启发
3.1 IOC 和 DL
- new 实例化:强依赖,编译期需要保证代码完整有效,否则报错,但是运行期肯定是能成功的。
- 工厂实例化:弱依赖,运行时才去通过 BeanFactory 获取 properties 中的 value,然后根据这个全限定类名 value 去强转为 UserDao 对象,如果失败,则抛出异常。
new 的感觉,就是我们自己来创建对象,所以需要保证它一开始就是对的。而使用工厂在运行时创建对象,就是将控制权交给了别人(代码中的工厂)。
所以 ,IOC(Inverse Of Contro) 也就是控制反转,它是一种思想,也就是将在程序中创建对象的控制权,从自己手上交到 Spring 框架中,IOC 可以理解为一个工厂,创建对象的时候,只需要配置好文件或者注解就可以了,对象在背后如何被创建,完全无需操心。不仅效率高了,而且耦合度也大大的降低了。
而这个工厂根据指定的 beanName 获取创建对象的过程就叫做依赖查找( Dependency Lookup , DL )。
3.2 启发
- 善用静态工厂,它可将多处的依赖进行抽取和分离。
- 外部化配置文件+反射 = 可解决配置的硬编码问题。
- 缓存 + 单例可以解决对象实例个数的问题,规避线程风险。