SSM 整合、 Spring
SSM 整合
零、配置
1、导入依赖
junit,数据库驱动,连接池,servlet,jsp,mybatis,spring,mybattis-spring…
2、IDEA连接数据库
3、Spring配置文件

4、Mybatis配置文件

5、配置数据库

一、MyBatis
DAO层
作用:直接操作数据库
使用MyBatis,需要的文件有.java,.xml,在程序运行时,MyBatis会自动生成动态代理实现类取操作数据库。
.java 负责写接口定义
.xml 映射文件,负责写SQL语句

注意:需要在mybatis-config.xml中注册Mapper接口文件,否则MyBatis找不到

Serive层
作用:实现业务功能
Service层调用DAO层

二、Spring
关联spring和DAO层


关联spring和service层

三、SpringMVC
与web相连


SpringMVC

Tips
lombok 偷懒
注解:
@Data:自动生成所有属性的get、set、toString、equals、hashCode方法
@AllArgsConstructor:生成全参构造
@NoArgsConstructor:生成无参构造
@SELECT
Spring
一、Bean的获取方式
1、在spring配置文件中直接配置类的全路径
spring会自动调用该类的无餐构造方法来实例化Bean


2、简单工厂模式
一个工厂对应所有商品。
在Spring配置文件中告诉Spring,调用哪个类的哪个方法来获取Bean
spring.xml:

简单工厂模式的方法是静态方法

3、factory-bean 工厂方法模式
和方法2的区别:
- 一个工厂只能对应一个商品,获取商品的方法是实例化商品
- factory本身也是一个bean,也需要被spring进行管理。

工厂方法模式中的具体工厂角色的方法是实例方法

4、FactoryBean接口
编写的类去实现FactoryBean接口


其中isSingleton返回的内容是指这个类对应的对象是否为单例对象,如果是则返回true
在程序运行过程中,只会有这一个对象,节省内存。
BeanFactory和FactoryBean的区别?
BeanFactory是Been工厂,负责创建Bean对象
FactoryBean本身是一个Bean,用于辅助Spring实例化其他普通的Bean对象
实战-注入Date

方法一、把日期类型当做简单类型
直接写在property中
方法二、当做非简单类型,只能获取系统当前时间

方法三、通过工厂Bean:DateFactoryBean来返回普通Bean:java.util.Date


二、Bean的生命周期
1、五步
- 实例化Bean(调用无参构造)
- 给Bean属性赋值(调用setXxx方法)(set方法的名称需要与property中的属性名称一致)
- 初始化Bean(调用Bean的init方法。init需要自己写,自己配)
- 使用Bean
- 销毁Bean(调用Bean的destroy方法。destroy需要自己写,自己配)

2、七步
- 实例化Bean
- Bean属性赋值
- 执行“Bean后处理器”的before方法
- 初始化Bean
- 执行“Bean后处理器”的after方法
- 使用Bean
- 销毁Bean


注意:Bean后处理器作用于整个.xml文件中的所有bean,每个bean的生命周期都会执行这两个处理器。
3、十步
- 实例化Bean
- Bean属性赋值
- 检查Bean是否实现了Aware相关接口,并调用接口方法(传递一些数据,让你更加方便使用)
- 执行“Bean后处理器”的before方法
- 检查Bean是否实现了InitializingBean接口,并调用接口方法
- 初始化Bean
- 执行“Bean后处理器”的after方法
- 使用Bean
- 检查Bean是否实现了DisposableBean接口,并调用接口方法(保证资源释放,用在中间件的开发)
- 销毁Bean
添加这三个点位的特点都是在检查这个Bean是否实现了某些接口,如果实现了这些接口,则Spring会调用这个接口中的方法。
Bean的作用域不同,管理方式不同
Spring只对singleton的Bean进行完整生命周期的管理,如果是prototype作用域的Bean,Spring容器只负责将该Bean初始化完毕。等客户端程序一旦获取这个Bean后,Spring就不再管理它的生命周期了。
(第9步开始就不管了,也就是不管销毁的流程)

把自己new的对象纳入Spring容器管理

三、Bean的循环依赖
3.1 singleton和setter模式(正常)

分析:在这种模式下,Spring对Bean的管理分为清晰的两个阶段
- Spring容器加载的时候,实例化Bean,只要其中任意一个Bean实例化之后,马上进行曝光【不等属性赋值就曝光】
- Bean曝光之后,再进行属性的赋值(调用set方法)
流程举例:
- 实例化husband
- 曝光husband,通知别人可以引用husband了(因为是单例的,反正就这一个,要用你就先拿着吧)
- husband运行set方法,发现需要wife,并且wife不存在
- 实例化wife
- 曝光wife
- wife运行set方法,需要使用husband(已曝光),直接引用即可【wife 完成全流程】
- 回到husband,运行set方法,引用wife【husband 完成全流程】
3.2 prototype和setter模式(异常)
prototype导致每次创建的bean都是新的,husband使用set方法后发现要创建wife,wife使用set方法后发现要创建husband,会导致这样循环下去。
3.3 一个singleton一个prototype(正常)
单例的Bean在new ClassPathXmlApplicationContext("spring.xml");的时候就创建了。
3.4 构造注入模式(异常)
即在构造方法中进行赋值。
husband在构造方法中发现要创建wife(此时都没构造完成,无法曝光),wife在构造方法中发现要创建husband(也无法曝光),导致循环依赖。
final. 源码分析
Bean都是单例的情况下,可以把所有单例的Bean实例化出来,放到一个集合中(可以称之为缓存),当所有单例Bean全部实例化完成之后,再慢慢调用setter方法给属性赋值。解决了循环依赖的问题。
三级缓存

存储的内容
一级缓存:完整的单例Bean对象,也就是说这个缓存中的Bean对象的属性都已经赋值了。是一个完整的Bean对象。
二级缓存:早期的单例Bean对象,这个缓存中的单例Bean对象的属性没有赋值。只是一个早期的实例对象。
三级缓存:单例工厂对象。这个里面存储了大量的“工厂对象”,每一个单例Bean对象都会对应一个单例工厂对象。
流程:先去一级缓存拿Bean对象,拿不到就去二级缓存,也拿不到就去三级缓存,通过工厂对象获取Bean对象,然后把它放到二级缓存中。
四、回顾反射机制
调用一个方法,包含的四要素:
- 对象
- 方法
- 传参
- 返回值
举例:
1、通过反射运行函数

2、通过反射对属性进行赋值

五、Spring IoC 注解式开发
IoC(Inversion of Contro)控制反转:“创建和管理对象”的权力,从“程序员/代码本身”转移(反转)给了“Spring 容器”。
注解式开发的作用:简化XML的配置
5.1 回顾-自定义注解

5.2 回顾-反射获取注解

5.3 回顾-组件扫描
只知道一个包的名字,扫描这个包下所有的类。当这个类上有@xxx注解时,实例化该对象,放到Map集合中。
070-Spring IoC注解之组件扫描原理_哔哩哔哩_bilibili
注解的作用就是让程序通过反射获取注解中的内容,有xx注解就做某某事情,没有就不做。因此,省去了自己编写某些常用程序的时间。
5.4 声明Bean的注解
- @Component(老大,其他三个其实是Component的别名,为了增强程序的可读性,引入下面三个注解)
- @Controller
- @Service
- @Repository (仓库 -> DAO层)
5.5 Spring注解的使用
- 加入aop的依赖
- 在配置文件中添加context命名空间

- 在配置文件中指定扫描的包

- 在Bean类上使用注解

如果只写Bean的注解,把整个value属性全部省略了,bean的默认名称是:类名(首字母变小写)
5.6 解决多个包扫描的问题
方法一:在指定扫描包的时候,写多个包,用逗号隔开

方法二:指定多个包共同的 父包,牺牲一部分效率

5.7 选择性实例化Bean
由于特殊业务需要,只要求Controller注解进行实例化,如何完成?
方法一
use-default-filters="false" ,并在其中添加include-filter,来指定要实例化的注解

方法二
use-default-filters="true",并在其中添加exclude-filter,来指定失效的注解

5.8 负责注入的注解
给Bean的属性赋值
- @Value 注入简单类型,代替Bean配置代码中的property行。(可以用在 属性、方法、构造方法 上)
- @Autowired
- @Qualifier
- @Resource
**5.8.1 @Value **
注入简单类型
有这个注解以后,可以不提供set方法。
属性:

方法:

构造方法:

5.8.2 @Autowired和@Qualifier
注入非简单类型
只用@Autowired,默认根据类型装配【byType】想要根据名字进行装配需要配合@qualifier
使用的时候不需要指定属性的值,直接根据类型进行装配。

(如果一个类的构造方法只有一个,并且构造方法上的参数和属性能够对应上。@Autowired注解可以省略)
5.8.3 @Resource
非简单类型的注入。
与@Autowired的区别:
- @Resource是JDK扩展包中的,是标准规范,更加通用。@Autowired是Spring框架自己的。
- @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的时候会自动通过byType装配。
- @AutoWired默认根据类型装配byType,如果需要根据名称装配,需要配合@Qualifier
- @Resource用在 属性、set方法 上
- @Autowired用在 属性、set方法、构造方法、构造方法参数 上

5.8.4 全注解开发
不使用spring配置文件,使用一个配置类来替代配置文件。
配置类:


六、GoF之代理模式
6.1 作用
- 一个对象需要受到保护的时候
- 需要给某个对象进行功能增强的时候
- A对象和B对象无法直接交互时,也可以使用代理模式来解决(找个中间人)
代理模式中有三大角色
- 目标对象(演员)
- 代理对象(替身)
- 目标对象和代理对象的公共接口(两个对象应该具有相同的行为动作->不想让观众看出来是替身。即客户端无法察觉出是代理对象。)
6.2 静态代理
如果需要对所有业务接口中的每一个业务方法统计耗时,应该怎么办?
- 方法一:硬编码。在每个方法中添加耗时统计的代码
- 缺点1:违背OCP开闭原则
- 缺点2:代码没有得到复用
- 方法二:编写业务类的子类,让子类继承业务类,对每个业务方法进行重写。(super.xxx方法前后添加统计耗时的代码)
- 缺点1:采用了继承关系,耦合度很高
- 缺点2:代码依然没有得到服用
- 方法三:静态代理
- 优点1:解决了OCP问题
- 优点2:降低了耦合度,是has a,而不是is a
- 缺点:类爆炸。假设系统有1000个接口,每个接口都需要对应的代理类,这样类会急剧膨胀。不好维护。

- 运行流程:创建目标对象、创建代理对象、调用代理对象的代理方法
- 为什么传入的参数类型是接口而不是某一类?
- 公共接口,降低耦合度。只要实现了这一个接口的类都可以代理(向上转型,在使用时会调用对应对象实现的方法)。
- 【补充】类的继承也可以向上转型,比如子类赋值给父类。但是调用方法的时候还是使用子类的方法。(必须得是父类也有的方法,否则编译无法通过。)【编译看左边,运行看右边。】舍弃掉实现类/子类的特殊细节,把他们统一当做高层级的抽象类型来看待。
- 方法四:动态代理
- 添加了字节码生成技术。可以在内存中动态生成一个class字节码,这个字节码就是代理类。(不需要自己写代理类的代码了)
6.3 动态代理
- JDK动态代理技术:只能代理接口。
- CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的(目标类不能用final修饰)。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
- Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态”AOP”框架。
6.3.1 JDK 动态代理

Proxy.newProxyInstance()做了两件事:
- 在内存中动态生成了一个代理类的字节码class文件
- new了一个对象。通过动态生成的class文件实例化了代理对象
在调用处理器中(需要实现 InvocationHandler 接口),需要写的内容:
- 一个对象(用于接收目标对象,用Object去接收比较好【向上转型,降低耦合度】)
- 构造函数(将目标对象赋值给变量)
- 实现invoke方法(其中用
method.invoke调用目标对象的方法,前后写上增强代码)
带返回值的目标函数,如何让代理方法返回?
在目标函数返回值给invoke后,invoke方法必须继续返回(相同的返回值返回了两层)

6.3.2 CGLIB 动态代理


七、面向切面编程 AOP
7.1 切面
切面:在业务流程中与具体业务无关的通用代码提取出来,形成横向的切面(交叉业务),业务是纵向的,把切面应用于不同业务的过程,叫做面向切面编程。
优点:
- 代码复用性强
- 代码易维护,只需要修改一遍
- 开发者可以更加专注于业务逻辑

Spring的AOP使用JDK+CGLIB动态代理(这两个动态代理就是AOP的具体实现)
- 代理接口,默认使用JDK。
- 代理某个类,使用CGLIB。
7.2 七大术语
1、连接点 joinpoint
在程序的整个执行流程中,可以织入切面的位置。也就是所有的方法。(候选)
2、切点 pointcut
在程序执行流程中,真正织入切面的方法。
切点是一组特定的连接点的集合。
3、通知 advice
又叫增强,就是你具体要织入的代码。
- 前置通知
- 后置通知
- 环绕通知
- 异常通知
- 最终通知
4、切面 Aspect
切点+通知
5、织入 Weaving
把通知应用到目标对象的过程
6、代理对象 Proxy
一个目标对象被织入通知后产生的新对象
7、目标对象 Target
被织入通知的对象

注意:这张图上的连接点画错了,应该是所有方法,而不是间隙。
7.3 切点表达式
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符:
- 可选项。
- 没写,就是4个权限都包括。
- 写public就表示只包括公开的方法。
返回值类型:
- 必填项。
- * 表示返回值类型任意。
全限定类名:
- 可选项。
- 两个点“..”代表当前包以及子包下的所有类。
- 省略时表示所有的类。
方法名:
- 必填项。
- *表示所有方法。
- set*表示所有的set方法。
形式参数列表:
必填项
() 表示没有参数的方法
(..) 参数类型和个数随意的方法
(*) 只有一个参数的方法
(*, String) 第一个参数类型随意,第二个参数是String的。
异常:
- 可选项。
- 省略时表示任意异常类型。
八、Spring AOP的实现
三种方式:
- Spring框架结合AspectJ框架实现的AOP,基于注解方式
- Spring框架结合AspectJ框架实现的AOP,基于XML
- Spring框架自己实现的AOP,基于XML配置方式(没用)
8.1 准备工作
引入依赖
1 | <!--spring context依赖--> |
添加context命名空间和aop命名空间
1 |
|
8.2 基于注解的实现步骤
1、定义目标类和目标方法(并用注解在spring中注册)
1 | package com.powernode.spring6.service; |
2、定义切面类(并注册)
1 | package com.powernode.spring6.service; |
3、在Spring配置文件中添加组件扫描
1 | <context:component-scan base-package="com.powernode.spring6.service"/> |
4、在切面类中添加通知
1 | package com.powernode.spring6.service; |
5、在通知上添加切点表达式(筛选要增强的切点)
1 | package com.powernode.spring6.service; |
@Before表示这是一个前置通知
6、在Spring配置文件中开启自动代理
1 | <aop:aspectj-autoproxy proxy-target-class="true"/> |
开启自动代理后,凡是带有@Aspect注解的bean,Spring都会根据代理表达式生成目标对象的代理对象。
proxy-target-class="true" 表示采用cglib动态代理。
proxy-target-class="false" 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类。
7、使用目标对象(自动使用代理对象)
1 | package com.powernode.spring6.test; |
8.3 通知类型
- 前置通知:
@Before目标方法执行之前的通知 - 后置通知:
@AfterReturning目标方法执行之后的通知 - 环绕通知:
@Around目标方法之前添加通知,同时目标方法执行之后添加通知。 - 异常通知:
@AfterThrowing发生异常之后执行的通知 - 最终通知:
@After放在finally语句块中的通知
环绕通知:
1 |
|
是范围最大的通知

8.4 多个切面的顺序
在切面类上添加@Order(数字)注解,数字越小,优先级越高。
8.5 通用切点
同类的通用切点

跨类的通用切点

如果一个切面需要有多个切点,怎么设置?
把每个切点单独写一个通用切点表达式,然后在切面的切点表达式中用||连接

8.6 连接点 JoinPoint
是执行过程中的所有位置(方法),是切点的候选者。
触发通知时,Spring传入的参数是JoinPoint对象。切点是一组特定的连接点的集合。在代码中的体现为切点表达式。

8.7 Spring AOP 全注解开发
使用配置类代替xml文件


8.8 Spring AOP基于XML的实现

九、Spring 事务
9.1 概述
事务:在一个业务流程中,需要多条DML数据修改(增删改)语句,这些语句必须同时成功或者同时失败
四个特性:ACID
9.2 Spring 对事务的实现
编程式事务
通过编写代码的方式来实现(很少使用)
声明式事务
- 基于注解的方式
- 基于XML配置的方式
9.3 事务管理器接口
PlatformTransactionManager接口:Spring事务管理器的核心接口。
在Spring6中它有两个实现:
- DataSourceTransactionManager:支持JdbcTemplate、Mybatis、Hibernate等事务管理
- JtaTransactionManager:支持分布式事务管理



9.4 事务-注解方式

9.5 事务的传播行为
在一个类中有a方法和b方法,a方法和b方法上都有事务。当a方法调用了b方法,事务是如何传递的?合并到一个事务,还是开启一个新的事务?
七种传播行为:
- REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
- MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
- REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
- NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
- NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
- NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】

9.6 事务的隔离级别

读未提交:READ_UNCOMMITTED
读提交:READ_COMMITTED
可重复读:REPEATABLE_READ
序列化:SERIALIZABL
不可重复读和幻读的区别:
- 不可重复读是事务A多次读取同一条记录,但是结果不一样。(读到了事务B已经提交的内容)【一条记录内的值】
- 幻读是在同一事务内,事务A多次执行相同的范围查询,查询到的行数不一样。(读到了B已经提交货删除的记录)【一批记录的数量变化】
9.7 事务超时
@Transactional(timeout = 10)
超过10s如果事务中所有DML语句还没有执行完毕,就回滚。
默认值-1,表示没有时间限制。
这个时间计算的是最后一条DML语句结束的时间,如果最后一条DML语句在10s内结束,就不算超时。
9.8 只读事务
@Transactional(readOnly = true)
在这个事务中不能执行增删改语句,只允许执行select语句
作用:启动spring的优化策略,提高select语句执行效率
9.9 是否回滚
设置哪些异常回滚事务
@Transactional(rollbackFor = RuntimeException.class)
表示只有发生RuntimeExceptiopn或该异常的子类才回滚
设置哪些异常不回滚事务
@Transactional(noRollbackFor = NullPointerException.class)
发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。
9.10 全注解开发
1 | package com.powernode.bank; |