第一章 初识Spring
1.1 Spring简介
-
Spring是一个为简化企业级开发而生的开源框架。
- 开源:开放源代码
-
Spring是一个IOC(DI)和AOP容器框架。
-
IOC全称:Inversion Of Control【控制反转】
- 控制反转:将对象控制权由程序员自己管理反转给Spring框架管理
-
DI全称:Dependency Injection【依赖注入】
- 依赖注入:Spring管理对象与对象之间的依赖关系
-
AOP全称:Aspect-Oriented Programming【面向切面编程设计】
- OOP:Object-Oriented Programming【面向对象编程设计】
-
-
Spring官网:https://spring.io
1.2 搭建Spring框架
-
导入jar包
<!--导入spring-context--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.1</version> </dependency> <!--导入junit4.12--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
-
创建POJO
public class Employee { private Integer id; //员工id private String lastName; //员工名称 private String email; //员工邮箱 private Double salary; //员工薪资 @Override public String toString() { return "Employee{" + "id=" + id + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + ", salary=" + salary + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } public Employee() { } public Employee(Integer id, String lastName, String email, Double salary) { this.id = id; this.lastName = lastName; this.email = email; this.salary = salary; } }
-
编写配置文件
-
名称:applicationContext.xml【beans.xml或spring.xml】
-
位置:src/main/resouces
-
示例代码
<?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"> <!-- 装配pojo对象 将empChai装配到的IOC容器中 --> <bean id="empChai" class="com.atguigu.pojo.Employee"> <property name="id" value="1001"></property> <property name="lastName" value="chails"></property> <property name="email" value="chails@163.com"></property> <property name="salary" value="1000.5"></property> </bean> </beans>
-
-
使用核心类库【容器对象:ApplicationContext】
@Test public void testSpring(){ //使用Spring之前 // Employee employee = new Employee(); //使用Spring之后 //创建IOC容器对象 ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); //从容器中获取对象 Employee empChai = (Employee) ioc.getBean("empChai"); System.out.println("empChai = " + empChai); }
1.3 Spring基本特性
- 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API。
- 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期。
- 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
- 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的JDBCTemplate)。
第二章 Spring中getBean()三种方式
-
getBean(String beanId):通过beanId获取对象
- 不足:需要强制类型转换
-
getBean(Class clazz):通过Class获取对象
-
不足:如容器中存在多个对象时,会报如下错
available: expected single matching bean but found 2: empChai,empLiu
-
-
getBean(String beanId,Class clazz):通过beanId&Class获取对象
- 推荐使用
第三章 Spring中IOC底层实现
- Spring底层就是一个对象工厂【BeanFactory】
- BeanFactory结构
- BeanFactory【面向Spring框架】
- …
- ApplicationContext【面向程序员】
- ConfigurableApplicationContext【是ApplicationContext的扩展对象,提供关闭&刷新容器对象的方法】
- …
- ClassPathXmlApplicationContext:基于类路径检索xml文件
- FileSystemXmlApplicationContext:基于系统路径检索xml文件
- …
- ConfigurableApplicationContext【是ApplicationContext的扩展对象,提供关闭&刷新容器对象的方法】
- ApplicationContext【面向程序员】
- …
- BeanFactory【面向Spring框架】
第四章 Spring中DI【依赖注入】三种方式
JavaSE为属性赋值
- 通过setXXX()方法,为属性赋值
- 通过构造器,为属性赋值
- 反射,为属性赋值
4.1 setter注入
-
语法:标签
- name属性:设置属性名称
- value属性:设置属性数值
-
示例代码
<bean id="empChai" class="com.dudu.pojo.Employee"> <property name="id" value="1001"></property> <property name="lastName" value="chailaoshi"></property> <property name="salary" value="10000.5"></property> <property name="email" value="chailaoshi@163.com"></property> </bean>
4.2 构造注入
-
语法:
- name属性:设置属性名称
- value属性:设置属性数值
-
示例代码
<!-- 装配刘老师--> <bean id="empLiu" class="com.dudu.pojo.Employee"> <constructor-arg name="id" value="1002"></constructor-arg> <constructor-arg name="lastName" value="liulaoshi"></constructor-arg> <constructor-arg name="salary" value="10000.5"></constructor-arg> <constructor-arg name="email" value="liulaoshi@163.com"></constructor-arg> </bean>
4.3 p名称注入
-
导入p名称空间
-
示例代码:
<?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:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="yulaoshi" class="com.dudu.pojo.Employee" p:id="1003" p:lastName="taotao" p:email="taotao@163.com" p:salary="10000.5"> </bean> </beans>
-
p名称空间注入,底层使用setter注入。
第五章 Spring中依赖注入【数值问题】
5.1 字面量数值
-
字面量
- 数据类型:基本类型&包装类型&String
- 语法结构:value属性或value标签
-
CDATA区
-
语法:<![CDATA[]]>
-
作用:解决xml中特殊字符
-
示例代码
<!-- mybatis映射文件 --> <select id="selectEmpAndDeptByEmpIdAssociationStep" resultMap="empAndDeptAssociationStepRm"> <![CDATA[ select id, last_name, email, salary, dept_id from tbl_employee where id = #{empId} ]]> </select> <!-- spring配置文件--> <bean id="empChai" class="com.dudu.pojo.Employee"> <property name="id" value="1001"></property> <property name="lastName"> <value><![CDATA[<chailaoshi>]]></value> </property> <property name="salary" value="10000.5"></property> <property name="email"> <value>chailaoshi@163.com</value> </property> </bean>
-
5.2 外部bean
-
注意:使用外部bean支持级联属性赋值,如级联属性数值更改,也会影响外部bean属性数值
-
示例代码
<!-- 定义dept的bean--> <bean id="dept1" class="com.dudu.pojo.Dept"> <property name="deptId" value="101"></property> <property name="deptName" value="研发部门"></property> </bean> <bean id="empJing" class="com.dudu.pojo.Employee"> <property name="id" value="1004"></property> <property name="lastName" value="jingjign"></property> <property name="email" value="jingjing@163.com"></property> <property name="salary" value="10000.5"></property> <property name="dept" ref="dept1"></property> <property name="dept.deptName" value="教学部门"></property> </bean>
5.3 内部bean
-
内部Bean:在一个bean中完整的定义另一个bean
-
注意:内部bean不会直接装配到IOC容器中
-
示例代码
<!-- 测试内部bean--> <bean id="empLi" class="com.dudu.pojo.Employee"> <property name="id" value="1005"></property> <property name="lastName" value="jinlong"></property> <property name="email" value="longge@163.com"></property> <property name="salary" value="10000.5"></property> <property name="dept"> <bean class="com.dudu.pojo.Dept"> <property name="deptId" value="102"></property> <property name="deptName" value="教务部门"></property> </bean> </property> </bean>
5.4 装配list
<!--list-->
<bean id="dept1" class="com.dudu.pojo.Dept">
<property name="deptId" value="1"></property>
<property name="deptName" value="教学部"></property>
<property name="empList">
<list>
<ref bean="empId"></ref>
<ref bean="empId1"></ref>
</list>
</property>
</bean>
<!--提取list-->
<util:list id="empList">
<ref bean="empId"></ref>
<ref bean="empId1"></ref>
</util:list>
<bean id="dept11" class="com.dudu.pojo.Dept">
<property name="deptId" value="1"></property>
<property name="deptName" value="教学部"></property>
<property name="empList" ref="empList"></property>
</bean>
5.5 装配map
<!--map-->
<bean id="dept2" class="com.dudu.pojo.Dept">
<property name="deptId" value="1"></property>
<property name="deptName" value="教学部"></property>
<property name="empMap">
<map>
<entry>
<key>
<value>111</value>
</key>
<ref bean="empId"></ref>
</entry>
<entry>
<key>
<value>222</value>
</key>
<ref bean="empId1"></ref>
</entry>
</map>
</property>
</bean>
<!--提取map-->
<util:map id="empMap">
<entry>
<key>
<value>111</value>
</key>
<ref bean="empId"></ref>
</entry>
<entry>
<key>
<value>222</value>
</key>
<ref bean="empId1"></ref>
</entry>
</util:map>
<bean id="dept22" class="com.dudu.pojo.Dept">
<property name="deptId" value="1"></property>
<property name="deptName" value="教学部"></property>
<property name="empMap" ref="empMap"></property>
</bean>
Test
public void testSpringGetBean(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// Employee empId = (Employee) context.getBean("empId");
// System.out.println(empId);
// System.out.println("====================================");
Employee bean = context.getBean(Employee.class);
System.out.println(bean);
// System.out.println("====================================");
// Employee empId1 = context.getBean("empId1", Employee.class);
// System.out.println(empId1);
Employee empId3 = context.getBean("empId3", Employee.class);
System.out.println(empId3);
System.out.println("======================");
//测试list
Dept dept1 = context.getBean("dept11", Dept.class);
System.out.println(dept1);
System.out.println("========================");
//测试map
Dept dept2 = context.getBean("dept22", Dept.class);
System.out.println(dept2);
}
导入Maven工程
第六章 Spring管理第三方Bean【DruidDataSource】
6.1 使用Spring管理DruidDataSource步骤
-
导jar包
<!--导入spring-context--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.1</version> </dependency> <!--导入junit4.12--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--导入druid的jar包--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!--导入mysql的jar包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.26</version> </dependency>
-
编写外部属性文件:db.properties
db.driverClassName=com.mysql.cj.jdbc.Driver db.url=jdbc:mysql://localhost:3306/db220227?serverTimeZone=UTC db.username=root db.password=12356
-
编写spring配置文件
- 加载外部属性文件
- 装配DruidDataSource
<?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" 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"> <!-- 加载外部属性文件--> <context:property-placeholder location="classpath:db.properties"></context:property-placeholder> <!-- 装配DruidDataSource--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${db.driverClassName}"></property> <property name="url" value="${db.url}"></property> <property name="username" value="${db.username}"></property> <property name="password" value="${db.password}"></property> </bean> </beans>
测试
public void testDruid() throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext_druid.xml");
DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
DruidPooledConnection connection = dataSource.getConnection();
System.out.println(connection);
}
第七章 Spring中FactoryBean【工厂bean】
7.1 FactoryBean的概述
- Spring中有两种类型的bean
- 一种是普通bean
- 另一种是工厂bean【可参与对象的创建过程中】
7.2 FactoryBean使用
public class MyFactoryBean implements FactoryBean<Dept> {
/**
* 参与对象创建过程中
* @return
* @throws Exception
*/
@Override
public Dept getObject() throws Exception {
Dept dept = new Dept(101,"研发部门");
return dept;
}
/**
* 返回参与创建对象Class
* @return
*/
@Override
public Class<?> getObjectType() {
return Dept.class;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dept" class="com.dudu.factory.MyFactoryBean"></bean>
</beans>
@Test
public void testFactoryBean(){
//创建容器对象
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext_factoryBean.xml");
//获取对象
Dept dept = context.getBean("dept", Dept.class);
System.out.println("dept = " + dept);
}
第八章 Spring中bean的作用域【重点】
8.1 语法
- 在bean标签中,添加scope属性,设置bean的作用域
- 作用:决定这个bean是单实例的还是多实例的。
8.2 bean的四个作用域
类别 | 说明 |
---|---|
singleton | 在Spring的IOC容器中仅存在一个Bean实例 |
prototype | 每次调用getBean方法都返回一个新的Bean实例 |
request | 每次HTTP请求都会创建一个新的Bean实例,该作用域仅适用于WebApplicationContext环境 |
session | 同一个Session会话共享一个Bean实例,该作用域仅适用于WebApplicationContext环境 |
- singleton:单例
- 创建容器对象时,spring创建Dept对象
- prototype:多例
- 调用getBean()方法时,spring创建Dept对象
- request作用域
- 表示bean在当前请求中有效,离开当前请求失效
- 如何高效区分是否为当前请求:看浏览器地址栏
- 地址栏无变化:表示当前请求
- 地址栏有变化:表示不在当前请求
- 如何高效区分是否为当前请求:看浏览器地址栏
- 表示bean在当前请求中有效,离开当前请求失效
- session作用域
- 表示bean在当前会话中有效,离开当前会话失效
- 如何高效区分是否在当前会话:看浏览器
- 如浏览器未关闭&未更换,默认即为当前会话
- 如浏览器关闭或更换,不为当前会话
- 如何高效区分是否在当前会话:看浏览器
- 表示bean在当前会话中有效,离开当前会话失效
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dept" class="com.dudu.pojo.Dept" scope="prototype">
<property name="deptId" value="101"></property>
<property name="deptName" value="研发部门"></property>
</bean>
</beans>
第九章 Spring中bean的生命周期&后置处理器
9.1 Spring中bean的生命周期
① 通过构造器或工厂方法创建bean实例
② 为bean的属性设置值和对其他bean的引用
③ 调用bean的初始化方法
④ bean可以使用了
⑤ 当容器关闭时,调用bean的销毁方法
Dept.java
省略了其他方法
public class Dept {
private Integer deptId;
private String deptName;
private List<Employee> empList;
private Map<Integer,Employee> empMap;
public void init() {
System.out.println("3、初始化");
}
public void destroy(){
System.out.println("5、销毁");
}
public void setDeptId(Integer deptId) {
System.out.println("2、设置bean");
this.deptId = deptId;
}
public Dept() {
System.out.println("1.无参构造");
}
}
applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--测试生命周期-->
<bean id="lifeCycle" class="com.dudu.pojo.Dept" init-method="init" destroy-method="destroy">
<property name="deptId" value="001"></property>
<property name="deptName" value="教育部"></property>
</bean>
<!--测试后置处理器-->
<bean class="com.dudu.processor.MyBeanPostProcessor"></bean>
</beans>
Test
public void testLifeCycle(){
ConfigurableApplicationContext context=new ClassPathXmlApplicationContext("applicationContext_lifecycle.xml");
Dept lifeCycle = context.getBean("lifeCycle", Dept.class);
System.out.println("4.使用 "+lifeCycle);
context.close();
}
9.2 设置生命周期的初始化&销毁bean对象语法
- 在bean标签中添加以下两个属性即可
- init-method:设置初始化方法
- destroy-method:设置销毁方法
9.3 后置处理器【BeanPostProcessor】
- 定义后置处理器
public class MyBeanPostProcessor implements BeanPostProcessor {
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean 初始化之前!!!");
return bean;
}
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("bean 初始化之后!!!");
return bean;
}
}
- 装配后置处理器
<!-- 测试后置处理器-->
<bean class="com.dudu.processor.MyBeanPostProcessor"></bean>
第十章 Spring中基于xml方式自动&手动装配
10.1 手动装配
- 手动装配:以value或ref的方式明确指定属性值都是手动装配。
10.2 自动装配
-
自动装配:不需要明确指定,Spring自动将匹配的属性值注入bean中。
- 语法:在bean标签中,添加autowire属性即可
-
autowire装配规则
-
byName:按照属性名称,与容器中id匹配
- 如容器存在【属性名称=id】对象,则装配成功
-
byType:按照属性类型,与容器中class匹配
-
如唯一匹配,则自动装配成功
-
如匹配0个,不会报错,不会装配,默认值为:null
-
如匹配多个,会报如下错误
available: expected single matching bean but found 2: employeeDao,employeeDao2
-
-
-
注意:
- 基于xml自动装配,只能装配**【非字面量】**数值
- 基于xml自动装配,底层使用setter注入
- 最终不推荐使用基于xml方式的自动装配,推荐使用基于注解自动装配
第十一章 Spring中基于注解管理bean【非常重要】
约束>【注解>配置】>代码
Spring中使用注解步骤
- 导入aop的jar包
- 开启组件扫描
11.1 使用注解管理【装配】bean
- 常用注解
- @Component:标识一个普通组件
- @Repository:标识一个持久化层组件
- @Service:标识一个业务逻辑层组件
- @Controller:标识一个【表示层、表述层、控制层】组件
- 注意
- Spring本质上无法区分不同层使用的注解,本质上:四个注解都是被@Component标识的,之所以拆分为四个注解的原因:提高代码可读性。
- 默认类名首字母小写,作为bean的id
- 使用注解的value属性可以定义bean的id,如只使用value属性时,value关键字可省略
11.2 使用注解管理【装配】bean中属性
-
@Autowired注解【重要】
-
位置:
- 属性上【装配方式:反射注入】
- setXXX()方法上【装配方式:set注入】
- 构造器上【装配方式:构造注入】
-
作用:为对象中属性【非字面量】自动装配
-
@Autowired装配规则【原理】【面试题】:
-
先按照byType匹配:
-
如唯一匹配则,装配成功
-
如匹配多个:再通过byName进行唯一筛选
-
筛选成功【属性名=beanId】,则装配成功
-
筛选失败【属性名!=beanId】,按照byType风格报错
available: expected single matching bean but found 2: employeeDao,employeeDao2
-
-
如匹配0个:
- 注意required属性值问题
- true:报如下错误
- available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
- false:不会报错,不会装配,默认值为:null【继续使用当前属性,会出现空指针异常】
- true:报如下错误
- 注意required属性值问题
-
-
-
属性:required
- 作用:表示当前注解标识的属性是否必须自动装配
- true【默认值】:表示被@Autowired注解标识的属性,必须自动装配,如未装配数值,会报错
- false:标识被@Autowired注解标识的属性,不必须自动装配,如未装配数值,不会报错,不会装配,默认值为:null【继续使用当前属性,会出现空指针异常】
- 作用:表示当前注解标识的属性是否必须自动装配
-
-
@Qualifier注解
- 作用:配合@Autowired注解使用,主要用于将容器中指定bean的Id设置到当前属性中
- 注意:@Qualifier注解不能单独使用,需要配合@Autowired使用
-
@Value注解
- 作用:为【字面量数值】属性,装配默认值
第十二章 组件扫描
使用注解必须,开启组件扫描
12.1 包含扫描
<!-- 假设当前项目中共有100个包,其中只想扫描dao层&service层-->
<context:component-scan base-package="com.dudu.annotation"
use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:include-filter type="assignable" expression="com.atguigu.annotation.controller.EmployeeController"/>
</context:component-scan>
12.2 排除扫描
<!-- 假设当前项目中共有100个包,其中不想扫描dao层&service层-->
<context:component-scan base-package="com.dudu.annotation">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
12.3 常用使用场景
<!--
开启组件扫描
base-package:设置扫描当前包及其子包
-->
<context:component-scan base-package="com.dudu.annotation"></context:component-scan>
第十三章 完全注解开发【0xml配置文件】
完全注解开发:指的是使用配置类代替配置文件
-
完全注解开发使用步骤
- 新建配置类
- 使用@Configuration注解标识当前类,是一个配置类
- 使用@ComponentScan标识组件扫描包
- 使用AnnotationConfigApplicationContext实现类代替【ClassPathXmlApplicationContext】
-
示例代码
//标识当前类是一个spring配置类 @Configuration //开启组件扫描【base-package:设置扫描当前包及其子包】 @ComponentScan(basePackages = "com.dudu.annotation") public class SpringConfig { }
@Test public void test0Xml(){ ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); Employee employee = context.getBean("employee", Employee.class); System.out.println("employee = " + employee); EmployeeDaoImpl employeeDao = context.getBean("employeeDao", EmployeeDaoImpl.class); System.out.println("employeeDao = " + employeeDao); EmployeeServiceImpl employeeService = context.getBean("employeeService", EmployeeServiceImpl.class); System.out.println("employeeService = " + employeeService); EmployeeController employeeController = context.getBean("employeeController", EmployeeController.class); System.out.println("employeeController = " + employeeController); }
第十四章 Spring整合Junit4
14.1 为什么需要整合?
-
因为节约两行代码
//创建容器对象 //从容器获取对象【EmployeeService】
14.2 Spring整合Junit4步骤
-
添加jar包支持【spring-test-5.3.1.jar】
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.1</version> </dependency>
-
指定Spring的配置文件的路径【Spring会为测试类自动底层创建容器对象】
-
指定Spring环境下运行Junit4的运行器
@ContextConfiguration(classes = SpringConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class TestSpringAndJunit4 {
@Autowired
private EmployeeService employeeService;
@Test
public void testSpringAndJunit(){
System.out.println(employeeService);
employeeService.deleteEmployee(1);
}
第十五章 代理模式
15.1 代理模式概述
-
生活中
- 房屋中介
- 代驾
- …
-
程序中代理
-
实现思路
- 实现Calc类基本方法【add()、sub()、mul()、div()】
- 添加日志功能
-
发现问题
-
日志功能代码比较分散
- 解决方案:将日志功能提取到工具类中【MyLogging】
-
日志功能代码比较混乱
-
解决方案:先将【非核心业务代码(日志功能)】横向提取,
再动态织入回业务代码中
-
-
-
解决方案
- 为CalcImpl创建一个代理对象
-
15.2 手动实现动态代理思路
a) 基于接口实现动态代理: JDK动态代理
- Proxy:JDK代理基类【类似Object】
- Proxy.newProxyInstance():创建代理对象
- InvocationHandler:使用当前接口实现【动态织入】效果,相当于使用InvocationHandler实现目标对象的相应功能
- invoke()方法
b) 基于继承实现动态代理: Cglib、Javassist动态代理
15.3 实现JDK动态代理步骤
- 创建类:MyProxy
- 创建一个有参构造器
- 创建一个目标对象
- 创建一个方法【获取代理对象】
15.4 示例代码
package com.dudu.aopbefore;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyProxy {
//创建一个目标对象
private Object target;
//创建一个有参构造器
public MyProxy(Object target){
this.target = target;
}
//创建一个方法【获取代理对象】
public Object getProxyObject(){
//代理对象
Object obj = null;
//获取目标对象类加载器
ClassLoader classLoader = target.getClass().getClassLoader();
//获取目标对象【实现接口】
Class<?>[] interfaces = target.getClass().getInterfaces();
//通过Proxy.newProxyInstance(),创建代理对象
obj = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取方法名称
String methodName = method.getName();
//添加日志
Mylogging.methodBefore(methodName,args);
//触发目标对象的相应方法【核心业务方法(加减乘除方法)】
Object rs = method.invoke(target,args);
//添加日志
Mylogging.methodAfter(methodName,rs);
return rs;
}
});
return obj;
}
}
- 总结:代理对象不能转换为目标对象,因为代理对象与目标对象是兄弟关系,不能相互转换
第十六章 AOP框架AspectJ
16.1 AspectJ概述
- AspectJ是Java社区里最完整最流行的AOP框架。
- 在Spring2.0以上版本中,可以使用基于AspectJ注解或基于XML配置的AOP。
16.2 AspectJ使用步骤
-
导入jar包
<!--spirng-aspects的jar包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency>
-
编写spring配置文件【applicationContext.xml】
- 开启组件扫描
- 开启AspectJ注解支持
<!-- - 开启组件扫描--> <context:component-scan base-package="com.dudu.aopAspectJ"></context:component-scan> <!-- - 开启AspectJ注解支持--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
在【非核心业务】类中添加注解
- 类上面添加【切面类】
- @Component:标识当前类是一个普通组件
- @Aspect:标识当前类是一个切面类
- 方法上添加【通知】
- @Before(value = “execution(public int com.dudu.spring.aop.Calculator.add(int , int ))”)
@Component //标识当前类是一个普通组件 @Aspect //标识当前类是一个**切面类** public class Mylogging { /** * 执行方法前代码【前置通知:在【目标方法】执行之前执行当前方法】 */ @Before(value = "execution(public int com.atguigu.aopAspectJ.CalcImpl.add(int, int))") public void methodBefore(JoinPoint joinPoint){ //获取方法名称 String methodName = joinPoint.getSignature().getName(); //获取参数 Object[] args = joinPoint.getArgs(); System.out.println("==>执行"+methodName+"方法,参数:"+ Arrays.toString(args)); } }
- 类上面添加【切面类】
-
总结
- 开 头 对 象 , 是 代 理 对 象 【 c o m . s u n . p r o x y . 开头对象,是代理对象【com.sun.proxy. 开头对象,是代理对象【com.sun.proxy.Proxy13】
16.3 AOP概述
- AOP(Aspect-Oriented Programming,面向切面编程)
- AOP是OOP(Object-Oriented Programming,面向对象编程)的补充|扩展
- AOP优势
- 先横向提取,再动态织入
- 解决了代码分散及代码混乱问题
- 提高代码可读性及扩展性
16.4 AOP相关术语
- 横切关注点
- 非核心业务【如:日志功能对于计算器而言属于,非核心业务】
- 提取之前,称之为:横切关注点
- 切面(Aspect)
- 封装【非核心业务】的类,称之为切面类
- 通知(Advice)
- 非核心业务【如:日志功能对于计算器而言属于,非核心业务】
- 提取之后,称之为:通知
- 目标(Target)
- 被代理的对象,称之为目标对象
- 如:CalcImpl【需要被帮助的对象,叫目标对象】
- 代理(Proxy)
- 可以代理目标对象的,称之为代理对象
- 如:com.sun.proxy**.$Proxy6**
- 连接点(Joinpoint)
- 横切关注点在程序代码中的具体位置,称之为连接点【通知之前】
- 如:被通知之前,之歌位置称之为连接点
- 切入点(Pointcut)
- 横切关注点在程序代码中的具体位置,称之为切入点【通知之后】
- 如:被通知之后,之歌位置称之为切入点
16.5 AspectJ中切入点表达式
-
语法:execution(权限修饰符 返回值类型 全类名.方法名(参数类型))
- eg:
- execution(public int com.dudu.aopAspectJ.CalcImpl.add(int, int))
- execution(* com.dudu.aopAspectJ.CalcImpl.*(…))
- eg:
-
切入点表达式中的通配符
【*】
- 可以代表任意权限修饰符&返回值类型
- 可以代表任意包及类名称
- 可以代表任意方法名称
【…】
- 可以代表任意参数类型
-
重用切入点表达式
-
语法:@Pointcut()
/** * 定义可重用的切入点表达式 */ @Pointcut(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))") public void myPointCut(){}
-
重用
/** * 执行方法前代码【前置通知:在【目标方法】执行之前执行当前方法】 */ @Before(value = "myPointCut()") /** * 后置通知【执行方法后代码】 */ @After("myPointCut()")
-
16.6 AspectJ中JoinPoint对象
- 连接点对象【JoinPoint】
- 常用API
- Signature st = joinPoint.getSignature():获取方法签名
- 方法签名:方法名+参数列表
- st.getName():获取方法名称
- Object[] args = joinPoint.getArgs(); 获取方法中参数
- Signature st = joinPoint.getSignature():获取方法签名
16.7 切面优先级
-
语法:@Order(int index)
- index建议使用正整数
- 数值越小优先级越高
- 默认值:default 2147483647
-
示例代码
@Component //标识当前类是一个普通组件 @Aspect //标识当前类是一个**切面类** @Order(value = 1) public class Mylogging {} @Component @Aspect @Order(2) public class MyValidate {}
16.8 基于XML配置AOP框架AspectJ
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置计算器实现类-->
<bean id="calculator" class="com.dudu.spring.aop.xml.CalculatorImpl"></bean>
<!--配置切面类-->
<bean id="loggingAspect" class="com.dudu.spring.aop.xml.LoggingAspect"></bean>
<!--AOP配置-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pointCut"
expression="execution(* com.dudu.spring.aop.xml.Calculator.*(..))"/>
<!--配置切面-->
<aop:aspect ref="loggingAspect">
<!--前置通知-->
<aop:before method="beforeAdvice" pointcut-ref="pointCut"></aop:before>
<!--返回通知-->
<aop:after-returning method="returningAdvice" pointcut-ref="pointCut" returning="result"></aop:after-returning>
<!--异常通知-->
<aop:after-throwing method="throwingAdvice" pointcut-ref="pointCut" throwing="e"></aop:after-throwing>
<!--后置通知-->
<aop:after method="afterAdvice" pointcut-ref="pointCut"></aop:after>
<!--环绕通知-->
<aop:around method="aroundAdvice" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
</aop:config>
</beans>
第十七章 Aspect中五种通知
17.1 前置通知
-
语法:@Before()
-
执行时机:在目标方法执行之前执行标识方法
-
注意事项:目标方法中存在异常【执行】
-
示例代码
/** * 执行方法前代码【前置通知:在【目标方法】执行之前执行当前方法】 */ @Before(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))") public void methodBefore(JoinPoint joinPoint){ //获取方法名称 String methodName = joinPoint.getSignature().getName(); //获取参数 Object[] args = joinPoint.getArgs(); System.out.println("==>前置通知!执行"+methodName+"方法,参数:"+ Arrays.toString(args)); }
17.2 后置通知
-
语法:@After()
-
执行时机:在目标方法执行之后执行标识方法【执行所有通知之后】
-
注意事项:目标方法中存在异常【执行】
-
示例代码
/** * 后置通知【执行方法后代码】 */ @After(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))") public void methodAfter(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("==>后置通知!执行"+methodName+"方法,参数:"+ Arrays.toString(args)); }
17.3 返回通知
-
语法:@AfterReturning(value=“切入点表达式”,returning=“rs”)
-
执行时机:在目标方法返回结果后执行标识方法
-
注意事项:
- 目标方法中存在异常【不执行】
- 返回通知要求returning的属性值与入参的参数名一致
-
示例代码
/** * 返回通知 */ @AfterReturning(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))",returning = "rs") public void afterReturning(JoinPoint joinPoint,Object rs){ String methodName = joinPoint.getSignature().getName(); System.out.println("==>返回通知!执行"+methodName+"方法,结果:"+rs); }
17.4 异常通知
-
语法:@AfterThrowing(value=“切入点表达式”,throwing=“ex”)
-
执行时机:在目标方法出现异常后执行标识方法
-
注意事项
- 目标方法存在异常执行,不存在异常不执行
- 异常通知要求:throwing中属性名与入参参数名一致
-
示例代码
/** * 异常通知 */ @AfterThrowing(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))" ,throwing = "ex") public void afterThrowing(JoinPoint joinPoint,Exception ex){ String methodName = joinPoint.getSignature().getName(); System.out.println("==>异常通知!执行"+methodName+"方法,出现异常:"+ex); }
-
小结
- 目标方法存在异常:前置通知->异常通知->后置通知
- 目标方法不存在异常:前置通知->返回通知->后置通知
17.5 环绕通知
环绕通知:是前面四个通知结合
-
语法:@Around
-
注意事项
- 使用环绕通知,参数中必须应用ProceedingJoinpoint对象
- ProceedingJoinpoint是JoinPoint子类
- ProceedingJoinpoint.proceed()可以调用目标对象的相应方法【加减乘除方法】
- 使用环绕通知,必须为方法设置返回值类型,否则报错
- 使用环绕通知,参数中必须应用ProceedingJoinpoint对象
-
示例代码
/** * 环绕通知 */ @Around(value = "execution(* com.dudu.aopAspectJ.CalcImpl.*(..))") public Object around(ProceedingJoinPoint pjp){ Object rs = null; //获取方法名称 String methodName = pjp.getSignature().getName(); //获取参数 Object[] args = pjp.getArgs(); try { //前置通知 System.out.println("==>前置通知!执行"+methodName+"方法,参数:"+ Arrays.toString(args)); //触发目标对象的相应方法【加减乘除方法】 rs = pjp.proceed(); //返回通知 System.out.println("==>返回通知!执行"+methodName+"方法,结果:"+rs); } catch (Throwable throwable) { throwable.printStackTrace(); //异常通知 System.out.println("==>异常通知!执行"+methodName+"方法,出现异常:"+throwable); } finally { //后置通知 System.out.println("==>后置通知!执行"+methodName+"方法,参数:"+ Arrays.toString(args)); } return rs; }
第十八章 Spring中JdbcTemplate
18.1 JdbcTemplate概述
- JdbcTemplate是一个持久层框架
- Mybatis是一个半自动化持久化层ORM框架
18.2 JdbcTemplate使用步骤
-
导入jar包
<!--spring-context--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.1</version> </dependency> <!--spring-jdbc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.1</version> </dependency> <!--spring-orm--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.3.1</version> </dependency> <!--导入druid的jar包--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <!--导入mysql的jar包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
-
编写配置文件
- 加载外部属性文件
- 配置DataSource数据源
- 装配JdbcTemplate核心类库
<!-- 加载外部属性文件--> <context:property-placeholder location="classpath:db.properties"></context:property-placeholder> <!-- 配置DataSource数据源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${db.driverClassName}"></property> <property name="url" value="${db.url}"></property> <property name="username" value="${db.username}"></property> <property name="password" value="${db.password}"></property> </bean> <!-- 装配JdbcTemplate核心类库--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean>
-
使用核心类库【JdbcTemplate】
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @ContextConfiguration(locations = "classpath:applicationContext_jdbcTemplate.xml") @RunWith(SpringJUnit4ClassRunner.class) public class TestJdbcTemplate { @Autowired private JdbcTemplate jdbcTemplate; @Test public void testJdbcTemplate(){ System.out.println("jdbcTemplate = " + jdbcTemplate); //测试CRUD //添加员工信息【JdbcTemplate自动提交事务】 String sql = "insert into tbl_dept(dept_id,dept_name) values(?,?)"; jdbcTemplate.update(sql,4,"程序员鼓励师!"); } }
18.3 JdbcTemplate常用API
- update(String sql,Object… args):增删改通用方法
- batchUpdate(String sql,List<Object[]> list):批量增删改通用方法
- queryForObject(String sql,Class clazz,Object… args):查询单个数值
- queryForObject(String sql,RowMapper rm,Object… args):查询单个对象
- query(String sql,RowMapper rm,Object… args):查询多个对象
18.4 JdbcTemplate搭建三层
<!-- 开启组件扫描-->
<context:component-scan base-package="com.dudu"></context:component-scan>
<!-- 加载外部属性文件-->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!-- 配置DataSource数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${db.driverClassName}"></property>
<property name="url" value="${db.url}"></property>
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
</bean>
<!-- 装配JdbcTemplate核心类库-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
@Repository("deptDao")
public class DeptDaoImpl implements DeptDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void insertDept(Dept dept) {
String sql = "insert into tbl_dept(dept_id,dept_name) values(?,?)";
jdbcTemplate.update(sql,dept.getDeptId(),dept.getDeptName());
}
}
第十九章 Spring声明式事务管理【非常重要】
19.1 回顾事务相关知识点
- 事务四大特性【ACID】
- 原子性【atomicity】
- 一致性【consistency】
- 隔离性【isolation】
- 持久性【durability】
- 事务三种行为【操作】
- .0开0启事务:connection.setAutoCommit(false);
- …提交事务:connection.commit();
- 回滚事务:connection.rollback();
19.2 Spring中支持两种事务管理
-
编程式事务管理【使用原生JDBC的API管理事务】
-
获取数据库连接Connection对象
-
取消事务的自动提交【开启事务】
-
执行操作【核心业务代码】
-
正常完成操作时手动【提交事务】
-
执行失败时【回滚事务】
-
关闭相关资源
- 发现问题
- 【核心业务代码】与事务管理代码相耦合,导致事务管理代码比较分散&比较混乱
- 解决方案
- 使用AOP思想【框架:AspectJ】,先将事务管理代码横向提取,再动态织入回核心业务代码中【称之为:声明式事务管理】
-
-
声明式事务管理
19.3 声明式事务管理使用
-
导入jar包【添加AspectJ支持】
<!--spirng-aspects的jar包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.3.1</version> </dependency>
-
编写配置文件
- 装配事务管理器【DataSourceTransactionManager】
- 开启事务注解支持
<!-- 开启组件扫描--> <context:component-scan base-package="com.dudu"></context:component-scan> <!-- 加载外部属性文件--> <context:property-placeholder location="classpath:db.properties"></context:property-placeholder> <!-- 配置DataSource数据源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${db.driverClassName}"></property> <property name="url" value="${db.url}"></property> <property name="username" value="${db.username}"></property> <property name="password" value="${db.password}"></property> </bean> <!-- 装配JdbcTemplate核心类库--> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 装配事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启事务注解支持--> <tx:annotation-driven></tx:annotation-driven>
-
在需要事务管理的业务上添加注解支持【@Transactional】
@Transactional public void purchase(String username, String isbn) { //查询book价格 Integer price = bookShopDao.findBookPriceByIsbn(isbn); //修改库存 bookShopDao.updateBookStock(isbn); //修改余额 bookShopDao.updateUserAccount(username, price); }
- 注意:添加事务管理后,获取代理对象【代理对象不能转换为目标对象】
19.4 Spring中声明式事务管理属性
-
propagation【事务传播行为】
-
Propagation概述:当一个事务方法被另一个事务方法调用时,此时设置当前事务方法的传播行为。
- 如:执行事务方法method1()【事务x】之后调用事务方法method2()【事务y】,此时需要设置method()2的执行事务【事务x或y】
-
类型:Propagation【枚举类型】
-
REQUIRED【默认值】
传播属性 描述 REQUIRED 如果有事务在运行,当前的方法就在这个事务内运行;否则就启动一个新的事务,并在自己的新事务内运行。 REQUIRES_NEW 当前的方法****必须****启动新事务,并在自己的事务内运行;如果有事务正在运行,应该将它挂起。 SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则可以不运行在事务中。 NOT_SUPPORTED 当前的方法不应该运行在事务中,如果有运行的事务将它挂起 MANDATORY 当前的方法必须运行在事务中,如果没有正在运行的事务就抛出异常。 NEVER 当前的方法不应该运行在事务中,如果有正在运行的事务就抛出异常。 NESTED 如果有事务正在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并在它自己的事务内运行。
-
-
事务传播行为应用场景
@Service("cashierService") public class CashierServiceImpl implements CashierService { /** * 去结账 * 结账时,账号余额支持买多少本书,就允许用户购买 * 如账户余额不足,怎么办? * 老板1:不允许用户购买所有图书 * @param username * @param isbns */ @Transactional public void checkOut(String username, List<String> isbns) { for (String isbn : isbns) { bookShopService.purchase(username,isbn); } } } @Service("bookShopService") public class BookShopServiceImpl implements BookShopService { /** * //买书->查询book价格->修改库存->修改余额 * * @param username * @param isbn */ @Transactional(propagation = Propagation.REQUIRED) public void purchase(String username, String isbn) { //查询book价格 Integer price = bookShopDao.findBookPriceByIsbn(isbn); //修改库存 bookShopDao.updateBookStock(isbn); //修改余额 bookShopDao.updateUserAccount(username, price); } }
@Service("cashierService") public class CashierServiceImpl implements CashierService { /** * 去结账 * 结账时,账号余额支持买多少本书,就允许用户购买 * 如账户余额不足,怎么办? * 老板2:不允许购买【导致余额不足的】最后一本书 解决思路:期望purchase()尽量多的执行【期望purchase()是一个独立事务】 * @param username * @param isbns */ @Transactional public void checkOut(String username, List<String> isbns) { for (String isbn : isbns) { bookShopService.purchase(username,isbn); } } } @Service("bookShopService") public class BookShopServiceImpl implements BookShopService { /** * //买书->查询book价格->修改库存->修改余额 * * @param username * @param isbn */ @Transactional(propagation = Propagation.REQUIRES_NEW) public void purchase(String username, String isbn) { //查询book价格 Integer price = bookShopDao.findBookPriceByIsbn(isbn); //修改库存 bookShopDao.updateBookStock(isbn); //修改余额 bookShopDao.updateUserAccount(username, price); } }
-
-
isolation【事务隔离级别】
-
概述:事务隔离级别就是事务与事务之间的隔离等级
-
隔离级别
- READ UNCOMMITTED【读未提交】:1
- READ COMMITTED【读已提交】:2
- REPEATABLE READ【可重复读】:4
- SERIALIZABLE【串行化】:8
-
各种数据库产品对事务隔离级别的支持程度
Oracle MySQL READ UNCOMMITTED × √ READ COMMITTED √(默认) √ REPEATABLE READ × √(默认) SERIALIZABLE √ √ -
图解隔离级别
-
-
readonly【事务只读】
-
默认值:false
-
作用:设置事务是否为只读
-
一般只查询数据时,会设置数据只读
-
如需增删改操作,不能将事务设置为只读,否则会报错
Connection is read-only. Queries leading to data modification are not allowed
-
-
优势:提交一点性能
-
-
timeout【事务超时】
- 作用:设置事务超时【超出指定时间,强制事务回滚】
- 类型:int类型
- 单位:秒
-
rollbackFor|noRollbackFor【事务回滚】
- rollbackFor:设置回滚异常类型
- noRollbackFor:设置不回滚异常类型
-
示例代码
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false, timeout = 3, noRollbackFor = ArithmeticException.class) public void purchase(String username, String isbn) { //查询book价格 Integer price = bookShopDao.findBookPriceByIsbn(isbn); try { int i = 1/0; } catch (Exception e) { e.printStackTrace(); } // try { // Thread.sleep(5000); // } catch (InterruptedException e) { // e.printStackTrace(); // } //修改库存 bookShopDao.updateBookStock(isbn); //修改余额 bookShopDao.updateUserAccount(username, price); }
19.5 基于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:aop="http://www.springframework.org/schema/aop" 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/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:druid.properties"></context:property-placeholder>
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
<property name="initialSize" value="${jdbc.initialSize}"></property>
<property name="maxActive" value="${jdbc.maxActive}"></property>
</bean>
<!--配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--配置数据源属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置BookShopDaoImpl-->
<bean id="bookShopDao" class="com.dudu.spring.tx.xml.BookShopDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--配置BookShopServiceImpl-->
<bean id="bookShopService" class="com.dudu.spring.tx.xml.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--配置数据源属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置声明式事务-->
<tx:advice id="tx" transaction-manager="transactionManager">
<!--设置添加事务的方法-->
<tx:attributes>
<!--设置查询的方法的只读属性为true-->
<tx:method name="find*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="purchase" propagation="REQUIRES_NEW" isolation="READ_COMMITTED"></tx:method>
</tx:attributes>
</tx:advice>
<!--AOP配置-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pointCut"
expression="execution(* com.dudu.spring.tx.xml.BookShopServiceImpl.purchase(..))"/>
<!--将事务方法和切入点表达式关联起来-->
<aop:advisor advice-ref="tx" pointcut-ref="pointCut"></aop:advisor>
</aop:config>
</beans>
第二十章 Spring5新特性
20.1 Spring5新增注解
名称 | 含义 | 可标注位置 |
---|---|---|
@Nullable | 可以为空 | @Target({ElementType.*METHOD*, ElementType.*PARAMETER*, ElementType.*FIELD*} |
@NonNull | 不应为空 | @Target({ElementType.*METHOD*, ElementType.*PARAMETER*, ElementType.*FIELD*}) |
@NonNullFields | 在特定包下的字段不应为空 | @Target(ElementType.*PACKAGE*) @TypeQualifierDefault(ElementType.*FIELD*) |
@NonNullApi | 参数和方法返回值不应为空 | @Target(ElementType.*PACKAGE*) @TypeQualifierDefault({ElementType.*METHOD*, ElementType.*PARAMETER*}) |
- @Nullable注解为例
- 位置:可以使用在方法上面&属性上面&参数前面
- 作用:表示方法返回可以为空&属性可以为空&参数可以为空
- 消除NullPointerException
20.2 Spring5整合Log4j2
-
导入jar包
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.11.2</version> <scope>test</scope> </dependency>
-
编写配置文件
-
位置:src/main/resource
-
名称:log4j2.xml
-
示例代码
<?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,可以看到log4j2内部各种详细输出--> <configuration status="INFO"> <!--先定义所有的appender--> <appenders> <!--输出日志信息到控制台--> <console name="Console" target="SYSTEM_OUT"> <!--控制日志输出的格式--> <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </console> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--> <!--root:用于指定项目的根日志,如果没有单独指定Logger,则会使用root作为默认的日志输出--> <loggers> <root level="DEBUG"> <appender-ref ref="Console"/> </root> </loggers> </configuration>
-
20.3 Spring5整合Junit5
-
导入junit5的相关jar包【并移除junit4的相关jar包】
<!--spring-test--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.1</version> </dependency> <!--导入junit4.12--> <!-- <dependency>--> <!-- <groupId>junit</groupId>--> <!-- <artifactId>junit</artifactId>--> <!-- <version>4.12</version>--> <!-- <scope>test</scope>--> <!-- </dependency>--> <!--junit5--> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.2</version> <scope>test</scope> </dependency>
-
在测试类上添加如下注解
-
方式一
//junit5 @ContextConfiguration(locations = "classpath:applicationContext_transactionManager.xml") @ExtendWith(SpringExtension.class) public class TestTransactionManager {}
-
方式二
//junit5方式二 @SpringJUnitConfig(locations="classpath:applicationContext_transactionManager.xml") public class TestTransactionManager {}
-