【Java学习】Spring(容器思想)

83 阅读18分钟

Spring启示录

OCP开闭原则

OCP是软件七大开发原则中最基本也是最核心的一个原则:开闭原则。

  • 对扩展开放
  • 对修改关闭

OCP开闭原则的核心: 在扩展系统功能的时候,没有修改以前写好的代码,那么就是符合OCP原则的。反之,如果在扩展系统的时候,修改了之前的代码,那么这个设计就是失败的,违背了OCP原则。

当进行系统功能扩展的时候,如果动了之前稳定的程序,修改了之前的程序,那之前的程序都需要进行重新测试,这将非常麻烦。

依赖倒置原则DIP

凡是上依赖下的,就是违背了依赖倒置原则。((上)表示层->业务层->持久层(下))

依赖倒置原则的核心: 倡导面向接口编程和面向抽象编程,而不是面向具体编程。

依赖倒置原则的目的: 降低程序的耦合度,提高扩展力。

控制反转IoC(编程思想/新型设计模式)

IoC控制反转 (Inversion of Control)。

反转是什么:

  • 不在程序中采用硬编码的方式new对象(new对象的权利交付出去)
  • 不在程序中采用硬编码的方式来维护对象的关系(关系的维护权也交付出去)

Spring框架

  • Spring框架实现了控制反转的IoC这种思想。
  • Spring是一个实现了IoC思想的容器。
  • 控制反转的实现方式有多种:
    • 依赖注入(Dependency Injection, DI)
      • set注入
      • 构造方法注入
  • 控制反转是思想,依赖注入是这种思想的实现。

Spring的特点:

  • 轻量
  • 控制反转:通过控制反转技术促进了松耦合。
  • 面向切面
  • 容器:Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器。
  • 框架:Spring可以将简单的组件配置,组合成复杂的应用。

依赖注入DI

  • 依赖:A对象和B对象的关系
  • 注入:是一种手段

Spring通过依赖注入的方式来完成Bean管理。

Bean管理是说:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。

set注入

在类中写set方法,并在.xml文件中相应配置(注意方法名)。

构造注入

核心原理:通过调用构造方法来给属性赋值。

Spring的配置文件(重点、难点)

在resources下创建spring配置文件xx.xml。

其中bean标签的两个重要属性:

  • id:是bean的身份证号,不能重复,是唯一的标识。
  • class:必须填写类的全路径,全限定类名(带包名的类名)。

在测试类中:

  • 第一步:获取Spring容器对象

截屏2023-04-21 21.57.23.png

  • 第二步:根据bean的id从Spring容器中获取这个对象

截屏2023-04-21 21.57.53.png

Spring是怎么实例化对象的?

  • 默认情况下Spring会通过反射机制,调用类的无参构造方法来实例化对象。

Spring框架中的简单类型:

  • 基本数据类型
  • String类型
  • Enum枚举
  • Number
  • Date(java.util.Date)
  • URL
  • Class等

级联属性赋值:

  • 在Spring配置文件中,注意顺序
  • 在Spring配置文件中,相应属性必须提供getter方法

自动装配根据类型注入的本质也是根据set注入。

Bean的作用域

  • Spring默认情况下是如何管理Bean的
    • 默认情况下Bean是单例的
    • 在Spring上下文初始化的时候实例化
    • 每一次调用getBean()方法的时候,都返回那个单例的对象
  • Scope属性(未全部列举)
    • singleton单例(默认)
    • prototype 原型/多例
    • request:一次请求当中一个bean
    • session:一次会话中只有一个bean

设计模式

设计模式:一种可以被重复利用的解决方案。

GoF(Gang of Four)23种设计模式。

其中,工厂模式是解决对象创建问题的,所以工厂模式属于创建型设计模式。而Spring框架底层使用了大量的工厂模式。

工厂模式的三种形态

  • 简单工厂模式(Simple Factory):不属于23种设计模式之一。简单工厂模式又叫做:静态工厂方法模式。
  • 工厂方法模式(Factory Method):23种设计模式之一。
  • 抽象工厂模式(Abstract Factory):是23种设计模式之一。

简单工厂模式

三个角色:

  • 抽象产品角色
  • 具体产品角色
  • 工厂类角色(静态方法)

简单工厂模式的优点:

简单工厂模式实现了职责分离,客户端不需要关心产品的生产细节。 客户端只负责消费,工厂类负责生产(生产者消费者分离)。

简单工厂模式的缺点:

  • 如果需要扩展新的产品,需要修改工厂类代码,违背了OCP原则。
  • 工厂类责任比较重大,不能出现任何问题,这个工厂类负责所有产品的生产,成为全能类。这个工厂类出现问题,系统将会瘫痪。

工厂方法模式(Factory Method Pattern)

工厂方法模式可以解决简单工厂模式中的OCP问题。

  • 一个工厂对应生产一种产品。

四个角色:

  • 抽象产品角色
  • 具体产品角色
  • 抽象工厂角色
  • 具体工厂角色

工厂方法模式的优点:

当需要扩展一个产品的时候,符合OCP原则,因为只需要添加两个类:

  • 具体产品类
  • 具体工厂类

工厂方法模式的缺点:

  • 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加。
  • 在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。

抽象工厂模式

一定程度上弥补了工厂方法模式的缺点。

Bean的实例化方式(底层都是构造方法)

  • 通过构造方法实例化
  • 通过简单工厂模式实例化
  • 通过factory-bean实例化
    • 通过工厂方法模式,使用factory-bean属性 + factory-method属性来共同完成
  • 通过FactoryBean接口实例化

BeanFactory和FactoryBean的区别

BeanFactory -- 是工厂

SpringIoC容器的顶级对象,BeanFactory被翻译为“Bean工厂”,在Spring的IoC容器中,“Bean工厂”负责创建Bean对象。

FactoryBean -- 是Bean

FactoryBean:它是一个Bean,是一个能够辅助Spring实例化其他Bean对象的一个Bean。

在Spring中,Bean可以分为两类:

  • 普通Bean
  • 工厂Bean

Bean的生命周期

什么是Bean的生命周期

Spring其实就是一个管理Bean对象的工厂。它负责对象的创建和销毁。

所谓的生命周期就是:对象从创建开始到最终销毁的整个过程。本质是在哪个时间节点上调用了哪个类的哪个方法。

Bean的生命周期之五步

  • 实例化Bean(调用无参构造方法)
  • Bean属性赋值(调用set方法)
  • 初始化Bean(调用Bean的init方法,需要自己写并手动指定)
  • 使用Bean
  • 销毁Bean(调用Bean的destroy方法,需要自己写并手动指定)

Bean的生命周期之七步

  • 实例化Bean
  • Bean属性赋值
  • 执行“BeanPostProcessor”的before方法
  • 初始化Bean
  • 执行“BeanPostProcessor”的after方法
  • 使用Bean
  • 销毁Bean

Bean的生命周期之十步

  • 实例化Bean
  • Bean属性赋值
  • 检查Bean是否实现了Aware接口,并设置相关依赖
  • 执行“BeanPostProcessor”的before方法
  • 检查Bean是否实现了InitializingBean接口,并调用接口方法
  • 初始化Bean
  • 执行“BeanPostProcessor”的after方法
  • 使用Bean
  • 检查Bean是否实现了DisposableBean接口,并调用接口方法
  • 销毁Bean

注意

Spring容器只对singleton的Bean进行完整的生命周期管理。

如果是prototype作用域的Bean,Spring容器只负责将Bean初始化完毕。等客户端程序一旦获取到该Bean之后,Spring容器就不再管理该对象的生命周期了。

Bean的循环依赖

单例和set模式下

Spring没有问题。

多例和set模式下(两个Bean的scope都是prototype)

Spring中会陷入死循环,一直new对象,出现BeanCurrentlyInCreationException(Bean正在处于创建中)异常。

构造注入模式下

创建对象的同时需要对属性赋值,不分实例化和属性赋值两个阶段,所以很显然,循环依赖也会出现BeanCurrentlyInCreationException异常,而且无法解决。

解决循环依赖的本质

在Spring + Setter模式下,为什么循环依赖不会出现问题,Spring是如何应对的?

  • 主要原因是,在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:
    • 第一个阶段:在Spring容器加载的时候,实例化Bean,只要其中任何一个Bean实例化之后,马上进行“曝光”(即使还没有进行属性赋值)。
    • 第二个阶段:Bean“曝光”之后,再进行属性的赋值(调用set方法)。
  • 核心解决方案是:实例化对象和对象的属性赋值分为两个阶段来完成。
    • 也就是说,Bean都是单例的,我们可以先把所有的单例Bean实例化出来,放到一个集合当中(可以称为缓存),所有的单例Bean全部实例化完成之后,再慢慢调用setter方法给属性赋值,以解决循环依赖的问题。

反射机制

调用一个方法(无论是否使用反射机制),涉及四要素:

  • 调用哪个对象
  • 调用哪个方法
  • 调用方法的时候传什么参数
  • 方法执行结束之后的返回结果

IoC注解

声明Bean的注解

  • @Component
  • @Controller
  • @Service
  • @Repository

Spring注解的使用

  • 加入aop的依赖
  • 在配置文件中添加context命名空间
  • 在配置文件中指定扫描的包
  • 在Bean类上使用注解

负责注入的注解

给Bean属性赋值需要用到这些注解:

  • @Value
    • 注入简单类型
  • @Autowired
    • 默认根据类型byType进行注入
    • 接口下有多个实现类时不好用
  • @Qualifier
    • 和@Autowired联合使用可以根据名字进行注入
    • @Qualifier指定名字
  • @Resource
    • 推荐使用
    • 默认根据名字byName进行注入,如果找不到,启动byType

代理模式(重点、难点)

在java程序中使用代理模式的作用:

  • 当一个对象需要受到保护的时候,可以考虑使用代理对象来完成某个行为
  • 需要给某个对象的功能进行功能增强的时候,可以考虑找一个代理进行增强
  • A对象无法和B对象直接交互时,也可以使用代理模式来解决

代理模式中的三个角色:

  • 目标对象
  • 代理对象
  • 目标对象和代理对象的公共接口

使用代理模式的时候,对于客户端程序来说是透明的,客户端在使用代理对象的时候就像在使用目标对象。

代理模式在代码上的实现:

  • 静态代理
    • 将目标对象作为代理对象的一个属性,这种关系叫做关联关系,比继承关系的耦合度低。
    • 代理对象中含有目标对象的引用。
    • 缺点:类爆炸,假设系统中有1000个接口,那么每个接口都需要对应代理类,这样类会急剧膨胀,不好维护。
  • 动态代理
    • 在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量,解决代码复用的问题。
    • 在内存当中动态生成类的技术包括:
      • JDK动态代理技术:只能代理接口
      • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目,是一个强大的、高性能的、高质量的Code生成类库,它可以在运行扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM)
      • Javassist动态代理技术:Javassist是一个开源的分析,编辑和创建Java字节码的类库。它已加入了开放源代码JBoss应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态“AOP”框架。

AOP面向切面编程(重点、难点)

IoC使软件组件松耦合。AOP让你能够捕捉系统中经常使用的功能,把它转化成组件。

AOP(Aspect Oriented Programming):底层就是使用动态代理来实现。

  • JDK动态代理 +
  • CGLIB动态代理技术

AOP介绍

一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些通用的系统服务被称为:交叉业务

  • 交叉业务:在业务流程当中和业务不挂钩的这种非业务逻辑通用代码

一句话总结:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。

AOP的优点

  • 代码复用性增强
  • 代码易维护
  • 使开发者更关注业务逻辑

AOP七大术语

  • 连接点Joinpoint -- 描述位置
    • 在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。
  • 切点Pointcut -- 本质是方法
    • 在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点)
  • 通知Advice -- 描述代码
    • 通知又叫增强,就是具体要织入的代码。
    • 通知包括:
      • 前置通知 @Before
      • 后置通知 @AfterReturning
      • 环绕通知 @Around 环绕的通知是最大范围
      • 异常通知 @AfterThrowing
      • 最终通知 @After
  • 切面Aspect -- 逻辑概念
    • 切点 + 通知 就是切面
  • 织入Weaving
    • 把通知应用到目标对象上的过程
  • 代理对象Proxy
    • 一个目标对象被织入通知后产生的新对象
  • 目标对象Target
    • 被织入通知的对象

使用Spring的AOP

Spring对AOP的实现包括以下3种方式:

  • Spring框架结合AspectJ框架实现的AOP,基于注解方式。
  • Spring框架结合AspectJ框架实现的AOP,基于XML方式。
  • Spring框架自己实现的AOP,基于XML配置方式。

AOP的实际案例

  • 编程式事务
  • 安全日志:记录一些危险操作

Spring对事务的支持

事务概述

什么是事务

  • 在一个业务流程当中,通常需要多条DML(insert、delete、update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。
  • 多条DML要么同时成功,要么同时失败,这就叫做事务。
  • 事务:Transaction

事务的四个处理过程

  • 第一步:开启事务(start transaction)
  • 第二步:执行核心业务代码
  • 第三步:提交事务(如果核心业务处理过程中没有出现异常)(commit transaction)
  • 第四步:回滚事务(如果核心业务处理过程中出现异常)(rollback transaction)

事务的四个特性

  • A 原子性:事务是最小的工作单元,不可再分。
  • C 一致性:事务要求要么同时成功,要么同时失败,事务前和事务后的总量不变。
  • I 隔离性:事务和事务之间因为有隔离性,才可以保证互不干扰。
  • D 持久性:持久性是事务结束的标志。

Spring实现事务的两种方式

  • 编程式事务
    • 通过编写代码的方式来实现事务的管理。
  • 声明式事务
    • 基于注解方式
    • 基于XML配置方式

事务隔离级别

事务隔离级别类似于教室A和教室B之间的那道墙,隔离几杯越高表示墙体越厚,隔音效果越好。

数据库中读取数据存在的三大问题:

  • 脏读:读取到没有提交到数据库的数据,叫做脏读。
  • 不可重复读:在同一个事务当中,第一次和第二次读取的数据不一样。
  • 幻读:读到的数据是假的。

事务隔离级别包括四个级别:

  • 读未提交:READ_UNCOMMITTED
    • 这种隔离级别,存在脏读问题,所谓的脏读表示能够读取到其他事务未提交的数据。
  • 读提交:READ_COMMITTED
    • 解决了脏读问题,其他事务提交之后才能读到,但存在不可重复读问题。
  • 可重复读:REPEATABLE_READ
    • 解决了不可重复读,可以达到可重复读效果,只要当前事务不结束,读取到的数据一直都是一样的,但存在幻读问题。
  • 序列化:SERIALIZABLE
    • 解决了幻读问题,事务排队执行,不支持并发。

事务超时

@Transactional(timeout = 0)

以上代码表示设置事务的超时时间为10秒。

表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话,最终结果会选择回滚。

默认值-1,表示没有时间限制。

事务的超时时间是指哪段时间?

在当前事务当中,最后一条DML语句执行之前的时间,如果最后一条DML语句后面很多很多业务逻辑,这些业务代码执行的时间不被计入超时时间。

只读事务

@Transaction(readOnly = true)

将当前事务设置为只读事务,在该事务执行过程中只允许 select 语句执行, deleteinsertupdate 均不可执行。

该特性的作用:启动spring的优化策略,提高 select 语句执行效率。

如果该事务中确实没有增删改操作,建议设置为只读事务。

设置哪些异常回滚事务

@Transaction(rollbackFor = RuntimeException.class)

表示只有发生 RuntimeException 异常或该异常的子类才回滚。

设置哪些异常不回滚

@Transaction(noRollbackFor = NullPointerException.class)

表示发生 NullPointerException 或该异常的子类异常不回滚,其他异常回滚。

Spring整合MyBatis

实现步骤

  • 第一步:准备数据库表
    • 使用xx表
  • 第二步:IDEA中创建一个模块,并引入依赖
    • spring-context
    • spring-jdbc
    • mysql驱动
    • mybatis
    • mybatis-spring:mybatis提供的与spring框架集成的依赖
    • druid连接池
    • junit
  • 第三步:基于三层架构实现,所以提前创建好所有的包
    • com.xxx.mapper
    • com.xxx.service
    • com.xxx.service.impl
    • com.xxx.pojo
  • 第四步:编写pojo
    • 属性私有化,提供公开的setter、getter和toString
  • 第五步:编写mapper接口
    • 定义方法
  • 第六步:编写mapper配置文件
    • 在配置文件中配置命名空间,以及每一个方法对应的sql
  • 第七步:编写service接口和service接口实现类
    • xxService
    • xxServiceImpl
  • 第八步:编写jdbc.properties配置文件
    • 数据库连接池相关信息
  • 第九步:编写mybatis-config.xml配置文件
    • 该文件可以没有,大部分配置可以转移到spring配置文件中
    • 如果遇到mybatis相关的系统级配置,还是需要这个文件
  • 第十步:编写spring.xml
    • 组件扫描
    • 引入外部的属性文件
    • 数据源
    • SqlSessionFactoryBean配置
      • 注入mybatis核心配置文件路径
      • 指定别名包
      • 注入数据库
    • Mapper扫描配置器
      • 指定扫描的包
    • 事务管理器DataSourceTransactionManager
      • 注入数据源
    • 启用事务注解
      • 注入事务管理器
  • 第十一步:编写测试程序,并添加事务,进行测试

Spring框架的八大设计模式

简单工厂模式

BeanFactory的getBean()方法,通过唯一标识来获取Bean对象,是典型的简单工厂模式(静态工厂模式)。

工厂方法模式

FactoryBean是典型的工厂方法模式,在配置文件中通过factory-method属性来指定工厂方法,该方法是一个实例方法。

单例模式

Spring用的是双重判断加锁的单例模式。

代理模式

Spring的AOP就是使用了动态代理实现的。

装饰器模式

希望能在尽可能少修改原有类代码的情况下,动态切换不同的数据源:

  • Spring根据每次请求的不同,将dataSource属性设置成不同的数据源,以达到切换数据源的目的。

Spring中类名带有:Decorator和Wrapper单词的类,就是装饰器模式。

观察者模式

定义对象间的一对多的关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。Spring中观察者模式一般用在listener的实现。

策略模式

策略模式是行为性模式,调用不同的方法,适应行为的变化,强调父类调用子类的特性。

getHandler是HandlerMapping接口中的唯一方法,用于根据请求找到匹配的处理器。

模板方法模式

Spring中的JdbcTemplate类就是一个模版类,它是一个模板方法设计模式的体现。在模板类的模板方法execute中编写核心算法,具体实现步骤在子类中完成。

总结

这节的学习跟着 动力节点 的视频学的,真的讲的挺好的!