广州大型网站建设公司排名,创业的好项目,微商推广哪家好,网站建设体会doc二、Spring
1、Spring简介
1.1、Spring概述
官网地址#xff1a;https://spring.io/ Spring 是最受欢迎的企业级 Java 应用程序开发框架#xff0c;数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。 Spring 框架是一个开源的 Jav…二、Spring
1、Spring简介
1.1、Spring概述
官网地址https://spring.io/ Spring 是最受欢迎的企业级 Java 应用程序开发框架数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。 Spring 框架是一个开源的 Java 平台它最初是由 Rod Johnson 编写的并且于 2003 年 6 月首 次在 Apache 2.0 许可下发布。 Spring 是轻量级的框架其基础版本只有 2 MB 左右的大小。 Spring 框架的核心特性是可以用于开发任何 Java 应用程序但是在 Java EE 平台上构建 web 应 用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用通过启用基于 POJO 编程模型来促进良好的编程实践。 1.2、Spring家族
项目列表https://spring.io/projects
1.3、Spring Framework
Spring 基础框架可以视为 Spring 基础设施基本上任何其他 Spring 项目都是以 Spring Framework为基础的。
1.3.1、Spring Framework特性
非侵入式使用 Spring Framework 开发应用程序时Spring 对应用程序本身的结构影响非常
小。对领域模型可以做到零污染对功能性组件也只需要使用几个简单的注解进行标记完全不会
破坏原有结构反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序
时结构清晰、简洁优雅。
控制反转IOC——Inversion of Control翻转资源获取方向。把自己创建资源、向环境索取资源
变成环境将资源准备好我们享受资源注入。
面向切面编程AOP——Aspect Oriented Programming在不修改源代码的基础上增强代码功
能。
容器Spring IOC 是一个容器因为它包含并且管理组件对象的生命周期。组件享受到了容器化
的管理替程序员屏蔽了组件创建过程中的大量细节极大的降低了使用门槛大幅度提高了开发
效率。
组件化Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML
和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭
建超大型复杂应用系统。 声明式很多以前需要编写代码才能实现的功能现在只需要声明需求即可由框架代为实现。 一站式在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且
Spring 旗下的项目已经覆盖了广泛领域很多方面的功能性需求可以在 Spring Framework 的基
础上全部使用 Spring 来实现。
1.3.2、Spring Framework五大功能模块
功能模块功能介绍Core Container核心容器在 Spring 环境下使用任何功能都必须基于 IOC 容器。AOPAspects面向切面编程Testing提供了对 junit 或 TestNG 测试框架的整合。Data Access/Integration提供了对数据访问/集成的功能。Spring MVC提供了面向Web应用程序的集成功能。
2、IOC
2.1、IOC容器
2.1.1、IOC思想
IOCInversion of Control翻译过来是反转控制。
①获取资源的传统方式
自己做饭买菜、洗菜、择菜、改刀、炒菜全过程参与费时费力必须清楚了解资源创建整个过程中的全部细节且熟练掌握。
在应用程序中的组件需要获取资源时传统的方式是组件主动的从容器中获取所需要的资源在这样的
模式下开发人员往往需要知道在具体容器中特定资源的获取方式增加了学习成本同时降低了开发效率。
②反转控制方式获取资源
点外卖下单、等、吃省时省力不必关心资源创建过程的所有细节。
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式反转了资源的获取方向——改由容器主动的将资源推送给需要的组件开发人员不需要知道容器是如何创建资源对象的只需要提供接收资源的方式即可极大的降低了学习成本提高了开发的效率。这种行为也称为查找的被动形式。
③DI
DIDependency Injection翻译过来是依赖注入。
DI 是 IOC 的另一种表述方式即组件以一些预先定义好的方式例如setter 方法接受来自于容器
的资源注入。相对于IOC而言这种表述更直接。
所以结论是IOC 就是一种反转控制的思想 而 DI 是对 IOC 的一种具体实现。
2.1.2、IOC容器在Spring中的实现
Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建bean 之前首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式
①BeanFactory
这是 IOC 容器的基本实现是 Spring 内部使用的接口。面向 Spring 本身不提供给开发人员使用。
②ApplicationContext
BeanFactory 的子接口提供了更多高级特性。面向 Spring 的使用者几乎所有场合都使用
ApplicationContext 而不是底层的 BeanFactory。
③ApplicationContext的主要实现类 类型名简介ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象ConfigurableApplicationContextApplicationContext 的子接口包含一些扩展方法refresh() 和 close() 让 ApplicationContext 具有启动、关闭和刷新上下文的能力。WebApplicationContext专门为 Web 应用准备基于 Web 环境创建 IOC 容器对象并将对象引入存入 ServletContext 域中。
2.2、基于XML管理bean
2.2.1、实验一入门案例
①创建Maven Module
②引入依赖
dependencies!-- 基于Maven依赖传递性导入spring-context依赖即可导入当前所需所有jar包 --dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.3.1/version/dependency!-- junit测试 --dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/versionscopetest/scope/dependency
/dependencies③创建类HelloWorld
public class HelloWorld {public void sayHello(){System.out.println(helloworld);}
}⑤在Spring的配置文件中配置bean
!--配置HelloWorld所对应的bean即将HelloWorld的对象交给Spring的IOC容器管理通过bean标签配置IOC容器所管理的bean属性id设置bean的唯一标识class设置bean所对应类型的全类名
--
bean idhelloworld classcom.atguigu.spring.bean.HelloWorld/bean⑥创建测试类测试
Test
public void testHelloWorld(){ApplicationContext ac newClassPathXmlApplicationContext(applicationContext.xml);HelloWorld helloworld (HelloWorld) ac.getBean(helloworld);helloworld.sayHello();
}⑦思路 ⑧注意
Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象这一点需要注意。如果在需要无参构造器时没有无参构造器则会抛出下面的异常 org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘helloworld’ defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.atguigu.spring.bean.HelloWorld]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.atguigu.spring.bean.HelloWorld. () 2.2.2、实验二获取bean
①方式一根据id获取
由于 id 属性指定了 bean 的唯一标识所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。
上个实验中我们使用的就是这种方式。
②方式二根据类型获取
Test
public void testHelloWorld(){ApplicationContext ac new ClassPathXmlApplicationContext(applicationContext.xml);HelloWorld bean ac.getBean(HelloWorld.class);bean.sayHello();
}③方式三根据id和类型
Test
public void testHelloWorld(){ApplicationContext ac newClassPathXmlApplicationContext(applicationContext.xml);HelloWorld bean ac.getBean(helloworld, HelloWorld.class);bean.sayHello();
}④注意
当根据类型获取bean时要求IOC容器中指定类型的bean有且只能有一个
当IOC容器中一共配置了两个
bean idhelloworldOne classcom.atguigu.spring.bean.HelloWorld/bean
bean idhelloworldTwo classcom.atguigu.spring.bean.HelloWorld/bean根据类型获取时会抛出异常 org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.atguigu.spring.bean.HelloWorld’ available: expected single matching bean but found 2: helloworldOne,helloworldTwo ⑤扩展
如果组件类实现了接口根据接口类型可以获取 bean 吗 可以前提是bean唯一 如果一个接口有多个实现类这些实现类都配置了 bean根据接口类型可以获取 bean 吗 不行因为bean不唯一 ⑥结论
根据类型来获取bean时在满足bean唯一性的前提下其实只是看『对象 instanceof 指定的类
型』的返回结果只要返回的是true就可以认定为和类型匹配能够获取到。
2.2.3、实验三依赖注入之setter注入
①创建学生类Student
public class Student {private Integer id;private String name;private Integer age;private String sex;public Student() {}public Integer getId() {return id;}public void setId(Integer id) {this.id id;}public String getName() {return name;}public void setName(String name) {this.name name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age age;}public String getSex() {return sex;}public void setSex(String sex) {this.sex sex;}Overridepublic String toString() {return Student{ id id , name name \ , age age , sex sex \ };}
}②配置bean时为属性赋值
bean idstudentOne classcom.atguigu.spring.bean.Student!-- property标签通过组件类的setXxx()方法给组件对象设置属性 --!-- name属性指定属性名这个属性名是getXxx()、setXxx()方法定义的和成员变量无关--!-- value属性指定属性值 --property nameid value1001/propertyproperty namename value张三/propertyproperty nameage value23/propertyproperty namesex value男/property
/bean③测试
Test
public void testDIBySet(){ApplicationContext ac new ClassPathXmlApplicationContext(springdi.xml);Student studentOne ac.getBean(studentOne, Student.class);System.out.println(studentOne);
}2.2.4、实验四依赖注入之构造器注入
①在Student类中添加有参构造
public Student(Integer id, String name, Integer age, String sex) {this.id id;this.name name;this.age age;this.sex sex;
}②配置bean
bean idstudentTwo classcom.atguigu.spring.bean.Studentconstructor-arg value1002/constructor-argconstructor-arg value李四/constructor-argconstructor-arg value33/constructor-argconstructor-arg value女/constructor-arg
/bean注意 constructor-arg标签还有两个属性可以进一步描述构造器参数 index属性指定参数所在位置的索引从0开始name属性指定参数名 ③测试
Test
public void testDIBySet(){ApplicationContext ac new ClassPathXmlApplicationContext(springdi.xml);Student studentOne ac.getBean(studentTwo, Student.class);System.out.println(studentOne);
}2.2.5、实验五特殊值处理
①字面量赋值 什么是字面量 int a 10; 声明一个变量a初始化为10此时a就不代表字母a了而是作为一个变量的名字。当我们引用a 的时候我们实际上拿到的值是10。 而如果a是带引号的‘a’那么它现在不是一个变量它就是代表a这个字母本身这就是字面 量。所以字面量没有引申含义就是我们看到的这个数据本身。 !-- 使用value属性给bean的属性赋值时Spring会把value属性的值看做字面量 --
property namename value张三/②null值
property namenamenull /
/property注意 property namename valuenull/property以上写法为name所赋的值是字符串null ③xml实体
!-- 小于号在XML文档中用来定义标签的开始不能随便使用 --
!-- 解决方案一使用XML实体来代替 --
property nameexpression valuea lt; b/④CDATA节
property nameexpression!-- 解决方案二使用CDATA节 --!-- CDATA中的C代表Character是文本、字符的含义CDATA就表示纯文本数据 --!-- XML解析器看到CDATA节就知道这里是纯文本就不会当作XML标签或属性来解析 --!-- 所以CDATA节中写什么符号都随意 --value![CDATA[a b]]/value
/property2.2.6、实验六为类类型属性赋值
①创建班级类Clazz
public class Clazz {private Integer clazzId;private String clazzName;public Integer getClazzId() {return clazzId;}public void setClazzId(Integer clazzId) {this.clazzId clazzId;}public String getClazzName() {return clazzName;}public void setClazzName(String clazzName) {this.clazzName clazzName;}Overridepublic String toString() {return Clazz{ clazzId clazzId , clazzName clazzName \ };}public Clazz() {}public Clazz(Integer clazzId, String clazzName) {this.clazzId clazzId;this.clazzName clazzName;}
}②修改Student类
在Student类中添加以下代码
private Clazz clazz;
public Clazz getClazz() {return clazz;
}
public void setClazz(Clazz clazz) {this.clazz clazz;
}③方式一引用外部已声明的bean
配置Clazz类型的bean
bean idclazzOne classcom.atguigu.spring.bean.Clazzproperty nameclazzId value1111/propertyproperty nameclazzName value财源滚滚班/property
/bean为Student中的clazz属性赋值
bean idstudentFour classcom.atguigu.spring.bean.Studentproperty nameid value1004/propertyproperty namename value赵六/propertyproperty nameage value26/propertyproperty namesex value女/property!-- ref属性引用IOC容器中某个bean的id将所对应的bean为属性赋值 --property nameclazz refclazzOne/property
/bean错误演示
bean idstudentFour classcom.atguigu.spring.bean.Studentproperty nameid value1004/propertyproperty namename value赵六/propertyproperty nameage value26/propertyproperty namesex value女/propertyproperty nameclazz valueclazzOne/property
/bean如果错把ref属性写成了value属性会抛出异常 Caused by: java.lang.IllegalStateException: Cannot convert value of type ‘java.lang.String’ to required type ‘com.atguigu.spring.bean.Clazz’ for property ‘clazz’: no matching editors or conversion strategy found 意思是不能把String类型转换成我们要的Clazz类型说明我们使用value属性时Spring只把这个 属性看做一个普通的字符串不会认为这是一个bean的id更不会根据它去找到bean来赋值 ④方式二内部bean
bean idstudentFour classcom.atguigu.spring.bean.Studentproperty nameid value1004/propertyproperty namename value赵六/propertyproperty nameage value26/propertyproperty namesex value女/propertyproperty nameclazz!-- 在一个bean中再声明一个bean就是内部bean --!-- 内部bean只能用于给属性赋值不能在外部通过IOC容器获取因此可以省略id属性 --bean idclazzInner classcom.atguigu.spring.bean.Clazzproperty nameclazzId value2222/propertyproperty nameclazzName value远大前程班/property/bean/property
/bean③方式三级联属性赋值
bean idstudentFour classcom.atguigu.spring.bean.Studentproperty nameid value1004/propertyproperty namename value赵六/propertyproperty nameage value26/propertyproperty namesex value女/property!-- 一定先引用某个bean为属性赋值才可以使用级联方式更新属性 --property nameclazz refclazzOne/propertyproperty nameclazz.clazzId value3333/propertyproperty nameclazz.clazzName value最强王者班/property
/bean2.2.7、实验七为数组类型属性赋值
①修改Student类
在Student类中添加以下代码
private String[] hobbies;
public String[] getHobbies() {return hobbies;
}
public void setHobbies(String[] hobbies) {this.hobbies hobbies;
}②配置bean
bean idstudentFour classcom.atguigu.spring.bean.Studentproperty nameid value1004/propertyproperty namename value赵六/propertyproperty nameage value26/propertyproperty namesex value女/property!-- ref属性引用IOC容器中某个bean的id将所对应的bean为属性赋值 --property nameclazz refclazzOne/propertyproperty namehobbiesarrayvalue抽烟/valuevalue喝酒/valuevalue烫头/value/array/property
/bean2.2.8、实验八为集合类型属性赋值
①为List集合类型属性赋值
在Clazz类中添加以下代码
private ListStudent students;
public ListStudent getStudents() {return students;
}
public void setStudents(ListStudent students) {this.students students;
}配置bean
bean idclazzTwo classcom.atguigu.spring.bean.Clazzproperty nameclazzId value4444/propertyproperty nameclazzName valueJavaee0222/propertyproperty namestudentslistref beanstudentOne/refref beanstudentTwo/refref beanstudentThree/ref/list/property
/bean若为Set集合类型属性赋值只需要将其中的list标签改为set标签即可 ②为Map集合类型属性赋值
创建教师类Teacher
public class Teacher {private Integer teacherId;private String teacherName;public Integer getTeacherId() {return teacherId;}public void setTeacherId(Integer teacherId) {this.teacherId teacherId;}public String getTeacherName() {return teacherName;}public void setTeacherName(String teacherName) {this.teacherName teacherName;}public Teacher(Integer teacherId, String teacherName) {this.teacherId teacherId;this.teacherName teacherName;}public Teacher() {}Overridepublic String toString() {return Teacher{ teacherId teacherId , teacherName teacherName \ };}
}在Student类中添加以下代码
private MapString, Teacher teacherMap;
public MapString, Teacher getTeacherMap() {return teacherMap;
}
public void setTeacherMap(MapString, Teacher teacherMap) {this.teacherMap teacherMap;
}配置bean
bean idteacherOne classcom.atguigu.spring.bean.Teacherproperty nameteacherId value10010/propertyproperty nameteacherName value大宝/property
/bean
bean idteacherTwo classcom.atguigu.spring.bean.Teacherproperty nameteacherId value10086/propertyproperty nameteacherName value二宝/property
/bean
bean idstudentFour classcom.atguigu.spring.bean.Studentproperty nameid value1004/propertyproperty namename value赵六/propertyproperty nameage value26/propertyproperty namesex value女/property!-- ref属性引用IOC容器中某个bean的id将所对应的bean为属性赋值 --property nameclazz refclazzOne/propertyproperty namehobbiesarrayvalue抽烟/valuevalue喝酒/valuevalue烫头/value/array/propertyproperty nameteacherMapmapentrykeyvalue10010/value/keyref beanteacherOne/ref/entryentrykeyvalue10086/value/keyref beanteacherTwo/ref/entry/map/property
/bean③引用集合类型的bean
!--list集合类型的bean--
util:list idstudentsref beanstudentOne/refref beanstudentTwo/refref beanstudentThree/ref
/util:list
!--map集合类型的bean--
util:map idteacherMapentrykeyvalue10010/value/keyref beanteacherOne/ref/entryentrykeyvalue10086/value/keyref beanteacherTwo/ref/entry
/util:map
bean idclazzTwo classcom.atguigu.spring.bean.Clazzproperty nameclazzId value4444/propertyproperty nameclazzName valueJavaee0222/propertyproperty namestudents refstudents/property
/bean
bean idstudentFour classcom.atguigu.spring.bean.Studentproperty nameid value1004/propertyproperty namename value赵六/propertyproperty nameage value26/propertyproperty namesex value女/property!-- ref属性引用IOC容器中某个bean的id将所对应的bean为属性赋值 --property nameclazz refclazzOne/propertyproperty namehobbiesarrayvalue抽烟/valuevalue喝酒/valuevalue烫头/value/array/propertyproperty nameteacherMap refteacherMap/property
/bean使用util:list、util:map标签必须引入相应的命名空间可以通过idea的提示功能选择 2.2.9、实验九p命名空间
引入p命名空间后可以通过以下方式为bean的各个属性赋值
bean idstudentSix classcom.atguigu.spring.bean.Studentp:id1006 p:name小明 p:clazz-refclazzOne p:teacherMap-refteacherMap/bean2.2.10、实验十引入外部属性文件
①加入依赖
!-- MySQL驱动 --
dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.16/version
/dependency
!-- 数据源 --
dependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.0.31/version
/dependency②创建外部属性文件 jdbc.userroot
jdbc.passwordatguigu
jdbc.urljdbc:mysql://localhost:3306/ssm?serverTimezoneUTC
jdbc.drivercom.mysql.cj.jdbc.Driver③引入属性文件
!-- 引入外部属性文件 --
context:property-placeholder locationclasspath:jdbc.properties/④配置bean
bean iddruidDataSource classcom.alibaba.druid.pool.DruidDataSourceproperty nameurl value${jdbc.url}/property namedriverClassName value${jdbc.driver}/property nameusername value${jdbc.user}/property namepassword value${jdbc.password}/
/bean⑤测试
Test
public void testDataSource() throws SQLException {ApplicationContext ac new ClassPathXmlApplicationContext(spring-datasource.xml);DataSource dataSource ac.getBean(DataSource.class);Connection connection dataSource.getConnection();System.out.println(connection);
}2.2.11、实验十一bean的作用域
①概念
在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围各取值含义参加下表
取值含义创建对象的时机singleton默认在IOC容器中这个bean的对象始终为单实例IOC容器初始化时prototype这个bean在IOC容器中有多个实例获取bean时
如果是在WebApplicationContext环境下还会有另外两个作用域但不常用
取值含义request在一个请求范围内有效session在一个会话范围内有效
②创建类User
public class User {
private Integer id;
private String username;
private String password;
private Integer age;public User() {}public User(Integer id, String username, String password, Integer age) {this.id id;this.username username;this.password password;this.age age;}public Integer getId() {return id;}public void setId(Integer id) {this.id id;}public String getUsername() {return username;}public void setUsername(String username) {this.username username;}public String getPassword() {return password;}public void setPassword(String password) {this.password password;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age age;}Overridepublic String toString() {return User{ id id , username username \ , password password \ , age age };}
}③配置bean
!-- scope属性取值singleton默认值bean在IOC容器中只有一个实例IOC容器初始化时创建
对象 --
!-- scope属性取值prototypebean在IOC容器中可以有多个实例getBean()时创建对象 --
bean classcom.atguigu.bean.User scopeprototype/bean④测试
Test
public void testBeanScope(){ApplicationContext ac new ClassPathXmlApplicationContext(spring-scope.xml);User user1 ac.getBean(User.class);User user2 ac.getBean(User.class);System.out.println(user1user2);
}2.2.12、实验十二bean的生命周期
①具体的生命周期过程
bean对象创建调用无参构造器给bean对象设置属性bean对象初始化之前操作由bean的后置处理器负责bean对象初始化需在配置bean时指定初始化方法bean对象初始化之后操作由bean的后置处理器负责bean对象就绪可以使用bean对象销毁需在配置bean时指定销毁方法IOC容器关闭
②修改类User
public class User {private Integer id;private String username;private String password;private Integer age;public User() {System.out.println(生命周期1、创建对象);}public User(Integer id, String username, String password, Integer age) {this.id id;this.username username;this.password password;this.age age;}public Integer getId() {return id;}public void setId(Integer id) {System.out.println(生命周期2、依赖注入);this.id id;}public String getUsername() {return username;}public void setUsername(String username) {this.username username;}public String getPassword() {return password;}public void setPassword(String password) {this.password password;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age age;}public void initMethod(){System.out.println(生命周期3、初始化);}public void destroyMethod(){System.out.println(生命周期5、销毁);}Overridepublic String toString() {return User{ id id , username username \ , password password \ , age age };}
}注意其中的initMethod()和destroyMethod()可以通过配置bean指定为初始化和销毁的方法 ③配置bean
!-- 使用init-method属性指定初始化方法 --
!-- 使用destroy-method属性指定销毁方法 --
bean classcom.atguigu.bean.User scopeprototype init-methodinitMethoddestroy-methoddestroyMethodproperty nameid value1001/propertyproperty nameusername valueadmin/propertyproperty namepassword value123456/propertyproperty nameage value23/property
/bean④测试
Test
public void testLife(){ClassPathXmlApplicationContext ac newClassPathXmlApplicationContext(spring-lifecycle.xml);User bean ac.getBean(User.class);System.out.println(生命周期4、通过IOC容器获取bean并使用);ac.close();
}⑤bean的后置处理器
bean的后置处理器会在生命周期的初始化前后添加额外的操作需要实现BeanPostProcessor接口
且配置到IOC容器中需要注意的是bean后置处理器不是单独针对某一个bean生效而是针对IOC容器中所有bean都会执行
创建bean的后置处理器
package com.atguigu.spring.process;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanProcessor implements BeanPostProcessor {Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException {System.out.println(☆☆☆ beanName bean);return bean;}Overridepublic Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException {System.out.println(★★★ beanName bean);return bean;}
}在IOC容器中配置后置处理器 bean idmyBeanProcessorclass“com.atguigu.spring.process.MyBeanProcessor”/ 2.2.13、实验十三FactoryBean
①简介
FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同配置一个
FactoryBean类型的bean在获取bean的时候得到的并不是class属性中配置的这个类的对象而是
getObject()方法的返回值。通过这种机制Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来只把最简洁的使用界面展示给我们。
将来我们整合Mybatis时Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory;
import org.springframework.lang.Nullable;
/**
* Interface to be implemented by objects used within a {link BeanFactory}
which
* are themselves factories for individual objects. If a bean implements this
* interface, it is used as a factory for an object to expose, not directly as a
* bean instance that will be exposed itself.
*
* pbNB: A bean that implements this interface cannot be used as a normal
bean./b
* A FactoryBean is defined in a bean style, but the object exposed for bean
* references ({link #getObject()}) is always the object that it creates.
*
* pFactoryBeans can support singletons and prototypes, and can either create
* objects lazily on demand or eagerly on startup. The {link SmartFactoryBean}
* interface allows for exposing more fine-grained behavioral metadata.
*
* pThis interface is heavily used within the framework itself, for example
for
* the AOP {link org.springframework.aop.framework.ProxyFactoryBean} or the
* {link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for
* custom components as well; however, this is only common for infrastructure
code.
*
* pb{code FactoryBean} is a programmatic contract. Implementations are not
* supposed to rely on annotation-driven injection or other reflective
facilities./b
* {link #getObjectType()} {link #getObject()} invocations may arrive early in
the
* bootstrap process, even ahead of any post-processor setup. If you need access
to
* other beans, implement {link BeanFactoryAware} and obtain them
programmatically.
*
* pbThe container is only responsible for managing the lifecycle of the
FactoryBean
* instance, not the lifecycle of the objects created by the FactoryBean./b
Therefore,
* a destroy method on an exposed bean object (such as {link
java.io.Closeable#close()}
* will inot/i be called automatically. Instead, a FactoryBean should
implement
* {link DisposableBean} and delegate any such close call to the underlying
object.
*
* pFinally, FactoryBean objects participate in the containing BeanFactorys
* synchronization of bean creation. There is usually no need for internal
* synchronization other than for purposes of lazy initialization within the
* FactoryBean itself (or the like).
*
* author Rod Johnson
* author Juergen Hoeller
* since 08.03.2003
* param T the bean type
* see org.springframework.beans.factory.BeanFactory
* see org.springframework.aop.framework.ProxyFactoryBean
* see org.springframework.jndi.JndiObjectFactoryBean
*/
public interface FactoryBeanT {/**
* The name of an attribute that can be
* {link org.springframework.core.AttributeAccessor#setAttribute set} on a
* {link org.springframework.beans.factory.config.BeanDefinition} so that
* factory beans can signal their object type when it cant be deduced from
* the factory bean class.
* since 5.2
*/String OBJECT_TYPE_ATTRIBUTE factoryBeanObjectType;/**
* Return an instance (possibly shared or independent) of the object
* managed by this factory.
* pAs with a {link BeanFactory}, this allows support for both the
* Singleton and Prototype design pattern.
* pIf this FactoryBean is not fully initialized yet at the time of
* the call (for example because it is involved in a circular reference),
* throw a corresponding {link FactoryBeanNotInitializedException}.
* pAs of Spring 2.0, FactoryBeans are allowed to return {code null}
* objects. The factory will consider this as normal value to be used; it
* will not throw a FactoryBeanNotInitializedException in this case anymore.
* FactoryBean implementations are encouraged to throw
* FactoryBeanNotInitializedException themselves now, as appropriate.
* return an instance of the bean (can be {code null})
* throws Exception in case of creation errors
* see FactoryBeanNotInitializedException
*/NullableT getObject() throws Exception;/**
* Return the type of object that this FactoryBean creates,
* or {code null} if not known in advance.
* pThis allows one to check for specific types of beans without
* instantiating objects, for example on autowiring.
* pIn the case of implementations that are creating a singleton object,
* this method should try to avoid singleton creation as far as possible;
* it should rather estimate the type in advance.
* For prototypes, returning a meaningful type here is advisable too.
* pThis method can be called ibefore/i this FactoryBean has
* been fully initialized. It must not rely on state created during
* initialization; of course, it can still use such state if available.
* pbNOTE:/b Autowiring will simply ignore FactoryBeans that return
* {code null} here. Therefore it is highly recommended to implement
* this method properly, using the current state of the FactoryBean.
* return the type of object that this FactoryBean creates,
* or {code null} if not known at the time of the call
* see ListableBeanFactory#getBeansOfType
*/NullableClass? getObjectType();/**
* Is the object managed by this factory a singleton? That is,
* will {link #getObject()} always return the same object
* (a reference that can be cached)?
* pbNOTE:/b If a FactoryBean indicates to hold a singleton object,
* the object returned from {code getObject()} might get cached
* by the owning BeanFactory. Hence, do not return {code true}
* unless the FactoryBean always exposes the same reference.
* pThe singleton status of the FactoryBean itself will generally
* be provided by the owning BeanFactory; usually, it has to be
* defined as singleton there.
* pbNOTE:/b This method returning {code false} does not
* necessarily indicate that returned objects are independent instances.
* An implementation of the extended {link SmartFactoryBean} interface
* may explicitly indicate independent instances through its
* {link SmartFactoryBean#isPrototype()} method. Plain {link FactoryBean}
* implementations which do not implement this extended interface are
* simply assumed to always return independent instances if the
* {code isSingleton()} implementation returns {code false}.
* pThe default implementation returns {code true}, since a
* {code FactoryBean} typically manages a singleton instance.
* return whether the exposed object is a singleton
* see #getObject()
* see SmartFactoryBean#isPrototype()
*/default boolean isSingleton() {return true;}
}②创建类UserFactoryBean
public class UserFactoryBean implements FactoryBeanUser {Overridepublic User getObject() throws Exception {return new User();}Overridepublic Class? getObjectType() {return User.class;}
}③配置bean
bean iduser classcom.atguigu.bean.UserFactoryBean/bean④测试
Test
public void testUserFactoryBean(){//获取IOC容器ApplicationContext ac new ClassPathXmlApplicationContext(springfactorybean.xml);User user (User) ac.getBean(user);System.out.println(user);
}2.2.14、实验十四基于xml的自动装配 自动装配 根据指定的策略在IOC容器中匹配某一个bean自动为指定的bean中所依赖的类类型或接口类 型属性赋值 ①场景模拟
创建类UserController
public class UserController {private UserService userService;public void setUserService(UserService userService) {this.userService userService;}public void saveUser(){userService.saveUser();}
}创建接口UserService
public interface UserService {void saveUser();
}创建类UserServiceImpl实现接口UserService
public class UserServiceImpl implements UserService {private UserDao userDao;public void setUserDao(UserDao userDao) {this.userDao userDao;}Overridepublic void saveUser() {userDao.saveUser();}
}创建接口UserDao
public interface UserDao {void saveUser();
}创建类UserDaoImpl实现接口UserDao
public class UserDaoImpl implements UserDao {Overridepublic void saveUser() {System.out.println(保存成功);}
}②配置bean 使用bean标签的autowire属性设置自动装配效果 自动装配方式byType byType根据类型匹配IOC容器中的某个兼容类型的bean为属性自动赋值 若在IOC中没有任何一个兼容类型的bean能够为属性赋值则该属性不装配即值为默认值 null 若在IOC中有多个兼容类型的bean能够为属性赋值则抛出异常 NoUniqueBeanDefinitionException bean iduserControllerclasscom.atguigu.autowire.xml.controller.UserController autowirebyType
/bean
bean iduserServiceclasscom.atguigu.autowire.xml.service.impl.UserServiceImpl autowirebyType
/bean
bean iduserDao classcom.atguigu.autowire.xml.dao.impl.UserDaoImpl/bean自动装配方式byName byName将自动装配的属性的属性名作为bean的id在IOC容器中匹配相对应的bean进行赋值 bean iduserControllerclasscom.atguigu.autowire.xml.controller.UserController autowirebyName
/bean
bean iduserServiceclasscom.atguigu.autowire.xml.service.impl.UserServiceImpl autowirebyName
/bean
bean iduserServiceImplclasscom.atguigu.autowire.xml.service.impl.UserServiceImpl autowirebyName
/bean
bean iduserDao classcom.atguigu.autowire.xml.dao.impl.UserDaoImpl
/bean
bean iduserDaoImpl classcom.atguigu.autowire.xml.dao.impl.UserDaoImpl
/bean③测试
Test
public void testAutoWireByXML(){ApplicationContext ac new ClassPathXmlApplicationContext(autowire-xml.xml);UserController userController ac.getBean(UserController.class);userController.saveUser();
}2.3、基于注解管理bean
2.3.1、实验一标记与扫描
①注解
和 XML 配置文件一样注解本身并不能执行注解本身仅仅只是做一个标记具体的功能是框架检测
到注解标记的位置然后针对这个位置按照注解标记的功能来执行具体操作。
本质上所有一切的操作都是Java代码来完成的XML和注解只是告诉框架中的Java代码如何执行。
举例元旦联欢会要布置教室蓝色的地方贴上元旦快乐四个字红色的地方贴上拉花黄色的地方贴上气球。 班长做了所有标记同学们来完成具体工作。墙上的标记相当于我们在代码中使用的注解后面同学们做的工作相当于框架的具体操作。
②扫描
Spring 为了知道程序员在哪些地方标记了什么注解就需要通过扫描的方式来进行检测。然后根据注解进行后续操作。
③新建Maven Module
dependencies!-- 基于Maven依赖传递性导入spring-context依赖即可导入当前所需所有jar包 --dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.3.1/version/dependency!-- junit测试 --dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/versionscopetest/scope/dependency
/dependencies④创建Spring配置文件 ⑤标识组件的常用注解 Component将类标识为普通组件 Controller将类标识为控制层组件 Service将类标 识为业务层组件 Repository将类标识为持久层组件 问以上四个注解有什么关系和区别 通过查看源码我们得知Controller、Service、Repository这三个注解只是在Component注解的基础上起了三个新的名字。
对于Spring使用IOC容器管理这些组件来说没有区别。所以Controller、Service、Repository这
三个注解只是给开发人员看的让我们能够便于分辨组件的作用。
注意虽然它们本质上一样但是为了代码的可读性为了程序结构严谨我们肯定不能随便胡乱标记。
⑥创建组件
创建控制层组件
Controller
public class UserController {
}创建接口UserService
public interface UserService {
}创建业务层组件UserServiceImpl
Service
public class UserServiceImpl implements UserService {
}创建接口UserDao
public interface UserDao {
}创建持久层组件UserDaoImpl
Repository
public class UserDaoImpl implements UserDao {
}⑦扫描组件
情况一最基本的扫描方式
context:component-scan base-packagecom.atguigu
/context:component-scan情况二指定要排除的组件
context:component-scan base-packagecom.atguigu!-- context:exclude-filter标签指定排除规则 --!--type设置排除或包含的依据typeannotation根据注解排除expression中设置要排除的注解的全类名typeassignable根据类型排除expression中设置要排除的类型的全类名--context:exclude-filter typeannotationexpressionorg.springframework.stereotype.Controller/!--context:exclude-filter typeassignableexpressioncom.atguigu.controller.UserController/--
/context:component-scan情况三仅扫描指定组件
context:component-scan base-packagecom.atguigu use-default-filtersfalse!-- context:include-filter标签指定在原有扫描规则的基础上追加的规则 --!-- use-default-filters属性取值false表示关闭默认扫描规则 --!-- 此时必须设置use-default-filtersfalse因为默认规则即扫描指定包下所有类 --!--type设置排除或包含的依据typeannotation根据注解排除expression中设置要排除的注解的全类名typeassignable根据类型排除expression中设置要排除的类型的全类名--context:include-filter typeannotationexpressionorg.springframework.stereotype.Controller/!--context:include-filter typeassignableexpressioncom.atguigu.controller.UserController/--
/context:component-scan⑧测试
Test
public void testAutowireByAnnotation(){ApplicationContext ac newClassPathXmlApplicationContext(applicationContext.xml);UserController userController ac.getBean(UserController.class);System.out.println(userController);UserService userService ac.getBean(UserService.class);System.out.println(userService);UserDao userDao ac.getBean(UserDao.class);System.out.println(userDao);
}⑨组件所对应的bean的id
在我们使用XML方式管理bean的时候每个bean都有一个唯一标识便于在其他地方引用。现在使用
注解后每个组件仍然应该有一个唯一标识。 默认情况 类名首字母小写就是bean的id。例如UserController类对应的bean的id就是userController。 自定义bean的id 可通过标识组件的注解的value属性设置自定义的bean的id Service(“userService”)//默认为userServiceImpl public class UserServiceImpl implements UserService {} 2.3.2、实验二基于注解的自动装配
①场景模拟 参考基于xml的自动装配 在UserController中声明UserService对象 在UserServiceImpl中声明UserDao对象 ②Autowired注解
在成员变量上直接标记Autowired注解即可完成自动装配不需要提供setXxx()方法。以后我们在项
目中的正式用法就是这样。
Controller
public class UserController {Autowiredprivate UserService userService;public void saveUser(){userService.saveUser();}
}public interface UserService {void saveUser();
}
Service
public class UserServiceImpl implements UserService {Autowiredprivate UserDao userDao;Overridepublic void saveUser() {userDao.saveUser();}
}public interface UserDao {void saveUser();
} Repository
public class UserDaoImpl implements UserDao {Overridepublic void saveUser() {System.out.println(保存成功);}
}③Autowired注解其他细节 Autowired注解可以标记在构造器和set方法上 Controller
public class UserController {private UserService userService;Autowiredpublic UserController(UserService userService){this.userService userService;}public void saveUser(){userService.saveUser();}
}Controller
public class UserController {private UserService userService;Autowiredpublic void setUserService(UserService userService){this.userService userService;}public void saveUser(){userService.saveUser();}
}④Autowired工作流程 首先根据所需要的组件类型到IOC容器中查找 能够找到唯一的bean直接执行装配如果完全找不到匹配这个类型的bean装配失败和所需类型匹配的bean不止一个 没有Qualifier注解根据Autowired标记位置成员变量的变量名作为bean的id进行匹配能够找到执行装配找不到装配失败使用Qualifier注解根据Qualifier注解中指定的名称作为bean的id进行匹配能够找到执行装配找不到装配失败
Controller
public class UserController {AutowiredQualifier(userServiceImpl)private UserService userService;public void saveUser(){userService.saveUser();}
}Autowired中有属性required默认值为true因此在自动装配无法找到相应的bean时会装 配失败 可以将属性required的值设置为true则表示能装就装装不上就不装此时自动装配的属性为 默认值 但是实际开发时基本上所有需要装配组件的地方都是必须装配的用不上这个属性。 3、AOP
3.1、场景模拟
3.1.1、声明接口
声明计算器接口Calculator包含加减乘除的抽象方法
public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}3.1.2、创建实现类 public class CalculatorPureImpl implements Calculator {Overridepublic int add(int i, int j) {int result i j;System.out.println(方法内部 result result);return result;}Overridepublic int sub(int i, int j) {int result i - j;System.out.println(方法内部 result result);return result;}Overridepublic int mul(int i, int j) {int result i * j;System.out.println(方法内部 result result);return result;}Overridepublic int div(int i, int j) {int result i / j;System.out.println(方法内部 result result);return result;}
}3.1.3、创建带日志功能的实现类 public class CalculatorLogImpl implements Calculator {Overridepublic int add(int i, int j) {System.out.println([日志] add 方法开始了参数是 i , j);int result i j;System.out.println(方法内部 result result);System.out.println([日志] add 方法结束了结果是 result);return result;}Overridepublic int sub(int i, int j) {System.out.println([日志] sub 方法开始了参数是 i , j);int result i - j;System.out.println(方法内部 result result);System.out.println([日志] sub 方法结束了结果是 result);return result;}Overridepublic int mul(int i, int j) {System.out.println([日志] mul 方法开始了参数是 i , j);int result i * j;System.out.println(方法内部 result result);System.out.println([日志] mul 方法结束了结果是 result);return result;}Overridepublic int div(int i, int j) {System.out.println([日志] div 方法开始了参数是 i , j);int result i / j;System.out.println(方法内部 result result);System.out.println([日志] div 方法结束了结果是 result);return result;}
}3.1.4、提出问题
①现有代码缺陷
针对带日志功能的实现类我们发现有如下缺陷
对核心业务功能有干扰导致程序员在开发核心业务功能时分散了精力附加功能分散在各个业务功能方法中不利于统一维护
②解决思路
解决这两个问题核心就是解耦。我们需要把附加功能从业务功能代码中抽取出来。
③困难
解决问题的困难要抽取的代码在方法内部靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。
3.2、代理模式
3.2.1、概念
①介绍
二十三种设计模式中的一种属于结构型模式。它的作用就是通过提供一个代理类让我们在调用目标方法的时候不再是直接对目标方法进行调用而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法减少对目标方法的调用和打扰同时让附加功能能够集中在一起也有利于统一维护。 使用代理后
②生活中的代理
广告商找大明星拍广告需要经过经纪人合作伙伴找大老板谈合作要约见面时间需要经过秘书房产中介是买卖双方的代理
③相关术语
代理将非核心逻辑剥离出来以后封装这些非核心逻辑的类、对象、方法。目标被代理“套用”了非核心逻辑代码的类、对象、方法。
3.2.2、静态代理
创建静态代理类
public class CalculatorStaticProxy implements Calculator {// 将被代理的目标对象声明为成员变量private Calculator target;public CalculatorStaticProxy(Calculator target) {this.target target;}Overridepublic int add(int i, int j) {// 附加功能由代理类中的代理方法来实现System.out.println([日志] add 方法开始了参数是 i , j);// 通过目标对象来实现核心业务逻辑int addResult target.add(i, j);System.out.println([日志] add 方法结束了结果是 addResult);return addResult;}
}静态代理确实实现了解耦但是由于代码都写死了完全不具备任何的灵活性。就拿日志功能来 说将来其他地方也需要附加日志那还得再声明更多个静态代理类那就产生了大量重复的代 码日志功能还是分散的没有统一管理。 提出进一步的需求将日志功能集中到一个代理类中将来有任何日志需求都通过这一个代理 类来实现。这就需要使用动态代理技术了。 3.2.3、动态代理 生产代理对象的工厂类
public class ProxyFactory {private Object target;public ProxyFactory(Object target) {this.target target;}public Object getProxy(){/*** newProxyInstance()创建一个代理实例* 其中有三个参数* 1、classLoader加载动态生成的代理类的类加载器* 2、interfaces目标对象实现的所有接口的class对象所组成的数组* 3、invocationHandler设置代理对象实现目标对象方法的过程即代理类中如何重写接口中的抽象方法*/ClassLoader classLoader target.getClass().getClassLoader();Class?[] interfaces target.getClass().getInterfaces();InvocationHandler invocationHandler new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {/*** proxy代理对象* method代理对象需要实现的方法即其中需要重写的方法* argsmethod所对应方法的参数*/Object result null;try {System.out.println([动态代理][日志] method.getName()参数 Arrays.toString(args));result method.invoke(target, args);System.out.println([动态代理][日志] method.getName()结 果 result);} catch (Exception e) {e.printStackTrace();System.out.println([动态代理][日志] method.getName()异常e.getMessage());} finally {System.out.println([动态代理][日志] method.getName()方法执行完毕);}return result;} }; return Proxy.newProxyInstance(classLoader, interfaces,invocationHandler);}
} 3.2.4、测试
Test
public void testDynamicProxy(){ProxyFactory factory new ProxyFactory(new CalculatorLogImpl());Calculator proxy (Calculator) factory.getProxy();proxy.div(1,0);//proxy.div(1,1);
}3.3、AOP概念及相关术语
3.3.1、概述
AOPAspect Oriented Programming是一种设计思想是软件设计领域中的面向切面编程它是面向对象编程的一种补充和完善它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。
3.3.2、相关术语
①横切关注点
从每个方法中抽取出来的同一类非核心业务。在同一个项目中我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面天然存在的而是根据附加功能的逻辑上的需要有十个附加功能就有十个横切关注点。
②通知
每一个横切关注点上要做的事情都需要写一个方法来实现这样的方法就叫通知方法。
前置通知在被代理的目标方法前执行返回通知在被代理的目标方法成功结束后执行寿终正寝异常通知在被代理的目标方法异常结束后执行死于非命后置通知在被代理的目标方法最终结束后执行盖棺定论环绕通知使用try…catch…finally结构围绕整个被代理的目标方法包括上面四种通知对应的所
③切面 ④目标
被代理的目标对象。
⑤代理
向目标对象应用通知之后创建的代理对象。
⑥连接点
这也是一个纯逻辑概念不是语法定义的。
把方法排成一排每一个横切位置看成x轴方向把方法从上到下执行的顺序看成y轴x轴和y轴的交叉点就是连接点。
⑦切入点
定位连接点的方式。
每个类的方法中都包含多个连接点所以连接点是类中客观存在的事物从逻辑上来说。
如果把连接点看作数据库中的记录那么切入点就是查询记录的 SQL 语句。
Spring 的 AOP 技术可以通过切入点定位到特定的连接点。
切点通过 org.springframework.aop.Pointcut 接口进行描述它使用类和方法作为连接点的查询条
件。
3.3.3、作用 简化代码把方法中固定位置的重复的代码抽取出来让被抽取的方法更专注于自己的核心功能提高内聚性。 代码增强把特定的功能封装到切面类中看哪里有需要就往上套被套用了切面逻辑的方法就被切面给增强了。
3.4、基于注解的AOP
3.4.1、技术说明 动态代理InvocationHandlerJDK原生的实现方式需要被代理的目标类必须实现接口。因
为这个技术要求代理对象和目标对象实现同样的接口兄弟两个拜把子模式。 cglib通过继承被代理的目标类认干爹模式实现代理所以不需要目标类实现接口。 AspectJ本质上是静态代理将代理逻辑“织入”被代理的目标类编译得到的字节码文件所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
3.4.2、准备工作
①添加依赖
在IOC所需依赖基础上再加入下面依赖即可
!-- spring-aspects会帮我们传递过来aspectjweaver --
dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion5.3.1/version
/dependency②准备被代理的目标资源
接口
public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}实现类
Component
public class CalculatorPureImpl implements Calculator {Overridepublic int add(int i, int j) {int result i j;System.out.println(方法内部 result result);return result;}Overridepublic int sub(int i, int j) {int result i - j;System.out.println(方法内部 result result);return result;}Overridepublic int mul(int i, int j) {int result i * j;System.out.println(方法内部 result result);return result;}Overridepublic int div(int i, int j) {int result i / j;System.out.println(方法内部 result result);return result;}
}3.4.3、创建切面类并配置
// Aspect表示这个类是一个切面类
Aspect
// Component注解保证这个切面类能够放入IOC容器
Component
public class LogAspect {Before(execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..)))
public void beforeMethod(JoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());System.out.println(Logger--前置通知方法名methodName参数args);}After(execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..)))public void afterMethod(JoinPoint joinPoint){String methodName joinPoint.getSignature().getName();System.out.println(Logger--后置通知方法名methodName);}AfterReturning(value execution(*com.atguigu.aop.annotation.CalculatorImpl.*(..)), returning result)public void afterReturningMethod(JoinPoint joinPoint, Object result){String methodName joinPoint.getSignature().getName();System.out.println(Logger--返回通知方法名methodName结果result);} AfterThrowing(value execution(*com.atguigu.aop.annotation.CalculatorImpl.*(..)), throwing ex)public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){String methodName joinPoint.getSignature().getName();System.out.println(Logger--异常通知方法名methodName异常ex);}Around(execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..)))public Object aroundMethod(ProceedingJoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());Object result null;try {System.out.println(环绕通知--目标对象方法执行之前);//目标对象连接点方法的执行result joinPoint.proceed();System.out.println(环绕通知--目标对象方法返回值之后);} catch (Throwable throwable) {throwable.printStackTrace();System.out.println(环绕通知--目标对象方法出现异常时);} finally {System.out.println(环绕通知--目标对象方法执行完毕);}return result;}
}在Spring的配置文件中配置 !--基于注解的AOP的实现1、将目标对象和切面交给IOC容器管理注解扫描2、开启AspectJ的自动代理为目标对象自动生成代理3、将切面类通过注解Aspect标识--context:component-scan base-packagecom.atguigu.aop.annotation
/context:component-scanaop:aspectj-autoproxy /3.4.4、各种通知
前置通知使用Before注解标识在被代理的目标方法前执行返回通知使用AfterReturning注解标识在被代理的目标方法成功结束后执行寿终正寝异常通知使用AfterThrowing注解标识在被代理的目标方法异常结束后执行死于非命后置通知使用After注解标识在被代理的目标方法最终结束后执行盖棺定论环绕通知使用Around注解标识使用try…catch…finally结构围绕整个被代理的目标方法包
括上面四种通知对应的所有位置 各种通知的执行顺序 Spring版本5.3.x以前 前置通知目标操作后置通知返回通知或异常通知 Spring版本5.3.x以后 前置通知目标操作返回通知或异常通知后置通知 3.4.5、切入点表达式语法
①作用 ②语法细节
用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限在包名的部分一个“”号只能代表包的层次结构中的一层表示这一层是任意的。 例如.Hello匹配com.Hello不匹配com.atguigu.Hello 在包名的部分使用“…”表示包名任意、包的层次深度任意在类名的部分类名部分整体用号代替表示类名任意在类名的部分可以使用号代替类名的一部分 *例如*Service匹配所有名称以Service结尾的类或接口 在方法名部分可以使用号表示方法名任意在方法名部分可以使用号代替方法名的一部分 例如*Operation匹配所有方法名以Operation结尾的方法 在方法参数列表部分使用(…)表示参数列表任意在方法参数列表部分使用(int,…)表示参数列表以一个int类型的参数开头在方法参数列表部分基本数据类型和对应的包装类型是不一样的 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的 在方法返回值部分如果想要明确指定一个返回值类型那么必须同时写明权限修饰符 例如execution(public int …Service.(…, int)) 正确例如execution( int *…Service.(…, int)) 错误 3.4.6、重用切入点表达式
①声明
Pointcut(execution(* com.atguigu.aop.annotation.*.*(..)))
public void pointCut(){}②在同一个切面中使用
Before(pointCut())
public void beforeMethod(JoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());System.out.println(Logger--前置通知方法名methodName参数args);
}③在不同切面中使用
Before(com.atguigu.aop.CommonPointCut.pointCut())
public void beforeMethod(JoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());System.out.println(Logger--前置通知方法名methodName参数args);
}3.4.7、获取通知的相关信息
①获取连接点信息
获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参
Before(execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..)))
public void beforeMethod(JoinPoint joinPoint){//获取连接点的签名信息String methodName joinPoint.getSignature().getName();//获取目标方法到的实参信息String args Arrays.toString(joinPoint.getArgs());System.out.println(Logger--前置通知方法名methodName参数args);
}②获取目标方法的返回值
AfterReturning中的属性returning用来将通知方法的某个形参接收目标方法的返回值
AfterReturning(value execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..)), returning result)public void afterReturningMethod(JoinPoint joinPoint, Object result){String methodName joinPoint.getSignature().getName();System.out.println(Logger--返回通知方法名methodName结果result);
}③获取目标方法的异常
AfterThrowing中的属性throwing用来将通知方法的某个形参接收目标方法的异常
AfterThrowing(value execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..)), throwing ex)public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){String methodName joinPoint.getSignature().getName();System.out.println(Logger--异常通知方法名methodName异常ex);
}3.4.8、环绕通知
Around(execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..)))
public Object aroundMethod(ProceedingJoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());Object result null;try {System.out.println(环绕通知--目标对象方法执行之前);//目标方法的执行目标方法的返回值一定要返回给外界调用者result joinPoint.proceed();System.out.println(环绕通知--目标对象方法返回值之后);} catch (Throwable throwable) {throwable.printStackTrace();System.out.println(环绕通知--目标对象方法出现异常时);} finally {System.out.println(环绕通知--目标对象方法执行完毕);}return result;
}3.4.9、切面的优先级
相同目标方法上同时存在多个切面时切面的优先级控制切面的内外嵌套顺序。
优先级高的切面外面优先级低的切面里面
使用Order注解可以控制切面的优先级
Order(较小的数)优先级高Order(较大的数)优先级低 3.5基于XML的AOP了解
3.5.1、准备工作
参考基于注解的AOP环境
3.5.2、实现
context:component-scan base-packagecom.atguigu.aop.xml/context:componentscan
aop:config!--配置切面类--aop:aspect refloggerAspectaop:pointcut idpointCut expressionexecution(*com.atguigu.aop.xml.CalculatorImpl.*(..))/aop:before methodbeforeMethod pointcut-refpointCut/aop:beforeaop:after methodafterMethod pointcut-refpointCut/aop:afteraop:after-returning methodafterReturningMethod returningresultpointcut-refpointCut/aop:after-returningaop:after-throwing methodafterThrowingMethod throwingex pointcut-refpointCut/aop:after-throwingaop:around methodaroundMethod pointcut-refpointCut/aop:around/aop:aspectaop:aspect refvalidateAspect order1aop:before methodvalidateBeforeMethod pointcut-refpointCut/aop:before/aop:aspect
/aop:config4、声明式事务
4.1、JdbcTemplate
4.1.1、简介
Spring 框架对 JDBC 进行封装使用 JdbcTemplate 方便实现对数据库操作
4.1.2、准备工作
①加入依赖
dependencies!-- 基于Maven依赖传递性导入spring-context依赖即可导入当前所需所有jar包 --dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.3.1/version/dependency!-- Spring 持久化层支持jar包 --!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中需要使用orm、jdbc、tx三个jar包 --!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 --dependencygroupIdorg.springframework/groupIdartifactIdspring-orm/artifactIdversion5.3.1/version/dependency!-- Spring 测试相关 --dependencygroupIdorg.springframework/groupIdartifactIdspring-test/artifactIdversion5.3.1/version/dependency!-- junit测试 --dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/versionscopetest/scope/dependency!-- MySQL驱动 --dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.16/version/dependency!-- 数据源 --dependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.0.31/version/dependency
/dependencies②创建jdbc.properties
jdbc.userroot
jdbc.passwordatguigu
jdbc.urljdbc:mysql://localhost:3306/ssm
jdbc.drivercom.mysql.cj.jdbc.Driver③配置Spring的配置文件
!-- 导入外部属性文件 --
context:property-placeholder locationclasspath:jdbc.properties /
!-- 配置数据源 --
bean iddruidDataSource classcom.alibaba.druid.pool.DruidDataSourceproperty nameurl value${atguigu.url}/property namedriverClassName value${atguigu.driver}/property nameusername value${atguigu.username}/property namepassword value${atguigu.password}/
/bean
!-- 配置 JdbcTemplate --
bean idjdbcTemplate classorg.springframework.jdbc.core.JdbcTemplate!-- 装配数据源 --property namedataSource refdruidDataSource/
/bean4.1.3、测试
①在测试类装配 JdbcTemplate
RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(classpath:spring-jdbc.xml)
public class JDBCTemplateTest {Autowiredprivate JdbcTemplate jdbcTemplate;
}②测试增删改功能
Test
//测试增删改功能
public void testUpdate(){String sql insert into t_emp values(null,?,?,?);int result jdbcTemplate.update(sql, 张三, 23, 男);System.out.println(result);
}③查询一条数据为实体类对象
Test
//查询一条数据为一个实体类对象
public void testSelectEmpById(){String sql select * from t_emp where id ?;Emp emp jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(Emp.class), 1);System.out.println(emp);
}④查询多条数据为一个list集合
Test
//查询多条数据为一个list集合
public void testSelectList(){String sql select * from t_emp;ListEmp list jdbcTemplate.query(sql, new BeanPropertyRowMapper(Emp.class));list.forEach(emp - System.out.println(emp));
}⑤查询单行单列的值
Test
//查询单行单列的值
public void selectCount(){String sql select count(id) from t_emp;Integer count jdbcTemplate.queryForObject(sql, Integer.class);System.out.println(count);
}4.2、声明式事务概念
4.2.1、编程式事务
事务功能的相关操作全部通过自己编写代码来实现
Connection conn ...;
try {// 开启事务关闭事务的自动提交conn.setAutoCommit(false);// 核心操作// 提交事务conn.commit();
}catch(Exception e){// 回滚事务conn.rollBack();
}finally{// 释放数据库连接conn.close();
}编程式的实现方式存在缺陷
细节没有被屏蔽具体操作过程中所有细节都需要程序员自己来完成比较繁琐。代码复用性不高如果没有有效抽取出来每次实现功能都需要自己编写代码代码就没有得到复用。
4.2.2、声明式事务
既然事务控制的代码有规律可循代码的结构基本是确定的所以框架就可以将固定模式的代码抽取出来进行相关的封装。
封装起来后我们只需要在配置文件中进行简单的配置即可完成操作。
好处1提高开发效率好处2消除了冗余的代码好处3框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题进行了健壮性、性
能等各个方面的优化
所以我们可以总结下面两个概念
编程式自己写代码实现功能声明式通过配置让框架实现功能
4.3、基于注解的声明式事务
4.3.1、准备工作
①加入依赖
dependencies!-- 基于Maven依赖传递性导入spring-context依赖即可导入当前所需所有jar包 --dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.3.1/version/dependency!-- Spring 持久化层支持jar包 --!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中需要使用orm、jdbc、tx三个jar包 --!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 --dependencygroupIdorg.springframework/groupIdartifactIdspring-orm/artifactIdversion5.3.1/version/dependency!-- Spring 测试相关 --dependencygroupIdorg.springframework/groupIdartifactIdspring-test/artifactIdversion5.3.1/version/dependency!-- junit测试 --dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/versionscopetest/scope/dependency!-- MySQL驱动 --dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.16/version/dependency!-- 数据源 --dependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.0.31/version/dependency
/dependencies②创建jdbc.properties
jdbc.userroot
jdbc.passwordatguigu
jdbc.urljdbc:mysql://localhost:3306/ssm?serverTimezoneUTC
jdbc.drivercom.mysql.cj.jdbc.Driver③配置Spring的配置文件
!--扫描组件--
context:component-scan base-packagecom.atguigu.spring.tx.annotation
/context:component-scan
!-- 导入外部属性文件 --
context:property-placeholder locationclasspath:jdbc.properties /
!-- 配置数据源 --
bean iddruidDataSource classcom.alibaba.druid.pool.DruidDataSourceproperty nameurl value${jdbc.url}/property namedriverClassName value${jdbc.driver}/property nameusername value${jdbc.username}/property namepassword value${jdbc.password}/
/bean
!-- 配置 JdbcTemplate --
bean idjdbcTemplate classorg.springframework.jdbc.core.JdbcTemplate!-- 装配数据源 --property namedataSource refdruidDataSource/
/bean④创建表
CREATE TABLE t_book (book_id int(11) NOT NULL AUTO_INCREMENT COMMENT 主键,book_name varchar(20) DEFAULT NULL COMMENT 图书名称,price int(11) DEFAULT NULL COMMENT 价格,stock int(10) unsigned DEFAULT NULL COMMENT 库存无符号,PRIMARY KEY (book_id)
) ENGINEInnoDB AUTO_INCREMENT3 DEFAULT CHARSETutf8;
insert into t_book(book_id,book_name,price,stock) values (1,斗破苍穹,80,100),(2,斗罗大陆,50,100);
CREATE TABLE t_user (user_id int(11) NOT NULL AUTO_INCREMENT COMMENT 主键,username varchar(20) DEFAULT NULL COMMENT 用户名,balance int(10) unsigned DEFAULT NULL COMMENT 余额无符号,PRIMARY KEY (user_id)
) ENGINEInnoDB AUTO_INCREMENT2 DEFAULT CHARSETutf8;
insert into t_user(user_id,username,balance) values (1,admin,50);⑤创建组件
创建BookController
Controller
public class BookController {Autowiredprivate BookService bookService;public void buyBook(Integer bookId, Integer userId){bookService.buyBook(bookId, userId);}
}创建接口BookService
public interface BookService {void buyBook(Integer bookId, Integer userId);
}创建实现类BookServiceImpl
Service
public class BookServiceImpl implements BookService {Autowiredprivate BookDao bookDao;Overridepublic void buyBook(Integer bookId, Integer userId) {//查询图书的价格Integer price bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);}
}创建接口BookDao
public interface BookDao {Integer getPriceByBookId(Integer bookId);void updateStock(Integer bookId);void updateBalance(Integer userId, Integer price);
}创建实现类BookDaoImpl
Repository
public class BookDaoImpl implements BookDao {Autowiredprivate JdbcTemplate jdbcTemplate;Overridepublic Integer getPriceByBookId(Integer bookId) {String sql select price from t_book where book_id ?;return jdbcTemplate.queryForObject(sql, Integer.class, bookId);}Overridepublic void updateStock(Integer bookId) {String sql update t_book set stock stock - 1 where book_id ?;jdbcTemplate.update(sql, bookId);}Overridepublic void updateBalance(Integer userId, Integer price) {String sql update t_user set balance balance - ? where user_id ?;jdbcTemplate.update(sql, price, userId);}
}4.3.2、测试无事务情况
①创建测试类
RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(classpath:tx-annotation.xml)
public class TxByAnnotationTest {Autowiredprivate BookController bookController;Testpublic void testBuyBook(){bookController.buyBook(1, 1);}
}②模拟场景
用户购买图书先查询图书的价格再更新图书的库存和用户的余额
假设用户id为1的用户购买id为1的图书
用户余额为50而图书价格为80
购买图书之后用户的余额为-30数据库中余额字段设置了无符号因此无法将-30插入到余额字段
此时执行sql语句会抛出SQLException
③观察结果
因为没有添加事务图书的库存更新了但是用户的余额没有更新
显然这样的结果是错误的购买图书是一个完整的功能更新库存和更新余额要么都成功要么都失败
4.3.3、加入事务
①添加事务配置
在Spring的配置文件中添加配置
bean idtransactionManagerclassorg.springframework.jdbc.datasource.DataSourceTransactionManagerproperty namedataSource refdataSource/property
/bean
!--开启事务的注解驱动通过注解Transactional所标识的方法或标识的类中所有的方法都会被事务管理器管理事务
--
!-- transaction-manager属性的默认值是transactionManager如果事务管理器bean的id正好就
是这个默认值则可以省略这个属性 --
tx:annotation-driven transaction-managertransactionManager /注意导入的名称空间需要 tx 结尾的那个。
②添加事务注解
因为service层表示业务逻辑层一个方法表示一个完成的功能因此处理事务一般在service层处理
在BookServiceImpl的buybook()添加注解Transactional
③观察结果
由于使用了Spring的声明式事务更新库存和更新余额都没有执行
4.3.4、Transactional注解标识的位置
Transactional标识在方法上咋只会影响该方法
Transactional标识的类上咋会影响类中所有的方法
4.3.5、事务属性只读
①介绍
对一个查询操作来说如果我们把它设置成只读就能够明确告诉数据库这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
②使用方式
Transactional(readOnly true)
public void buyBook(Integer bookId, Integer userId) {//查询图书的价格Integer price bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);//System.out.println(1/0);
}③注意
对增删改操作设置只读会抛出下面异常
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification
are not allowed
4.3.6、事务属性超时
①介绍
事务在执行过程中有可能因为遇到某些问题导致程序卡住从而长时间占用数据库资源。而长时间占用资源大概率是因为程序运行出现了问题可能是Java程序或MySQL数据库或网络连接等等。
此时这个很可能出问题的程序应该被回滚撤销它已做的操作事务结束把资源让出来让其他正常程序可以执行。
概括来说就是一句话超时回滚释放资源。
②使用方式
Transactional(timeout 3)
public void buyBook(Integer bookId, Integer userId) {try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}//查询图书的价格Integer price bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);//System.out.println(1/0);
}③观察结果
执行过程中抛出异常
org.springframework.transaction.TransactionTimedOutException: Transaction timed out:
deadline was Fri Jun 04 16:25:39 CST 2022
4.3.7、事务属性回滚策略
①介绍
声明式事务默认只针对运行时异常回滚编译时异常不回滚。
可以通过Transactional中相关属性设置回滚策略
rollbackFor属性需要设置一个Class类型的对象rollbackForClassName属性需要设置一个字符串类型的全类名noRollbackFor属性需要设置一个Class类型的对象rollbackFor属性需要设置一个字符串类型的全类名
②使用方式
Transactional(noRollbackFor ArithmeticException.class)
//Transactional(noRollbackForClassName java.lang.ArithmeticException)
public void buyBook(Integer bookId, Integer userId) {//查询图书的价格Integer price bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);System.out.println(1/0);
}③观察结果
虽然购买图书功能中出现了数学运算异常ArithmeticException但是我们设置的回滚策略是当
出现ArithmeticException不发生回滚因此购买图书的操作正常执行
4.3.8、事务属性事务隔离级别
①介绍
数据库系统必须具有隔离并发运行各个事务的能力使它们不会相互影响避免各种并发问题。一个事
务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别不同隔离级别对应不同
的干扰程度隔离级别越高数据一致性就越好但并发性越弱。
隔离级别一共有四种
读未提交READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。
读已提交READ COMMITTED、
要求Transaction01只能读取Transaction02已提交的修改。
可重复读REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值即Transaction01执行期间禁止其它
事务对这个字段进行更新。
串行化SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行在Transaction01执行期间禁止其它
事务对这个表进行添加、更新、删除操作。可以避免任何并发问题但性能十分低下。
各个隔离级别解决并发问题的能力见下表
隔离级别脏读不可重复读幻读READ UNCOMMITTED有有有READ COMMITTED无有有REPEATABLE READ无无有SERIALIZABLE无无无
各种数据库产品对事务隔离级别的支持程度
隔离级别OracleMySQLREAD UNCOMMITTED×√READ COMMITTED√(默认)√REPEATABLE READ×√(默认)SERIALIZABLE√√
②使用方式
Transactional(isolation Isolation.DEFAULT)//使用数据库默认的隔离级别
Transactional(isolation Isolation.READ_UNCOMMITTED)//读未提交
Transactional(isolation Isolation.READ_COMMITTED)//读已提交
Transactional(isolation Isolation.REPEATABLE_READ)//可重复读
Transactional(isolation Isolation.SERIALIZABLE)//串行化4.3.9、事务属性事务传播行为
①介绍
当事务方法被另一个事务方法调用时必须指定事务应该如何传播。例如方法可能继续在现有事务中运行也可能开启一个新事务并在自己的事务中运行。
②测试
创建接口CheckoutService
public interface CheckoutService {void checkout(Integer[] bookIds, Integer userId);
}创建实现类CheckoutServiceImpl
Service
public class CheckoutServiceImpl implements CheckoutService {Autowiredprivate BookService bookService;OverrideTransactional//一次购买多本图书public void checkout(Integer[] bookIds, Integer userId) {for (Integer bookId : bookIds) {bookService.buyBook(bookId, userId);}}
}在BookController中添加方法
Autowired
private CheckoutService checkoutService;
public void checkout(Integer[] bookIds, Integer userId){checkoutService.checkout(bookIds, userId);
}在数据库中将用户的余额修改为100元
③观察结果
可以通过Transactional中的propagation属性设置事务传播行为
修改BookServiceImpl中buyBook()上注解Transactional的propagation属性
Transactional(propagation Propagation.REQUIRED)默认情况表示如果当前线程上有已经开
启的事务可用那么就在这个事务中运行。经过观察购买图书的方法buyBook()在checkout()中被调
用checkout()上有事务注解因此在此事务中执行。所购买的两本图书的价格为80和50而用户的余额为100因此在购买第二本图书时余额不足失败导致整个checkout()回滚即只要有一本书买不
了就都买不了
Transactional(propagation Propagation.REQUIRES_NEW)表示不管当前线程上是否有已经开启的事务都要开启新事务。同样的场景每次购买图书都是在buyBook()的事务中执行因此第一本图书购买成功事务结束第二本图书购买失败只在第二次的buyBook()中回滚购买第一本图书不受影响即能买几本就买几本
4.4、基于XML的声明式事务
4.3.1、场景模拟
参考基于注解的声明式事务
4.3.2、修改Spring配置文件
将Spring配置文件中去掉tx:annotation-driven 标签并添加配置
aop:config!-- 配置事务通知和切入点表达式 --aop:advisor advice-reftxAdvice pointcutexecution(*com.atguigu.spring.tx.xml.service.impl.*.*(..))/aop:advisor
/aop:config
!-- tx:advice标签配置事务通知 --
!-- id属性给事务通知标签设置唯一标识便于引用 --
!-- transaction-manager属性关联事务管理器 --
tx:advice idtxAdvice transaction-managertransactionManagertx:attributes!-- tx:method标签配置具体的事务方法 --!-- name属性指定方法名可以使用星号代表多个字符 --tx:method nameget* read-onlytrue/tx:method namequery* read-onlytrue/tx:method namefind* read-onlytrue/!-- read-only属性设置只读属性 --!-- rollback-for属性设置回滚的异常 --!-- no-rollback-for属性设置不回滚的异常 --!-- isolation属性设置事务的隔离级别 --!-- timeout属性设置事务的超时属性 --!-- propagation属性设置事务的传播行为 --tx:method namesave* read-onlyfalse rollback-forjava.lang.Exception propagationREQUIRES_NEW/tx:method nameupdate* read-onlyfalse rollback-forjava.lang.Exception propagationREQUIRES_NEW/tx:method namedelete* read-onlyfalse rollback-forjava.lang.Exception propagationREQUIRES_NEW//tx:attributes
/tx:advice注意基于xml实现的声明式事务必须引入aspectJ的依赖 dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion5.3.1/version
/dependencyLE)//串行化 ### 4.3.9、事务属性事务传播行为#### ①介绍当事务方法被另一个事务方法调用时必须指定事务应该如何传播。例如方法可能继续在现有事务中运行也可能开启一个新事务并在自己的事务中运行。#### ②测试创建接口CheckoutServicejava
public interface CheckoutService {void checkout(Integer[] bookIds, Integer userId);
}创建实现类CheckoutServiceImpl
Service
public class CheckoutServiceImpl implements CheckoutService {Autowiredprivate BookService bookService;OverrideTransactional//一次购买多本图书public void checkout(Integer[] bookIds, Integer userId) {for (Integer bookId : bookIds) {bookService.buyBook(bookId, userId);}}
}在BookController中添加方法
Autowired
private CheckoutService checkoutService;
public void checkout(Integer[] bookIds, Integer userId){checkoutService.checkout(bookIds, userId);
}在数据库中将用户的余额修改为100元
③观察结果
可以通过Transactional中的propagation属性设置事务传播行为
修改BookServiceImpl中buyBook()上注解Transactional的propagation属性
Transactional(propagation Propagation.REQUIRED)默认情况表示如果当前线程上有已经开
启的事务可用那么就在这个事务中运行。经过观察购买图书的方法buyBook()在checkout()中被调
用checkout()上有事务注解因此在此事务中执行。所购买的两本图书的价格为80和50而用户的余额为100因此在购买第二本图书时余额不足失败导致整个checkout()回滚即只要有一本书买不
了就都买不了
Transactional(propagation Propagation.REQUIRES_NEW)表示不管当前线程上是否有已经开启的事务都要开启新事务。同样的场景每次购买图书都是在buyBook()的事务中执行因此第一本图书购买成功事务结束第二本图书购买失败只在第二次的buyBook()中回滚购买第一本图书不受影响即能买几本就买几本
4.4、基于XML的声明式事务
4.3.1、场景模拟
参考基于注解的声明式事务
4.3.2、修改Spring配置文件
将Spring配置文件中去掉tx:annotation-driven 标签并添加配置
aop:config!-- 配置事务通知和切入点表达式 --aop:advisor advice-reftxAdvice pointcutexecution(*com.atguigu.spring.tx.xml.service.impl.*.*(..))/aop:advisor
/aop:config
!-- tx:advice标签配置事务通知 --
!-- id属性给事务通知标签设置唯一标识便于引用 --
!-- transaction-manager属性关联事务管理器 --
tx:advice idtxAdvice transaction-managertransactionManagertx:attributes!-- tx:method标签配置具体的事务方法 --!-- name属性指定方法名可以使用星号代表多个字符 --tx:method nameget* read-onlytrue/tx:method namequery* read-onlytrue/tx:method namefind* read-onlytrue/!-- read-only属性设置只读属性 --!-- rollback-for属性设置回滚的异常 --!-- no-rollback-for属性设置不回滚的异常 --!-- isolation属性设置事务的隔离级别 --!-- timeout属性设置事务的超时属性 --!-- propagation属性设置事务的传播行为 --tx:method namesave* read-onlyfalse rollback-forjava.lang.Exception propagationREQUIRES_NEW/tx:method nameupdate* read-onlyfalse rollback-forjava.lang.Exception propagationREQUIRES_NEW/tx:method namedelete* read-onlyfalse rollback-forjava.lang.Exception propagationREQUIRES_NEW//tx:attributes
/tx:advice注意基于xml实现的声明式事务必须引入aspectJ的依赖 dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion5.3.1/version
/dependency 文章转载自: http://www.morning.mqbsm.cn.gov.cn.mqbsm.cn http://www.morning.hxbjt.cn.gov.cn.hxbjt.cn http://www.morning.xskbr.cn.gov.cn.xskbr.cn http://www.morning.kdnrc.cn.gov.cn.kdnrc.cn http://www.morning.madamli.com.gov.cn.madamli.com http://www.morning.wrysm.cn.gov.cn.wrysm.cn http://www.morning.wtsr.cn.gov.cn.wtsr.cn http://www.morning.hwljx.cn.gov.cn.hwljx.cn http://www.morning.jtybl.cn.gov.cn.jtybl.cn http://www.morning.bwttj.cn.gov.cn.bwttj.cn http://www.morning.kyfnh.cn.gov.cn.kyfnh.cn http://www.morning.bkryb.cn.gov.cn.bkryb.cn http://www.morning.knscf.cn.gov.cn.knscf.cn http://www.morning.dkzwx.cn.gov.cn.dkzwx.cn http://www.morning.rrhfy.cn.gov.cn.rrhfy.cn http://www.morning.jkfyt.cn.gov.cn.jkfyt.cn http://www.morning.fsqbx.cn.gov.cn.fsqbx.cn http://www.morning.divocn.com.gov.cn.divocn.com http://www.morning.pcxgj.cn.gov.cn.pcxgj.cn http://www.morning.jhgxh.cn.gov.cn.jhgxh.cn http://www.morning.mwcqz.cn.gov.cn.mwcqz.cn http://www.morning.bpmtz.cn.gov.cn.bpmtz.cn http://www.morning.ryfq.cn.gov.cn.ryfq.cn http://www.morning.cybch.cn.gov.cn.cybch.cn http://www.morning.kzdgz.cn.gov.cn.kzdgz.cn http://www.morning.gbwfx.cn.gov.cn.gbwfx.cn http://www.morning.rgzc.cn.gov.cn.rgzc.cn http://www.morning.qbfs.cn.gov.cn.qbfs.cn http://www.morning.blzrj.cn.gov.cn.blzrj.cn http://www.morning.ypzr.cn.gov.cn.ypzr.cn http://www.morning.xyyplp.cn.gov.cn.xyyplp.cn http://www.morning.cgmzt.cn.gov.cn.cgmzt.cn http://www.morning.rkrcd.cn.gov.cn.rkrcd.cn http://www.morning.dqwykj.com.gov.cn.dqwykj.com http://www.morning.xlclj.cn.gov.cn.xlclj.cn http://www.morning.wnrcj.cn.gov.cn.wnrcj.cn http://www.morning.fjkkx.cn.gov.cn.fjkkx.cn http://www.morning.ndxmn.cn.gov.cn.ndxmn.cn http://www.morning.kqzt.cn.gov.cn.kqzt.cn http://www.morning.qqbjt.cn.gov.cn.qqbjt.cn http://www.morning.yrdn.cn.gov.cn.yrdn.cn http://www.morning.tymnr.cn.gov.cn.tymnr.cn http://www.morning.bwxph.cn.gov.cn.bwxph.cn http://www.morning.zhffz.cn.gov.cn.zhffz.cn http://www.morning.bmts.cn.gov.cn.bmts.cn http://www.morning.yqndr.cn.gov.cn.yqndr.cn http://www.morning.kpbq.cn.gov.cn.kpbq.cn http://www.morning.qpntn.cn.gov.cn.qpntn.cn http://www.morning.wskn.cn.gov.cn.wskn.cn http://www.morning.slzkq.cn.gov.cn.slzkq.cn http://www.morning.jtkfm.cn.gov.cn.jtkfm.cn http://www.morning.rfhmb.cn.gov.cn.rfhmb.cn http://www.morning.ksggr.cn.gov.cn.ksggr.cn http://www.morning.xzjsb.cn.gov.cn.xzjsb.cn http://www.morning.cxtbh.cn.gov.cn.cxtbh.cn http://www.morning.c7491.cn.gov.cn.c7491.cn http://www.morning.djlxz.cn.gov.cn.djlxz.cn http://www.morning.ffbp.cn.gov.cn.ffbp.cn http://www.morning.kpgms.cn.gov.cn.kpgms.cn http://www.morning.yzygj.cn.gov.cn.yzygj.cn http://www.morning.xczyj.cn.gov.cn.xczyj.cn http://www.morning.ljxxl.cn.gov.cn.ljxxl.cn http://www.morning.xrhst.cn.gov.cn.xrhst.cn http://www.morning.webife.com.gov.cn.webife.com http://www.morning.lhptg.cn.gov.cn.lhptg.cn http://www.morning.nhrkc.cn.gov.cn.nhrkc.cn http://www.morning.wcrcy.cn.gov.cn.wcrcy.cn http://www.morning.nqgjn.cn.gov.cn.nqgjn.cn http://www.morning.jklns.cn.gov.cn.jklns.cn http://www.morning.rlhh.cn.gov.cn.rlhh.cn http://www.morning.mmxt.cn.gov.cn.mmxt.cn http://www.morning.jrrqs.cn.gov.cn.jrrqs.cn http://www.morning.dxpzt.cn.gov.cn.dxpzt.cn http://www.morning.smxyw.cn.gov.cn.smxyw.cn http://www.morning.bnmfq.cn.gov.cn.bnmfq.cn http://www.morning.tntbs.cn.gov.cn.tntbs.cn http://www.morning.wfqcs.cn.gov.cn.wfqcs.cn http://www.morning.tnjz.cn.gov.cn.tnjz.cn http://www.morning.sfwfk.cn.gov.cn.sfwfk.cn http://www.morning.nlnmy.cn.gov.cn.nlnmy.cn