1、第一部分 Spring 概述

1、Spring的简介

Spring是分层(可应用三层的任何一层)的full-Stack(全栈)轻量级开源框架,以IoC和AOP为内核,提供了展现层SpringMVC和业务层事务管理等众多的企业级应用技术,还能整合众多著名的第三方框架和类库。 通常讲的Spring是指Spring Framework
Spring 官网地址: http://spring.io/

2、Spring的优势

1、方便解耦,简化开发

通过Spring提供的loC容器, 可以将对象间的依赖关系交由Spring进行控制,(通过Spring来帮我们去创建对象,不需要我们自己手动创建对象,这样两个类之间的耦合度就降低) 。避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。

2、AOP编程的支持

通过Spring的AOP功能, 方便进行面向切面的编程, 许多不容易用传统OOP实现的功能可以通过AOP轻松应付。

3、声明式事务的支持

@Transactional
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。

4、方便程序的测试

可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。

5、方便集成各种优秀框架

Spring可以降低各种框架的使用难度, 提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等) 的直接支持。

6、降低Java EE API的使用难度

Spring对Java EE API(如JDBC、JavaMail、远程调用等) 进行了薄薄的封装层, 使这些API的使用难度大为降低。

7、源码是经典的Java学习范例

Spring的源代码设计精妙、结构清晰、匠心独用, 处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无意是Java技术的最佳实践的范例。

3、Spring的核心结构

Spring是一个分层非常清晰并且依赖关系、职责定位非常明确的轻量级框架, 主要包括几个大模块:数据处理模块(Data Access)、Web模块、AOP(Aspect Oriented Programming) /Aspects模块、Core Container模块和Test模块, 如下图所示, Spring依靠这些基本模块, 实现了一个令人愉悦的融合了现有解决方案的零侵入的轻量级框架。

模块化编程,使用哪个模块引入对应的模块即可,不需要的不用引入。

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/2397310/1601546624023-8839854b-cd5d-4ce4-bc20-c87e5155d991.png#height=398&id=LQu4Y&originHeight=686&originWidth=797&originalType=binary&ratio=1&size=0&status=done&style=none&width=462)![image.gif](https://cdn.nlark.com/yuque/0/2020/gif/2397310/1601546625374-8fff5d9a-b7cf-404e-857f-99e15bf2fad2.gif#height=1&id=HsQAR&name=image.gif&originHeight=1&originWidth=1&originalType=binary&ratio=1&size=43&status=done&style=none&width=1)
  1. Spring核心容器(Core Container) 容器是Spring框架最核心的部分, 它管理着Spring应用中bean的创建、配置和管理。在该模块中, 包括了Spring bean工厂, 它为Spring提供了DI的功能。基于bean工厂, 我们还会发现有多种Spring应用上下文的实现。所有的Spring模块都构建于核心容器之上。

  2. 面向切面编程(AOP) /Aspects, Spring对面向切面编程提供了丰富的支持。这个模块是Spring应用系统中开发切面的基础, 与DI一样, AOP可以帮助应用对象解耦。

  3. 数据访问与集成(Data Access/Integration),Spring的JDBC和DAO模块封装了大量样板代码, 这样可以使得数据库代码变得简洁, 也可以更专注于我们的业务, 还可以避免数据库资源释放失败而引起的问题。另外, Spring AOP为数据访问提供了事务管理服务, 同时Spring还对ORM进行了集成, 如Hibernate、MyBatis等。该模块由JDBC、Transactions、ORM、OXM和JMS等模块组成。

  4. Web该模块提供了Spring MVC框架给Web应用, 还提供了多种构建和其它应用交互的远程调用方案。Spring MVC框架在Web层提升了应用的松耦合水平。

  5. Test为了使得开发者能够很方便的进行测试, Spring提供了测试模块以致力于Spring应用的测试。通过该模块, Spring为使用Servlet、JNDI等编写单元测试提供了一系列的mock对象实现。

4、看官网的框架和jdk

下载一个Spring框架
一、容器设计实现 - 图1image.gif

一、容器设计实现 - 图3image.gif

一、容器设计实现 - 图5
image.gif
一、容器设计实现 - 图7image.gif

第二部分,核心思想

第一节 IOC

注意:IOC和AOP不是spring提出的,在spring之前就已经存在,只不过更偏向于理论化,spring在技术层次把这两个思想做了非常好的实现(Java)

1.1 什么是IoC?

IoC Inversion of Control (控制反转/反转控制),注意:它是一个技术思想,不是一个技术实现
描述的事情:Java开发领域对象的创建,管理的问题

传统开发方式:比如类A依赖于类B,往往会在类A中new一个B的对象
IoC思想下开发方式:我们不用自己去new对象了,而是由IoC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使用哪个对象,去问IoC容器要即可(就好比衣来伸手饭来张口)

我们丧失了一个权利(创建、管理对象的权利),得到了一个福利(不用考虑对象的创建、管理等一系列事情)
为什么叫做控制反转?
控制:指的是对象创建(实例化、管理)的权利
反转:控制权交给外部环境了(spring框架、IoC容器)
不反转: 就是我们自己去创建,去new

传统模式依赖于哪一个,就需要new哪一个如A依赖于B。那么就需要在类A中new一个类B。如下所示。无IOC容器的情况下
IOC模式:A如要依赖于B。先在IoC实例化a、b两个对象(因为A依赖于B)。那么就把b对象注入到A类中。如下所示:有IOC容器的情况下
如何注入呢:可以是set注入,也可以是构造器注入。

                    ![](https://cdn.nlark.com/yuque/0/2020/png/2397310/1601546624181-0dcafbba-5d02-4bda-bec7-1509e21cbbce.png#height=426&id=fVqcn&originHeight=627&originWidth=820&originalType=binary&ratio=1&size=0&status=done&style=none&width=557)![image.gif](https://cdn.nlark.com/yuque/0/2020/gif/2397310/1601546625407-622e99fb-b443-49a1-8ab1-451631f96786.gif#height=1&id=uvVyx&name=image.gif&originHeight=1&originWidth=1&originalType=binary&ratio=1&size=43&status=done&style=none&width=1)

理解:IoC需要依赖DI,因为IoC负责对象的创建和管理,那么如何创建(通过对象实例化),创建完如何到IoC容器呢(通过依赖注入到容器)。这样容器才有我们需要的实例化对象,什么时候调用,什么时候问IoC拿。

1.2 IoC解决了什么问题

IoC解决对象之间的耦合问题
都是面向接口开发,所以必须有接口(为什么是面向接口?)
Service是做业务的,Dao层是做数据交互的

面向接口开发,不应该看到具体的实现类,看到具体的实现类了,就会产生耦合。
一旦业务需要调整,则下面的Dao的实现类都需要改动,而且重新编译,打包等等,这就是耦合对问题。
也就是使用new关键字产生了强耦合。但是有了IOC时,有Spring来实例化。

使用Spring的IOC时候,只需要去声明接口即可。
如下左边所示:声明一个接口类型的属性即可,不是实现类。
private UserDao userdao用来简化降低程序之间的耦合问题。

我的理解:dao层、service层都是面向接口的,每一个接口都有对应的一个实现类impl,虽然通过注入接口属性,但是接口始终是不能实例化的,那ioc肯定是通过找到接口的实例化对象impl进行实例化,注入到容器。
一、容器设计实现 - 图9image.gif

1.3 IOC和DI的区别

=》DI:Dependancy InJection(依赖注入)
一、容器设计实现 - 图11image.gif

第2节 AOP

2.1 什么是AOP?

AOP:Aspect oriented Programming 面向切面/方面编程
AOP是OOP的延续,从OOP 说起
OOP三大特征:封装、继承、多态
OOP是一种垂直继承体系

1、OOP思想垂直继承体系

相同的模块提取出来作为父类。这个是OOP的思想。如下图所示:
子子孙孙可以一直继承下去。

                  ![](https://cdn.nlark.com/yuque/0/2020/png/2397310/1601546624316-2841551d-064c-4534-852f-86fabc32a002.png#height=353&id=XmB6U&originHeight=483&originWidth=855&originalType=binary&ratio=1&size=0&status=done&style=none&width=624)![image.gif](https://cdn.nlark.com/yuque/0/2020/gif/2397310/1601546625430-418ef9a1-b014-4f7b-83ba-b59e77b55ab5.gif#height=1&id=huNDR&name=image.gif&originHeight=1&originWidth=1&originalType=binary&ratio=1&size=43&status=done&style=none&width=1)

OOP编程思想可以解决大多数的代码重复问题,但是有一些情况是处理不了的,比如下面的在顶级父类 Animal中的多个方法中相同位置出现了重复代码,OOP就解决不了

性能监控的代码是不影响业务逻辑代码的,只是监控作用。如下图所示:出现了多个相同的性能重复代码。

               ![](https://cdn.nlark.com/yuque/0/2020/png/2397310/1601546624296-089670e6-65d5-4cd2-b221-e11e7849dbf9.png#height=518&id=iJpBt&originHeight=672&originWidth=761&originalType=binary&ratio=1&size=0&status=done&style=none&width=587)![image.gif](https://cdn.nlark.com/yuque/0/2020/gif/2397310/1601546625423-f6440d74-694f-4d9f-b40d-1a673e3dae80.gif#height=1&id=nFWzH&name=image.gif&originHeight=1&originWidth=1&originalType=binary&ratio=1&size=43&status=done&style=none&width=1)

上图中性能监控的重复代码,在OOP思想不能解决,所以要采用下面的AOP思想来解决。

2、AOP思想解决横切逻辑代码

性能监控、事物控制、权限校验、日志等场景(它们不影响业务代码,但又需要,只能通过aop处理,如果不用aop那么就会产生很多冗余代码)

一个方法就相当于一个纵向流程,多个方法就看成是多个纵向流程。eat方法就看成是一个纵向流程。
流程中的start性能代码,end的性能代码都看成是这个eat流程的子流程。
一、容器设计实现 - 图13image.gif

横切逻辑代码存在的问题

  1. 横切代码重复(多个纵向流程中出现重复的子流程,就像是一把刀,横切过去。)
  2. 逻辑代码和业务代码混杂,代码臃肿,维护困难*(看下面的业务逻辑图即可)
  3. 横切逻辑代码的使用场景很有限:一般是事务控制,权限校验,日志(Mybatis集成日志需要了解)

横向抽取,将业务逻辑和横切逻辑拆分,拆分容易,但是合起来比较难。
难点:如何在不改变原有业务逻辑的情况下,悄无声息的把横切逻辑代码应用到原有的业务逻辑中,达到原来的效果?这个是比较难的。

              ![](https://cdn.nlark.com/yuque/0/2020/png/2397310/1601546624352-b4119a19-f90a-4f9d-bb4c-2f675dc0d501.png#height=337&id=VfgVj&originHeight=450&originWidth=766&originalType=binary&ratio=1&size=0&status=done&style=none&width=573)![image.gif](https://cdn.nlark.com/yuque/0/2020/gif/2397310/1601546625438-055dff7b-9fb2-40f1-a75c-763cf2ba12e5.gif#height=1&id=iC7Y9&name=image.gif&originHeight=1&originWidth=1&originalType=binary&ratio=1&size=43&status=done&style=none&width=1)

2.2 AOP在解决什么问题?

在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦,避免横切逻辑代码重复。

2.3 为什么叫面向切面编程?

【切】:指横切逻辑,原有业务逻辑代码不能动,只能操作横切逻辑代码,所以面向横切逻辑。
【面】:指逻辑代码往往需要影响的是很多个方法,每个方法如同一个点,多个点构成面,有一个面的概念在里面

第三部分 手写实现IoC和AOP

此处准备了一个『银行转账』的案例,请分析该案例在代码层次有什么问题 ?分析之后使用我们已有知识解决这些问题(痛点)。其实这个过程我们就是在一步步分析并手写实现 IoC 和 AOP。

1、项目引入Tomcat插件运行

image.png image.png

输入http://localhost:8080/
image.png

image.png

image.png

2、银行转账案例代码调用关系

     ![](https://cdn.nlark.com/yuque/0/2020/png/2397310/1601546624428-152034d4-4193-4ff5-8af8-cb457314cc73.png#height=439&id=iNSn5&originHeight=585&originWidth=630&originalType=binary&ratio=1&size=0&status=done&style=none&width=473)![image.gif](https://cdn.nlark.com/yuque/0/2020/gif/2397310/1601546625441-ac098861-de4a-4955-83e3-72cf076f4532.gif#height=1&id=jzQRU&name=image.gif&originHeight=1&originWidth=1&originalType=binary&ratio=1&size=43&status=done&style=none&width=1)

3、银行转账案例关键代码

TransferServlet类中
一、容器设计实现 - 图20image.gif

4、银行转账案例代码问题分析

一、容器设计实现 - 图22image.gif

5、具体分析

image.png
image.gif

一、容器设计实现 - 图26

对于问题一,就是通过只注入接口,不new实现类(避免耦合问题),那么如何有实例化对象呢?为了避免硬编码,可以用反射(根据全限定类名获取类对象,根据类对象进行实例化)。

6、解决方案

问题一的思考及解决方案(属于IoC解决)

image.png
以前需要对象直接new的改为,将实例化对象配置到xml中,工厂读取并解析xml,通过反射技术(根据全限定类名获取类对象,根据类对象进行实例化),得到实例化的对象,放到容器。那么是通过注入接口名来拿的,那么还需要做接口名和实例化对象的映射关系(可以放到map中)

image.gif

1、是set注入。2、是构造器注入。

一、容器设计实现 - 图29image.gif

小总结
1、将需要实例化的类都配置在xml中,然后通过工厂模式,读取配置信息,再用反射技术实例化对象(通过xml配置的全限定类名获取得到Class对象,根据Class对象进行实例化),并将实例化后的对象存到工厂中。
2、无论哪一层需要实例化对象,则在哪一层只需先声明接口,不需要new实现类,然后通过set注入或者构造器注入的方式进行注入实例化对象。

问题二的思考及解决方案(属于AOP解决)

service 层没有添加事务控制,怎么办?没有事务就添加上事务控制,手动控制 JDBC 的 Connection 事务,但要注意将Connection和当前线程绑定(即保证一个线程只有一个 Connection,这样操作才针对的是同一个Connection,进而控制的是同一个事务
image.png
image.gif将一个线程绑定同一个Connection,在这个线程内所有与数据库相关的操作都去获取这个Connection即可。

1、在service层添加事务控制

先创建事物管理器

public class TransactionManager {

    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    // 开启手动事务控制(将自动提交关闭才能进行手动提交)
    public void beginTransaction() throws SQLException {
        connectionUtils.getCurrentThreadConn().setAutoCommit(false);
    }

    // 提交事务
    public void commit() throws SQLException {
        connectionUtils.getCurrentThreadConn().commit();
    }

    // 回滚事务
    public void rollback() throws SQLException {
        connectionUtils.getCurrentThreadConn().rollback();
    }
}

然后再service层添加事物控制(try catch)

  @Override
    public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {

        try{
            // 开启事务(关闭事务的自动提交)
            TransactionManager.getInstance().beginTransaction();*/

            Account from = accountDao.queryAccountByCardNo(fromCardNo);
            Account to = accountDao.queryAccountByCardNo(toCardNo);

            from.setMoney(from.getMoney()-money);
            to.setMoney(to.getMoney()+money);

            accountDao.updateAccountByCardNo(to);
            int c = 1/0;
            accountDao.updateAccountByCardNo(from);

           // 提交事务

            TransactionManager.getInstance().commit();
        }catch (Exception e) {
            e.printStackTrace();
            // 回滚事务
            TransactionManager.getInstance().rollback();

            // 抛出异常便于上层servlet捕获
            throw e;
        }
    }

但是如果这个类里面有很多方法需要事物控制就很不方便了(需要每一个都加),所以需要用到下面的动态代理,而且上面的业务代码和横切代码混合在一起了。

2、采用JDK动态代理模式

必须实现接口

    public static void main(String[] args) {

        IRentingHouse rentingHouse = new RentingHouseImpl();  // 委托对象---委托方

        // 从代理对象工厂获取代理对象
        IRentingHouse jdkProxy = (IRentingHouse) ProxyFactory.getInstance().getJdkProxy(rentingHouse);

        // 这里直接调用代理对象,而不用调用委托对象
        jdkProxy.rentHosue();// 通过代理对象调用方法,那么执行方法时,肯定会进入invoke方法

    }

    private ProxyFactory(){

    }

    private static ProxyFactory proxyFactory = new ProxyFactory();

    public static ProxyFactory getInstance() {
        return proxyFactory;
    }

/**
     * 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;

                        // 写增强逻辑
                        System.out.println("中介(代理)收取服务费3000元");
                        // 横切代码已经写好,且不重复,只需要插入业务逻辑代码即可
                        // 调用原有业务逻辑
                        result = method.invoke(obj,args);

                        System.out.println("客户信息卖了3毛钱");

                        return result;
                    }
                });

    }

3、CGLIB动态代理扩展

不需要实现接口
先引入插件依赖

 <!--引入cglib依赖包-->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.1_2</version>
    </dependency>

然后通过glib去获取代理对象

   /**
     * 使用cglib动态代理生成代理对象
     * @param obj 委托对象
     * @return
     *MethodInterceptor能够对各个方法进行拦截,类似于JDK动态代理的InvocationHandler
     */
    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;
                System.out.println("中介(代理)收取服务费3000元");
                result = method.invoke(obj,objects);
                System.out.println("客户信息卖了3毛钱");
                return result;
            }
        });
    }

4、采用动态代理改造service的事物控制

动态代理下的代码

在不改变原有业务逻辑的情况下,增强其事物控制,也就是横切逻辑代码
事物已经控制在动态代理当中,不管多少个方法都很方便添加横切代码

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 {
                        // method方法就是要代理的方法。
                        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;
            }
        });
    }
}

在Servlet层

原生对象变成了委托对象,再通过委托对象获取代理对象。

 */
@WebServlet(name="transferServlet",urlPatterns = "/transferServlet")
public class TransferServlet extends HttpServlet {

    // 首先从BeanFactory获取到proxyFactory代理工厂的实例化对象
    private ProxyFactory proxyFactory = (ProxyFactory) BeanFactory.getBean("proxyFactory");
    // 再从动态代理当中获取其service事物
    private TransferService transferService = (TransferService) proxyFactory.getJdkProxy(BeanFactory.getBean("transferService")) ;

此时业务控制已经在动态代理当中了。

现在的service就很简洁了

  @Override
    public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {

            Account from = accountDao.queryAccountByCardNo(fromCardNo);
            Account to = accountDao.queryAccountByCardNo(toCardNo);

            from.setMoney(from.getMoney()-money);
            to.setMoney(to.getMoney()+money);

            accountDao.updateAccountByCardNo(to);
            int c = 1/0;
            accountDao.updateAccountByCardNo(from);
    }

image.png

要使用某一类,就先声明其属性,然后通过set注入的方式整进去。如下所示
image.png
还需要在xml添加bean及维护其依赖关系


 <!--id标识对象,class是类的全限定类名-->
    <bean id="accountDao" class="com.lagou.edu.dao.impl.JdbcTemplateDaoImpl">
        <!--Dao的实现类也依赖于connectionUtils-->
        <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>

    <!--事务管理器-->
    <bean id="transactionManager" class="com.lagou.edu.utils.TransactionManager">
        <!--事物管理器依赖于connectionUtils-->
        <property name="ConnectionUtils" ref="connectionUtils"/>
    </bean>

    <!--代理对象工厂-->
    <bean id="proxyFactory" class="com.lagou.edu.factory.ProxyFactory">
      <!--工厂依赖于事物管理器-->
      <property name="TransactionManager" ref="transactionManager"/>
    </bean>