Spring学习详细代码+图片解释笔记
零、框架编写流程导包编写配置文件测试一、IOC:容器功能,重要容器功能用来整合框架。1、IOC 简介ioc:InversionOf Control,控制反转。控制资源对的获取方式发生了根本性的改变。传统方式,主动式:直接newspring,被动式:由ioc容器控制传统与spring对资源的控制方式不同,称为控制反转。2、容器的概念管理所有组件,就是一个map集合。容器自动探查出那些组件需要用到另一
闲聊虾扯蛋:
O(∩_∩)O哈哈~,终于到了上传博客的时候了,该Spring笔记写于寒假疫情期间,但家中网络属实让我哭笑不得,所以时隔一个寒假(9个月),现在将自己的笔记上传到此。以此留作纪念。革命尚未成功,通知仍需努力。
零、框架编写流程
- 导包
- 编写配置文件
- 测试
一、IOC:容器功能,重要
容器功能用来整合框架。
1、IOC 简介
ioc:Inversion Of Control,控制反转。控制资源对的获取方式发生了根本性的改变。
传统方式,主动式:直接new
spring,被动式:由ioc容器控制
传统与spring对资源的控制方式不同,称为控制反转。
2、容器的概念
管理所有组件,就是一个map集合。容器自动探查出那些组件需要用到另一些组件,容器帮我们创建组件,并注入需要的组件中。IOC容器类似现实生活中的婚介所。
3、DI(Dependency Injection)依赖注入
IOC是一种思想,DI就是IOC思想的实现。
通过DI容器知道组件运行时需要那些组件,然后通过反射,将容器准备好的组件注入到组件中。
4、第一个spring程序HelloWorld 、通过set方法(属性)为组件属性赋值
1、导包
使用maven搞定
2、编写配置文件
<?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">
<!--将person组件注册到ioc 容器中-->
<!--
class:组件的全类名
id:组件的唯一标识
-->
<bean id="persoon01" class="com.gzk.bean.Person">
<!--
property:给组件的属性赋值,实质通过set方法赋值
-->
<property name="lastName" value="guozhikang"/>
<property name="age" value="18"/>
<property name="email" value="emial@qq.com"/>
<property name="gender" value="男"/>
</bean>
</beans>
3、测试
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
//spring容器帮我们创建对象
Person persoon01 = (Person) context.getBean("persoon01");
System.out.println(persoon01);
}
说明:
ClassPathXmlApplicationContext:加载类路径下的配置文件。(推荐使用)
FileSystemXmlApplicationContext:加载文件系统下的配置文件。
注意点:
- 容器启动完成,组件就已经创建好了。
- 同一个组件在IOC容器中是单实例的。
- 若准备从容器获取不存在的组件,直接报错。
- property标签使用set方法进行赋值。
- javabean的属性名是由set方法决定的。例如set方法为
setLastName()
则属性为lastName
。
5、从容器中获取bean的几种方式。
1、根据bean组件的唯一标识符id获取。
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
//spring容器帮我们创建对象
Person persoon01 = (Person) context.getBean("persoon01");
System.out.println(persoon01);
}
2、根据组件的类型(class)获取。
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
Person persoon01 = context.getBean(Person.class);
System.out.println(persoon01);
}
这种方法获取组件不用进行类型的强制转换,但是可能出现下面的异常错误。
如果容器中注册多个同类型(class)的组件时,spring会报错。NoUniqueBeanDefinitionException
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.gzk.bean.Person' available: expected single matching bean but found 2: persoon01,persoon
3、根据组件的id和类型同时获取组件
public void test3() {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
Person person = context.getBean("persoon01", Person.class);
System.out.println(person);
}
6、通过构造器为bean的属性赋值
constructor-arg标签
1、配置文件
<bean id="person02" class="com.gzk.bean.Person">
<!--
name:必须和构造方法中的参数名相同
-->
<constructor-arg name="lastName" value="李明明"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="email" value="mingming@qq.com"/>
<constructor-arg name="gender" value="男"/>
</bean>
2、测试
public void test4() {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
Person person = context.getBean("person02", Person.class);
System.out.println(person);
}
说明一下:
- 还可以将 name 属性进行省略,这时参数的顺序必须和构造方法一致。
- 还可以增加index属性,为参数指定索引位置。
- 还可以增加type属性,指定参数的类型
- 以上三种都不推荐使用,因为对于某些情况的处理不足。使用name即可,简简单单多好啊。
7、使用 p 名称空间为属性赋值
- 名称空间是用来防止标签的重复问题,与java中的package(包)作用类似。
- 使用
名称空间
必须导入名称空间。
1、引入 p 名称空间
2、使用 p 名称空间,编写配置文件,不推荐,属性多了导致xml格式混乱。
<bean id="person02" class="com.gzk.bean.Person"
p:age="18"
p:email="ha@qq.com"
p:gender="男"
p:lastName="Libai"
/>
8、给 bean 的各种类型属性赋值。【数组,列表,map ,set,对象】
所有的复杂类型的值都在 标签
Person对象,Person.java
public class Person {
private String lastName;
private Integer age;
private String gender;
private String email;
private Car car;
private List<Book> books;
private Map<String,Object> map;
private Properties properties;
}
1、基本的类型直接使用value
进行赋值
<bean id="person01" class="com.gzk.bean.Person">
<!--
property:给组件的属性赋值,实质通过set方法赋值
-->
<property name="lastName" value="guozhikang"/>
<property name="age" value="18"/>
<property name="email" value="emial@qq.com"/>
<property name="gender" value="男"/>
</bean>
2、null
给引用对象赋空(null)值。
<property name="lastName">
<null/>
</property>
3、ref
给引用外部bean给对象赋值
<bean id="car01" class="com.gzk.bean.Car">
<property name="carName" value="宝马"/>
<property name="carPrice" value="100000"/>
<property name="color" value="绿色"/>
</bean>
<bean id="person01" class="com.gzk.bean.Person">
<property name="car" ref="car01"/>
</bean>
注意:如果通过ref
给person
设置属性,那么 person.getCar() == context.getBean(“car01”,Car.class); 是成立的。也就是person中的car与ioc容器中的car是同一个。
Car car1 = person01.getCar();
Car car2 = context.getBean("car01",Car.class);
System.out.println(car1 == car2); //输出结果为True
4、通过内部bean标签给引用对象赋值
<bean id="person01" class="com.gzk.bean.Person">
<property name="car">
<bean class="com.gzk.bean.Car">
<property name="carName" value="自行车"/>
<property name="carPrice" value="100"/>
<property name="color" value="红色"/>
</bean>
</property>
</bean>
注意:使用内部bean
创建car
,形式类似 Car car = new Car();
5、List 标签给List
类型进行赋值
<bean id="person01" class="com.gzk.bean.Person">
<property name="books">
<list>
<bean class="com.gzk.bean.Book">
<property name="bookName" value="西游记"/>
<property name="author" value="吴承恩"/>
</bean>
<ref bean="book01"/>
</list>
</property>
</bean>
6、map标签给 map 类型进行赋值
<bean id="person01" class="com.gzk.bean.Person">
<property name="map">
<map>
<!--一个entry代表一对key-value-->
<entry key="key01" value="张三"/>
<entry key="key02" value="18"/>
<entry key="key03" value-ref="book01"/>
<entry key="key04">
<bean class="com.gzk.bean.Car" p:carName="保时捷"/>
</entry>
</map>
</property>
</bean>
注意:map
标签在底层使用的LinkedHashMap
.
7、props标签给properties类型赋值
<bean id="person01" class="com.gzk.bean.Person">
<property name="properties">
<props>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
注意:properties类型的所有的 key – value 都是字符串类型。
8、使用util名称空间创建集合类型的bean
使用util
名称空间创建外部集合类型的bean
1、导入util
名称空间[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0gsrbRL3-1600505372964)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596377255046.png)]
1、使用util名称空间创建外部的map集合
<util:map id="map01">
<entry key="key01" value="张三"/>
<entry key="key02" value="18"/>
<entry key="key03" value-ref="book01"/>
<entry key="key04">
<bean class="com.gzk.bean.Car" p:carName="保时捷"/>
</entry>
</util:map>
<bean id="person01" class="com.gzk.bean.Person">
<property name="map" ref="map01"/>
</bean>
9、级联属性赋值 ,重点
级联属性:属性的属性
<bean id="person02" class="com.gzk.bean.Person">
<property name="car" ref="car01"/>
<property name="car.carPrice" value="1000"/>
<property name="car.color" value="yellow"/>
<property name="car.carName" value="QQ"/>
</bean>
注意:
使用ref
必须注意,因为它会修改容器中的对象。
10、通过 parent 继承实现bea的配置信息的重用
注意:仅仅标识配置信息的继承
11、通过 abstract 属性创建一个模板bean
注意:通过 abstract 属性指定某个bean为抽象,不能获取实例,只能被其他bean继承
12、bean之间的依赖
13、bean的作用域(scope)、重点
bean的作用域是指bean对象是单实例还是多例。
注意:
scope有哪些选择范围:
-
singleton:单实例,默认
在容器启动完成之前,bean对象就创建完成了,保存到容器中了。任何时候获取都是得到之前创建好了的。
-
prototype:多实例
多实例bean容器启动默认不会创建bean对象,当获取的时候创建bean。每次获取都会创建一个新的对象。
-
request:web情况下使用,一次请求会创建一个bean。(没用)
-
session:web情况下使用,一次回话会创建一个bean。(没用)
14、spring配置工厂模式创建对象、重点
bean的创建默认是spring使用反射邦我们创建的。
工厂模式:工厂帮我们创建对象。
1、静态工厂
工厂本身不用创建对象,都是通过静态方法调用。
1、我们自己创建一个静态工厂
public class AirPlaneStaticFactory {
//静态工厂方法
public static AirPlane getAirPlane(String jzName){
AirPlane airPlane = new AirPlane();
airPlane.setFdj("太行");
airPlane.setFjsName("lfy");
airPlane.setJzName(jzName);
airPlane.setPersonNum(300);
airPlane.setFjsName("黎明");
airPlane.setYc("189cm");
return airPlane;
}
}
2、在xml中配置该静态工厂,让spring使用该工厂创建对象。
<!--静态工厂配置-->
<bean id="airplane01" class="com.gzk.factory.AirPlaneStaticFactory" factory-method="getAirPlane">
<constructor-arg value="李四" name="jzName"/>
</bean>
2、实例工厂
工厂本身需要创建对象,需要new一个工厂实例。
1、我们自己去创建一个实例工厂
public class AirPlaneInstanceFactory {
//实例工厂方法
public AirPlane getAirPlane(String jzName){
AirPlane airPlane = new AirPlane();
airPlane.setFdj("太行");
airPlane.setFjsName("lfy");
airPlane.setJzName(jzName);
airPlane.setPersonNum(300);
airPlane.setFjsName("黎明");
airPlane.setYc("189cm");
return airPlane;
}
}
2、在xml中配置该实例工厂,让spring使用该工厂创建对象。
<!--实例工厂配置-->
<bean id="airPlaneInstanceFactory" class="com.gzk.factory.AirPlaneInstanceFactory"/>
<bean id="airplane02" class="com.gzk.bean.AirPlane"
factory-bean="airPlaneInstanceFactory"
factory-method="getAirPlane">
<constructor-arg value="张三"/>
</bean>
3、实现 FactoryBean 的工厂
FactoryBean
是spring
为我们提供的一个工厂接口,只要实现了这个接口,spring
就知道该类就是一个工厂方法。
静态工厂和实例工厂都是我们自己定义了一个工厂类,然后通过xml的配置告知spring我们这是一个工厂,然后spring通过我们的配置使用我们自定义的工厂帮我们创建对象。
通过实现FactoryBena接口的类,spring不用通过xml配置也知道该类是一个工厂,因为FactoryBena就是spring给我们提供的接口。
1、实现 FactoryBena接口。
public class MyFactoryBean implements FactoryBean<AirPlane> {
public AirPlane getObject() throws Exception {
return new AirPlane();
}
public Class<?> getObjectType() {
return AirPlane.class;
}
}
2、在spring配置文件中注册该工厂。
<bean class="com.gzk.factory.MyFactoryBean" id="myFactoryBean"/>
总结:
- 自定义的工厂类spring会在容器何时调用工厂方法为我们创建对象取决于scope属性,如果scope为singleton,spring会在容器创建时调用工厂方法给我们创建对象。如果为prototype,spring会在获取对象时,调用工厂方法帮我创建对象 。然而,实现FactoryBean的工厂,无论scope属性是啥,spring都是在我们获取对象时调用工厂方法去创建对象。
15、bean对象的生命周期
生命周期:bean的创建和销毁。
单实例bean:在容器启动时创建,在容器关闭时销毁。
多实例bean:获取对象时创建。
我们可以为bean自定义一些生命周期方法,spring在创建、销毁的时候会调用这些方法。
1、在bean对象中定义生命周期方法
public void bookInit(){
System.out.println("Book 初始化方法");
}
public void BookDestroy(){
System.out.println("Book 销毁方法");
}
2、在spring配置文件中指定生命周期方法
<bean id="book01" class="com.gzk.bean.Book" init-method="bookInit" destroy-method="BookDestroy" scope="prototype">
<property name="bookName" value="陆垚知马俐"/>
</bean>
3、定义init-method
方法的规则
4、定义destroy-methord
方法的规则
5、单例bean的生命周期:
- 构造器
- 属性设置set方法(如果有属性设置的话)
- 初始化init-method方法
- 销毁方法 的story-method方法
- 容器销毁
6、多实例的生命周期,销毁方法是永远不会执行的。
- 获取bean的时候执行构造器
- 属性设置set方法(如果有属性设置的话)
- 初始化 init-metod 方法
- 容器销毁
16、bean的后置处理器
spring有一个接口BeanPostProcessor
,该接口就是后置处理器。后置处理器的作用就是在初始化bean的前后调用方法。无论是否有初始化方法,后置处理器都会正常工作。
后置处理器可以在bean的初始化前后对bean进行改造。
1、实现BeanPostProcessor
接口。
public class MyBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
System.out.println(beanName + "将要被初始化了");
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println(beanName + "初始化了完毕了");
return bean;
}
}
2、将后置处理器注册在spring容器中
<bean class="com.gzk.bean.MyBeanPostProcessor" id="myBeanPostProcessor"/>
17、引用外部属性文件
我们主要使用其管理连接池。数据库连接池作为单实例是最好的,一个项目就一个连接池,连接池中管理很多连接,连接是直接从连接池中获取的。
数据库连接池是单例的这一特性使用我们是spring容器去实现是很方便的。
1、不适用外部属性文件配置数据库连接池
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="c3p0-dataSource">
<property name="user" value="root"/>
<property name="password" value="root"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
</bean>
2、使用外部属性文件配置数据库连接池
引入外部属性文件需要引入 context 名称空间
-
引入 context 名称空间
-
引入外部匹配置文件
<context:property-placeholder location="classpath:jdbc.properties"/>
3. 获取配置文件中的值
使用${key}
获取外部属性文件的值
上面的配置还有一点问题就是在外部的属性配置文件中的key
不能使用spring中的关键字。例如username
是spring的关键字,所以在使用${username}
获取值的话,会获取到spring
中的关键字的值,而不是我们外部属性配置文件指定的值。所以要注意这一点。
针对以上问题,我们一般给我们的外部属性文件的key
添加前缀。例如:
jdbc.username=root
jdbc.password=root
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/test
jdbc.driverClass=com.mysql.jdbc.Driver
18、基于xml的自动装配
自动装配,也就是自动赋值。
java
中的基本类型不会使用自动装配,它没这回事。
java
中的自定义类型(Javabean)的属性是一个对象,这个对象可能在容器中存在,那么这个属性就可自动装配。
1、首先我们来看看一般我们是如何给属性进行赋值的。也就是手动赋值。
<bean class="com.gzk.bean.Car" id="car01">
<property name="carName" value="小黄车"/>
<property name="color" value="绿色"/>
</bean>
<bean class="com.gzk.bean.Person" id="person01">
<!--手动为 car 属性赋值-->
<property name="car" ref="car01"/>
</bean>
2、自动装配,自动赋值
<bean class="com.gzk.bean.Person" id="person02" autowire="byName"/>
3、自动装配策略
自动装配策略 | 策略的含义 |
---|---|
autowire="default" | spring 默认情况,不自动装配 |
autowire="no" | 与 default 一样,不自动装配 |
autowire="byName" | 根据属性的名字自动装配,如果没有和属性名一样id的bean对象,就装配NULL |
autowire="byType" | 根据属性的类型作为查找条件去容器中找这个组件。如果容器中存在多个这个类型的组件,将会报错。如果没有找到该类型的组件,将会装配null |
autowire="constructor" | 根据构造器进行赋值,先按照有残构造器的参数类型进行装配,如果容器中没有这种类型的组件,则装配null,如果容器中有多个这种类型的组件,则会将参数名作为id进行装配,找到装配,找不到装配null 。 |
说明:如果属性有List<Book> books
这个属性,装配策略为byType
时,容器将会把容器中所有的book封装到list赋值给这个属性。
自动装配只适用于自定义类型的属性
19、spring 表达式语言 SPEL
<bean class="com.gzk.bean.Person" id="person01">
<!--字面量-->
<property name="age" value="#{12*5-20}"/>
<!--引用其他bean的属性-->
<property name="lastName" value="#{car01.carName}"/>
<!--引用其他bean-->
<property name="car" value="#{car01}"/>
<!--调用静态方法-->
<property name="email" value="#{T(java.util.UUID).randomUUID().toString().replace('-','.')}"/>
<!--调用非静态方法-->
<property name="gender" value="#{car01.showInfo('nihao1')}"/>
</bean>
20、Spring注解
通过给bean
上添加注解,可以快速的的将bean
加入到ioc
容器中。
spring中有四个注解
- @Controller :控制器层(servlet)
- @Service :业务逻辑层(service)
- @Repository :数据库交互层(dao)
- @Component :给其他类型组件
注意:
注解可以随便加,spring不会验证你个组件是否为所标注的注解所说的类型。但我们推荐各自层加各自注解。
1、使用注解将组件注册到容器中的步骤。
-
给要注册的组件上标四个注解的任何一个。
-
告诉spring,自动扫明加了注解的组件,这个工作依赖
context
名称空间。<context:component-scan base-package="com.gzk" ></context:component-scan>
3. 导入spring的aop
包
2、使用注解修改组件的行为
-
修改组件的
id
:设置注解的value属性即可@Controller(value = "con1") public class BookServlet { } //===================================== @Repository(value = "dao") public class BookDao { } //===================================== @Service(value = "ser1") public class BookService { }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NCqCS2nX-1600505373023)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596523494996.png)]
-
修改组件的作用域,是否为单例,使用scope注解。
@Controller(value = "con1") @Scope(value = "singleton") public class BookServlet { }
3、context:exclude-filter包扫描时排除某些包
<!--开启基于注解的包扫描-->
<context:component-scan base-package="com.gzk" >
<!--扫描时排除一些组件-->
<!--
type:指定排除规则
type="annotation":根据注解排除 expression="":注解全类名
type="assignable":指定排除某个类 expression="":类的全类名
type="aspectj":根据aspectj表达式排除 expression="":
type="custom": expression="":
type="regex":根据正则表达式 expression="":
-->
<context:exclude-filter type="annotation"expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
4、context:include-filter 指定只扫描那些包
<!--开启基于注解的包扫描-->
<context:component-scan base-package="com.gzk" use-default-filters="false">
<!--指定只扫描那些组件-->
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<!--指定排除那些组件-->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>
5、注意
- 加了注解的组件的
id
就是组件类名首字母小写 - 使用注解的组件和以前使用
xml
配置文件配置的组件的默认行为一致。
21、使用@Autowired
注解实现根据类型自动装配
1、示例
@Service
public class BookService {
@Autowired
BookDao bookDao;
public void save(){
System.out.println("bookService 正在调用dao帮您保存图书");
}
}
2、@AutoWired 原理
- 先按照属性的类型去容器中找到对应组件
- 找到一个,进行属性自动装配
- 没有找到,报错,抛异常
- 找到多个
- 按照变量名作为id继续匹配。
- 通过id找到,进行属性自动装配
- 通过id没有找到,报错,抛异常。
- 没有匹配上的原因是因为默认使用属性名作为
id
去匹配的,我们可以使用==@Qualifier()
==这个注解告知spring要匹配bean的id。- 匹配上,进行属性的自动装配
- 没有找到,报错
- 没有匹配上的原因是因为默认使用属性名作为
- 按照变量名作为id继续匹配。
@Controller
@Scope(value = "singleton")
public class BookServlet {
@Qualifier("bookServiceExt")
@Autowired
BookService bookService;
public void doGet(){
bookService.save();
}
}
3、使用@Autowired(required = false)的required 属性指定该属性是否必须被装配
在以上使用@Autowired()
注解进行自动装配时,如果没有装配上则会报错。我们可以使用@Autowired的required 属性,指定该属性是否必须被装配。
@Controller
@Scope(value = "singleton")
public class BookServlet {
@Qualifier("bookServiceExt")
@Autowired(required = false)
BookService bookService;
public void doGet(){
bookService.save();
}
}
4、在方法形参上使用@Autowired()
1、方法上有@Autowired()
的作用
- 方法会在bean创建的时候自动执行
- 这个方法的每一个参数都会自动注入值
@Autowired
public void haha(@Qualifier("bookServiceExt") BookService bookService){
bookService.save();
}
2、运行原理
同上。
5、@Autowired和@Resource的区别
@Resource -----------------> javax.annotation.Resource;
@Autowired -----------------> org.springframework.beans.factory.annotation.Autowired;
@Autowired更加强大,@Resource 扩展性更强
22、spring的单元测试
1、步骤
-
导入
spring-test-5.2.0.RELEAS.jar
包<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.0.RELEASE</version> <scope>test</scope> </dependency>
-
使用
@ContextConfiguration(locations = "classpath:ioc1.xml")
指定spring
配置文件的位置@ContextConfiguration(locations = "classpath:ioc1.xml") public class MyTest1 { }
-
@RunWith(SpringJUnit4ClassRunner.class)
指定使用哪种驱动进行单元测试,默认是junit
,这里修改为spring的单元测试模块执行。@ContextConfiguration(locations = "classpath:ioc1.xml") @RunWith(SpringJUnit4ClassRunner.class) public class MyTest1 { }
2、最终结果
@ContextConfiguration(locations = "classpath:ioc1.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class MyTest1 {
@Autowired
BookService bookService;
@Test
public void test1(){
System.out.println(bookService);
}
}
23、泛型依赖注入
1、泛型依赖注入原理
结论:spring中可以使用带泛型的父类来确定子类的类型。
注意:内部bean无法在外部引用
二、AOP:面向切面编程功能,重要
AOP:ASpect Oriented Programming,面向切面编程
OOP:Object Oriented Programming,面向对象编程
AOP基于OOP基础之上的新的编程思想
AOP的作用:在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的编程方式
1、Java动态代理实现AOP
1、简介
通过动态代理给当前对象设置一个代理对象,然后使用代理对象执行目标方法,这样就可以在代理对象执行目标方法的前后去插入我们呢想要执行的代码,从而达到切面编程的功能。通过这种方式不需要将要插入的代码硬编码到当前对象中,只是将要需要插入的代码写入到被代理对象中,从而实现解耦。
2、使用java.lang.reflect.Proxy
给当前类生成代理对象
package com.gzk.inter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class CalculatorProxy {
public static Calculator getCalculator(final Calculator calculator){
ClassLoader classLoader = calculator.getClass().getClassLoader();
Class<?>[] interfaces = calculator.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(calculator, args);
return result;
}
};
Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return (Calculator) o;
}
}
3、给计算器类添加日志
package com.gzk.inter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class CalculatorProxy {
public static Calculator getCalculator(final Calculator calculator) {
ClassLoader classLoader = calculator.getClass().getClassLoader();
Class<?>[] interfaces = calculator.getClass().getInterfaces();
InvocationHandler invocationHandler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "开始执行了,方法参数为" + Arrays.asList(args));
Object result = null;
try {
result = method.invoke(calculator, args);
System.out.println(method.getName() + "执行完毕了,结果为" + result);
} catch (Exception e) {
System.out.println(method.getName()+"执行出现异常了:"+e.getCause());
}finally {
System.out.println(method.getName()+"最终执行完成了");
}
return result;
}
};
Object o = Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
return (Calculator) o;
}
}
4、动态代理的问题:
- 难
- 对于没有实现任何接口,是无法为他创建代理对象的。
5、代理对象与被代理对象
代理对象与被代理对象唯一能产生的关联就是实现了统一个接口
java JDK
默认的动态代理,如果目标对象没有实现任何接口是无法为他创建代理对象的。
2、spring实现AOP功能
Spring底层使用动态代理实现AOP功能,spring可以实现一句代码去实现AOP
spring实现AOP非常简单,而且没有强制要强目标对象必须存在接口
1、AOP专业术语
2、AOP的使用步骤
-
导包
spring支持面向切面编程的包
- spring-aspects,spring基础版的切面编程
- com.springsource.org.aopalliance:一下三个为spring的插件,加强版的面向切面编程,既是目标对象没有任何接口也能创建动态代理
- com.springsource.net.sf.cglib
- com.springsource.org.aspectj.weaver
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.0.RELEASE</version> </dependency> <dependency> <groupId>net.sourceforge.cglib</groupId> <artifactId>com.springsource.net.sf.cglib</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.aopalliance</groupId> <artifactId>com.springsource.org.aopalliance</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>com.springsource.org.aspectj.weaver</artifactId> <version>1.6.8.RELEASE</version> </dependency>
-
写配置
- 将目标类和切面类(封装了通知方法的类)【通知方法就是在目标方法前后执行的方法】,加入ioc容器。
将目标类加入IOC容器
将切面类加入IOC容器中
在Spring配置文件中开启基于注解的包扫描
2. 告知spring那个是切面类@Aspect
3. 告知spring切面类的每一个通知方法都是在何时何地运行/** * @Before 在目标方法执行前执行 前置通知 * @After 在目标方法结束之后执行 后置通知 * @AfterThrowing 在目标方法抛出异常之后执行 异常通知 * @AfterReturning 在目标方法正常返回之后执行 返回通知 * @Around 环绕通知 */ package com.gzk.utils; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class LogUtils { //想在目标方法执行之前执行 @Before("execution(public int com.gzk.inter.MyCalculator.*(int,int))") public static void logStart() { System.out.println("开始执行了,方法参数为"); } //想在目标方法正常执行完成之后执行 @AfterReturning("execution(public int com.gzk.inter.MyCalculator.*(int,int)))") public static void logResult() { System.out.println("执行完毕了,结果为"); } //想在目标方法发生异常时执行 @AfterThrowing("execution(public int com.gzk.inter.MyCalculator.*(int,int)))") public static void logException() { System.out.println("xxx" + "执行出现异常了"); } //想在目标方法执行结束之后执行 @After("execution(public int com.gzk.inter.MyCalculator.*(int,int)))") public static void logEnd() { System.out.println("xxxx" + "最终执行完成了"); } }
4. 开启基于注解的AOP功能
<aop:aspectj-autoproxy />
3. 测试
@ContextConfiguration(locations = "classpath:aop.xml")
@RunWith(SpringJUnit4ClassRunner.class)
public class AopTest {
@Autowired
Calculator calculator;
@Test
public void test1(){
calculator.add(1,2);
}
}
3、AOP细节
1、AOP细节一:IOC容器中保存的是组件的代理对象
AOP的底层就是动态代理。容器中保存的组件是代理对象。也就是我们不能从容器中获取当前类的实例,因为容器中保存的是该对象的代理对象。那我们如何从容器中获取当前对象的代理对象呢。其中一种方式就是那当前类的接口类型去获取,因为代理类和当前类都实现当前类的接口。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nh6Anx0L-1600505373064)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596689347678.png)]
补充:cglib为没有接口的的组件创建代理对象。
上面我们知道,我们可以使用当前类的接口类型从容器中获取当前类的代理对象。那如果当前类没有实现任何接口的话,如何解决呢?如果当前类没有实现任何接口,那么我们可以使用当前类类型去获取代理对象。这时的代理对象是CGlib
帮我们创建的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GDn3XY4K-1600505373065)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596689140119.png)]
2、AOP细节二:切入点表达式写法(通配符)
固定格式:execution(访问权限符 返回值类型 方法全类名(参数表))
通配符:
- *:匹配一个或多个字符,一层路径
- *:匹配任意一个参数
- …:匹配任意多个参数,任意参数类型
- …:匹配多层路径
- 权限位置不能写 * ,但权限修饰符可以省略
3、AOP细节三:通知方法的执行顺序
1、正常执行:
- 前置通知@Before
- 后置通知@After
- 返回通知@AfterReturning
2、异常执行
- 前置通知@Before
- 后置通知@After
- 异常通知@AfterThrowing
4、AOP细节四:JoinPoint获取目标方法的详细信息
做法:只需为通知方法的参数列表添加一个JoinPoint
参数。
JoinPoint封装了当前方法的详细信息。
@Before("execution(public int com.gzk.inter.MyCalculator.*(int,int))")
public static void logStart(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
Object[] args = joinPoint.getArgs();
System.out.println(name+"开始执行了,方法参数为"+ Arrays.asList(args));
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NE8MDW1L-1600505373066)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596692322178.png)]
5、AOP细节五:throwing,returning来指定那个参数
解决返回通知获取返回结果和异常通知获取异常的问题。
//想在目标方法正常执行完成之后执行
@AfterReturning(value="execution(public int com.gzk.inter.MyCalculator.*(int,int)))",returning = "result")
public static void logResult(JoinPoint joinPoint,Object result) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name+"执行完毕了,结果为"+result);
}
//想在目标方法发生异常时执行
@AfterThrowing(value = "execution(public int com.gzk.inter.MyCalculator.*(int,int)))",throwing = "e")
public static void logException(JoinPoint joinPoint,Exception e) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "执行出现异常了"+e.getCause());
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TZ3VrT90-1600505373067)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596693101414.png)]
6、AOP细节六:spring对通知方法的约束
spring唯一要求通知方法的参数列表一定不能乱写,因为通知方法时spring自动调用的,所以参数表上的任何参数spring必须知道是啥。
补充:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tqyw7YBR-1600505373068)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596699349819.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i2Krr7xQ-1600505373069)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596699582419.png)]
7、AOP细节七:抽取可重用的切入点表达式
步骤:
- 随便声明一个没有实现的返回void的空方法。
- 给方法上标注@PonitCut注解
@Pointcut("execution(public int com.gzk.inter.MyCalculator.*(int,int))")
public void myPoint(){
}
//想在目标方法执行之前执行
@Before(value = "myPoint()")
public static void logStart(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
Object[] args = joinPoint.getArgs();
System.out.println(name+"开始执行了,方法参数为"+ Arrays.asList(args));
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FLGK7BAI-1600505373070)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596701218535.png)]
8、AOP细节八:环绕通知
@Around 环绕通知是spring中最强大的一个通知,本身就是一个动态代理。
环绕通知方法有一个牛逼的参数ProceedingJoinPoint
,环绕通知就是因为这个参数而牛逼的。
@Around("myPoint()")
public Object myAround(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
Object proceed=null;
try {
System.out.println(name+"环绕前置通知执行了,方法参数为:"+args);
proceed = joinPoint.proceed(args);
System.out.println(name+"环绕返回通知执行了,结果为:"+proceed);
} catch (Throwable throwable) {
System.out.println(name+"环绕异常通知执行了,异常信息为:"+throwable);
throwable.printStackTrace();
} finally {
System.out.println(name+"环绕后置通知执行了");
}
return proceed;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S1QjiyLM-1600505373072)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596704042204.png)]
9、AOP细节九:环绕通知的执行顺序&抛出异常让其他通知方法感受到
1、环绕通知优先于其他通知方法执行。执行顺序如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Az3BcmxM-1600505373074)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596705038301.png)]
2、环绕通知与其他通知一起工作的时候的问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TYEE3UBt-1600505373075)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596705171456.png)]
为何环绕通知发生了异常,但为何其他通知却正常返回了,而且返回结果为null。这是因为,在环绕通知中我们使用try - catch - finally 将执行目标方法的语句进行了包围,于是当目标方法执行发生异常时被环绕通知获取了,并且使用try - catch - finally 进行了捕获,那么其他通知就无法获取到异常信息了,所以其他通知正常结束,返回null值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dussVgqG-1600505373076)(C:\Users\21232\AppData\Roaming\Typora\typora-user-images\1596705608369.png)]
解决方法:在环绕通知中将捕获的异常抛出去,使得其他通知方法仍然可以感受到异常的存在。
@Around("myPoint()")
public Object myAround(ProceedingJoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String name = joinPoint.getSignature().getName();
Object proceed=null;
try {
System.out.println(name+"环绕前置通知执行了,方法参数为:"+Arrays.asList(args));
proceed = joinPoint.proceed(args);
System.out.println(name+"环绕返回通知执行了,结果为:"+proceed);
} catch (Throwable throwable) {
System.out.println(name+"环绕异常通知执行了,异常信息为:"+throwable);
throw new RuntimeException(throwable);
} finally {
System.out.println(name+"环绕后置通知执行了");
}
return proceed;
}
10、AOP细节十:多切面运行顺序
至于先进入那个切面的问题,spring可以使用@order
注解来注明切面的顺序。如果切面@order
指定的顺序相同,则根据切面的类名排序来确定切面执行的顺序。
1、使用@Order
注解来确定切面的顺序
@Aspect
@Component
@Order(1)
public class LogUtils {
@Pointcut("execution(public int com.gzk.inter.MyCalculator.*(int,int))")
public void myPoint(){
}
多层AOP其实就是动态代理的嵌套。
切面一:
try{
@before:前置通知
method.invoke()【joinPoint.proceed(args)】{:目标方法的执行
/******************************************/
切面二:
try{
@befor:前置通知
method.invoke()【joinPoint.proceed(args)】:目标方法的执
@AfterReturning:返回通知
}catch(Execption e){
@AfterThrowing:异常通知
}finally{
@After:返回通知
}
/******************************************/
}
@AfterReturning:返回通知
}catch(Execption e){
@AfterThrowing:异常通知
}finally{
@After:返回通知
}
4、AOP的使用场景
- AOP加日志保存到数据库中
- AOP全权限验证,可以替代Filter
- AOP做安全检查
- AOP做事务控制
5、基于配置的AOP
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册组件-->
<bean class="com.gzk.inter.MyCalculator" id="myCalculator"/>
<bean class="com.gzk.utils.LogUtils" id="logUtils"/>
<bean class="com.gzk.utils.LogUtils2" id="logUtils2"/>
<!--切面的配置-->
<aop:config>
<!--指定全局的切入点表达式-->
<aop:pointcut id="globalPoint" expression="execution(* com.gzk.inter.*.*(..))"/>
<!--指定那个是切面-->
<aop:aspect ref="logUtils" order="1">
<!--切面内部的切入点表达式-->
<aop:pointcut id="mypoint" expression="execution(* com.gzk.inter.*.*(..))"/>
<!--配置通知方法-->
<aop:before method="logStart" pointcut="execution(* com.gzk.inter.*.*(..))"/>
<aop:before method="logEnd" pointcut-ref="mypoint"/>
<aop:after-returning method="logResult" pointcut-ref="mypoint" returning="result"/>
<aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="e"/>
<aop:around method="myAround" pointcut-ref="mypoint"/>
</aop:aspect>
<!--配置切面-->
<aop:aspect ref="logUtils2" order="10">
<aop:before method="logStart" pointcut="execution(* com.gzk.inter.*.*(..))"/>
<aop:before method="logEnd" pointcut-ref="mypoint"/>
<aop:after-returning method="logResult" pointcut-ref="mypoint" returning="result"/>
<aop:after-throwing method="logException" pointcut-ref="mypoint" throwing="e"/>
</aop:aspect>
</aop:config>
</beans>
使用order属性指定切面执行的顺序,如果不指定,则按配置顺序执行。
6、注解和配置大PK
1、注解:快速方便
2、配置:功能完善、
3、重要的切面使用配置,不重要的切面使用注解。
三、JDBCTemplate(不重要)
四、声明式事务
编程式事务:
TransactionFilter{
try{
//获取连接
//设置事务非自动提交
chain.doFilter();
//提交事务
}catch(Exception e){
//出现异常,事务回滚
}finally{
//关闭连接,释放资源
}
}
声明式事务:AOP的环绕通知可以实现。
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理
spring中的事务切面称之为事务管理器
事务的四个特性
- 原子性
- 一致性
- 隔离性
- 持久性
事务管理器
可以在目标方法运行前后进行事务管理
1、事务控制案例
<?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: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">
<!--开启包扫描-->
<context:component-scan base-package="com.gzk"/>
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--配置数据源-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
</bean>
<!--配置jdbctemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务控制-->
<!--1、注册事务管理器,必须导入面向切面编程的几个包-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<!--事务控制器控制事务实际上是需要控制连接对象,所以控制数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--2、开启基于注解的事务控制-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--3、给事务方法添加事务-->
</beans>
注意:由于事务控制其实是利用AOP面型切面编程完成的,所以必须导入面向切面编程的jar包。
五、事务的细节
事务的细节:
/**
* 事务细节
* Propagation propagation;事务的传播行为
* Isolation isolation;事务的隔离级别
* int timeout;超时,事务超出指定时长后自动终止并回滚
* boolean readOnly;设置事务为只读事务
* Class[] rollbackFor();那些异常事务需要回滚
* String[] rollbackForClassName();那些异常事务需要回滚
* Class[] noRollbackFor();那些异常事务可以不回滚
* String[] noRollbackForClassName();那些异常事务可以不回滚
*/
1、事务细节一:超时设置
int timeout
超时,事务超出指定时长后自动终止并回滚,这里的时间为秒
@Transactional(timeout = 3)
public void checkout(String userName,String isbn){
//减库存
bookDao.updateStock(isbn);
//查价格
int price = bookDao.getPirce(isbn);
//减余额
bookDao.updateBalance(userName,price);
}
2、事务的细节二:事务的只读属性
boolean readOnly
设置事务为只读事务
@Transactional(readOnly = true)
public void checkout(String userName, String isbn) {
//减库存
bookDao.updateStock(isbn);
//查价格
int price = bookDao.getPirce(isbn);
//减余额
bookDao.updateBalance(userName, price);
}
readOnly
属性只适用于整个事务方法只有查询操作,将readOnly
设置为true可以进行事务优化,加快查询速度,不用管理事务的那一堆。但如果事务方法中有增删改的操作,这将会报错。
3、事务的细节三:指定默认运行时,那些异常不用回滚
Class[] noRollbackFor();那些异常事务可以不回滚,可以让原来默认回滚的异常不回滚。
String[] noRollbackForClassName();那些异常事务可以不回滚,可以让原来默认回滚的异常不回滚。
事务的回滚,默认发生运行时异常都回滚,发生编译时异常不会回滚
异常分类:
运行时异常:非检查异常,默认进行事务回滚
编译时异常:检查异常,可以使用 try - catch 或 throws处理,默认不回滚。
@Transactional(noRollbackFor = {ArithmeticException.class,NullPointerException.class})
public void checkout(String userName,String isbn){
//减库存
bookDao.updateStock(isbn);
//查价格
int price = bookDao.getPirce(isbn);
//减余额
bookDao.updateBalance(userName,price);
int i = 1/0;
}
String[] noRollbackForClassName()指定的是全类名,一般不使用。
运行时异常默认回滚。
让不回滚的事务回滚。
4、事务的细节四:指定默认运行时,原本不回滚的异常回滚
Class[] rollbackFor();那些异常事务需要回滚
String[] rollbackForClassName();那些异常事务需要回滚
public void checkout(String userName,String isbn){
//减库存
bookDao.updateStock(isbn);
//查价格
int price = bookDao.getPirce(isbn);
//减余额
bookDao.updateBalance(userName,price);
try {
new FileInputStream("d://eee.ext");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
5、事务的细节五、isolation指定事务运行级别
1、数据库并发的问题
- 脏读
- 不可重复读
- 幻读
2、数据库的隔离级别
- 读未提交,会发生脏读
- 读已提交,避免脏读
- 可重复读
- 串行化,数据库没有并发行为
@Transactional(isolation = Isolation.READ_COMMITTED)
public void checkout(String userName, String isbn) {
//减库存
bookDao.updateStock(isbn);
//查价格
int price = bookDao.getPirce(isbn);
//减余额
bookDao.updateBalance(userName, price);
}
6、事务的细节六,并发修改数据库时的排队
当两个事务并发修改数据库同一条数据库时,数据库底层会给事务进行排队。
7、事务的细节七,IOC容器中保存的时业务逻辑组件(具有事务)的代理对象
我们知道声明式事务其实是利用AOP面向切面编程实现的,AOP底层是动态代理实现的。所以我们从容器中获取的其实代理对象。
8、事务的细节八:事务的传播行为
事务的传播行为其实讨论的问题是:如果有多个事务进行嵌套运行,子事务是否要和大事务共用一个事务
事务的传播行为讨论的是当事务c【tx_c】出现异常时,事务b【tx_b】是否需要回滚,事务a【tx_a】是否需要回滚的问题。
Aservice{
//事务a
tx_a{
//事务a中嵌套了事务b
tx_b{
}
//事务a中嵌套了事务b,事务b与事务c同级
tx_c{
}
}
}
/**
*事务的传播行为讨论的是当事务c【tx_c】出现异常时,事务b【tx_b】是否需要回滚,事务a【tx_a】是否需要回滚的问题。
*/
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void checkout(String userName, String isbn) {
//减库存
bookDao.updateStock(isbn);
//查价格
int price = bookDao.getPirce(isbn);
//减余额
bookDao.updateBalance(userName, price);
}
@Transactional(propagation = Propagation.REQUIRED)
public void updatePrice(String isbn,int price){
bookDao.updatePrice(isbn,price);
int i = 10/0;
}
@Transactional
public void MulTx(){
bookService.checkout("Tom","ISBN-001");
bookService.updatePrice("ISBN-001",998);
}
在事务的传播行为时要考虑方法异常的处理。如果当前方法发生了异常,但是他没有进行处理。既是它开启了新的事务,当这个事务回滚时不会影响其他事务。但是由于异常没有处理,导致异常抛给了其他事务方法,导致其他事务进行了回滚。
9、事务的细节九、REQUIRED事务的属性来源于大事务
REQUIRED:将之前事务使用的connection对象传递给这个方法使用。
REQUIRES_NEW:方法直接使用新的connection对象。
10、事务细节十、本类事务方法之间的调用就只是一个事务
六、基于XML配置的事务
<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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 https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--
1、开启包扫描
-->
<context:component-scan base-package="com.gzk"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" value="#{dataSource}"/>
</bean>
<!--基于XML配置的事务,依赖tx名称空间和aop名称空间-->
<!--配置事务管理器,事务切面-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--给事务切面社会切入点表达式-->
<aop:config>
<!--设置一个切入点表达式-->
<aop:pointcut id="txPointCut" expression="execution(* com.gzk.service.*.*(..))"/>
<!--事务建议,事务增强,advice-ref指向事务管理器的配置-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPointCut"></aop:advisor>
</aop:config>
<tx:advice transaction-manager="transactionManager" id="myAdvice">
<!--事务属性-->
<tx:attributes>
<!--指明那些方法是事务方法,切入点表达式只是说,事务管理器要切入这些方法,那些方法需要进行事务管理需要使用tx:method指定-->
<tx:method name="*"/>
<!--可以设置事务方法的属性-->
<tx:method name="checkout" isolation="READ_COMMITTED" timeout="-1"/>
</tx:attributes>
</tx:advice>
</beans>
七、spring和javaweb整合使用
1、导包
2、写配置
3、测试
1、使用监听器启动ioc容器
项目启动{
ioc创建完成
}
项目销毁{
ioc 销毁
}
可以写一个监听器完成这个工作。spring推荐我们这样操作,而且这个监听器spring都帮我们写好了。
1、在web.xml中配置,该监听器。
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:Spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
2、使用ContextLoader获取ioc容器
public static <T>T getBean(Class<T> clazz){
//获取ioc容器
WebApplicationContext ioc = ContextLoader.getCurrentWebApplicationContext();
return ioc.getBean(clazz);
}
更多推荐
所有评论(0)