Spring框架及源码学习---手写IOC和AOP
Spring手写IOC和AOP
一主要内容
- Spring 概述(基本情况)
- 核⼼思想 IoC 和 AOP
- ⼿写实现 IoC 和 AOP(⾃定义spring框架)
- Spring IoC ⾼级应⽤
基础知识
⾼级特性 - Spring IoC 源码深度剖析
设计⾮常优雅
设计模式
注意:原则、⽅法和技巧 - Spring AOP ⾼级应⽤
声明式事务控制 - Spring AOP 源码深度剖析
必要的笔记、必要的图、通俗易懂的语⾔化解知识难点
第⼀部分 Spring 概述
第1节 Spring 简介
Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 Spring MVC 和业务层事务管理等众多的企业级应⽤技术,还能整合开源世界众多著名的第三⽅框架和类库,已 经成为使⽤最多的 Java EE 企业应⽤开源框架。
我们经常说的 Spring 其实指的是Spring Framework(spring 框架)。
第2节 Spring 发展历程
- 1997年 IBM 提出了EJB的思想; 1998年,SUN 制定开发标准规范EJB1.0; 1999年,EJB 1.1发 布;
2001年,EJB 2.0发布; 2003年,EJB 2.1发布; 2006年,EJB 3.0发布; - Rod Johnson(spring之⽗)
- Expert One-to-One J2EE Design and Development(2002)
阐述了J2EE使⽤EJB开发设计的优 点及解决⽅案 - Expert One-to-One J2EE Development without EJB(2004)
阐述了J2EE开发不使⽤EJB的解决 ⽅式(Spring雏形)
2017 年 9 ⽉份发布了 Spring 的最新版本 Spring 5.0 通⽤版(GA)
第3节 Spring 的优势
整个 Spring 优势,传达出⼀个信号,Spring 是⼀个综合性,且有很强的思想性框架,每学习⼀ 天,就能体会到它的⼀些优势。
-
⽅便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的 过度程序耦合。⽤户也不必再为单例模式类、属性⽂件解析等这些很底层的需求编写代码,可以更 专注于上层的应⽤。
-
AOP编程的⽀持
通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过 AOP轻松应付。
-
声明式事务的⽀持
@Transactional
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式⽅式灵活的进⾏事务的管理,提⾼ 开发效率和质量。 -
⽅便程序的测试
可以⽤⾮容器依赖的编程⽅式进⾏⼏乎所有的测试⼯作,测试不再是昂贵的操作,⽽是随⼿可做的 事情。
-
⽅便集成各种优秀框架
Spring可以降低各种框架的使⽤难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、 Quartz等)的直接⽀持。
-
降低JavaEE API的使⽤难度
Spring对JavaEE API(如JDBC、JavaMail、远程调⽤等)进⾏了薄薄的封装层,使这些API的使⽤ 难度⼤为降低。
-
源码是经典的 Java 学习范例
Spring的源代码设计精妙、结构清晰、匠⼼独⽤,处处体现着⼤师对Java设计模式灵活运⽤以及对 Java技术的⾼深造诣。它的源代码无疑是Java技术的最佳实践的范例。
第4节 Spring 的核⼼结构
Spring是⼀个分层⾮常清晰并且依赖关系、职责定位⾮常明确的轻量级框架,主要包括⼏个⼤模块:数据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Container模块 和 Test 模块,如下图所示,Spring依靠这些基本模块,实现了⼀个令⼈愉悦的融合了现有解决⽅案的零 侵⼊的轻量级框架。
-
Spring核⼼容器(Core Container) 容器是Spring框架最核⼼的部分,它管理着Spring应⽤中
bean的创建、配置和管理。在该模块中,包括了Spring bean⼯⼚,它为Spring提供了DI的功能。
基于bean⼯⼚,我们还会发现有多种Spring应⽤上下⽂的实现。所有的Spring模块都构建于核⼼ 容器之上。 -
⾯向切⾯编程(AOP)/Aspects Spring对⾯向切⾯编程提供了丰富的⽀持。这个模块是Spring应
⽤系统中开发切⾯的基础,与DI⼀样,AOP可以帮助应⽤对象解耦。 -
数据访问与集成(Data Access/Integration)
Spring的JDBC和DAO模块封装了⼤量样板代码,这样可以使得数据库代码变得简洁,也可以更专 注于我们的业务,还可以避免数据库资源释放失败⽽引起的问题。 另外,Spring AOP为数据访问 提供了事务管理服务,同时Spring还对ORM进⾏了集成,如Hibernate、MyBatis等。该模块由 JDBC、Transactions、ORM、OXM 和 JMS 等模块组成。 -
Web 该模块提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅案。
SpringMVC框架在Web层提升了应⽤的松耦合⽔平。 -
Test 为了使得开发者能够很⽅便的进⾏测试,Spring提供了测试模块以致⼒于Spring应⽤的测 试。
通过该模块,Spring为使⽤Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现。
第⼆部分 核⼼思想
**注意:**IOC和AOP不是spring提出的,在spring之前就已经存在,只不过更偏向于理论化,spring在技 术层次把这两个思想做了⾮常好的实现(Java)
第1节 IoC
1.1 什么是IoC?
==IoC Inversion of Control (控制反转/反转控制),注意它是⼀个技术思想,不是⼀个技术实现 ==
描述的事情: Java开发领域对象的创建,管理的问题
传统开发⽅式:⽐如类A依赖于类B,往往会在类A中new⼀个B的对象
IoC思想下开发⽅式:我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对 象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可。
我们丧失了⼀个权利(创建、管理对象的权利),得到了⼀个福利(不⽤考虑对象的创建、管理等⼀系列 事情)
为什么叫做控制反转?
控制: 指的是对象创建(实例化、管理)的权利
反转: 控制权交给外部环境了(spring框架、IoC容器)
1.2 IoC解决了什么问题
IoC解决对象之间的耦合问题
1.3 IoC和DI的区别
DI:Dependancy Injection(依赖注⼊)
怎么理解:
IOC和DI描述的是同⼀件事情,只不过⻆度不⼀样罢了
第2节 AOP
2.1 什么是AOP
AOP: Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程 AOP是OOP的延续,从OOP说起。
OOP三⼤特征:封装、继承和多态 oop是⼀种垂直继承体系
OOP编程思想可以解决⼤多数的代码重复问题,但是有⼀些情况是处理不了的,⽐如下⾯的在顶级⽗类 Animal中的多个⽅法中相同位置出现了重复代码,OOP就解决不了。
横切逻辑代码
横切逻辑代码存在什么问题:
- 横切代码重复问题
- 横切逻辑代码和业务代码混杂在⼀起,代码臃肿,维护不⽅便
AOP出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析
代码拆分容易,那么如何在不改变原有业务逻辑的情况下,悄⽆声息的把横切逻辑代码应⽤到原有的业务逻辑中,达到和原来⼀样的效果,这个是⽐较难的
2.2 AOP在解决什么问题
在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
2.3 为什么叫做⾯向切⾯编程
「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑
「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个 ⾯的概念在⾥⾯
第三部分 ⼿写实现 IoC 和 AOP
上⼀部分我们理解了 IoC 和 AOP 思想,我们先不考虑 Spring 是如何实现这两个思想的,此处准备了⼀个『银⾏转账』的案例,请分析该案例在代码层次有什么问题 ?分析之后使⽤我们已有知识解决这些问题(痛点)。其实这个过程我们就是在⼀步步分析并⼿写实现 IoC 和 AOP。
第1节 银⾏转账案例界⾯
第2节 银⾏转账案例表结构
第3节 银⾏转账案例代码调⽤关系
第4节 银⾏转账案例关键代码
第5节 银⾏转账案例代码问题分析
(1)问题⼀: 在上述案例实现中,service层
实现类在使⽤ dao 层
对象时,直接在 TransferServiceImpl
中通过 AccountDao accountDao = new JdbcAccountDaoImpl()
获得了 dao层
对象,然⽽⼀个 new 关键字却将 TransferServiceImpl 和 dao 层具体的⼀个实现类 JdbcAccountDaoImpl 耦合在了⼀起,如果说技术架构发⽣⼀些变动,dao 层的实现要使⽤其它技术, ⽐如 Mybatis,思考切换起来的成本?每⼀个 new 的地⽅都需要修改源代码,重新编译,⾯向接⼝开发 的意义将⼤打折扣?
(2)问题⼆: service 层代码没有竟然还没有进⾏事务控制 ?如果转账过程中出现异常,将可能导致 数据库数据错乱,后果可能会很严重,尤其在⾦融业务。
第6节 问题解决思路
针对问题⼀思考:
-
实例化对象的⽅式除了 new 之外,还有什么技术?
反射 (需要把类的全限定类名配置在xml 中)
-
考虑使⽤设计模式中的⼯⼚模式解耦合,另外项⽬中往往有很多对象需要实例化,那就在⼯⼚中使⽤反射技术实例化对象,⼯⼚模式很合适
- 更进⼀步,代码中能否只声明所需实例的接⼝类型,不出现 new 也不出现⼯⼚类的字眼,如下图?
能!声明⼀个变量并提供 set ⽅法,在反射的时候将所需要的对象注⼊进去吧
针对问题⼆思考:
- service 层没有添加事务控制,怎么办?
没有事务就添加上事务控制,⼿动控制
JDBC
的Connection
事务,但要注意将Connection
和当前线程绑定(即保证⼀个线程只有⼀个Connection
,这样操作才针对的是同⼀个Connection
,进⽽控制的是同⼀个事务)
第7节 案例代码改造
(1)针对问题⼀的代码改造
lagou-transfer-sxIoc 由new对象的方式升级为bean.xml解析对象
- beans.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!--跟标签beans,里面配置一个又一个的bean子标签,每一个bean子标签都代表一个类的配置-->
<beans>
<!--id标识对象,class是类的全限定类名-->
<bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl">
<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>
<bean id="transferService" class="com.lagou.edu.service.impl.TransferServiceImpl">
<!--set+ name 之后锁定到传值的set方法了,通过反射技术可以调用该方法传入对应的值-->
<property name="AccountDao" ref="accountDao"></property>
</bean>
<!--配置新增的三个Bean-->
<bean id="connectionUtils" class="com.lagou.edu.utils.ConnectionUtils"></bean>
</beans>
- 增加 BeanFactory.java
/**
* 读取bean.xml文件,解析里面的所有标签,
* 进行对象实例化后放入到map中,后续使用可以直接从map中取即可
* 工厂类,生产对象(使用反射技术)
*/
public class BeanFactory {
/**
* 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
* 任务二:对外提供获取实例对象的接口(根据id获取)
*/
// 存储对象
private static Map<String, Object> map = new HashMap<>();
/**
* XPath 使用路径表达式来选取 XML 文档中的节点或节点集
*
* 经常使用到的路径表达式,如下
*
* nodename 选取此节点的所有子节点。
* / 从根节点选取。
* // 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
* . 选取当前节点。
* .. 选取当前节点的父节点。
* @ 选取属性。
*
*/
static {
// 任务一:读取解析xml,通过反射技术实例化对象并且存储待用(map集合)
// 加载xml
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
// 解析xml
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
//查找所有的bean标签
List<Element> beanList = rootElement.selectNodes("//bean");
for (int i = 0; i < beanList.size(); i++) {
Element element = beanList.get(i);
// 处理每个bean元素,获取到该元素的id 和 class 属性
String id = element.attributeValue("id"); // accountDao
String clazz = element.attributeValue("class"); // com.lagou.edu.dao.impl.JdbcAccountDaoImpl
// 通过反射技术实例化对象
Class<?> aClass = Class.forName(clazz);
// 实例化之后的对象
Object o = aClass.newInstance();
// 存储到map中待用,key-类名称 value--实例化后的类对象
map.put(id, o);
}
// 实例化完成之后维护对象的依赖关系,检查哪些对象需要传值进入,根据它的配置,我们传入相应的值
// 有property子元素的bean就有传值需求
List<Element> propertyList = rootElement.selectNodes("//property");
// 解析property,获取父元素
for (int i = 0; i < propertyList.size(); i++) {
Element element = propertyList.get(i);
//<property name="AccountDao" ref="accountDao"></property>
String name = element.attributeValue("name");
String ref = element.attributeValue("ref");
// 找到当前需要被处理依赖关系的bean
Element parent = element.getParent();
// 调用父元素对象的反射功能
String parentId = parent.attributeValue("id");
Object parentObject = map.get(parentId);
// 遍历父对象中的所有方法,找到"set" + name
Method[] methods = parentObject.getClass().getMethods();
for (int j = 0; j < methods.length; j++) {
Method method = methods[j];
if (method.getName().equalsIgnoreCase("set" + name)) {
// 该方法就是 setAccountDao(AccountDao accountDao)
method.invoke(parentObject, map.get(ref));
}
}
// 把处理之后的parentObject重新放到map中
map.put(parentId, parentObject);
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
// 任务二:对外提供获取实例对象的接口(根据id获取)
public static Object getBean(String id) {
return map.get(id);
}
}
- 修改 TransferServlet
- 修改 TransferServiceImpl
- 改造代码分析图
(2)针对问题⼆的改造
原问题对应代码分析
第一次改造后对应代码分析
lagou-transfer-shoudong-sw 为手动事务代码
虽然能够进行处理事务,但是我们发现service层中需要手写事务处理,如果我们有100个service那是不是就要手动写一百次事务处理。那不得疯了。这种事务代码和业务代码严重耦合在一起。
我们可以使用AOP动态代理来改造。
第二次改造分析
源码地址 lagou-transfer-procxy-sw
public class ProxyFactory {
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
/**
* Jdk动态代理
* @param obj 委托对象
* @return 代理对象
*/
public Object getJdkProxy(Object obj) {
// 获取代理对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
try{
// 开启事务(关闭事务的自动提交)
transactionManager.beginTransaction();
result = method.invoke(obj,args);
// 提交事务
transactionManager.commit();
}catch (Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 抛出异常便于上层servlet捕获
throw e;
}
return result;
}
});
}
/**
* 使用cglib动态代理生成代理对象
* @param obj 委托对象
* @return
*/
public Object getCglibProxy(Object obj) {
return Enhancer.create(obj.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
try{
// 开启事务(关闭事务的自动提交)
transactionManager.beginTransaction();
result = method.invoke(obj,objects);
// 提交事务
transactionManager.commit();
}catch (Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
// 抛出异常便于上层servlet捕获
throw e;
}
return result;
}
});
}
}
lagou-transfer-sxIoc 由new对象的方式升级为bean.xml解析对象
lagou-transfer-shoudong-sw 为手动事务代码
lagou-transfer-procxy-sw 使用动态代理模式增强事务
相关文章
- EasyCVR对接华为iVS订阅摄像机和用户变更请求接口介绍
- 精选 | 腾讯云CDN内容加速场景有哪些?
- 模块化网络防止基于模型的多任务强化学习中的灾难性干扰
- 用搜索和注意力学习稳健的调度方法
- 用于多变量时间序列异常检测的学习图神经网络
- 助力政企自动化自然生长,华为WeAutomate RPA是怎么做到的?
- 使用腾讯轻量云搭建Fiora聊天室
- TSRC安全测试规范
- 云计算“功守道”
- 助力成本优化,腾讯全场景在离线混部系统Caelus正式开源
- Flink 利器:开源平台 StreamX 简介
- 腾讯云实践 | 一图揭秘腾讯碳中和?解决方案
- 深度学习中的轻量级网络架构总结与代码实现
- 信息系统项目管理师(高项复习笔记三)
- Adobe国际认证让科技赋能时尚
- c++该怎么学习(面试吃土记)
- 面试官问发布订阅模式是在问什么?
- 面试官:请实现一个通用函数把 callback 转成 promise
- 空中悬停、翻滚转身、成功着陆,我用强化学习「回收」了SpaceX的火箭
- 中山大学林倞解读视觉语义理解新趋势:从表达学习到知识及因果融合