Spring系统架构
- Core Container:核心容器
- AOP:面向切面编程
- Aspects:AOP思想实现
- Data Access:数据访问
- Data Integration:数据集成
- Transaction:事务
- Web:Web开发
- Test:单元测试与集成测试
Spring学习线路
1.核心容器
核心概念:IOC/DI
容器基本操作
2.整合Mybatis
3.AOP
4.事务
一:配置文件
核心容器
由于在类里面写了其他的一些实现,导致耦合度偏高 。
解决方案:使用对象时,在程序中不要主动使用new产生对象,转换由外部提供对象。引出IOC。
目标:充分解耦。使用IOC容器管理bean,在IOC容器内将有依赖关系的bean进行关系绑定。
1.核心概念
IOC :使用对象时,由主动new产生对象转换为由外部提供对象,对象的创建控制权由程序转移到外部,这种思想称为控制反转。就是解耦。
IOC容器 :(Core Container)Spring提供了这个容器,用来充当IOC思想中的”外部“。
这两个就是同一个”外部“。
Bean :IOC容器负责对象的创建,初始化等一系列工作,被创建或被管理的对象在IOC容器中统称为bean。
DI :在容器中建立bean与bean之间的依赖关系的整个过程,就称为依赖注入。
2.IOC案例
2.1 思路
2.1.1管理什么?Service和Dao
2.1.2 如何将被管理的对象告诉IOC容器?配置
2.1.3 被管理的对象交给IOC容器,如何获取到IOC容器?接口
2.1.4 IOC容器得到后,如何从容器中获取bean?接口方法
2.1.5 使用Spring导入哪些坐标?pom.xml
2.2 步骤
①在pom.xml导入spring的坐标spring-context
②定义sping管理的类
③ 在applicationContect.xml中写配置文件,把要管理的对象交给IOC容器(配置bean),配置对应类作为spring管理的Bean
注意:bean定义时id属性在同一个上下文中不能重复
④ 要想拿到bean,就要获取IOC容器(接口),初始化IOC容器,通过容器获取bean
⑤ 从容器中获取bean,调用接口方法
.idea是什么?
.idea是IntelliJ IDEA工程的配置文件和目录。当在使用IntelliJ IDEA创建一个新的项目时,它会自动在项目的根目录下创建一个
.idea
目录。该目录包含了项目的配置信息,包括项目的结构、依赖项、编译选项、版本控制设置等。
.idea
目录中的文件和子目录通常是自动生成的,不需要手动编辑或修改。这些文件和目录的作用是帮助IntelliJ IDEA正确地加载和配置你的项目。
.idea
目录中常见的一些文件和目录:
workspace.xml
:保存了IntelliJ IDEA工作区的全局配置,包括窗口布局、插件设置等。modules.xml
:保存了项目的模块配置信息,包括模块的依赖关系、源代码路径等。libraries
目录:保存了项目的依赖库配置信息,包括项目使用的外部库和框架。runConfigurations
目录:保存了运行和调试配置信息,包括启动项目的配置参数、环境变量等。vcs.xml
:保存了版本控制系统的配置信息,例如Git或SVN的设置。
Dependency 'org.springframework:spring-context:5.2.10.RELEASE' not found
解决办法
Maven的用户设置文件(User Settings File)是一个XML文件,用于配置Maven的全局设置和个人偏好。它包含了Maven运行时所需的一些配置信息,比如本地仓库路径、远程仓库的配置、代理设置、插件配置等。
将Maven的用户设置文件更改为Local repository后,pom.xml文件中的项目不再显示红色可能是由于以下原因:
- 本地仓库路径正确:如果你正确地将Maven的用户设置文件中的本地仓库路径更改为实际的本地仓库路径,那么Maven将能够正确地解析和检索项目所需的依赖项。这将消除在pom.xml文件中引用的依赖项无法解析而导致的红色警告。
- 依赖项被正确解析:Maven使用本地仓库来存储已下载的依赖项。当你将Maven的用户设置文件更改为本地仓库后,Maven将从本地仓库中解析和加载项目所需的依赖项。如果依赖项成功解析并可用,那么pom.xml文件中的项目将不再显示红色。
java: 错误: 不支持发行版本 5
上述代码放到maven\conf\setting.xml中
3.DI入门案例
3.1 思路
①基于IOC管理Bean
②Service中使用new形式创建的Dao对象不保留(如果保留,耦合度必然很高)
③Service中需要的Dao对象如何进入到Service中?提供方法
④Service与Dao之间的关系如何描述?配置
3.2 步骤
⑥ 删除业务层中使用new的方式创建的dao对象
⑦删除之后怎么创建dao对象呢?提供对应的set方法
⑧ 配置service与dao的关系
注意:这两个dao不一样
Bean的配置
1.bean基础配置
id用来定义bean的名称
class用来定义bean的类型
2.bean的别名配置
使用到未出现的别名时会爆出如下异常
如果name中正确定义了别名,就不会显示这个异常。
3.bean作用范围控制
在不写这个scope的时候,默认就是singleton(单例对象)。
①为什么bean默认为单例?
Spring在帮我们管理对象的时候,就是帮助我们管理那些可以复用的对象。用一次还会用它,还会从容器中拿,这样效率才会高。
②哪些bean适合造单例?
表现层对象,业务层对象,数据层对象,工具对象
③不适合交给容器进行管理的bean?
封装实体的域对象
通过这个scope可以控制这个对象是否为单例对象。
bean实例化/bean是如何创建出来的?
1.构造方法(常用)
bean本质上就是对象,创建bean使用构造方法完成。且调用的是无参构造方法。
无参构造方法如果不存在,将抛出异常BeanCreationException。
且用私有的构造方法也可以。
报错怎么看?
对于Spring的报错信息,一般拉到异常的最后面,看Caused by
2.通过使用静态工厂创建对象(了解即可)
3.实例工厂(了解)
4.代替原始实例工厂创建对象的方法(实用)
bean生命周期
生命周期:从创建到消亡的完整过程。
bean生命周期:bean从创建到销毁的整体过程。
bean生命周期控制:在bean创建后到销毁前做一些事情。
1.生命周期控制方法
一:配置生命周期控制方法
① 实现这两个方法
② 要想看到对应的destroy方法的执行,必须要关闭容器。
close关闭相对暴力,而钩子函数在虚拟机退出之前回调此函数。
二:接口控制(了解)
2.bean在整个初始化过程中经理了哪些阶段?
① 初始化容器
- 创建对象(内存分配)
- 执行构造方法
- 执行属性注入(set操作)
- 使用bean初始化方法
② 使用bean
- 执行业务操作
③ 关闭/销毁容器
- 执行bean销毁方法
3.bean销毁时机
容器关闭前触发bean的销毁。关闭容器方式:
依赖注入
setter注入:
① 引用类型
② 简单类型
构造器注入:
① 引用类型:把setter方法删了换成构造器方法(了解)
并且在配置文件中改变标签,把property标签改成constructor-arg标签
② 简单类型同理(了解)
问题来了。
如果这里面的参数的名称变了
那么配置文件里面这个也要跟着变,所以耦合度很高。
所以第一种解决方案:把name改成type,不就解耦了吗?但是。。。如果两种类型都是String或者int呢?
第二种解决:直接写形参下标?看似解决参数类型重复问题,使用位置解决参数匹配。但是不靠谱。
依赖注入方式选择
① 强制使用依赖使用构造器进行。使用setter注入有概率不进行注入导致null对象出现。
强制依赖:bean运行必须要用的构造器处理。
② 可选依赖使用setter注入进行,灵活性强。
③ Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的方式进行数据初始化,相对严谨。
④ 如果有必要可以两者同时使用,使用构造器注入完成依赖强制项的注入,使用setter注入完成可选依赖的注入。
⑤ 实际开发过程中话要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入。
⑥ 自己开发的模块推荐使用setter注入。
依赖自动装配
IOC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配。
自动装配方式:
① 按类型(常用)
autowire属性开启自动装配。
配置bookDao的bean必须要写,而且也只能写一个。
你说如果你不写或者说写两个,它去装配谁?
提示:
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作。
- 使用按类型装配时(byType)必须保障容器中相同类型的Bean唯一,推荐使用。
- 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量与配置耦合,不推荐使用。
- 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效。
② 按名称
③ 按构造方法
④ 不启动自动装配
集合注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.itheima.dao.impl.BookDaoImpl">
<!--数组注入-->
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<!--list集合注入-->
<property name="list">
<list>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>chuanzhihui</value>
</list>
</property>
<!--set集合注入-->
<property name="set">
<set>
<value>itcast</value>
<value>itheima</value>
<value>boxuegu</value>
<value>boxuegu</value>
</set>
</property>
<!--map集合注入-->
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
<!--Properties注入-->
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
</bean>
</beans>
案例
数据源对象管理(Spring管理第三方资源)
①导入druid坐标,
②配置数据源对象作为spring管理的bean 。
引出问题:配置写在配置文件中不合适,应该单独抽出来。
加载properties文件
①在配置文件要想读取到properties的信息,得先去加载这个文件。Spring加载这个文件方式比较特殊,先去开启一个全新context的命名空间(灰色的)。
②使用context空间加载properties文件
③使用属性占位符¥{}读取properties文件中的属性。如上
问题:如果改成username,那么和系统中属性名称同名,在加载配置文件时就加一个属性并设置为NEVER。
如果有多个properties,最标准的方式如下:
总结以上几种:
容器
1.创建容器
方式①:加载类路径下的配置文件
方式②:从文件系统下(绝对路径)加载配置文件
2.获取bean
3.容器类层次结构
4.BeanFactory
核心容器总结
一.容器相关
- BeanFactory是IOC容器的顶层接口,初始化BeanFactory对象时,加载的bean延迟加载。
- ApplicationContext接口是Spring容器的核心接口,初始化bean立即加载。
- ApplicationContext接口提供基础的bean操作相关方法,通过其他接口扩展其功能
- ApplicationContext接口常用初始化类:ClassPathXmlApplicationContext,FileSystemXmlApplication
二.bean相关
三.依赖注入相关
二.使用注解开发
注解开发
1.注解开发定义bean
①不采用配置文件的形式,采用注解的话,class里是全类名,既然对这个类进行配置(BookDaoImpl),就要考虑在这个类中去使用注解。
②在类上加上注解,把注解写上就代表了配置里面的<bean>,就配置好了bean,但是没有起名称。
③起名称
点开component的源码,有一个value()属性,由于可以默认不写,就直接如上图。
④问题来了,Spring的配置文件怎么指知道它写了这一行话呢?在核心配置文件中通过扫描组件加载bean,指定这个注解所在的类的位置。
<context:component-scan base-package="com.itheima"/>
Spring提供@Component注解的三个衍生注解
- @Controller :用于表现层bean定义
- @Service :用于业务层bean定义
- @Repository (数据仓库):用于数据层bean定义
2.纯注解开发
Spring3.0升级了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道。
提问:有下面一句话,既然要用纯注解开发,所以要把这一行用类来代替。
所以现新建一个类,使用配置类。加上这个注解就代表是一个配置类。
这个注解代表了这些东西。
但是这个还没有发过去。
再加上一个注解,扫描component。
现在这个程序不能运行了,因为它是加载配置文件的。
重新写一个程序加载配置类。
总结 :在纯注解开发中,用Java类代替Spring核心配置文件。
多个数据用数组形式,用大括号隔开。
bean管理
1.bean作用范围
2.bean生命周期
依赖注入
1.引用类型注入
2.简单类型注入
第三方bean
1.第三方bean管理
管理第三方bena,由于不能把配置写在别人的源代码中,只能在配置类里搞了。
①定义一个方法获得bean,要管理的对象。
②添加Bean,表示当前方法的返回值是一个bean。
但是所有的配置类都写在这里面,写爆了咋整?
方法:使用独立的配置类管理第三方bean。如将上面的重新使用独立的配置类命名为JdbcConfig。
扫描式不建议。
问题来了,如果上面那个bean运行的时候,还需要依赖其他的bean怎么办?
2.第三方bean依赖注入
注解开发总结
三.整合Mybatis
将配置文件进行转换
整合junit
四.AOP
AOP,面向切面编程,一种编程范式,指导开发者如何组织程序结构。
作用:在不惊动原始设计的基础上为其进行功能增强。
看例子。update,delete,select方法是不是没有业务执行万次的方法?
现在来看打印结果。现在update没有那个方法为什么打印了上万次啊?说明通知和切入点之间绑定了关系。
在不惊动原始设计的基础上,想为谁增加功能就为谁增加功能,这就是AOP。体现了Spring理念:无入侵式/无侵入式。
连接点:原始方法,或者说制作出来的功能,例如save(),update()...
切入点:要追加功能的方法。要执行对应通知的方法。
通知:要让大家都有的功能。
切面:上面说的,通知和切入点之间有关系,把他们绑到了一块。
说一下切入点和连接点的区别:连接点是所有方法,而切入点是匹配某些方法。
代理(Proxy):SpringAOP的核心本质是采用代理模式实现的。
它允许通过创建一个代理对象来控制对原始对象的访问。
代理模式的核心思想是:代理对象与原始对象实现相同的接口,从而可以完全替代原始对象。当客服端调用代理对象的方法时,代理对象可以在调用前后执行额外的逻辑。
标准AOP核心概念:
AOP入门案例
思路分析:
1.导入坐标(pom.xml)
//导入aop的坐标,导入这个自动导入aop
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
//导入aspect的坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2.制作连接点方法
3.制作切入点方法
说明:切入点定义依托一个不具有实际意义的方法进行,即无参数,无返回值,方法无实际逻辑。
4.共性功能就是上面的method()
提出问题:现在能运行吗?不能,必须得受Spring控制,加个component,让他变成spring控制的bean。并且要告诉spring,扫描到以后,把这个当成aop处理。还要加个注解aspect。
@Before,指定通知添加到原始连接点的具体执行位置。
在配置类里面还不知道aop是注解开发的。在配置类加上注解EnableAspectJAutoProxy。
这个EnableAspectJAutoProxy启动了@Aspect,而这个配置告诉你要识别哪些。
AOP工作流程
目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的。
代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现。
AOP切入点表达式
切入点:要进行增强的方法。
切入点表达式:要进行增强的方法的描述的表达式。
标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
eg:execution(public User com.itheima.service.UserService.findById(int))
标注颜色的可以省略。
通配符 :快速描述
*:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现。
execution(public * com.itheima.*.UserService.find*(*)) //有*必带参数
..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
+
execution(* *..*Service+.*(..))
书写技巧
eg:execution(* com.itheima.*.*Service.save(..))
AOP通知类型
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要加到合理的位
置。
前置通知:
后置通知
环绕通知
返回后通知
抛出异常后通知
AOP案例:测量业务层接口万次执行效率
业务功能:业务层接口执行前后分别记录时间,求差值得到执行效率
通知类型选择前后均可以增强的类型---环绕通知
补充说明:当前测试的接口执行效率仅仅时一个理论值,并不是一次完整的执行过程。
AOP通知获取数据
1.获取切入点方法的参数
JoinPoint:适用于前置,后置,返回后,抛出异常前通知
ProceedJoinPoint:适用于环绕通知
2.获取切入点方法返回值
返回后通知
环绕通知
3.获取切入点方法运行异常信息
抛出异常后通知
环绕通知
案例:百度网盘密码数据兼容处理
正常情况下在未处理空格时的结果应该是false。
现在使用AOP使得在不惊动原始设计的基础上,想为其增加处理空格的功能。典型的AOP技术。
① 开启注解
② 加上两注解,写切入点。
注意:要把处理后的参数传进去。
否则得到的结果就是未处理空格前的长度。
五:Spring事务
事务作用:在数据层保障一系列额数据库操作同时成功或者同时失败。
Spring事务作用:在数据层或业务层保障一系列额数据库操作同时成功或者同时失败。
案例:模拟银行转账
模拟异常后,操作失败。
引出事务。①在业务层接口上写@Transactional 。
注意事项:Spring注解式事务通常添加到业务层接口中而不会添加到业务层实现类中,降低耦合。
注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务。
② 开了事务,但是用什么样的事务管理器未说。在配置类说明。
事务管理器要根据实现技术进行选择。
Mybatis框架使用的是JDBC事务。
③ Spring还不知道使用注解的形式进行事务操作。
Spring事务角色
事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法。
事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法。
事务相关配置
看@Transactional注解源码。可以在注解上加上一些东西,属性。
重点掌握事务回滚异常,有些异常是不会默认回滚的。
①非受检异常(Unchecked Exception):当事务中发生非受检异常(如NullPointerException、IllegalArgumentException等)时,事务不会回滚。这是因为非受检异常通常表示程序错误或逻辑错误,回滚事务可能无法解决这些问题。
②Error类异常:当事务中发生Error类异常(如OutOfMemoryError、StackOverflowError等)时,事务不会回滚。Error类异常通常表示严重的系统错误,回滚事务可能无法恢复系统的正常运行。
③事务提交前的异常:如果事务在提交之前发生异常,事务不会回滚。这是因为在提交之前,事务的修改操作尚未持久化到数据库中,因此无需回滚。
④不受事务管理的方法:如果一个方法没有被事务管理器管理,即没有被@Transactional注解标记或配置为事务处理的方法,那么其中的异常不会引发回滚。
以上是一般情况下的规则,具体的回滚行为还取决于所使用的事务管理器和数据库。
所以转账日志必须保留,这个必须单独开启一个事务。引出事务传播行为。
事务传播行为:事务协调员对事务管理员所携带事务的处理态度。
在注解上加上属性,表示新开启一个事务。
propagation对应的值:
上面就是把默认值改成新开启一个事务。