zl程序教程

您现在的位置是:首页 >  IT要闻

当前栏目

Spring框架及源码学习---手写IOC和AOP

2023-04-18 16:50:12 时间

一主要内容

  • 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 其实指的是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 层没有添加事务控制,怎么办?

没有事务就添加上事务控制,⼿动控制 JDBCConnection事务,但要注意将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 使用动态代理模式增强事务