- MyBatis(已更完)
- Spring(正在更新…)
上节进度
上节完成了基本代码的编写,本节我们继续。本节将进行 IoC的实现。
IoC 实现
此时我们需要一个 Bean 的管理容器,当我们需要 new 对象的时候,可以直接从容器中获取出来。但是我们需要在程序启动的时候(当然也可以懒加载)就把这些对象初始化出来,所以我们需要 XML 来告诉容器需要加载什么内容。
IoC(Inversion of Control,控制反转)简介
控制反转(IoC)是一种设计原则,用于实现组件间的解耦,是面向对象编程中非常重要的概念之一。IoC的核心思想是将程序中对对象的控制权从调用方转移到框架或容器中,使得对象之间的依赖关系由容器来管理。
IoC 的特点
解耦
IoC通过将依赖的管理和创建责任交给容器,减少了模块之间的耦合性,增强了系统的可维护性和可扩展性。
动态依赖管理
容器根据配置或注解动态地将依赖注入到对象中,而不需要硬编码的依赖关系。
灵活性
对象的依赖可以在运行时动态修改,只需更改配置即可,不需要修改代码。
IoC 的应用场景
IoC被广泛应用于各种软件开发框架和项目中,以下是几个典型应用:
- Spring Framework:Spring框架使用IoC容器管理Bean的生命周期和依赖关系。开发者只需定义组件及其依赖,具体的实例化和依赖注入由Spring容器完成。
- Guice 和 Dagger:这些轻量级DI框架在Java项目中也很流行,提供了简单高效的依赖注入功能。
- 前端框架(如Angular):Angular中也实现了IoC,通过其依赖注入系统来管理组件和服务之间的依赖关系。
IoC 的优势
- 增强模块化:通过减少模块之间的直接依赖,促进模块化设计。
- 提高测试性:依赖注入可以方便地替换依赖对象,从而支持单元测试。
- 增强灵活性:通过配置或注解动态注入依赖,无需修改代码即可适应变化。
- 便于维护:解耦使得系统在增加或修改功能时影响最小。
IoC 的限制
- 学习曲线:初学者需要一定时间理解IoC和DI的概念。
- 运行时性能开销:IoC容器在运行时解析依赖关系可能会引入一些性能开销。
- 复杂性:在大型项目中,过度使用IoC可能导致配置和依赖关系变得复杂。
Resources
Resources 目录下
beans.xml
我们先编写一个 XML 来保存我们的 Bean 的信息:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- BeanFactory 类会进行处理这块内容 -->
<beans>
<!-- WzkConnectionUtils 交给容器管理 -->
<bean id="wzkConnectionUtils" class="wzk.utils.WzkConnectionUtils"></bean>
<!-- 依赖了工具类 WzkConnectionUtils -->
<!-- id 是放入到容器中的名称 -->
<bean id="wzkAccountDao" class="wzk.dao.impl.JdbcWzkAccountDaoImpl">
<!-- name是成员变量名字 ref是引用从容器中拿对象 -->
<property name="WzkConnectionUtils" ref="wzkConnectionUtils"/>
</bean>
<!-- 依赖了 WzkAccountDao -->
<bean id="wzkTransferService" class="wzk.service.impl.WzkTransferServiceImpl">
<!-- name是成员变量名字 ref是引用从容器中拿对象 -->
<property name="WzkAccountDao" ref="wzkAccountDao"></property>
</bean>
</beans>
对应的内容截图如下所示:
注意:需要将 beans.xml 放置到 resources 目录下。
Proxy
BeanFactory
public class BeanFactory {
private BeanFactory() {}
/**
* 全局容器 对象都存储到这里
*/
private static Map<String, Object> map = new HashMap<>();
static {
// 对 XML 进行处理,这里可以参考我们之前 手写的MyBatis的部分
// 静态代码块 来进行初始化
InputStream resourceAsSteam = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsSteam);
Element rootElement = document.getRootElement();
List<Element> beanList = rootElement.selectNodes("//bean");
for (Element element : beanList) {
// 处理每个 Bean 元素
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
// 反射拿到对象
Class<?> aClass = Class.forName(clazz);
Object object = aClass.newInstance();
// 保存到容器中
map.put(id, object);
}
List<Element> propertyList = rootElement.selectNodes("//property");
for (Element element : propertyList) {
// 处理每个依赖关系
String name = element.attributeValue("name");
String ref = element.attributeValue("ref");
// 拿到父级节点 它下边的 property都是它的依赖
Element parentElement = element.getParent();
String parentId = parentElement.attributeValue("id");
// 通过父级的 ID 拿到对象
Object parentObject = map.get(parentId);
// 拿到父级的所有方法 Get Set 等等
Method[] methods = parentObject.getClass().getMethods();
for (Method method : methods) {
// 如果是 set 方法的话 我们调用就可以将它的依赖赋值给父级
if (method.getName().equalsIgnoreCase("set" + name)) {
method.invoke(parentObject, map.get(ref));
}
}
// 记得更新容器的内容
map.put(parentId, parentObject);
}
System.out.println("BeanFactory 初始化完毕 map:" + map);
} catch (Exception e) {
System.out.println("BeanFactory 初始化失败");
e.printStackTrace();
}
}
public static Object getBean(String id) {
return map.get(id);
}
}
对应的截图如下所示:
Controller
WzkServlet
我们对 WzkServlet 进行一些修改:
@WebServlet(name="wzkServlet", urlPatterns = "/wzkServlet")
public class WzkServlet extends HttpServlet {
// ========================== 2 ==========================
// 由 BeanFactory 处理
private WzkTransferService wzkTransferService = (WzkTransferService) BeanFactory.getBean("wzkTransferService");
// =======================================================
@Override
protected void doGet(javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse resp) throws javax.servlet.ServletException, IOException {
System.out.println("=== WzkServlet doGet ===");
// ======================= 1 =============================
// 由于没有 Spring 的帮助 我们就需要手动去创建和维护依赖之间的关系
// 组装 DAO DAO层依赖于 ConnectionUtils 和 DruidUtils
// JdbcWzkAccountDaoImpl jdbcWzkAccountDaoImpl = new JdbcWzkAccountDaoImpl();
// jdbcWzkAccountDaoImpl.setConnectionUtils(new WzkConnectionUtils());
// 组装 Service
// WzkTransferServiceImpl wzkTransferService = new WzkTransferServiceImpl();
// wzkTransferService.setWzkAccountDao(jdbcWzkAccountDaoImpl);
// ======================================================
// 执行业务逻辑
try {
wzkTransferService.transfer("1", "2", 100);
} catch (Exception e) {
e.printStackTrace();
System.out.println("=== transfer error ====");
}
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print("=== WzkServlet doGet ===");
}
}
WzkServlet(2)
还有一种写法也是类似的,这样的处理方式也可以顺利执行的。
// ========================== 3 ==========================
// 另一种方式 相同的
private WzkTransferService wzkTransferService;
@Override
public void init() throws ServletException {
super.init();
this.wzkTransferService = (WzkTransferService) BeanFactory.getBean("wzkTransferService");
}
// ======================================================
测试运行
我们运行代码之后,可以看到控制台如下所示:
BeanFactory 初始化完毕 map:{wzkTransferService=wzk.service.impl.WzkTransferServiceImpl@5dfdd67c, wzkAccountDao=wzk.dao.impl.JdbcWzkAccountDaoImpl@380d9fc0, wzkConnectionUtils=wzk.utils.WzkConnectionUtils@2d150354}
=== WzkServlet doGet ===
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
11月 18, 2024 6:24:24 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1} inited
transfer fromResult: 1 toResult: 1
对应的控制台结束如下所示:
查看结果
数据库的结果已经变化了,可以看到结果如下:
当然,刚才的代码打印的内容,也说明我们的代码是正常工作的。
(你可以打断点进行调试)
目前问题
我们虽然已经实现了简易的 IoC,但是对于当前业务来说,我们还需要对事务进行控制,此时需要我们实现一个事务的管理器,将采取和数据库一样,用 ThreadLocal 来进行控制。