Spring学习
第一章 Spring概述
1.1 什么是Spring
Spring是2003年兴起的一个轻量级的Java开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring的核心是IOC(控制反转)和AOP(面向切面编程)。Spring是可以在JavaSE/EE中使用的轻量级开源框架。
Spring的主要作用是为代码解耦,降低代码间的耦合度。就是让对象和对象(模块和模块)之间关系不是按代码关联,而是通过配置来说明。即在Spring中说明对象(模块)关系。
总结:Spring是一个用java开发的,轻量级的,开源的框架。可在j2se,j2ee项目中使用。
核心技术:IOC,AOP
Spring又叫做:容器,spring作为容器,装的是java对象。可以让spring创建java对象,给属性赋值。
Spring作用:实现解耦合,降低java对象之间的耦合,降低模块之间的耦合。
tomcat也是容器:管理的是servlet,listener,filter等对象
创建HelloServlet类,写web.xml。
Spring:创建SomeServiceImpl,写spring的配置文件
1.2 Spring的特性
优点:
-
轻量:使用jar包小。核心功能所用jar包3M左右;Spring框架运行占用资源少,运行效率高。不依赖其他jar。
-
针对接口编程,解耦合:Spring提供了IOC控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式现在由容器完成,对象之间的依赖解耦合
-
AOP编程的支持:通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。在Spring中开发人员可以从繁杂的事务中管理代码中解脱,通过声明方式灵活的进行事物的管理,提高开发效率和质量
-
方便集成各种优秀框架:Spring不排斥各种优秀框架,相反,可以降低各种框架的使用难度,spring提供了对各种优秀框架(如:Stucts,Hibernate,Mybatis)等的直接支持,简化框架使用。
1.3 Spring框架体系结构
Spring的体系结构
目前最新的5.x版本中右面的portlet组件已经被废弃掉,同时增加了用于异步响应式处理的WebFlux组件。
第二章 IOC控制反转
2.1 概念
IOC:Inversion Of Control :控制反转,是一个理论,一个指导思想。知道开发人眼如何使用对象,管理对象。将对象的创建,属性赋值,对象的生命周期都交给代码之外的容器管理。
1)IOC分为:控制和反转
控制:对象创建,属性赋值,对象生命周期管理
反转:将开发人员管理对象的权力转移给代码之外的容器实现,由容器完成对象的管理。
正转:开发人员在代码中,使用new创建对象。开发人员掌握了对象的创建,属性赋值,对象从开始到销毁的全部过程。开发人员对 对象 完全控制。
通过容器,可以使用容器中的对象(容器已经创建了对象,对象属性赋值了,对象也组装好了)
Spring就是一个容器,可以管理对象,创建对象,给属性赋值
2)IOC的技术的实现:
DI(Depency Injectio):是IOC的一种技术实现。程序只需提供要使用的对象的名称既可以了,对象如何创建,如何从容器中查找,获取都由容器自己内部实现
依赖:比如说ClassA类使用了ClassB类的属性或方法,叫做ClassA依赖ClassB
public class B{
public void createOrder();
}
public class A{
private B b=new B();
public void buy(){
b.createOrder();
}
}
执行A的buy()
A a=new A();
a.buy();
注入:赋值。A要使用B的createOrder就要给B的对象赋值,从而调用该方法。
3)Spring框架使用的DI实现IOC
通过Spring框架,只需要提供使用的对象名称就可以了。从容器获取名称对应的对象。
Spring底层使用的是反射机制。通过反射创建对象,给属性赋值。
创建对象方式:反射,序列化,new
2.2 创建第一个Spring项目
-
实现步骤:
ch01-first:第一个Spring项目 使用Spring:Spring作为容器管理对象,开发人员从Spring获取要使用的对象 实现步骤: 1.新建maven项目 2.加入依赖,修改pom.xml spring-context:spring的季基础依赖 junit:单元测试 3.开发人员定义类:接口和实现类 也可以没有接口。接口和实现类定义:与没有Spring一样 4.创建Spring配置文件,作用:声明对象。 将对象交给Spring来创建和管理。 使用<bean>标签标识对象声明,一个bean标识一个java对象 5.使用容器中的对象: 创建一个表示Spring容器的对象 ApplicationContext 从容其中,根据名称获取对象,使用getBean("对象名称")
-
Spring的配置文件
<?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"> <!-- 声明对象; id:自定义对象名称,唯一值(可以没有,Spring会提供默认名称) class:类的全限定名称(不能是接口,没有意义),spring通过反射机制创建对象,把对象放入到spring的一个map文件 spring根据id,class创建对象,把对象放入到spring的一个map对象 map.put(id,对象) --> <bean id="someService" class="com.henu.service.impl.SomeServiceImpl"/> </beans> Spring标准的配置文件 1)根标签是beans 2)beans后是约束文件说明 3)beans里面是bean的声明 4)什么是bean:bean就是java对象,spring容器管理的java对象,叫做bean
测试代码:
//1.指定Spring配置文件:从类路径(classPath)之下开始的路径
String config="beans.xml";
//2.创建容器对象:从类路径中获取
ApplicationContext ctx=new ClassPathXmlApplicationContext(config);
//3.从容器中获取指定名称的对象
SomeService sm=(SomeService)ctx.getBean("someService");
//4.调用接口方法
sm.doSome();
2.3 Spring容器创建对象的特点
-
容器对象:ApplicationContext:接口;
通过ApplicationContext对象,获取要使用的其他java对象
执行getBean(“的id”)
-
Spring默认调用的是类的无参数构造方法。因此类中必须有无参构造方法
-
Spring读取配置文件,一次性创建好所有的java对象,都放到map中。
-
获取容器信息:
//获取容器中对象的信息 @Test public void test04(){ String config="beans.xml"; ApplicationContext ctx=new ClassPathXmlApplicationContext(config); int counts=ctx.getBeanDefinitionCount(); System.out.println(counts); //获取容器中定义对象的名称 String[] names=ctx.getBeanDefinitionNames(); for(String name:names){ System.out.println(name); } }
2.4 DI:给属性赋值
Spring调用类的无参构造方法给属性赋值,创建对象。对象创建后给属性赋值。
给属性赋值可以使用
1)xml文件中的标签和属性;
2)使用注解
DI分类:1 set注入,也叫设值注入;2 构造注入。
2.4.1 基于xml的DI
在xml配置文件中使用标签和属性,完成对象创建,属性赋值
-
set注入,也叫设值注入
概念:Spring调用类中的方法,在set方法中可以完成属性赋值。推荐使用。
简单类型与引用类型:
<!--声明bean对象--> <!-- DI:给属性赋值 1.set注入:spring调用类的set方法,通过set方法完成属性赋值 简单类型(java基本数据类型与String)的set注入: <bean id="xxx" class="yyy"> <property name="属性名" value="简单类型属性值"></property> 属性名:实际为setXXX(yyy);不管类中有没有XXX属性只要有setXXX(yyy)方法就能正常执行 </bean> 引用类型set注入: <bean id="xxx" class="yyy"> <property name="属性名" value="简单类型属性值"> <property id="属性名" ref="bean的id/> .... </property> </bean> --> <bean id="myStudent" class="com.henu.ba02.Student"> <property name="age" value="18"></property><!--setName("李四")--> <property name="name" value="李四"></property> <property name="school" ref="mySchool"/> </bean> <bean id="mySchool" class="com.henu.ba02.School"> <property name="name" value="河南大学"/> <property name="adress" value="河南开封"/> </bean>
-
构造注入:
构造注入:Spring调用类中的有参构造方法,在创建对象的同时,给属性赋值。
<!-- 构造注入:spring调用类的有参构造方法,创建对象的同时给属性赋值 <bean id="myStudent" class="com.henu.ba03.Student"> <constructor-arg ></constructor-arg>:一个构造方法的形参 标签有属性:name:构造方法形参名 index:构造方法的参数位置,从左往右0,1,2..... value:简单类型的引用值 ref:应用类型的形参值 name与index有一个即可 index可省略,不过参数传值顺序必须与参数列表顺序一致 <constructor-arg value="李四"></constructor-arg> <constructor-arg value="20"></constructor-arg> <constructor-arg ref="mySchool"></constructor-arg> </bean> --> <bean id="myStudent" class="com.henu.ba03.Student"> <constructor-arg name="name" index="0" value="李四"></constructor-arg> <constructor-arg name="age" index="1" value="20"></constructor-arg> <constructor-arg name="school" ref="mySchool"></constructor-arg> </bean> <bean id="mySchool" class="com.henu.ba03.School"> <property name="name" value="河南大学"/> <property name="address" value="河南开封"/> </bean> <bean id="mydate" class="java.util.Date"> <property name="time" value="4562676879"></property> </bean> <!--构造注入创建File对象--> <bean id="myFile" class="java.io.File"> <constructor-arg name="parent" value="D:/IdeaPrj/ch01-first"/> <constructor-arg name="child" value="readme.txt"/> </bean>
-
引用类型的自动注入
概念:Spring可以通过调用类中的有参数方法,在创建对象时,给属性赋值,只对引用类型有效。规则byName,byType
(1).byName:按名称注入,java类中引用类型属性名称和spring容器中的bean的id一样的,且数据类型是一样的,这些bean能够赋值给引用类型。
(2)byType(按类型注入):java类中引用类型的数据类型和spring容器中bean的class值是同源关系的,这样的bean赋值给引用类型。
示例:
<!--声明bean对象--> <!-- 使用引用类型自动注入:Spring根据byNme,byType规则给引用类型赋值 1.byName(按名称注入):java类中引用类型的属性名称与Spring容器中bean的id名称一样,且数据类型一样,这样 的bean能够赋值给引用类型 语法规则: <bean id="xxx" class="yy" autowire="byName"> 简单类型赋值 </bean> 2.bytype(按类型注入):java类中引用类型的数据类型和bean的class是同源的, 这些bean能够赋值给引用类型。 以下三种情况认为同源: 1.java中引用类型的数据类型和bean中的class值是一样的 2.java中引用类型的数据类型和bean的class值是父子关系 3.java中引用类型的数据类型和bean的class值是接口和实现类关系的 同一xml配置文件下只能有一个同源bean,否则会报错。 --> <bean id="myStudent" class="com.henu.ba05.Student" autowire="byType"> <property name="name" value="李四"></property> <property name="age" value="20"/> </bean> <!-- <bean id="mySchool" class="com.henu.ba05.School">--> <!-- <property name="name" value="郑州大学"/>--> <!-- <property name="adress" value="河南郑州"/>--> <!-- </bean>--> <bean id="myPrimarySchool" class="com.henu.ba05.PrimarySchool"> <property name="name" value="北京小学"/> <property name="adress" value="北京大兴"/> </bean> <bean id="mydate" class="java.util.Date"> <property name="time" value="4562676879"></property> </bean> <!--构造注入创建File对象--> <bean id="myFile" class="java.io.File"> <constructor-arg name="parent" value="D:/IdeaPrj/ch01-first"/> <constructor-arg name="child" value="readme.txt"/> </bean>
-
练习:
需求:模拟一个用户注册操作
要求:定义一个Dao接口(UserDao),接口中的方法是insertUser(SysUser user)
该方法不需要操作数据库,只需输出“使用了dao执行的insert操作”
定义接口的实现类是MySqlUserDao
定义一个service接口(UserService),定义接口的实现类为UserSeerviceImpl。在service的实现类里有一个UserDao类型的属性。service类中有一个方法addUser(SysUser user)
操作是:service类里的addUser(){userDao.insertUser()}完成注册
定义一个实体类SysUser表示用户数据。
要求实现:
程序中的UserService,mySqlUserDao这些类通过Spring容器创建和管理,同时要给UserServiceImpl类中的UserDao属性赋值。从Spring容器中获取UserServiceImpl类型的对象,调用addUser()方法,输出使用了dao执行的insert操作”;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-srfnzAax-1634019551922)(C:\Users\linhuashang\Desktop\绘图\spring工作.png)]
-
项目中使用Spring多个配置文件
分配多个配置文件的方式:
1.按功能模块分,一个模块一个配置文件
2.按类的功能分,数据库操作相关的类在一个配置文件,service类在一个配置文件,配置redis,事务等等的一个配置文件。
Spring管理多个配置文件:常用的是包含关系的配置文件。项目中有一个总的文件,里面有import标签包含的其他的多个配置文件
语法:
总的文件(xml)<import resource="classpath:其他文件的路径1"/><import resource="classpath:其他文件的路径2"/>关键字:"classpath:":表示类路径,也就是类文件所在的目录。Spring到类路径中加载文件 什么时候使用classpath:在一个文件中要使用其他的文件,需要使用classpath
<!--声明bean对象--> <!-- 总的文件包含其他配置文件,一般不声明bean 语法: <import resource="classpath:其他文件的路径1"/> <import resource="classpath:其他文件的路径2"/> classpath:表示类路径。类文件所在的目录。Spring通过类路径加载配置文件 --><!-- <import resource="classpath:ba06/spring-school.xml"/>--><!-- <import resource="classpath:ba06/spring-student.xml"/>--> <!--包含关系的配置文件,可使用*(通配符)表示任意字符 注意:总的文件名称不能包含在通配符的范围内(appliactionContext.xml不能叫做 spring-applicationContext.xml ) --> <import resource="classpath:ba06/spring-*.xml"/>
2.4.2 基于注解的DI
基于注解的DI:使用Spring提供的注解,完成java对象创建,赋值。
1.注解使用核心步骤:
-
在源代码中加入注解,例如@Component
@Component(value="myStudent")//value可以省略public class Student
若没有提供自定义对象名称,则会使用框架提供的默认对象名称:类名首字母小写
-
在Spring配置文件加入组件扫描器标签
<!--声明组件扫描器:使用注解必须加入这个语句 context:是前缀 component-scan:组件扫描器,组件是java对象。 属性:base-package 注解在你项目中的包名 框架会扫描这个包和子包中的所有类,找类中的所有注解 遇到注解后,按照注解表示的功能,去创建对象给属性赋值 --><context:component-scan base-package="com.henu.ba01"/>
创建对象的四个注解
@Component("myStudent") 表示创建对象,对象放到容器中。作用与bean相同* 属性:value,表示对象名称,也就是bean的id属性值* 位置:在类的上面,表示创建此类的对象** @Component("myStudent")等同于<bean id="myStudent class="com.henu.ba01.student"></bean>*与@Component功能相同的创建对象的注解:* 1)@Repository:放在dao接口的实现类上面,表示创建的是dao对象,持久层对象,能够访问数据库* 2)@Service:放在业务层接口的首先类上,表示创建的业务层对象,业务层对象有事务的功能* 3)@Controller:放在控制器类的上面,表示创建控制器对象,属于表示层对象* 控制器对象能接收请求,并能将请求的结果显示给用户* 以上都能创建对象,但是@Repository @Service @Controller有角色说明,表示对象是分层的。* 表示对象是属于不同层的,具有额外的功能*/
扫描多个包的三种方式:
不要重复扫描,会导致对象创建多次
<context:component-scan base-package="com.henu.ba01"/> <!--扫描多个包的三种方式--> <!--第一种,多次使用组件扫描器--> <context:component-scan base-package="com.henu.ba01"/> <context:component-scan base-package="com.henu.ba02"/> <context:component-scan base-package="com.henu.ba03"/> <!--第二种,使用分隔符(;或者,或空格,不建议用空格)指定多个包--> <context:component-scan base-package="com.henu.ba02;com.henu.ba01"/> <!--第三种,指定父包--> <context:component-scan base-package="com.henu"/>
2.给属性赋值
-
基本类型数据赋值
直接使用@Value(value=“属性值”)
/** * 简单类型属性赋值:@value * 属性value 简单类型属性值 * 位置:1)在属性定义的上面,无需set方法,推荐使用 * 2)在set方法上面,必须要有sei方法 * */@Value(value = "黎明")private String name;//@Value(value = "18")//使用外部属性文件中的数据:${"key"}@Value("${myage}")private int age;
从外部配置文件给基本数据类型赋值
properties文件:
schoolname=北京大学schooladdress=南开myname=张三myage=20
<!--Spring配置文件中添加: 读取外部属性配置文件 context:property-placeholder:读取properties这样的文件 Property类。 --> <context:property-placeholder location="classpath:/myconf.properties" file-encoding="UTF-8"/>//小插曲:未设置properties文件编码格式会出现乱码。使用file-encoding="UTF-8"解决乱码
赋值
@Value("${myname}")private String name;@Value("${myage}")private int age;
-
引用数据类型赋值
@Autowired注解
/** * 引用类型赋值: * @Autowireed:Spring框架提供的,给他引用类型赋值的,使用自动注入原理 * 支持byName,byType.默认是byType * 属性:required:boolean类型的属性,默认true * true:spring在启动,创建容器对象时,会检查引用类型是否赋值成功。 * 若赋值失败,终止程序执行,并报错 * false:引用类型赋值失败,程序正常执行,不报错。引用类型的只是null。 * 使用位置: * 1)属性定义上面,无需set方法 * 2)set方法上面 * * 使用byName自动注入: * 1)@Autowired:给引用类型赋值 * 2)@Qualifer(value="bean的id"):从容其中找到指定名称的对象;把这个对象赋值给引用类型。 * */@Autowired(required = false)@Qualifier(value = "myschoolAAA")private School school;
@Resource注解
/** * 引用类型赋值: * @Resource: 来自jdk中,给引用类型赋值的,支持byName,byType,默认是byName * Spring支持这个注解的使用 * 位置:1)在属性定义的上面,无需set方法,推荐使用 * 2)在set方法上面 *说明:jdk1.8带有@Resource注解,高于1.8.,没有@Resource * 需要加入一个依赖: * <dependency> * <groupId>javax.annotation</groupId> * <artifactId>javax.annotation-api</artifactId> * <version>1.3.2</version> * </dependency> * * @Resource只使用byName赋值 * 使用注解属性name="bean的id" *///默人byName自动注入//先使用byName赋值,如果赋值失败,再使用byType@Resource(name = "myschool")private School school;
第三章 AOP面向切面编程
3.1 增加功能导致的问题
在源代码中,业务方法增加功能,存在问题
- 源代码改动较多
- 重复代码比较多
- 代码维护困难
3.2 AOP面向切面编程
3.2.1 AOP概念
AOP(Aspect Orient Programming):面向切面编程
Aspect:切面,给业务方法增加的功能,叫做切面。切面一般都是非业务功能,而且切面功能一般可以复用
例如:日志功能,事务功能,检查权限,参数检查,统计信息等。
Orient:面向
Programming:编程
OOP:面向对象编程
如何理解?以切面为核心设计开发应用
- 设计项目时找出切面功能:如日志
- 安排切面的执行时间,执行位置
3.2.2AOP作用
- 使切面概念复用
- 让开发人员专注业务逻辑,提高开发效率
- 实现业务功能与其他非业务功能解耦合
- 给存在的业务方法增加功能,不用修改方法
3.3 AOP术语
1)Aspect:切面,给业务方法增加的功能
2)joinPoint:连接点,连接切面的业务方法。在这个业务方法执行时,会同时执行切面的功能
3)Pointcut:切入点,是一个或多个连接点集合,表示这些方法执行时,都能增加切面的功能。表示切面执行的位置。
4)target:目标对象。给哪个对象增加切面的功能,这个对象就是目标对象
5)Advice:通知,表示切面的执行时间,是在目标方法之前执行切面,还是在目标方法之后执行切面
AOP中重要的三个要素:Aspect,Pointcut,Advice。在Advice时间,在Pointcut的位置,执行Aspect
AOP是动态的思想,在程序运行期间,创建代理,使用代理执行方法时,增加切面的功能。这个代理对象是存在内存中的。
3.4 什么时候用AOP
要给某些方法增加相同的功能,且源代码不能改。给业务方法增加非业务功能,也可以使用AOP
3.5 AOP技术思想的实现
使用框架实现AOP,实现AOP的框架很多。有名的有两个
1)Spring:Spring 框架实现AOP思想中的部分功能。Spring框架实现AOP的操作比较繁琐,笨重
2)Aspectj:独立的框架,专门做AOP。属于Eclipse
3.6 使用AspectJ框架实现AOP
AspectJ框架可以使用注解和xml配置两种方式实现AOP
3.6.1 通知
AspectJ表示切面执行时间,用的通知(Advice)。这个通知可以用注解表示
学习5个注解,表示切面的5个执行时间。这些注解叫做通知注解。
@Before:前置通知
@AfterReturning:后置通知
@Around:环绕通知
@AfterThrowing:异常通知
@After:最终通知
3.6.2 Pointcut位置
Pointcut用来表示切面执行的位置,使用AspectJ中切入点表达式
切入点表达式语法:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
解释:
modifiers-pattern:访问权限类型
ret-type-pattern: 返回值类型
declaring-type-pattern:包名类名
name-pattern(param-pattern):方法名(参数类型与个数)
throws-pattern :抛出异常类型
?表示可选的部分
例如:execution(public void com.henu.service.serviceImpl.SomeServiceImpl.doSome(String,Integer));
访问修饰符 返回值 包名 类名 方法名 参数类型
以上表达式包含司部分
excution(访问权限 方法返回值 方法声明(参数)异常类型)
其中,访问权限 异常类型 可省略;方法返回值 方法声明(参数)不能省略
通配符:
符号 | 表示意义 |
---|---|
* | 0至任意多个字符 |
… | 用在方法参数中表示任意多个参数;用在包名后,表示当前包和其子包路径 |
+ | 用在类名后表示当前类及其子类;用在接口后,表示当前接口及其实现类 |
示例:
**excution(public * *(…))😗*指定切入点为:任意公共方法
excution(public * set(…)):* 指定切入点为任意以“set”开头的方法
excution( com.xyz.service.*.*(…))* 指定切入点为:定义在service包里的任意类的任意方法
excution( com.xyz.service…*.*(…)):* 指定切入点为:定义在service包或者service子包里的任意类的任意方法。"…“出现在类名中时,后面必须跟”*",表示包,子包下的所有类
*excution( *…service.*.*(…))😗*指定任意包下的service子包下所有类(接口)中所有方法为接入点
切入点表达式表达的是哪些方法能够在执行时能够加入切面功能。
3.6.3 前置通知(掌握)
实现通知步骤
ch07-aspectj-before:使用aspectj框架的注解实现前置通知实现步骤:1,新建maven项目2.修改pom.xml加入依赖 spring-context依赖, spring-aspectj依赖(能够使用aspectj框架的功能) junit3.创建业务接口及其实现类。4.创建一个切面类。是一个普通类 1)在类的上面加入@Aspect 2)在类中定义方法,方法表示切面的功能 在方法的上面加入Aspect框架中的通知注解,例如@Before(value="切入点表达式")5.创建Spring配置文件 1)声明目标对象 2)声明切面类对象 3)声明自动代理生成器6.创建测试类,测试目标方法执行时,增加切面的功能。
@Before
/** * @Before:前置通知 * 属性:value 切入点表达式,表示切面的执行位置。在执行这个方法时,会同时执行切面的功能 * 位置:方法的上面 * 特点: * 1)执行时间:在目标方法之前执行的 * 2)不会影响目标方法执行 * 3)不会修改目标方法执行的结果 *//** * 同一方法两个前置通知执行顺序不一定,但都会在该方法执行前执行 * 不过意义不大,可将前置通知方法合并为一个即可 */ /** * 切面类中的通知方法,可以有参数 * jionPoint必须是它 * * joinPoint:表示正在执行的业务方法,相当于反射的Method * 使用要求:必须是参数列表的第一个 * 作用:获取方法执行时的信息,例如方法名称,方法参数类型 */ @Before(value="execution(* *..impl.SomeServiceImpl.do*(..))") public void myBefore2(JoinPoint joinPoint){ //获取方法定义,名称 System.out.println("前置通知,方法定义:"+joinPoint.getSignature()); System.out.println("前置通知,方法定义:"+joinPoint.getSignature().getName()); //获取方法执行参数 Object[] obj=joinPoint.getArgs(); for(Object o:obj){ System.out.println("参数"+o); } //根据方法信息可以做自己的业务逻辑。比如是doSome执行什么操作,是doOther执行什么操作 //切面功能代码 System.out.println("前置通知2,切面的功能,在方法之前执行:"+new Date()); }
3.6.4 后置通知(掌握)
@AfterReturning:在目标方法之后执行(目标方法返回值类型为void也可)
目标方法执行有异常,则直接抛出异常,后置通知方法不执行。
package com.henu.handle;
import com.henu.domain.Student;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 切面类注解,
* 位置:放置在某个类的上面
* 作用:表示当前类时切面类
*
* 切面类:包含切面功能的类
*/
@Aspect
public class MyAspect {
/**
* 后置通知方法定义
* 1)方法是public
* 2)方法是void
* 3)方法名称自定义
* 4)方法有参数,推荐使用Object类型
* @param joinPoint
*/
/**
* @AfterReturning:后置通知
* 属性:value:切入点表达式
* returning 自定义的变量,表示目标方法的返回值
* 自定义变量名称必须和通知方法的形参名一样
* 位置:在方法上面
* 特点:
* 1.在目标方法之后执行
* 2.能够获取到目标方法中的执行结果
* 3.不会影响目标方法结果
*
* 方法的参数:
* Object res:表示目标方法的返回值,使用res接受doOther的调用结果
* Object res=doOther()
*
* 后置通知的执行顺序:
* Object res=doOther()
* myAfterReturning(res);
* Object res作用
* 根据res的结果做业务逻辑判断
* 根据res的不同的值,做不同的增强功能。
*
*在myAfterReturning修改res值并不影响doOther返回值
* 注意,若后置通知方法使用参数JoinPoint,则其必须位于参数列表第一个位置,否则,书写不会报错,
* 运行会抛出异常。
* */
@AfterReturning(value = "execution(* *..SomeServiceImpl.do*e*(..))",returning = "res")
public void myAfterReturning(Object res){
res="111";
System.out.println("后置通知,目标方法执行结果为:"+res);
}
@AfterReturning(value = "execution(* *..SomeServiceImpl.doStudent(..))",returning = "res")
public void myAfterReturning2(Object res){
Student s=(Student)res;
s.setAge(100);
s.setName("赵六");
res=s;
System.out.println("后置通知,目标方法执行结果为:"+res);
}
}
- 思考:
- 1.doOther方法返回的是String,Integer,…等基本数据类型
- 在后置通知中,修改返回值,是不会影响目标方法的最后调用结果的
- 2.doOther方法返回的是对象类型,例如:Student。
- 在后置通知方法中,修改Student的属性值,会不会影响最后调用结果。
答案是会。最终输出Student{name=“赵六” ,age=100}
3.6.5 环绕通知(掌握)
@Around(value=“切入点表达式”)
使用环绕通知:就是调用切面类的通知方法
package com.henu.handle;
import com.henu.domain.Student;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 切面类注解,
* 位置:放置在某个类的上面
* 作用:表示当前类时切面类
*
* 切面类:包含切面功能的类
*/
@Aspect
public class MyAspect {
/**
* @Around:环绕通知
* 方法定义:
* 1)方法是public
* 2)方法必须有返回值,推荐使用Object类型
* 3)方法名自定义
* 4)方法必须有ProceedingJoinPoint参数
*/
/**
* @Around:环绕通知
* 属性:value=切入点表达式
* 位置:在方法上面
*
*
* 返回值:Object表示调用目标方法希望的得到的结果(不一定是目标方法自己的返回值)
* 参数:ProceedingJoinPoint相当于反射中的Method
* 作用:执行目标方法。相当于Method.invoke()
* public interface ProcedingJionPoint extends JoinPoint()
*
* 特点:
* 1.在目标执行前和后都能增强功能
* 2.控制目标方法是否执行
* 3.修改目标方法的执行结果
*/
@Around(value = "execution(* *..SomeService.doFirst(..))")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("执行了环绕通知myAround");
//执行目标方法
Object res=joinPoint.proceed();//执行目标方法
return "OK";
}
}
3.6.6@AfterThrowing异常通知
语法:@AfterThrowing(value=切入点表达式,throwing=自定义变量)
package com.henu.handle;
import com.henu.domain.Student;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.concurrent.Executor;
/**
* 切面类注解,
* 位置:放置在某个类的上面
* 作用:表示当前类时切面类
*
* 切面类:包含切面功能的类
*/
@Aspect
public class MyAspect {
/**
* 异常通知方法定义
* 1)方法是public
* 2)没用返回值,是void
* 3)方法名称自定义
* 4)方法有参数是Exception
*
*/
/**
* @AfterThrowing:异常通知
* 属性:value 切入点表达式
* throwing 自定义变量,表示目标方法抛出的异常
* 变量名必须和通知方法的参数名一样
* 位置:方法上面
* 特点:
* 1.在目标方法抛出异常后执行。没有异常不执行
* 2.能获取目标方法的异常信息
* 3.不是异常处理程序,可以得到发送异常的通知,可以发送邮件,短信通知开发人员
* 可以看作目标方法的监控程序
*/
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "e")
public void myAfterThrowing(Exception e){
System.out.println("异常通知,在目标方法抛出异常时处理,异常原因是:"+e.getMessage());
/*
异常发生可以做:
1.记录异常发生的时间,位置等信息
2.发送邮件,短信,通知开发人员
*/
}
}
3.6.7 @After最终通知
语法:@After(value=“切入点表达式”)
package com.henu.handle;
import com.henu.domain.Student;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.concurrent.Executor;
@Aspect
public class MyAspect {
/**
* 最终通知
* 1)方法时public
* 2)方法无返回值,为void
* 3)方法名称自定义
* 4)方法没有参数
*/
/**
* @After 最终通知
* 属性:value 切入点表达式
* 位置:方法上面
* 特点:
* 1.在目标方法执行之后执行
* 2.总会被执行
* 3.可用来做程序最后收尾动作。例如清理临时数据,变量,清理内存
*
* 最终通知,无论程序有无异常都会被执行
*/
@After(value = "execution(* *..SomeServiceImpl.doThird(..))")
public void myAfter(){
System.out.println("最终通知总会被改");
}
}
第四章 Spring集成Mybatis
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OBQ0jTZm-1634019551926)(C:\Users\linhuashang\AppData\Roaming\Typora\typora-user-images\image-20211011103001366.png)]
步骤:
ch14
实现步骤:
1.使用MySql数据库使用学生表student(id int primary key,name varchar(80),age int)
2.创建maven项目
3.加入依赖
Spring依赖,MyBatis依赖,MySQL驱动,junit依赖
mybatis-spring依赖(mybatis网站提供的,用来在spring项目中创建mybatis对象)
spring有关事务的依赖。
mybatis与spring整合时,事务默认自动提交。
4.创建实体类:Student
5. 创建Dao接口和mapper文件写sql语句
6.写mybatis主配置文件
7.创建service接口及其实现类
8.创建Spring配置文件
1)声明数据源DataSource,使用阿里的Druid连接池
2)声明SqlSessionFactoryBean类,在这个类中创建SqlSessionFactory对象
3)声明MapperScannerConfiguration类,在内部创建dao代理对象
创建的对象都在Spring容器中
4)声明Service对象,将3)中的dao赋值给service属性
9.测试dao访问数据库
pom.xml依赖
ch14
实现步骤:
1.使用MySql数据库使用学生表student(id int primary key,name varchar(80),age int)
2.创建maven项目
3.加入依赖
Spring依赖,MyBatis依赖,MySQL驱动,junit依赖
mybatis-spring依赖(mybatis网站提供的,用来在spring项目中创建mybatis对象)
spring有关事务的依赖。
mybatis与spring整合时,事务默认自动提交。
4.创建实体类:Student
5. 创建Dao接口和mapper文件写sql语句
6.写mybatis主配置文件
7.创建service接口及其实现类
8.创建Spring配置文件
1)声明数据源DataSource,使用阿里的Druid连接池
2)声明SqlSessionFactoryBean类,在这个类中创建SqlSessionFactory对象
3)声明MapperScannerConfiguration类,在内部创建dao代理对象
创建的对象都在Spring容器中
4)声明Service对象,将3)中的dao赋值给service属性
9.测试dao访问数据库
Mybatis主配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--别名-->
<typeAliases>
<package name="com.henu.domain"/>
</typeAliases>
<mappers>
<!--
resource=“mapper文件按的路径使用 / 分隔路径”
-->
<!--使用package要求
mapper文件与dao接口位于同一目录下,且名称完全相同
-->
<package name="com.henu.dao"/>
</mappers>
</configuration>
dao接口配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.henu.dao.StudentDao">
<!-- 使用insert,update,delete,select标签写sql语句-->
<select id="selectStudent" resultType="com.henu.domain.Student">
select id ,name,age from student2
</select>
<insert id="insertStudent" >
insert into student2(name ,age) values (#{name},#{age})
</insert>
</mapper>
Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--加载外部属性配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--声明数据源-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close">
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--声明SqlSessionFactoryBean,在这个类的内部创建SqlSessionFactory-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--指定数据源-->
<property name="dataSource" ref="myDataSource"/>
<!--指定mybatis主配置文件
resource可以直接使用value赋值
-->
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--声明MapperScannerConfiguration
SqlSession.getMapper(StudentDao.class)
MapperScannerConfiguration作用:循环basePackage所表示的包,
把包中的每个接口都找到,调用SqlSession。getMapper
把每个dao接口都创建出dao对象,dao代理对象都放在容器中
相当于:
ApplicationContext ctx=...
SqlSessionFactory sqlSessionFactory=ctx.getBean("factory");
SqlSession session=sqlSessionFactory.openSession();
for(接口:com.henu.dao){
接口 对象 = session.getMapper(接口);
springMap.put(对象名(接口名的首字母小写),对象);
}
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定SqlSessionFactory对象名称-->
<property name="sqlSessionFactoryBeanName" value="factory"/>
<!--指定基本包,dao接口所在的包名-->
<property name="basePackage" value="com.henu.dao"/>
</bean>
<bean id="studentService" class="com.henu.service.serviceImpl.StudentServiceImpl">
<property name="studentDao" ref="studentDao"/>
</bean>
</beans>
第五章 Spring事务
5.1 事务的概念
什么是事务?事务是一些sql语句的集合,作为一个整体执行,要么都执行成功,要么都执行失败。
mysql执行事务
beginTransaction 开启事务
insert into student() values......
select * from student where id=1005
endTransaction 事务结束
什么情况下需要事务?
一个操作需要多条(2条或2条以上) sql语句一起完成,操作才能成功
5.2在程序中在哪说明事务
事务:加在业务类的方法上面(public方法上面),表示业务方法执行时,需要事务支持
public class AccountService{
private AccountDao dao;
private MoneyDao dao2;
//在service(业务类)的public方法上面需要说明事务
public void trans(String a,String b,Integer money){
dao.updateA();
dao.updateB();
}
}
public class AccountDao{
public void updateA();
public void updateB();
}
public class MOneyDao{
public void insertA();
public void insertB();
}
5.3 事务管理器
5.3.1 不同的数据库访问技术,处理事务是不同的
1)使用jdbc访问数据库,事务处理
public void updateAccount(){
Connection conn=...
conn.setAutoCommit(false);
stat.insert();
stat.update();
conn.commit();
conn.setAutoCommit(true);
}
2)使用MyBatis访问数据库,事务处理
public class updateAccount(){
SqlSession session=sqlSessionFactory.openSesssion(false);
try{
sesson.insert(.....);
session.update(....);
session.commit
}catch(Exception e){
session.roolback;
}
}
5.3.2 Spring统一管理事务,把不同的数据库访问技术的事务处理统一起来
使用Spring地事务管理,管理不同数据库访问技术的事务处理。开发人员只需要掌握Spring的事务处理方案,就可以实现使用不同数据库访问技术的事务处理。
管理事务面向的Spring,由Spring管理事务,做事务提交,事务回滚
5.3.3 Spring事务管理器
Spring框架使用事务管理器管理所有的事务
事务管理器接口:PlatformTransactionManager
作用:定义了事务的操作,主要是commit(),roolback()
事务管理器有很多的实现类:一种数据库访问技术有一个实现类。由实现类具体完成事务的提交,回滚
意味着:jdbc或者mybatis访问数据库有自己的事务管理器实现类:DataSourceTransactionManager
hibernate框架,他的事务管理器对象实现类:HibernateTransactionManager。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6D417mvr-1634019551932)(C:\Users\linhuashang\Desktop\绘图\spring事务.png)]
5.3.4 事务的提交和回滚实际
什么时候提交事务,什么时候回滚事?当你的业务方法正常执行时,没有异常,事务就提交
如果业务方法抛出了运行时异常,事务是回滚的。
异常分类:
Error:严重错误。回滚事务
Exception:异常类,可以处理的异常情况
1)运行时异常:RuntimeException和它的子类都是运行时异常,在程序执行错误过程中抛出的异常。常见的运行时异常:NullPointerException,NumberFormatException,IndexOutOfBoundsException,ArithmeticException
2)受查异常:编写java代码的时候,必须处理的异常。例如:IOException,SQLException,FileNotFoundException(此时默认提交事务)
如何记忆:
方法抛出了运行时异常,事务就回滚,其他情况(正常执行,受查异常)就提交事务
5.3.5 事务使用的AOP的环绕通知
环绕通知:可以在目标方法的前和后都增强功能,不需要修改代码。
Spring给业务方法在执行时,增加事务的切面功能
@Around("executi(所有业务类中的方法)")
public Object myAroung(ProceedingJointPoint pjp){
try{
//使用事务管理器开启事务
PlatformTransactionManager.beginTransaction();
pjp.proceed();//执行目标方法
PlatformTransactionManager.commit();//业务方法正常执行,提交事务
}catch(Exception e){
PlatformTransactionManager.rollback();//业务方法正常执行回滚事务
}
}
5.4 事务定义接口
TransactionDefinition接口:定义了三类常量,定义了有关事务控制的属性
事务的属性:隔离级别;传播行为;事务的超时
给事务方法说明事务属性,与ACID不一样
5.4.1 隔离级别
隔离级别:控制事物之间影响的程度
5个值,四个隔离级别
- DEFAULT:采用DB默认的事务隔离级别。MySQL默认为REPEATABLE_READ;Oracle默认READ_COMMITED
- READ_UNCOMMIT:读未提交,为解决任何并发问题
- READ_COMMITED:读已提交,存在不可重复度与幻读
- REPEATABLE_READ:可重复读。解决脏读,不可重复读,存在幻读
- SERIALIZABLE:串行化。不存在并发问题
5.4.2 事务超时
超时时间,以秒为单位。整数值,默认-1;
超时时间:表示一个业务方法最长的执行时间。到达时间没有执行完毕,spring回滚事务
5.4.3 传播行为
传播行为有7个值。
传播行为:业务方法在调用时,事务方法之间的传递和使用
使用传播行为:标识方法有无事务
掌握:
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
了解:
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
- PROPAGATION_REQUIRED:Spring的默认传播行为,方法在调用的时候,如果存在事务,就使用当前的事务;如果没有事务,就创建事务,方法在新事物中执行
- PROPAGATION_SUPPORTS:支持。方法有事务可以正常执行,没有事务也可以正常执行。例如:查询操作
- PROPAGATION_REQUIRES_NEW:方法需要一个新事务。如果调用方法时,存在一个事务,则原来的事务暂停,直到新事物执行完毕;如果方法调用时没有新事物,则新建一个事务,在新事物执行代码
Spring事务环境示例:
购买商品trans_sale项目
本例要实现购买商品,模拟用户下单,向订单添加销售记录,从商品表减少库存
实现步骤:
1.创建相关表
slae表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d0tIKiMr-1634019551937)(C:\Users\linhuashang\AppData\Roaming\Typora\typora-user-images\image-20211011163639312.png)]
goods表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OX7WWkv2-1634019551940)(C:\Users\linhuashang\AppData\Roaming\Typora\typora-user-images\image-20211011163835768.png)]
5.5 Spring框架使用自己的注解@Teansactional控制事务
@Transactional注解,使用注解的属性控制事物(隔离级别,传播行为,超时)。
适合中小项目。
属性:
- prooagation:事务的传播行为,使用的是Propagation类的枚举值。例如:Propagation.REQUIRED
- isolation:表示隔离级别,使用的是Isolation类的枚举值,表示隔离级别。例如,默认Isolation.DEFAULT
- readOnly:boolean类型,表示数据库操作是不是只读,默认false
- timeout:事务的超时时间,默认-1,整数值,单位为秒。例如timeout=20
- rollbackFor:表示回滚的异常类列表,值为一个数组。每个值是异常类的class。
- rollbackForClassName:表示回滚类的异常列表。值为异常类名称,是String类型的值
- noRollbackFor:表示不需要回滚的异常类列表,class类型
- noRollbackForClassName:不需要回滚的异常类型,是String类型的
位置:1)业务方法上面,是在public方法的上面
2)在类的上面
注解使用步骤:
1)在spring的配置文件声明事务的内容
声明事务管理器对象,说明使用哪个事务管理器
声明使用注解管理事务,开启事务注解驱动
<!--声明事务控制-->
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务注解驱动:告诉框架使用注解管理事务
transaction-manager:指定事务管理器id
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
2)在类的源代码中加入@Transaction
/* @Transactional:放在方法上面,表示方法有事务功能
Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,readOnly = false,timeout = 20,
rollbackFor = {NullPointerException.class,NotEnoughException.class})
Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,readOnly = false,timeout = 20,
Transactional
释roolbackFor的使用:
1)框架首先检查方法抛出的异常是否在rollbackFor的数组中,如果在一定回滚
2)如果方法抛出的异常不在rollbackFor数组中,框架会继续检查抛出的异常是不是运行时异常 Runtimeexception,
如果是则回滚
例如抛出受查异常:IOException,SQLException,若rollbackFor有,则回滚,没有不会滚
仅写@Transactional,默认propagation = Propagation.REQUIRED,若抛出RimtimeException也会回滚
*/
@Transactional
@Override
public void buy(Integer goodsId, Integer num) {
System.out.println("buy方法开始");
//生成销售记录
Sale sale=new Sale(goodsId,num);
saleDao.insertSale(sale);
//查询商品
Goods goods=goodsDao.selectById(goodsId);
if(goods==null){
throw new NullPointerException(goodsId+"商品不存在");
}else if(goods.getAmount()<num){
throw new NotEnoughException(goodsId+"库存不足");
}
//更新库存
Goods buyGoods=new Goods(goodsId,num);
goodsDao.updateGoods(buyGoods);
System.out.println("buy方法执行");
}
事务的控制模式:
1.编程式,在代码中编程控制事务
2.声明式事务,不用编写事务
5.6 使用AsectJ框架在Spring配置文件中,声明事务控制
使用aspectj的aop,声明事务控制叫做声明式事务
使用步骤:
1.pom.xml文件加入spring-aspects依赖
2.在spring的配置文件声明事务的内容
(1)声明事务管理器
(2)声明业务方法需要的事务属性
(3)声明切入点表达式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<context:property-placeholder location="classpath:jdbc.properties" file-encoding="UTF-8"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="factory"/>
<property name="basePackage" value="com.henu.dao"/>
</bean>
<bean id="buyService" class="com.henu.service.serviceImpl.BuyGoodsServiceImpl">
<property name="saleDao" ref="saleDao"/>
<property name="goodsDao" ref="goodsDao"/>
</bean>
<!--1.声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--
2.声明事务方法的事务属性(隔离级别,传播行为,超时)
id:给业务方法配置事务这段代码起个名称
transaction-manager:事务管理器id
-->
<tx:advice id="serviceAdvice" transaction-manager="transactionManager">
<!--给具体的事务方法添加事务的说明-->
<tx:attributes>
<!--给具体的业务方法说明它需要的事务属性
name:业务方法名称。配置name的值:1.业务方法的名称 2.带有部分通配符(*)的方法名称 3.使用*
propagation:指定传播行为的值
isolation:指定隔离级别
read-only:是否只读,默认false
timeout:超时时间
rollback-for:指定回滚的异常类列表,使用的是异常的全限定名称
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
read-only="false" timeout="20"
rollback-for="java.lang.NullPointerException,com.henu.exception.NotEnoughException"/>
<!--在业务方法有命名规则后,可以对一些方法使用事务-->
<tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="modify*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="remove*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<!--以上方法以外的:querySale,findSale,searchSale-->
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!--说明切入点表达式:表示哪些包中的类,类中的方法参与事务
若aop:config标签报错
在beans标签内引入aop:
xmlns:aop="http://www.springframework.org/schema/aop"
同时在xsi:schemaLocation下加入
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
即可
-->
<aop:config>
<!--声明切入点表达式
expression:切入点表达式,表示哪些类和类中的方法要参与事务
id:切入点表达式的名称,唯一值
expression如何写?
execution(* *..Service..*.*(..)):service包下及子包的类中的任意方法
-->
<aop:pointcut id="servicePointcut" expression="execution(* *..service..*.*(..))"/>
<!--关联切入点表达式和事务通知-->
<aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicePointcut"/>
</aop:config>
</beans>
声明式事务缺点:不易理解,配置复杂
声明式事务优点:代码与事务配置分离,控制事务源代码不用修改
能快速的了解和掌控项目的全部事务。适合大型项目
第六章 Spring和Web
6.1现在使用容器对象的原因
1.创建容器对象次数多
2.在多个servlet中,分别创建容器对象
6.2 需要一个什么样的容器对象
1.容器对象只有一个,创建一次就可以了
2.容器对象应该在整个项目中共享使用。多个servlet都能使用同一个容器对象
结局问题使用监听器servletContextListener(两个方法 初始时执行的方法,销毁时执行的方法)
在监听器中,创建好的容器对象,应该放在web应用中的ServletContext作用域中
6.3 ContetxLoaderListener
ContextLoaderListener是一个监听器对象,是spring框架提供的,使用这个监听器的作用:
1.创建容器对象,一次
2.将容器队象放入到ServletContext中
使用步骤:
1.pom.xml加入依赖spring-web
2.在web.xml中声明监听器
依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
在web.xml中配置监听器
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app >
<display-name>Archetype Created Web Application</display-name>
<!--自定义容器使用的配置文件路径
context-param:叫做上下文参数,给监听器提供参数
<param-name>contextConfigLocation</param-name>
名称是固定的,表示自定义spring文件配置文件的路径
<param-value>classpath:applicationContext.xml</param-value>
自定义配置文件的路径。路径有多个,多个路径之间使用,隔开
例如
<param-value>classpath:applicationContext.xml,springbeans.xml</param-value>
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<!--声明监听器
默认监听器:创建容器对象时,读取的配置文件:/WEN-INF/applicationContext.xml
-->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
6.4ContextLoaderListener源代码
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
}
private WebApplicationContext context;
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
} else {
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
//创建spring的容器对象
this.context = this.createWebApplicationContext(servletContext);
}
//把容器对象放入到ServletContext作用域中
//key=WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
//value=容器对象
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
} catch (Error | RuntimeException var8) {
}
}
}
//WebApplicationContext继承自ApplicationContext。是web项目中使用的容器对象
public interface WebApplicationContext extends ApplicationContext
容器使用:
复杂写法:
//使用监听器已经创建好了容器对象,从Servlet作用域获取容器
String key= WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
ServletContext sc= getServletContext();//ServletContext,servlet中的方法
ServletContext sc1=request.getServletContext();//HttpServletRequest对象的方法
Object attr=sc.getAttribute(key);
WebApplicationContext context=null;
if(attr!=null){
context=(WebApplicationContext)attr;
//容器对象拿到
}
Spring框架提供的方法:
ServletContext sc2=getServletContext();
WebApplicationContext context1= WebApplicationContextUtils.getRequiredWebApplicationContext(sc2);