Spring框架
Spring Framework 系统架构
Spring Framework是Spring生态圈中最基础的项目, 是其他项目的根基
架构图
上层依赖于下层
核心容器
IoC/DI
IoC ( Inversion of Control )控制反转:
- 使用对象时, 由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转
IoC容器和Bean
管理对象创建和初始化等工作,被创建或被管理的对象在IoC容器中统称为Bean
DI ( Dependency Injection )依赖注入
在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
目标:充分解耦
- 使用IoC容器管理bean (IoC)
- 在IoC容器内将有依赖关系的bean进行关系绑定 (DI)
最终效果
使用对象时不仅可以直接从IoC容器中获取, 并且获取到的bean已经绑定 了所有的依赖关系
IoC入门案例
实例步骤:
- 导入spring依赖
- 创建xml文件作为IoC容器
- 导入spring的坐标
- 配置bean
- 写测试主函数
- 获取IoC容器
- 获取bean
DI入门案例
- 删除业务层service中使用new创建的dao对象
- 提供得以让Dao对象进入Service的set方法
- 配置service与dao的关系(先判断是谁依赖谁)
- property标签表示配置当前bean的属性
- name属性表示配置哪一个具体的属性
- ref属性表示参照哪一个bean
bean基础配置
bean的别名
写在IoC容器的xml中名称和别名在spring系统中都等价
这个报错说明别名配置错误
bean的作用范围
spring默认创建的bean对象是单例的,因为bean一般是可复用的,
表现层对象、业务层对象、数据层对象、工具对象都很适合交给容器管理
可手动配置为多例scope="prototype"
不适合交给容器进行管理的bean:封装实体的域对象(有状态的对象,会记录成员变量的属性值)
bean的实例化
三种方式
bean本质上就是对象,创建bean使用无参构造方法完成
阅读spring的报错信息:从后往前看,信息逐步叠加
- 使用无参构造方法(无参构造方法如果不存在,将抛出异常BeanCreat ionException)
- 用静态工厂(兼容早期系统):(工厂模式通过封装对象的实例化过程,将对象的创建与使用分离,提供了一种灵活、可扩展的方式来创建对象;静态工厂:使用一个静态方法来创建和返回对象的实例,而不是直接调用构造函数。)
- 实例工厂:先造factory再造bean
- FactoryBean(对实例工厂的改造)
- 改造成非单例方法
bean的生命周期
生命周期:从创建到消亡的完整过程
bean生命周期: bean从创建到销毁的整体过程
bean生命周期控制:在bean创建后到销毁前做- -些事情
阶段
- 初始化容器
- 创建对象 (内存分配)
- 执行构造方法
- 执行属性注入( set操作)
- 执行bean初始化方法
- 使用bean
执行业务操作 - 关闭/销毁容器
执行bean销毁方法- 手工关闭容器
ConfigurableApplicat ionContext接口close( )操作 - 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
ConfigurableApplicationContext接口registerShutdownHook( )操作
- 手工关闭容器
手动配置
步骤:
- 定义并使用init()方法
- 在xml文件中指定init方法名
- 在想要控制的对象(如dao)的实现类中定义init()函数
- 使用:随bean的创造和销毁自动执行
- 定义并使用destroy()方法
- 配置:在xml文件中指定destroy方法名
- 在想要控制的对象(如dao)的实现类中定义destroy()函数
- 使用:
1.- 方法一:在测试函数中最后面加一个关闭方法,需要修改ctx的创建
- 方法二:加一个关闭钩子
- 随bean的创造和销毁自动执行
Spring接口控制
- 在想要控制的对象(如service)的实现类中让类实现这两个接口
- 按alt+enter生成快捷键覆盖这两个方法
依赖注入
方式
setter注入
- 简单类型
- 在dao的实现类中定义简单类型,写set方法
- 在xml文件中用property的value属性配置
- 引用类型:上面就是name标签里是实现类中定义的变量的名字
解耦后的改进方法:按类型自动装配,或者指定位置解决
构造器注入
-
简单类型:同下
-
引用类型
实现类:配置name标签是实现类中构造方法形参的名字
方式选择
- 强制依赖(必须要的依赖)使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
- 可选依赖使用setter注入进行,灵活性强,
- Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
- 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
- 实际开发过程中还要根据实际情况分析 ,如果受控对象没有提供setter方法就必须使用构造器注入
- 自己开发的模块推荐使用setter注入
依赖自动装配
IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
自动装配方式
- 按类型(常用)
- 按名称这两处名字做匹配
- 按构造方法
- 不启用自动装配
依赖自动装配特征
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时( byType )必须保障容器中相同类型的bean唯一 ,推荐使用
- 使用按名称装配时( byName )必须保障容器中具有指定名称的bean,因变量名与配置耦合, 不推荐使用
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
集合注入
数组
List
Set
Map
Properties
- 在dao的实现类定义集合和get方法和操作
- 注入集合对象:注意格式后面以此类推
案例:数据源对象管理
- 添加外部文件
- 管理DruidDataSource对象(bean)
- 先查看外部class里面有什么方法(快捷键ctrl+f12),有构造还是set
- 写连接池配置
- 添加另一个项目坐标:c3p8
- 找出连接池对象
- 写配置
- 添加jdbc坐标
加载properties
spring加载外部properties文件
- 开一个空间context
- 使用context空间加载properties文件
- 使用properties中的属性:把properties中的属性作为占位符写进xml的bean中
- 防止自己写的属性与系统属性重名导致被覆盖
- 加载多个properties文件或者或者.properties前面加上classpath:*
- 加载类路径或jar包中的properties的文件
容器
创建容器
加载类路径下的配置文件
从文件系统下加载配置文件
获取bean
按名称找,然后强制转换BookDao bookDao = ( BookDao) ctx. getBean( "bookDao");
按名称找,指定类型BookDao bookDao = ctx. getBean( s: "bookDao", BookDao. class);
按类型找BookDao bookDao = ctx. getBean( BookDao. class);|
容器类层次结构
BeanFactory
注解开发
- 在对应的类里面加这一句替代上面的bean配置语句
- 在核心配置文件中通过组件扫描加载bean(指定搜索路径)
衍生注解
- @Controller :用于表现层bean定义
- @Service : 用于业务层bean定义
- @Repository :用于数据层bean定义
纯注解开发
- 用一个SpringConfig.java类代替applicationContext.xml配置文件
- 在该类头加一个@Configuration
- 在该配置类中加一个扫描组件@CompomentScan,后面写上扫描路径
使用:
- 加载配置类
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig . class);
- 后面跟之前一样
bean管理
作用范围
使用非单例对象
使用 @Scope("prototype")
定义bean作用范围
生命周期
- 写初始化和销毁方法并指定
- 销毁方法还需关闭钩子或者关闭容器
依赖注入-自动装配
加一个@Autowired(按类型装配),set方法可以省略
加载多个类型
有多个相同类型的bean,用@Qualifier(“”)指定名称,该注解依赖Autowired
简单类型注入
用@Value可以实现简单类型注入
引用类型注入
直接在形参上加(set方法可以省略Autowired)
加载外部文件
- 在配置类中加载属性@PropertySource
- 使用:用${}
路径仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符*
管理第三方Bean
手工编程: jdbc一般要自己单独写一个配置类jdbcConfig.java
- 定义一个方法,获得要管理的对象
- 把方法的返回值定义成一个bean
- 在主配置类中导入jdbcConfig.java
主函数:
为第三方bean注入资源
- 简单类型:使用@Value引入的成员变量来加载
- 引用类型(按类型自动装配)
- 让SpringConfig.java扫描到dao的实现类
- 在目标类(如jdbc)中把dao作为参数写进去
整合
Spring整合Mybatis/Druid
MyBatis程序核心对象分析
// 1.创建SqLSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2.加载SqLMapConfig. xmL配置文件,将其作为输入流读取
InputStream inputStream = Resources . getResourceAsStream("SqlMapConfig.xml");
// 3.创建SqLSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build( inputStream);
// 4.获取SqlSession
SqlSession sqlSession = sqlSessionFactory . openSession();
// 5.执行sqlSession对象执行查询, 获取结果User
AccountDao accountDao = sqlSession. getMapper(AccountDao.class);
Account ac = accountDao . findById(2);
System.out . print1n(ac);
// 6.释放资源
sqlSession.close();
三四步解释:SqlSessionFactory
相对耗时,且是一个线程安全的对象(当多个线程同时访问一个对象时,线程安全的对象能够保证其内部状态的一致性和正确性,而不会导致意外的错误或数据损坏。), 应该在应用程序的生命周期内只创建一次。SqlSessionFactory
对象可以重复使用。通常情况下,每个数据库操作都需要创建一个新的 SqlSession
对象,并在完成操作后关闭它,以释放相关资源。
MyBatis配置文件:
<configuration>
<!-- 初始化属性数据-->
<properties resource= "jdbc. properties"></ properties>
<!-- 初始化类型别名-->
<typeAliases>
<package name="com.itheima.domain"/>
</typeAliases>
<!-- 初始化dataSource-->
<environments>
<environment id="mysql">
<transactionManager type="JDBC"/>
<datasource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</datasource>
</environment>
</environments>
<!-- 初始化mapper-->
<mappers>
<mapper resource="com/itheima/dao"/>
</mappers>
</configuration>
Mybatis管理的是SqlSessionFactory对象
-
pom文件配置
导入spring-context(实现IoC和Bean管理);连接池druid(管理数据库连接);mybatis和mysql的包;jdbc的包(spring操作数据库用的包);mybatis-spring(spring整合mybatis的包) -
创建SpringConfig.java文件并写好配置
-
创建JdbcConfig.java并写好配置
-
配置sqlsession对应的bean
-
创建MyBatisConfig.java让它返回sqlsession对象
-
设置ssfb对象,逐一分解xml文件,把有用的用set设置,设置datasource时要注入引用类型,直接加在形参上
设置mapper
-
主函数调用
Spring整合JUnit
- pom中导入junit坐标和spring-junit
- 创建测试类
- 设定JUnit测试器并加载上下文
- Autowired自动装配注入要测试的Bean
AOP
概念
- AOP(Aspect Oriented Programming) 面向切面编程,一种编程范式 ,指导开发者如何组织程序结构
00P(0bject Oriented Progr amming )面向对象编程 - 作用:在不惊动原始设计的基础上为其进行功能增强(无侵入式编程)
-
连接点:是在程序执行过程中可以被拦截的特定点。它代表着应用程序中的一个具体位置,例如方法执行的开始、结束或异常抛出的地方。连接点是AOP中的基本单位,它可以被AOP框架识别并与切面进行绑定。
-
切入点:是用于定义一组连接点的表达式。它是在连接点中进行筛选和匹配的规则。切入点可以基于连接点的类型、方法名、参数类型等来定义,用于确定哪些连接点将被拦截和应用切面的逻辑。
在SpringAOP中, -一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 一个具体方法: com. itheima . dao包下的BookDao接口中的无形参无返回值的save方法
- 匹配多个方法:所有的save方法, 所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
-
通知:是在特定的连接点(Join Point)上执行的代码
-
切面:描述通知与切入点的对应关系
入门案例
案例设定: 测定接口执行效率
简化设定:在接口执行前输出当前系统时间
开发模式: XML or注解
- 导入坐标( pom. xml )
- aop的包包含在context中
- 导入aspect包
- 标记程序应用注解开发aop
- 制作连接点方法(原始操作,Dao接口与实现类)
- 制作共性功能(通知类与通知)
- 创建通知类
- 抽取出共性功能
- 标记为受Spring容器管理的组件:@Component
标记为aop:@Aspect(加在最前面)
- 定义切入点
- 绑定切入点与通知关系,, 并指定通知添加到原始连接点的具体执行位置
工作流程
- Spring容器启动
- 读取所有切面配置中的切入点
- 初始化bean ,判定bean对应的类中的方法是否匹配到任意切入点
- 匹配失败,创建对象(Bean)
- 匹配成功,创建原始对象(目标对象)的代理对象
- 获取bean执行方法
- 获取bean ,调用方法并执行,完成操作
- 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
- 目标对象( Target ) : 原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
- 代理( Proxy ) :目标对象无法直接完成工作,需要对其进行功能回填, 通过原始对象的代理对象实现
切入点表达式
语法格式
- 切入点:要进行增强的方法
- 切入点表达式:要进行增强的方法的描述方式
- 描述方式:
- 描述方式一: 执行com. itheima . dao包下的BookDao接口中的无参数update方法
execution(void com. itheima . dao. BookDao. update()) - 描述方式二:执行com. itheima. dao . imp1包下的BookDaoImp1类中的无参数update方法
execution(void com. itheima . dao. impl . BookDaoImpl.update())
- 描述方式一: 执行com. itheima . dao包下的BookDao接口中的无参数update方法
- 标准格式:动作关键字(访问修饰符返回值包名.类/接口名.方法名(参数)异常名)
execution (public User com.itheima.service.UserService.findById (int))
- 描述方式:
通配符
通配符:可以使用通配符描述切入点,快速描述
*不可为空
- *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution (public * com.itheima.*.UserService.find* (*) )
匹配com. itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法 - … : 多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution (public User com..UserService.findById (..) )
匹配com包下的任意包中的UserService类或接口中所有名称为 findByld的方法 - +:专用于匹配子类类型
execution $(* * \ldots *$ Service+.* (...))
书写技巧
原则:精准、高效
- 所有代码按照标准规范开发 ,否则以下技巧全部失效
- 描述切入点通常描述接口,而不描述实现类
- 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
- 返回值类型对于增删改类使用精准类型加速匹配 ,对于查询类使用通配快速描述
- 包名书写尽量不使用. .匹配,效率过低,常用
*
做单个包描述匹配,或精准匹配 - 接口名/类名书写名称与模块相关的采用
*
匹配 ,例如UserService书写成*
Service , 绑定业务层接口名 - 方法名书写以动词进行精准匹配,名词采用
*
匹配,例如getByld书写成getBy*,selectAll书写成selectAll - 参数规则较为复杂 ,根据业务方法灵活调整
- 通常不使用异常作为匹配规则
通知类型
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同, 最终运行代码时要将其加入到合理的位置
AOP通知共分为5种类型
前置通知&后置通知
环绕通知(重点)
如果原方法有返回值,通知类的返回值应为Object,同时接收这个返回值后,在方法最后面return这个返回值
推荐抛出异常
返回后通知(了解)
在后置通知之后运行
只有在没有抛出异常正常结束时才能运行
抛出异常后通知(了解)
(@AfterThrowing)
AOP通知获取数据
获取参数
所有通知类型都能获取
在参数列表中加JoinPoint对象拿
可以对传进来的参数进行处理,仅对一个aop操作就行
获取返回值
只有返回后通知和环绕通知能获取返回值
- 环绕通知:和前面一样
- 返回后通知:
- 定义一个用来接收返回值的形参
- JoinPoint和String这两个形参的顺序不能变
获取异常
只有抛出异常后通知和环绕通知能获取异常
- 环绕通知使用try catch模块替代thowing
- 抛出异常后通知
- 定义一个形参接收异常对象
- 在原始操作中抛出异常
事务
概念
事务作用:在数据层保障一系列的数据库操作同成功同失败
Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
- 接口
- 实现类
入门案例-银行转账
步骤:
-
在业务层接口上添加Spring事务管理
Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中, 降低耦合
注解式事务可以添加到业务方法上表示当前方法开启事务, 也可以添加到接口上表示当前接口所有方法开启事务 -
在jdbcconfig中定义事务管理器
-
在springconfig中启用注解式事务驱动
Spring事务角色
通过相同的数据源,将多个事务管理为一个事务
- 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
Spring事务属性
事务配置
程序中遇见如下两种异常则回滚,否则不回滚
- error型异常:如溢出
- 运行时异常
IOException就不会回滚,可以设置
事务传播行为
案例:转账业务追加日志
分析:让log模块不参与回滚
解决方案:设置log模块的事务传播行为(事务协调员对事务管理员所携带事务的处理态度)
- 建立一个新事务
- 设置事务的传播行为
-1695176318605)]
Spring事务角色
通过相同的数据源,将多个事务管理为一个事务
[外链图片转存中…(img-OTamBE3L-1695176318606)]
- 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
Spring事务属性
事务配置
[外链图片转存中…(img-nntuKnCM-1695176318607)]
[外链图片转存中…(img-Kjm8LV6B-1695176318607)]
程序中遇见如下两种异常则回滚,否则不回滚
- error型异常:如溢出
- 运行时异常
IOException就不会回滚,可以设置[外链图片转存中…(img-Da25TWGN-1695176318608)]
事务传播行为
案例:转账业务追加日志
[外链图片转存中…(img-qC0gc3ce-1695176318609)]
分析:让log模块不参与回滚
解决方案:设置log模块的事务传播行为(事务协调员对事务管理员所携带事务的处理态度)
- 建立一个新事务
- 设置事务的传播行为[外链图片转存中…(img-MARduawX-1695176318610)]
[外链图片转存中…(img-aOAnvmPi-1695176318611)]