文章目录
- 1、Spring 框架体系结构
- 2、早期代码开发(不使用ioc)
- 3、Spring快速入门(xml)
- 4、Spring IoC 容器
- 5、 Spring AOP
- 6、Spring中的JdbcTemplate
- 7、Spring中的事务控制
1、Spring 框架体系结构

1.1 Core Container(核心容器)
核心容器由 spring-core,spring-beans,spring-context,spring-context-support和spring-expression(SpEL,Spring 表达式语言,Spring Expression Language)等模块组成
导入的jar包:
spring-beans-5.0.2.RELEASE.jar
spring-core-5.0.2.RELEASE.jar
spring-context-5.0.2.RELEASE.jar
spring-expression-5.0.2.RELEASE.jar
(1) spring-core
模块提供了框架的基本组成部分,包括 IoC 和依赖注入功能。
(2) spring-beans
提供 BeanFactory,工厂模式的微妙实现,它移除了编码式单例的需要,并且可以把配置和依赖从实际编码逻辑中解耦。
(3) spring-context
(4) spring-expression
提供了强大的表达式语言,用于在运行时查询和操作对象图。它是 JSP2.1 规范中定义的统一表达式语言的扩展,支持 set 和 get 属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从 Spring IoC 容器检索对象,还支持列表的投影、选择以及聚合等
1.2 Data Access数据访问
spring访问数据库模块(数据库访问、事务管理、整合hibernate)
数据访问/集成层包括JDBC
,ORM
,OXM
,JMS
和事务处理模块,它们的细节如下:
-
JDBC=Java Data Base Connectivity
-
ORM=Object Relational Mapping
-
OXM=Object XML Mapping
-
JMS=Java Message Service)
导入的jar包
spring-jdbc-5.0.2.RELEASE.jar
spring-orm-5.0.2.RELEASE.jar
spring-oxm-5.0.2.RELEASE.jar
spring-jms-5.0.2.RELEASE.jar
spring-tx-5.0.2.RELEASE.jar (事务)
1.3 Web
Web 层由 Web,Web-MVC,Web-Socket 和 Web-Portlet 组成,它们的细节如下:
- Web 模块提供面向 web 的基本功能和面向 web 的应用上下文,比如多部分(multipart)文件上传功能、使用 Servlet 监听器初始化 IoC 容器等。它还包括 HTTP 客户端以及 Spring 远程调用中与 web 相关的部分。
- Web-MVC 模块为 web 应用提供了模型视图控制(MVC)和 REST Web服务的实现。Spring 的 MVC 框架可以使领域模型代码和 web 表单完全地分离,且可以与 Spring 框架的其它所有功能进行集成。
- Web-Socket 模块为 WebSocket-based 提供了支持,而且在 web 应用程序中提供了客户端和服务器端之间通信的两种方式。
- Web-Portlet 模块提供了用于 Portlet 环境的 MVC 实现,并反映了 spring-webmvc 模块的功能。
导入jar包
spring-websocket(新的技术)-4.0.0.RELEASE、
spring-web-4.0.0.RELEASE、和原生的web相关(servlet)
spring-webmvc-4.0.0.RELEASE、开发web项目的(web)
spring-webmvc-portlet-4.0.0.RELEASE(开发web应用的组件集成)
1.4 AOP+Aspects
模块提供了面向方面(切面)的编程实现,允许你定义方法拦截器和切入点对代码进行干净地解耦,从而使实现功能的代码彻底的解耦出来
导入jar包
spring-aop-5.0.2.RELEASE.jar
spring-aspects-5.0.2.RELEASE.jar
完整依赖关系:
完整依赖关系如下图所示:
2、早期代码开发(不使用ioc)

step1: 新建实体类User
step2: 新建dao接口以及实现类
package com.uestc.dao;
import com.uestc.domain.User;
public interface IUserDao {
User findAll();
}
dao实现类UserDaoMysql
package com.uestc.dao.impl;
import com.uestc.dao.IUserDao;
import com.uestc.domain.User;
public class UserDaoMysql implements IUserDao {
public User findAll() {
System.out.println("Mysql实现类....");
return null;
}
}
dao实现类UserDaoOracleImpl
package com.uestc.dao.impl;
import com.uestc.dao.IUserDao;
import com.uestc.domain.User;
public class UserDaoOracleImpl implements IUserDao {
public User findAll() {
System.out.println("Oracle实现类。。。");
return null;
}
}
step3: 新建service及其实现类
package com.uestc.service;
import com.uestc.domain.User;
public interface IUserService {
User findAll();
}
package com.uestc.service.impl;
import com.uestc.dao.IUserDao;
import com.uestc.dao.impl.UserDaoMysql;
import com.uestc.dao.impl.UserDaoOracleImpl;
import com.uestc.domain.User;
import com.uestc.service.IUserService;
public class UserServiceImpl implements IUserService {
private IUserDao userDao=new UserDaoMysql();
public User findAll() {
return userDao.findAll();
}
}
step4:新建测试类
package com.uestc.test;
import com.uestc.service.IUserService;
import com.uestc.service.impl.UserServiceImpl;
/**
* 用户测试类
*/
public class Test01 {
public static void main(String[] args) {
IUserService service=new UserServiceImpl();
service.findAll();
}
}
出现的问题
-
我们在service实现类里将dao的实体类写死了
如果出现多个dao的实现类,比如UserDaoOracleImpl和UserDaoMysql,这个时候我们需要去更改service实现类的源代码,即serivce依赖于dao, 强耦合
如何解决:
更改service实现类: 使用set注入方法
package com.uestc.service.impl;
import com.uestc.dao.IUserDao;
import com.uestc.dao.impl.UserDaoMysql;
import com.uestc.domain.User;
import com.uestc.service.IUserService;
public class UserServiceImpl implements IUserService {
private IUserDao userDao;
//利用set进行动态注入
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
public User findAll() {
return userDao.findAll();
}
}
更改测试类
package com.uestc.test;
import com.uestc.dao.impl.UserDaoMysql;
import com.uestc.dao.impl.UserDaoOracleImpl;
import com.uestc.service.IUserService;
import com.uestc.service.impl.UserServiceImpl;
/**
* 用户测试类
*/
public class Test01 {
public static void main(String[] args) {
UserServiceImpl service=new UserServiceImpl();
service.setUserDao(new UserDaoMysql());
service.findAll();
service.setUserDao(new UserDaoOracleImpl());
service.findAll();
}
}
注意:
-
之前程序是主动创建对象,控制权在程序员身上!
-
使用 set注入方法之后,程序不再具有主动性,而是变成了被动的接受!
这种思想从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大降低,可以更加专注的在业务的实现上,这是IOC的原型!
3、Spring快速入门(xml)
注意:我们以spring基于xml配置为入门程序
step1: 导入Spring相关jar包(pom文件导入坐标)
注:spring需要导入commons-logging进行日志记录,我们利用maven会自动下载依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
</dependencies>
可以看到相关jar包已经导入

step2: 编写HelloSpring实体类
package com.uestc;
public class HelloSpring {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("hello"+name);
}
}
step3:创建 bean 的配置文件bean.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">
</beans>
可以在官网中找到:

-
让spring管理资源,在配置文件中配置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标签可以注册一个组件(对象、类) class:写要注册的组件的全类名 id:这个对象的唯一标示; --> <bean id="hello" class="com.uestc.HelloSpring"> <property name="name" value="lfTech"></property> </bean> </beans>
step4: 创建测试类
package com.uestc.test;
import com.uestc.HelloSpring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestHello {
public static void main(String[] args) {
//1.创建核心容器
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//2.获取bean对象
HelloSpring helloSpring=(HelloSpring) context.getBean("hello");
//3.调用对象的方法
helloSpring.show();
}
}

4、Spring IoC 容器
4.1 IOC介绍
- IoC(Inverse of Control:控制反转)是一种设计思想,就是 将原本在程序中手动创建对象的控制权,交由Spring框架来管理。DI(依赖注入)是实现Ioc的一种方法
- IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,value),Map 中存放的是各种对象。
- 将对象之间的
相互依赖关系
交给 IoC 容器来管理,并由 IoC 容器完成对象的注入
。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 - IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
- Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
4.2 入门案例(掌握)
4.21 案例准备

step1: 创建业务层接口和实现类
业务层接口:
package com.uestc.service;
/**
* 账户的业务层接口
*/
public interface IAccountService {
/**
* 保存账户
*/
void saveAccount();
}
业务层接口实现类:
package com.uestc.service.impl;
import com.uestc.dao.IAccountDao;
import com.uestc.dao.impl.AccountDaoImpl;
import com.uestc.service.IAccountService;
public class AccountService implements IAccountService {
private IAccountDao dao=new AccountDaoImpl();//此处的依赖关系有待解决
public void saveAccount() {
//调用dao的savaAccount方法
dao.saveAccount();
}
}
step2: 创建持久层接口和实现类
持久层接口:
package com.uestc.dao;
/**
* 账户的持久层接口
*/
public interface IAccountDao {
void saveAccount();
}
持久层接口实现类:
package com.uestc.dao.impl;
import com.uestc.dao.IAccountDao;
/**
* 账户的dao的实现类
*/
public class AccountDaoImpl implements IAccountDao {
public void saveAccount() {
System.out.println("保存账户");
}
}
4.22 基于XML的配置入门案例
第一步:在resources文件中创建bean.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">
可以在官网上找到:

第三步:让spring管理资源,在配置文件中配置service和dao
<?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标签:用于配置让spring创建对象,并且存入ioc容器之中
id属性:对象的唯一标识。
class属性:指定要创建对象的全限定类名
-->
<!-- 配置dao -->
<bean id="accountDao" class="com.uestc.dao.impl.AccountDaoImpl"></bean>
<!-- 配置service -->
<bean id="accountService" class="com.uestc.service.impl.AccountService"></bean>
</beans>
第四步: 测试配置是否成功
package com.uestc.test;
import com.uestc.dao.IAccountDao;
import com.uestc.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test01 {
public static void main(String[] args) {
//1.获取context
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
//2.根据bean的id获取对象
//IAccountService service=(IAccountService)context.getBean("accountService");需要强转
//System.out.println(service);
IAccountService service=context.getBean("accountService",IAccountService.class);
System.out.println(service);
IAccountDao accountDao=(IAccountDao) context.getBean("accountDao");
System.out.println(accountDao);
}
}

4.2 Spring提供IOC容器实现两个接口:
Spring 提供了以下两种不同类型的容器。
序号 | 容器 & 描述 |
---|---|
1 | Spring BeanFactory 容器它是最简单的容器,给 DI 提供了基本的支持,它用 org.springframework.beans.factory.BeanFactory 接口来定义。BeanFactory 或者相关的接口,如 BeanFactoryAware,InitializingBean,DisposableBean,在 Spring 中仍然存在具有大量的与 Spring 整合的第三方框架的反向兼容性的目的。 |
2 | Spring ApplicationContext 容器该容器添加了更多的企业特定的功能,例如从一个属性文件中解析文本信息的能力,发布应用程序事件给感兴趣的事件监听器的能力。该容器是由 org.springframework.context.ApplicationContext 接口定义。 |
4.21 类结构图


4.22 BeanFactory 和ApplicationContext 的区别
-
BeanFactory
才是Spring 容器中的顶层接口。ApplicationContext
是它的子接口。 -
创建对象的时间点不一样。
AppliationContext
:适用单例模式。它的构建核心容器时,创建对象采取的策略时采用立即加载
的方式,也就是说,只要一读取完配置文件马上就创建配置文件中的对象BeanFactory
:多例模式。它的构建核心容器时,创建对象采取的策略时采用延迟加载
的方式,也就是说,什么时候根据id获取对象了,什么时候才真正创建对象
4.23 ApplicationContext 接口的三种实现类
ClassPathXmlApplicationContext
:它可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话就加载不了FileSystemXmlApplicationContext
:它可以加载磁盘任意路径下的配置文件(必须有访问权限)AnnotationConfigApplicationContext
:它是用于读取注解创建容器
4.3 Spring IOC操作 Bean管理
4.31 什么是Bean管理?
Bean管理指的是两个操作:
(1)Spring创建对象
(2)Spirng注入属性
4.32 Bean管理操作有两种方式?
(1)基于xml配置文件方式实现
(2)基于注解方式实现
4.33 Bean管理-基于xml方式
4.331 bean标签
属性 | 描述 |
---|---|
id | 给对象在容器中提供一个唯一标识。用于获取对象。 |
class | 指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数 |
scope | 指定对象的作用范围。 |
init-method | 指定类中的初始化方法名称。 |
destroy-method | 指定类中销毁方法名称。 |
4.332 Bean的作用域
如何设置单实例还是多实例
- 在spring配置文件bean标签里面有属性(
scope
)用于设置单实例还是多实例- scope属性值
第一个值 默认值,singleton
,表示是单实例对象
第二个值prototype
,表示是多实例对象
作用域 | 描述 |
---|---|
singleton | 单例的,在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,默认值 |
prototype | 多例的,每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean() |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session共享一个Bean,不同Session使用不同的Bean,仅适用于WebApplicationContext环境 |
global-session | 一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境 |
singleton 作用域:
- singleton 是默认的作用域,当一个bean的作用域为 Singleton,那么 Spring IoC 容器中只会存在一个共享的 bean 实例,并且所有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回 bean 的同一实例。
- Singleton 是单例类型,就是在创建起容器时就同时自动创建了一个 bean 的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton 作用域是 Spring 中的缺省作用域。
prototype 作用域
- 当一个 bean 的作用域为 Prototype,表示一个 bean 定义对应多个对象实例。Prototype 作用域的 bean 会导致在每次对该 bean 请求(将其注入到另一个 bean 中,或者以程序的方式调用容器的 getBean() 方法)时都会创建一个新的 bean 实例。
- Prototype 是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的 bean 应该使用 prototype 作用域,而对无状态的bean则应该使用 singleton 作用域。
singleton和prototype区别
- singleton单实例,prototype多实例
- 设置scope值是singleton时候,加载spring配置文件时候就会创建单实例对象
设置scope值是prototype时候,不是在加载spring配置文件时候创建 对象,在调用getBean方法时候创建多实例对象
示例
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>
4.333 Bean的生命周期
init-method
: bean被初始化的时候执行的方法destroy-method
: bean被销毁的时候执行的方法
从对象创建到对象销毁的过程
(1)通过构造器创建bean实例(无参数构造)
(2)为bean的属性设置值和对其他bean引用(调用set方法)
(3)调用bean的初始化的方法(需要进行配置初始化的方法)
(4)bean可以使用了(对象获取到了)
(5)当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)
<!-- bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着。
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="prototype" init-method="init" destroy-method="destroy"></bean>
4.334 Bean实例化(创建对象)三种方式
第一种方式:使用默认无参构造函数
<!-- 第一种方式:使用默认构造函数创建。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
第二种方法:spring管理实例工厂-使用实例工厂的方法创建对象
package com.uestc.factory;
import com.uestc.service.IAccountService;
import com.uestc.service.impl.AccountServiceImpl;
/*
模拟一个工厂类(该类可能存在于jar包中,我们无法通过修改源码中的方法来提供默认的构造函数)
*/
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
<!-- 第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<!-- 此种方式是: 先把工厂的创建交给spring来管理。
然后在使用工厂的bean来调用里面的方法
factory-bean属性:用于指定实例工厂bean的id。
factory-method属性:用于指定实例工厂中创建对象的方法。 -->
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
-->
第三种方式:spring管理静态工厂-使用静态工厂的方法创建对象
/** * 模拟一个静态工厂,创建业务层实现类 */
package com.uestc.factory;
import com.uestc.service.IAccountService;
import com.uestc.service.impl.AccountServiceImpl;
public class StaticFactory {
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
<!-- 第三种方式:使用工厂中的静态方法创建对象
此种方式是: 使用StaticFactory类中的静态方法createAccountService创建对象,并存入spring容器
id属性:指定bean的id,用于从容器中获取
class属性:指定静态工厂的全限定类名
factory-method属性:指定生产对象的静态方法 -->
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
4.335 Spring 依赖注入
- 依赖注入:Dependency Injection。它是spring框架核心ioc的具体实现。 我们的程序在编写时,通过控制反转,把对象的创建交给了spring,但是代码中不可能出现没有依赖的情况。ioc解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。 那这种业务层和持久层的依赖关系,在使用spring之后,就让spring来维护了。 简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
- Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系。
依赖注入:Dependency Injection
Ioc的作用:降低程序的耦合(依赖关系)
依赖关系的管理:以后都交给了spring来维护, 在当前类中需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
依赖关系的维护:就称为依赖注入
能注入的类型有三类
-
基本类型
和string
类型 -
其他
bean类型
(在配置文件中或者注解配置过的bean) -
复杂类型/集合类型
注入的三种方式:
- 使用构造函数提供
- 使用set方法提供
- 使用注解提供
第一种:构造函数注入
顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让spring框架来为我们注入
使用的标签:constructor-arg
标签出现的位置:bean
标签的内部
标签中的属性:
-
指定给构造函数中哪个参数赋值
-
type
:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型 -
index
:用于指定要注入数据给构造函数中指定索引位置的参数赋值,索引的位置从0
开始 -
name
:用于指定给构造函数中指定名称的参数赋值,这是常用的
-
-
指定赋值参数具体的值
value
:用于提供给基本类型
和String类型
的数据ref
:用于指定其他的bean类型
数据,它指的就是在spring的Ioc核心容器中出现的bean对象
优势:在获取bean对象时。注入数据是必须的操作,否则对象无法创建成功
弊端:改变了bean对象实例化方式,使我们在创建对象时,如果用不到这些参数也必须提供
**示例:**使用构造函数的方式,给service中的属性传值
要求:类中需要提供一个对应参数列表的构造函数
//countServiceImpl.java
import com.uestc.service.IAccountService;
public class AccountServiceImpl implements IAccountService{
//如果经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name,Integer age,Date birthday){
this.name=name;
this.age=age;
this.birthday=birthday;
}
<!--bean.xml-->
<bean id="AccountService" class="com.uestc.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="test"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!--配置一个日期对象-->
<bean id="now" class="java.util.Date"></bean>
第二种: Set方法注入
就是在类中提供需要注入成员的set方法。
涉及的标签:property
出现的位置:bean
标签的内部
标签的属性
name
:用于指定注入时所调用的set方法名称value
:用于提供给基本类型
和String类型
的数据ref
:用于指定其他的bean类型
数据,它指的就是在spring的Ioc核心容器中出现的bean对象
优势:创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:如果某个成员必须有值,则获取对象时有可能set没有执行
package com.uestc.service.impl;
import java.util.*;
import com.uestc.service.IAccountService;
public class AccountServiceImpl implements IAccountService{
//如果经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public void setName(String name){
this.name=name;
}
public void setAge(Integer age){
this.age=age;
}
public void setBirthday(Date birthday){
this.birthday=birthday;
}
public AccountServiceImpl(String name,Integer age,Date birthday){
this.name=name;
this.age=age;
this.birthday=birthday;
}
}
<!--bean.xml-->
<bean id="AccountService" class="com.uestc.service.impl.AccountServiceImpl">
<property name="name" value="test"></property>
<property name="age" value="31"></property>
<property name="birthday" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
注入集合属性
就是给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。我们这里介绍注入
数组
,List,Set
,Map
,Properties
- 用于给list结构集合注入的标签:
<list><\list>
、<array><\arrat>
和<set><\set>
- 用于给map结构集合注入的标签
<map><\map>
,<entry><\entry>
,<props><\props>
,<prop><prop>
package com.uestc.service.impl;
import java.util.*;
import com.uestc.service.IAccountService;
public class AccountServiceImpl implements IAccountService{
//数组类型属性
private String [] myStrs;
//list集合类型属性
private List<String> myList;
//set集合类型属性
private Set<String> mySet;
//map集合类型属性
private Map<String,String> myMap;
//Properties集合类型属性
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
public void saveAccount(){
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
注入数组
<bean id="AccountService" class="com.uestc.service.impl.AccountServiceImpl">
<property name="myStrs">
<array>
<value>AAA</value>
<value>bbb</value>
<value>CCC</value>
</array>
</property>
</bean>
注入list集合
<bean id="AccountService" class="com.uestc.service.impl.AccountServiceImpl">
<property name="myList">
<list>
<value>AAA</value>
<value>bbb</value>
<value>CCC</value>
</list>
</property>
</bean>
注入set集合
<bean id="AccountService" class="com.uestc.service.impl.AccountServiceImpl">
<property name="mySet">
<set>
<value>AAA</value>
<value>bbb</value>
<value>CCC</value>
</set>
</property>
</bean>
注入map集合
<bean id="AccountService" class="com.uestc.service.impl.AccountServiceImpl">
<map>
<entry key="testa" value="aaa"></entry>
<entry key="testb">
<value>BBB</value>
</entry>
</map>
</bean>
注入Properties
<bean id="AccountService" class="com.uestc.service.impl.AccountServiceImpl">
<props>
<prop key="testc">ccc</prop>
<prop key="testd">ccc</prop>
</props>
</bean>
4.34 Bean管理-基于注解的方式
4.341 什么是注解
- 注解是代码特殊标记,格式:
@注解名称(属性名称=属性值, 属性名称=属性值..)
- 使用注解,注解作用在
类
上面,方法
上面,属性
上面 - 使用注解目的:
简化xml
配置
4.342 开启对注解的支持
第一步: 引入依赖

第二步:配置扫描器(componet-scan
)在你的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: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
http://www.springframework.org/schema/context/spring-context.xsd">
<!--告知spring在创建容器时要扫描的包,配置所需要的的标签不是在beans的约束中,而是
一个名为context名称空间和约束中-->
<context:component-scan base-package="com.uestc"></context:component-scan>
</beans>
4.343 用于创建对象的注解
@Component
@Service
@Controller
@Repository
相当于:
<bean id="" class="">
上面四个注解功能是一样的,都可以用来创建 bean实例
@Component:
作用:用于把当前类对象存入spring容器中,相当于在 xml 中配置一个 bean
属性:value
用于指定
bean
的id
。当我们不写时,它的默认值是当前类名,且首字母改小写
@Controller |@Service| @Repository
他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。 他们只不过是提供了更加明确的语义化。
@Controller
:一般用于表现层的注解。
@Service
:一般用于业务层的注解。
@Repository
:一般用于持久层的注解。
细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value在赋值是可以不写。
4.344 用于注入数据的注解
相当于:
<property name="" ref="">
和<property name="" value="">
1、
@Autowired
2、
@Qualifier
3、
@Resource
4、
@Value
注意:@Autowired
、@Qualifier
、@Resource
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现, 另外集合类型的注入只能通过xml实现。
@Autowired
作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
-
如果ioc容器中没有任何bean类型和要注入的变量类型匹配,则报错
-
如果ioc容器中有多个bean类型和注入的变量类型匹配,首先按照类型圈定匹配的对象,然后根据变量名称作为id在圈定的对象中继续查找,如果没有找到,注入失败,否则成功
出现的位置:可以是变量上,也可以是方法上
细节:在使用注解注入时,set方法就不是必须的了
@Qualifier
作用:在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。
注意:它在给字段
注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法
参数注入时,可以独立使用。
属性: value
用于指定 bean 的 id。
@Resource
属性:name
用于指定bean的id
作用:直接按照bean的id注入,它可以独立使用
@Value
作用:用于注入基本类型
和String类型
的数据
属性:value用于指定数据的值,他可以使用spring中的spEl(也就是spring的el表达式)
spEl的写法${表达式}
4.345 用于改变作用范围的注解
@Scop
相当于:<bean id="" class="" scope="">
@Scope
作用:用于指定bean的作用范围
属性:value指定范围的取值,通常取值:singleton
、prototype
、 request
、session
、globalsession
4.346 和生命周期相关的:(了解)
1、
@PostConstruct
2、
@PreDestroy
相当于:
<bean id="" class="" init-method="" destroy-method="" />
@PostConstruct
作用:用于指定初始化方法
@PreDestroy
作用:用于指定销毁方法
4.35 Spring 注解和 XML的选择问题
注解的优势: 配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
XML 的优势: 修改时,不用改源码。不涉及重新编译和部署。
Spring 管理 Bean方式的比较:
基于XML配置 | 基于注解配置 | |
---|---|---|
Bean定义 | <bean id="..." class=""/> | @Component 、Repository 、 @Service 、@Controller |
Bean名称 | 通过id 或者name 指定 | @Component("person") |
Bean注入 | <property> 或者通过p命名空间 | @Autowired 按类型自动装配 @Qualifier 按名称注入 |
Bean作用范围 | scope属性 | @Scope 设置作用范围 |
Bean生命周期 | init-method 、destory-method | @PostConstruct 初始化、@PostDestory销毁 |
适合场景 | Bean来自第三方 | Bean的实现类由用户自己开发 |
4.4 基于XML的IOC案例
需求:
使用 spring的 IoC的实现账户的 CRUD
技术要求
-
使用 spring 的 IoC 实现对象的管理
-
使用
DBAssit
作为持久层解决方案 -
使用
c3p0
数据源
实现

第一步:在pom文件添加依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.5.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
第二步:创建数据库和编写实体类
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci
insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);
package com.uestc.liufei;
/**
* 账户实体类
*/
public class Account {
private Integer id;
private String name;
private Float money;
public Account(){};
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setMoney(Float money) {
this.money = money;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
public Float getMoney() {
return money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
第三步:编写持久层代码
持久层接口:
package com.uestc.dao;
import com.uestc.liufei.Account;
import java.util.List;
/**
* 账户的持久层方法
*/
public interface IAccountDao {
/**
* 查询所有
* @return
*/
public List<Account> findAll();
/**
* 查询一个
* @return
*/
public Account findAccountById(Integer accountId);
/**
* 保存
* @param account
*/
public void saveAccount(Account account);
/**
* 更新
* @param account
*/
public void updateAccount(Account account);
/**
* 删除某个账户
* @param accountId
*/
public void deleteAccount(Integer accountId);
}
持久层实现类
package com.uestc.dao.impl;
import com.sun.org.apache.xpath.internal.SourceTree;
import com.uestc.dao.IAccountDao;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 持久层实现类
*/
public class AccountDaoImpl implements IAccountDao{
//这里使用的xml配置,因此需要set方法注入runner对象
private QueryRunner runner;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public List<Account> findAll() {
try{
return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try{
return runner.query("select * from account where id= ?",new BeanHandler<Account>(Account.class), accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try{
runner.update("insert into account(name, money) values(?,?)",account.getName(),account.getMoney());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try{
runner.update("update account set name=?, money=? where id=?",account.getName(),account.getMoney(), account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer accountId) {
try{
runner.update("delete account from account where id=?",accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
第四步:编写业务层代码:
业务层接口:
package com.uestc.service;
import com.uestc.liufei.Account;
import java.util.List;
/**
* 账户业务层接口
*/
public interface IAccountService {
/**
* 查询所有
* @return
*/
public List<Account> findAll();
/**
* 查询一个
* @return
*/
public Account findAccountById(Integer accountId);
/**
* 保存
* @param account
*/
public void saveAccount(Account account);
/**
* 更新
* @param account
*/
public void updateAccount(Account account);
/**
* 删除某个账户
* @param accountId
*/
public void deleteAccount(Integer accountId);
}
业务层实现:
package com.uestc.service.impl;
import java.util.*;
import com.uestc.dao.IAccountDao;
import com.uestc.dao.impl.AccountDaoImpl;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
/**
* 账户的业务层实现类
* 曾经的xml的配置
*/
@Service(value = "accountService")
public class AccountServiceImpl implements IAccountService{
//业务层需要调用持久层
private IAccountDao dao=null;
//这里使用的xml方法,因此需要使用set方法注入
public void setDao(IAccountDao dao) {
this.dao = dao;
}
public List<Account> findAll() {
return dao.findAll();
}
public Account findAccountById(Integer accountId) {
return dao.findAccountById(accountId);
}
public void saveAccount(Account account) {
dao.saveAccount(account);
}
public void updateAccount(Account account) {
dao.updateAccount(account);
}
public void deleteAccount(Integer accountId) {
dao.deleteAccount(accountId);
}
}
第五步:创建并编写配置文件
<?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"
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">
<!-- 配置service-->
<bean id="accountService" class="com.uestc.service.impl.AccountServiceImpl">
<!-- 需要注入的dao对象-->
<property name="dao" ref="accountDao"></property>
</bean>
<!--配置dao对象 -->
<bean id="accountDao" class="com.uestc.dao.impl.AccountDaoImpl" >
<!--需要注入QueryRunner -->
<property name="runner" ref="runner"></property>
</bean>
<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mysql"></property>
<property name="user" value="root"></property>
<property name="password" value="287216"></property>
</bean>
</beans>
第六步:测试类代码
package com.uestc.test;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
import javafx.application.Application;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class AccountServiceTest {
@Test
public void testFindAll() {
//1.获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
IAccountService as=ac.getBean("accountService",IAccountService.class);
//3.执行方法
List<Account> accounts=as.findAll();
for(Account account: accounts){
System.out.println(account);
}
}
@Test
public void testFindOne() {
//1.获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
IAccountService as=ac.getBean("accountService",IAccountService.class);
//3.执行方法
Account account=as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave() {
//1.获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
IAccountService as=ac.getBean("accountService",IAccountService.class);
//3.执行方法
Account account=new Account();
account.setMoney(9999.9f);
account.setName("liufei");
as.saveAccount(account);
}
@Test
public void testUpdata() {
//1.获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
IAccountService as=ac.getBean("accountService",IAccountService.class);
//3.执行方法
Account account=as.findAccountById(4);
account.setMoney(1119999.9f);
as.updateAccount(account);
}
@Test
public void testDelete() {
//1.获取容器
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
//2.得到业务层对象
IAccountService as=ac.getBean("accountService",IAccountService.class);
//3.执行方法
as.deleteAccount(1);
}
}
分析测试中的问题
通过上面的测试类,我们可以看出,每个测试方法都重新获取了一次spring的核心容器,造成了不必要的重复代码,增加了我们开发的工作量。这种情况,在开发中应该避免发生。 有些同学可能想到了,我们把容器的获取定义到类中去。例如:
package com.uestc.test;
import com.uestc.domain.Account;
import com.uestc.service.IAccountService;
import com.uestc.service.impl.AccountService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.Serializable;
import java.util.List;
public class TestAccount {
private IAccountService service;
@Before
public void init(){
//1. 获取核心容器
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
service=context.getBean("accuntService",AccountService.class);
}
@Test
public void testFindAll(){
List<Account> accounts=service.findAllAccount();
for(Account account:accounts){
System.out.println(account);
}
}
@Test
public void testFindById(){
Account account=service.findByAccountId(1);
System.out.println(account);
}
@Test
public void testSave(){
Account account=new Account();
account.setName("FEI");
account.setMoney(30000.0);
service.saveAccount(account);
}
@Test
public void testDelete(){
service.deleteAccount(2);
}
}
这种方式虽然能解决问题,但是扔需要我们自己写代码来获取容器。 能不能测试时直接就编写测试方法,而不需要手动编码来获取容器呢?
4.5 基于部分注解的IOC案例
第一步:配置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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.uestc"></context:component-scan>
<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mysql"></property>
<property name="user" value="root"></property>
<property name="password" value="287216"></property>
</bean>
</beans>
第二步:持久层实现类
package com.uestc.dao.impl;
import com.sun.org.apache.xpath.internal.SourceTree;
import com.uestc.dao.IAccountDao;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao{
@Autowired
private QueryRunner runner;
//这里使用注解,就不需要set方法了
public List<Account> findAll() {
try{
return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try{
return runner.query("select * from account where id= ?",new BeanHandler<Account>(Account.class), accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try{
runner.update("insert into account(name, money) values(?,?)",account.getName(),account.getMoney());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try{
runner.update("update account set name=?, money=? where id=?",account.getName(),account.getMoney(), account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer accountId) {
try{
runner.update("delete account from account where id=?",accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
第三步:业务层实现:
package com.uestc.service.impl;
import java.util.*;
import com.uestc.dao.IAccountDao;
import com.uestc.dao.impl.AccountDaoImpl;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
/**
* 账户的业务层实现类
* 曾经的xml的配置
*/
@Service(value = "accountService")
public class AccountServiceImpl implements IAccountService{
//业务层需要调用持久层
@Autowired
private IAccountDao dao=null;
public List<Account> findAll() {
return dao.findAll();
}
public Account findAccountById(Integer accountId) {
return dao.findAccountById(accountId);
}
public void saveAccount(Account account) {
dao.saveAccount(account);
}
public void updateAccount(Account account) {
dao.updateAccount(account);
}
public void deleteAccount(Integer accountId) {
dao.deleteAccount(accountId);
}
}
第四步:测试类
package com.uestc.test;
import com.uestc.domain.Account;
import com.uestc.service.IAccountService;
import com.uestc.service.impl.AccountService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class TestAccount {
private IAccountService service;
@Before
public void init(){
//1. 获取核心容器
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
service=context.getBean("accountService",AccountService.class);
}
@Test
public void testFindAll(){
List<Account> accounts=service.findAllAccount();
for(Account account:accounts){
System.out.println(account);
}
}
@Test
public void testFindById(){
Account account=service.findByAccountId(1);
System.out.println(account);
}
@Test
public void testSave(){
Account account=new Account();
account.setName("FEI");
account.setMoney(30000.0);
service.saveAccount(account);
}
@Test
public void testDelete(){
service.deleteAccount(2);
}
}
4.6 Spring的纯注解配置
写到此处,基于注解的IoC配置已经完成,但是大家都发现了一个问题:我们依然离不开spring的xml配置文件,那么能不能不写这个bean.xml,所有配置都用注解来实现呢?
待改造的问题
<!--我们发现,之所以我们现在离不开 xml 配置文件,是因为我们有一句很关键的配置:
告知spring框架在,读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中
如果他要也能用注解配置,那么我们就离脱离 xml 文件又进了一步。
另外,数据源和 JdbcTemplate 的配置也需要靠注解来实现-->
<context:component-scan base-package="com.uestc"></context:component-scan>
<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mysql"> </property>
<property name="user" value="root"></property>
<property name="password" value="287216"></property>
</bean>
@Configuration
该类是一个配置类,它的作用和bean.xml是一样的
Sping中的新注解Configuration
作用:指定当前类是一个配置类,当创建容器时会从该类上加载注解,
获取容器时需要使用AnnotationApplicationContext
(有@Configuration 注解的类.class)。
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写
@Configuration
public class SpringConfiguration {}
我们已经把配置文件用类来代替了,但是如何配置创建容器时要扫描的包呢? 请看下一个注解。
@ComponentScan
作用:用于通过注解指定spring在创建容器时要扫描的包
属性:values
它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包
我们使用了此注解就等同于在xml中配置了:
<context:component-scan base-package="com.uestc"></context:component-scan>
@Configuration
@ComponentScan(basePackages = {"com.uestc","config"})
public class SpringConfiguration {}
我们已经配置好了要扫描的包,但是数据源和JdbcTemplate对象如何从配置文件中移除呢? 请看下一个注解。
@Bean
作用:
该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:name
用于指定bean的id,当不写的时候默认值是当前方法的名称
细节:
当我们用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象
查找的方式和Autowired注解的作用一致
/**
* 连接数据库配置
*/
@Configuration
public class JdbcConfig {
/**
* 用于创建一个QueryRunner对象,并放入ioc容器中
* @param dataSource
* @return
*/
@Bean("runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
*用于创建一个DataSource对象,并放入ioc容器中
* @return
*/
@Bean("dataSource")
public DataSource createSource(){
try{
ComboPooledDataSource ds=new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/mysql");
ds.setUser("root");
ds.setPassword("287216");
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
注意: 我们已经把数据源和 DBAssit 从配置文件中移除了,此时可以删除 bean.xml 了。 但是由于没有了配置文件,创建数据源的配置又都写死在类中了。如何把它们配置出来呢? 请看下一个注解。
@PropertySource
作用:
用于加载.properties 文件中的配置。
例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
属性: value[]
用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:

新建SpringConfig
类
package com.uestc.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan(basePackages = {"com.uestc"})
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfig {
}
修改JdbcConfig
类
package config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import javax.sql.DataSource;
/**
* 连接数据库配置
*/
@Configuration
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean("runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
//用于创建一个DataSource对象,并放入ioc容器中
@Bean("dataSource")
public DataSource createSource(){
try{
ComboPooledDataSource ds=new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
注意: 此时我们已经有了两个配置类,但是他们还没有关系。如何建立他们的关系呢? 请看下一个注解。
@Import
作用:
用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问题。
当我们使用Import的注解之后,有Import注解的类就是父配置类,而导入的都是子配置类
属性: value[]
:
用于指定其他配置类的字节码。
@ComponentScan(basePackages = {"com.uestc","config"})
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}
注意: 我们已经把要配置的都配置好了,但是新的问题产生了,由于没有配置文件了,如何获取容器呢?
通过注解获取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
4.7 Spring 整合 Junit
4.71 测试类中的问题和解决思路
问题:
在测试类中,每个测试方法都有以下两行代码:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.class);
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
解决思路分析
针对上述问题,我们需要的是程序能自动帮我们创建容器。
一旦程序能自动为我们创建 spring 容器,我们就 无须手动创建了,问题也就解决了。
我们都知道,junit 单元测试的原理(在 web 阶段课程中讲过),但显然,junit 是无法实现的,
因为它自己都无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。
不过好在,junit 给我们暴露 了一个注解,可以让我们替换掉它的运行器。 这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我 们只需要告诉它配置文件在哪就行了
4.72 Spring整合junit的配置
-
导入spring整合junit的jar(坐标)
-
使用junit提供的一个注解把原有main方法替换了,替换成spring提供的@Runwith
-
告知spring的运行器,spring和ioc创建是基于xml还是基于注解的
@ContextConfiguration
属性:
Locations
:指定xml文件的位置,加上classpath关键字,表示在类路径下classes
:指定注解类所在地位置
注意:当我们使用spring 5.x版本时候,要求junit的jar必须是4.12以上
具体步骤:
第一步:拷贝整合junit的必备jar包到lib目录或者导入坐标

注意:导入jar包时,需要导入一个spring中aop的jar包。
第二步:使用@RunWith
注解替换原有运行器
@RunWith(SpringJUnit4ClassRunner.class)
public class AccountServiceTest { }
第三步:使用@ContextConfiguration
指定 spring 配置文件的位置
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=SpringConfiguration.class)
public class AccountServiceTest {
private ApplicationContext ac=null;
IAccountService as=null;
}
第四步:使用@Autowired
给测试类中的变量注入数据
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private ApplicationContext ac=null;
@Autowired
IAccountService as=null;
}
5、 Spring AOP
5.1 AOP概述
5.11 什么是AOP
AOP:全称是Aspect Oriented Programming即:面向切面编程。
通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
简单来说:就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理技术,在不修改源码的基础上,对我们的已有方法进行增强。
目前最流行的AOP框架有两个,分别为Spring AOP和AspectJ。Spring AOP使用纯Java代码实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标组织织入增强代码。AspectJ是一个基于Java语言的AOP框架,从Spring2.0开始,Spring AOP引入了对AspecJ的支持,AspectJ扩展了对Java语言,提供了专门的编译器,在编译的时提供横向代码植入。
5.12 AOP 的作用及优势
作用:在程序运行期间,不修改源码对已有方法进行增强。
优势 :
- 减少重复代码
- 提高开发效率
- 维护方便
5.13 AOP 的实现方式
使用动态代理技术
5.2 AOP底层原理-动态代理
特点:字节码随用随创建,随用随加载
作用:不修改源码的基础上对方法增强
动态代理常用的有两种方式

第一种 有接口情况,使用JDK动态代理
第二种 没有接口情况,使用CGLIB动态代理
5.21 基于接口的动态代理- JDK动态代理
5.211 JDK动态代理介绍
涉及的类:java.lang.reflect.Proxy
提供者:jdk官方
如何创建代理对象:
使用Proxy类中的
newProxyInstance
方法
创建代理对象的要求:
被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance
方法的参数:
-
Classloader:
类加载器它是用来加载代理对象字节码的。和被代理对象使用相同的类加载器,固定写法
-
Class[]
:字节码数组它是用于让代理对象和被代理对象有相同的方法,固定写法
-
InvocationHandleer
:用于提供增强的代码它是让我们写如何代理。我们一般都是写一个该接口的实现类。通常情况下都是匿名内部类,
但不是必须的。词接口的实现类都是谁用谁写
5.212 使用JDK官方的Proxy类创建代理对象
场景使用一个演员的例子: 在很久以前,演员和剧组都是直接见面联系的。没有中间人环节。 而随着时间的推移,产生了一个新兴职业:经纪人(中间人),这个时候剧组再想找演员就需要通过经纪人来找了
第一种:使用匿名内部类创建代理对象
包结构

接口类
package com.uestc.proxy;
public interface IActor {
/**
* 基本演出
* @param money
*/
void basicPerform(float money);
/**
* 危险演出
* @param money
*/
void dangerPerform(float money);
}
被代理类
package com.uestc.proxy;
/**
* 一个演员
*/
public class Actor implements IActor {
//实现了接口,就表示具有接口中的方法实现。即:符合经纪公司的要求
public void basicPerform(float money) {
System.out.println("拿到"+money+"钱,开始基本表演。。");
}
public void dangerPerform(float money) {
System.out.println("拿到"+money+"钱,开始危险表演。。");
}
}
使用匿名内部类创建代理对象
package com.uestc.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
/**
* 模拟剧组招演员
*/
public static void main(String[] args) {
final Actor actor=new Actor();
IActor proxy_actor=(IActor)Proxy.newProxyInstance(IActor.class.getClassLoader(), actor.getClass().getInterfaces(), new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法的所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name =method.getName();
Float money=(Float) args[0];
Object rtValue=null;
//每个经纪公司对不同演出收费不一样,此处开始判断
if(name.equals("basicPerform")){
//基本演出,没有2000不演
if(money>2000){
//看上去剧组是给了8000,实际到演员手里只有4000
// 这就是我们没有修改原来basicAct方法源码,对方法进行了增强
rtValue=method.invoke(actor,money/2);
}
}
if(name.equals("dangerPerform")){
if(money>5000){
//看上去剧组是给了50000,实际到演员手里只有25000
// 这就是我们没有修改原来dangerAct方法源码,对方法进行了增强
rtValue=method.invoke(actor,money/2);
}
}
return rtValue;
}
});
//剧组无法直接联系演员,而是由经纪公司找的演员
proxy_actor.basicPerform(8000f);
proxy_actor.dangerPerform(50000f);
}
}
第二种:创建代理对象
包结构

package com.uestc.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ActorProxy implements InvocationHandler {
//1. 创建的是谁的代理对象把谁传递过来
//有参构造传递
private Object object;
public ActorProxy(Object object){
this.object = object;
}
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法的所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String name =method.getName();
Float money=(Float) args[0];
Object rtValue=null;
//每个经纪公司对不同演出收费不一样,此处开始判断
if(name.equals("basicPerform")){
//基本演出,没有2000不演
if(money>2000){
//看上去剧组是给了8000,实际到演员手里只有4000
// 这就是我们没有修改原来basicAct方法源码,对方法进行了增强
rtValue=method.invoke(object,money/2);
}
}
if(name.equals("dangerPerform")){
if(money>5000){
//看上去剧组是给了50000,实际到演员手里只有25000
// 这就是我们没有修改原来dangerAct方法源码,对方法进行了增强
rtValue=method.invoke(object,money/2);
}
}
return rtValue;
}
}
package com.uestc.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
/**
* 模拟剧组招演员
*/
public static void main(String[] args) {
final Actor actor=new Actor();
IActor proxy_actor=(IActor)Proxy.newProxyInstance(IActor.class.getClassLoader(), actor.getClass().getInterfaces(), new ActorProxy(actor));
//剧组无法直接联系演员,而是由经纪公司找的演员
proxy_actor.basicPerform(8000f);
proxy_actor.dangerPerform(50000f);
}
}

5.22 基于子类的动态代理-CGLib
5.221 CGLib介绍
涉及的类:Enhancer
提供者:第三方cglib
库
如何创建代理对象
使用Enhancer类中的create方法
创建代理对象的要求:
被代理类不能用 final 修饰的类(最终类)
create方法的参数:
-
class
:字节码它是用来加载代理对象字节码的。和被代理对象使用相同的类加载器,固定写法
-
Callback
:用于提供增强的代码它是让我们写如何代理。我们一般都是写一个该接口的实现类。通常情况下都是匿名内部类,
但不是必须的。此接口的实现类都是谁用谁写
我们一般写的都是该接口的子接口实现类:
MethodIntercepter
5.221 使用CGLib的Enhancer类创建代理对象
1、引入第三方jar包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>
2、Actor类
package com.uestc.cglib;
/**
* 一个演员
*/
public class Actor {//没有实现任何接口
public void basicPerform(float money) {
System.out.println("拿到"+money+"钱,开始基本表演。。");
}
public void dangerPerform(float money) {
System.out.println("拿到"+money+"钱,开始危险表演。。");
}
}
3、代理类
package com.uestc.cglib;
import com.oracle.jrockit.jfr.Producer;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class Client {
public static void main(String[] args) {
final Actor actor = new Actor();
Actor cglibProducer=(Actor)Enhancer.create(actor.getClass(), new MethodInterceptor() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中的invoke方法是一样的
* @param methodProxy 当前执行方法的代理对象
* @return
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object rtValue=null;
//1.获取方法执行的参数
Float money =(Float)args[0];
//每个经纪公司对不同演出收费不一样,此处开始判断
if(method.getName().equals("basicPerform")){
//基本演出,没有2000不演
if(money>2000){
//看上去剧组是给了8000,实际到演员手里只有4000
// 这就是我们没有修改原来basicAct方法源码,对方法进行了增强
rtValue=method.invoke(actor,money/2);
}
}
if(method.getName().equals("dangerPerform")){
if(money>5000){
//看上去剧组是给了50000,实际到演员手里只有25000
// 这就是我们没有修改原来dangerAct方法源码,对方法进行了增强
rtValue=method.invoke(actor,money/2);
}
}
return rtValue;
}
});
cglibProducer.basicPerform(10000f);
cglibProducer.dangerPerform(20000f);
}
}
5.3 银行转账案例
遇到的问题
我们在业务层中多加入一个方法transfer
: 当我们执行时,由于执行有异常,转账失败。但是因为我们是每次执行持久层方法都是独立事务,导致无法实现事务控制(不符合事务的一致性)
//用于转账
public void transfer(String sourceName, String targetName, Float money) {
//1.根据名称查询转出账户
Account source=dao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target=dao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney()-money);
//4.转入账户加钱
target.setMoney(target.getMoney()+money);
//5.更新转出账户
dao.updateAccount(source);
int i=1/0;
//6.更新转入账户
dao.updateAccount(target);
}
问题的解决
解决办法: 让业务层来控制事务的提交和回滚。
5.31 两个工具类
ConnectionUtils
package com.uestc.utils;
import javax.sql.DataSource;
import java.sql.Connection;
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
private ThreadLocal<Connection> tl=new ThreadLocal<Connection>();
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
private DataSource dataSource;
public Connection getThreadConnection(){
try{
//1.先从ThreadLocal上获取
Connection conn=tl.get();
//2.判断当前线程是否有连接
if(conn==null){
//3.从数据源中获取一个连接并且和线程绑定(存入threadlocal中)
conn=dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把线程和连接解绑
*/
public void removeConnction(){
tl.remove();
}
}
TransactionManager
package com.uestc.utils;
/**
* 和事务管理相关的工具类,它包含了开启事务,提交事务,回滚事务,关闭事务
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try{
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try{
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try{
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 关闭事务
*/
public void release(){
try{
connectionUtils.getThreadConnection().close();//返回连接池
connectionUtils.removeConnction();//线程和连接解绑
}catch (Exception e){
e.printStackTrace();
}
}
}
持久层代码
package com.uestc.dao.impl;
import com.sun.org.apache.xpath.internal.SourceTree;
import com.uestc.dao.IAccountDao;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
import com.uestc.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao{
private QueryRunner runner;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public Account findAccountByName(String accountName) {
try{
List<Account> accounts= runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ?", new BeanListHandler<Account>(Account.class),accountName);
if(accounts==null || accounts.size()==0){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public List<Account> findAll() {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id= ?",new BeanHandler<Account>(Account.class), accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"insert into account(name, money) values(?,?)",account.getName(),account.getMoney());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"update account set name=?, money=? where id=?",account.getName(),account.getMoney(), account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer accountId) {
try{
runner.update(connectionUtils.getThreadConnection(),"delete account from account where id=?",accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
配置文件加入:
<!--配置dao对象 -->
<bean id="accountDao" class="com.uestc.dao.impl.AccountDaoImpl" >
<!--需要注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!--需要注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
</bean>
<!-- 配置connection的工具类ConnctionUtils -->
<bean id="connectionUtils" class="com.uestc.utils.ConnectionUtils">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mysql"></property>
<property name="user" value="root"></property>
<property name="password" value="287216"></property>
</bean>
<!--配置事务管理器-->
<bean id="tsManager" class="com.uestc.utils.TransactionManager">
<!--需要注入connectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
业务层改进后的代码
package com.uestc.service.impl;
import com.uestc.dao.IAccountDao;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.*;
import com.uestc.dao.IAccountDao;
import com.uestc.dao.impl.AccountDaoImpl;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
import com.uestc.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
/**
* 账户的业务层实现类
* 曾经的xml的配置
*/
@Service(value = "accountService")
public class AccountServiceOld implements IAccountService {
//业务层需要调用持久层
private TransactionManager tsManager;
public void setTsManager(TransactionManager tsManager) {
this.tsManager = tsManager;
}
private IAccountDao dao=null;
public void setDao(IAccountDao dao) {
this.dao = dao;
}
public void transfer(String sourceName, String targetName, Float money) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
//2.1.根据名称查询转出账户
Account source=dao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target=dao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
dao.updateAccount(source);
int i=1/0;
//2.6更新转入账户
dao.updateAccount(target);
//3.提交事务
tsManager.commit();
}catch (Exception e){
//4.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//5.释放连接
tsManager.release();
}
}
public List<Account> findAll() {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
List<Account> accounts=dao.findAll();
//3.提交事务
tsManager.commit();
//4.返回结果
return accounts;
}catch (Exception e){
//5.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
tsManager.release();
}
}
public Account findAccountById(Integer accountId) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
Account account =dao.findAccountById(accountId);
//3.提交事务
tsManager.commit();
//4.返回结果
return account;
}catch (Exception e){
//5.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
tsManager.release();
}
}
public void saveAccount(Account account) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
dao.saveAccount(account);
//3.提交事务
tsManager.commit();
}catch (Exception e){
//5.回滚操作
tsManager.rollback();
}finally {
//6.释放连接
tsManager.release();
}
dao.saveAccount(account);
}
public void updateAccount(Account account) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
dao.updateAccount(account);
//3.提交事务
tsManager.commit();
}catch (Exception e){
//5.回滚操作
tsManager.rollback();
}finally {
//6.释放连接
tsManager.release();
}
dao.updateAccount(account);
}
public void deleteAccount(Integer accountId) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
dao.deleteAccount(accountId);
//3.提交事务
tsManager.commit();
}catch (Exception e){
//5.回滚操作
tsManager.rollback();
}finally {
//6.释放连接
tsManager.release();
}
dao.deleteAccount(accountId);
}
}
5.33 新的问题
上一小节的代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。 试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。
5.4 用动态代理解决银行转账
用于创建service的代理对象的工厂

package com.uestc.factory;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
import com.uestc.utils.TransactionManager;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
/**
* 用于创建service的代理对象的工厂
*/
public class BeanFactory {
private IAccountService accountService;
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
private TransactionManager tsManager;
public void setTsManager(TransactionManager tsManager) {
this.tsManager = tsManager;
}
/**
* 获取service的代理对象
* @return
*/
public IAccountService getAccountService(){
/**
* 被执行被代理对象的任何接口方法都会经过该方法
*/
IAccountService iAccountService= (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue=null;
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
returnValue=method.invoke(accountService,args);
//3.提交事务
tsManager.commit();
//4.返回结果
return returnValue;
}catch (Exception e){
//5.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
tsManager.release();
}
}
});
return iAccountService;
}
}
最终的配置文件:
<!--配置代理的service-->
<bean id="proxyAccountService" factory-bean="beanfactory" factory-method="getAccountService" ></bean>
<!--配置beanfactory-->
<bean id="beanfactory" class="com.uestc.factory.BeanFactory">
<!--需要注入AccountService-->
<property name="accountService" ref="accountService"></property>
<!--需要注入transcationManager-->
<property name="tsManager" ref="tsManager"></property>
</bean>
<!-- 配置service-->
<bean id="accountService" class="com.uestc.service.impl.AccountServiceImpl">
<!-- 需要注入的dao对象-->
<property name="dao" ref="accountDao"></property>
</bean>
<!--配置dao对象 -->
<bean id="accountDao" class="com.uestc.dao.impl.AccountDaoImpl" >
<!--需要注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!--需要注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
</bean>
<!-- 配置connection的工具类ConnctionUtils -->
<bean id="connectionUtils" class="com.uestc.utils.ConnectionUtils">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mysql"></property>
<property name="user" value="root"></property>
<property name="password" value="287216"></property>
</bean>
<!--配置事务管理器-->
<bean id="tsManager" class="com.uestc.utils.TransactionManager">
<!--需要注入connectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
修改后的业务层代码:
package com.uestc.service.impl;
import java.util.*;
import com.uestc.dao.IAccountDao;
import com.uestc.dao.impl.AccountDaoImpl;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
import com.uestc.utils.TransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
/**
* 账户的业务层实现类
* 曾经的xml的配置
*/
@Service(value = "accountService")
public class AccountServiceImpl implements IAccountService{
//业务层需要调用持久层
private IAccountDao dao=null;
public void setDao(IAccountDao dao) {
this.dao = dao;
}
public void transfer(String sourceName, String targetName, Float money) {
//1.根据名称查询转出账户
Account source=dao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target=dao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney()-money);
//4.转入账户加钱
target.setMoney(target.getMoney()+money);
//5.更新转出账户
dao.updateAccount(source);
int i=1/0;
//6.更新转入账户
dao.updateAccount(target);
}
public List<Account> findAll() {
return dao.findAll();
}
public Account findAccountById(Integer accountId) {
return dao.findAccountById(accountId);
}
public void saveAccount(Account account) {
dao.saveAccount(account);
}
public void updateAccount(Account account) {
dao.updateAccount(account);
}
public void deleteAccount(Integer accountId) {
dao.deleteAccount(accountId);
}
}
5.5 Spring AOP 细节
5.51 说明
我们学习 spring 的 aop,就是通过配置的方式,实现上一章节的功能
5.52 Spring中AOP的细节
名称 | 说明 |
---|---|
Joinpoint (连接点) | 指那些被拦截到的点,在 Spring 中,这些点指的是方法, 因为 spring 只支持方法类型的连接点。 |
Pointcut (切入点) | 指要对哪些 Joinpoint 进行拦截,即被拦截的连接点。 简单来说(实际被增强的方法) |
Advice (通知) | 指拦截到 Joinpoint 之后要做的事情,即对切入点增强的内容。 通知的类型:前置通知, 后置通知, 异常通知, 最终通知, 环绕通知。 |
Target (目标) | 指代理的目标对象。 |
Weaving (织入) | 指把增强代码应用到目标上,生成代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。 |
Proxy (代理) | 一个类被 AOP 织入增强后,就产生一个结果代理类 |
Aspect (切面) | 切面通常指封装用于横向插入系统的功能(如事务、日志等)的类,该类要被Spring容器识别为切面需要在配置文件中通过 |
Introduction (引介) | 一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方 法或 Field。 |
5.53 学习 spring 中的 AOP 要明确的事
开发阶段
(我们做的) 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。 把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。 在配置文件中,声明切入点与通知间的关系,即切面。AOP 编程人员来做。
运行阶段
(Spring框架完成的) Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
5.54 关于代理的选择
在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
5.55 基于 XML 的 AOP 配置
示例: 我们在学习spring的aop时,采用账户转账作为示例。 并且把spring的ioc也一起应用进来。
5.551 spring中基于xml的AOP配置步骤
第一步:把通知bean
也交给spring来管理
第二步:使用aop:config
标签标明开始AOP配置
第三步:使用aop:aspect
标签表明配置切面
-
id
属性:是给切面提供一个唯一标识 -
ref
属性:是指定通知类bean的Id
第四步:在aop:aspect
标签的内部
使用对应的标签来配置通知的类型
5.552 切入点表达式说明:
切入表达式的写法:
关键字:execution
(表达式)
表达式语法: 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.uestc.service.impl.AccountServiceImpl.saveAccout()
1.访问修饰符可以省略
void com.uestc.service.impl.AccountServiceImpl.saveAccout()
2.返回值可以使用通配符*
,表示任意返回值
* com.uestc.service.impl.AccountServiceImpl.saveAccout()
3.1包名可以使用通配符*
,表示任意包,但是有几级就需要写几个*
* *.*.*.*.Account.saveAccout()
3.2 包名可以使用..
表示当前包及其子包:
* com..AccountServiceImpl.saveAccout()
4.类名和方法名都可以使用通配*
* *..*.*()
5.参数列表
5.1 可以直接写数据类型
-
基本类型
直接写名称 int -
引用类型
写包名.类名
的方式 java.lang.String
5.2 通配符*
-
参数列表可以使用
*
,表示参数可以是任意数据类型,但是必须有参数* com..*.*(*)
-
可以使用
..
有无参数均可,有参数可以是任意类型* com..*.*(..)
-
全通配符写法:
* *..*.*(..)
通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类下的所有方法:
* com.uestc.service.impl.*.*(..)
5.553 通知类型说明
aop:before (前置配置)
作用:用于前置配置
通知。指定增强的方法在切入点方法之前执行
属性:
method
:用于指定通知类中的增强方法名称ponitcut-ref
:用于指定切入点的表达式的引用poinitcut
:用于指定切入点表达式
执行时间点: 切入点方法执行之前执行
<aop:config>
<!--配置的代码都写在此处-->
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型要写在此处-->
<!--配置前置通知的类型,在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
</aop:aspect>
</aop:config>
aop:after-returning (后置通知)
作用: 用于配置后置通知
。指定增强的方法在切入点方方法正常执行之后,它和异常通知只能有
一个执行
属性:
-
method
:指定通知中方法的名称。 -
pointct
:定义切入点表达式 -
pointcut-ref
:指定切入点表达式的引用
执行时间点: 切入点方法正常执行之后。它和异常通知只能有一个执行
<aop:config>
<!--配置的代码都写在此处-->
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型要写在此处-->
<!--配置前置通知的类型,在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
</aop:aspect>
</aop:config>
aop:after-throwing(异常通知)
作用:用于配置异常通知
。切入点方法执行产生异常后执行。它和后置通知只能执行一个
属性:
-
method
:指定通知中方法的名称。 -
pointct
:定义切入点表达式 -
pointcut-ref
:指定切入点表达式的引用
执行时间点: 切入点方法执行产生异常后执行。它和后置通知只能执行一个
<aop:config>
<!--配置的代码都写在此处-->
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型要写在此处-->
<!--配置异常通知的类型,在切入点方法执行异常之后执行,它和异常通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
</aop:aspect>
</aop:config>
aop:after(最终通知)
作用:用于配置最终通知
。无论切入点方法执行时是否有异常,它都会在其后面执行
属性:
-
method
:用于指定通知类中的增强方法名称 -
pointcut-ref
:用于指定切入点的表达式的引用 -
pointcut
:用于指定切入点表达式
执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行
<aop:config>
<!--配置的代码都写在此处-->
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型要写在此处-->
<!--配置最终通知的类型,并且建立通知方法和切入点方法关联-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
aop:around(环绕通知)
作用: 用于配置环绕通知
属性:
-
method:指定通知中方法的名称。
-
pointct:定义切入点表达式
-
pointcut-ref:指定切入点表达式的引用
说明:它是spring框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
注意: 通常情况下,环绕通知都是独立使用的
<!--配置AOP-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.uestc.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
5.553 具体配置
1、环境搭建
Spring框架一般都是基于AspectJ实现AOP操作,AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spirng框架一起使用,进行AOP操作
需要ioc和aop的相关jar包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springlearning</artifactId>
<groupId>com.uestc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springday03</artifactId>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
</dependencies>
</project>
2、创建spring的配置文件并导入约束
此处要导入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: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
http://www.springframework.org/schema/aop/spring-aop.xsd">
3、配置spring的ioc
<?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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置service -->
<bean id="accountService" class="com.uestc.service.impl.AccountService">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.uestc.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner"></property>
</bean>
<!-- 配置数据库操作对象 -->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db5"></property>
<property name="user" value="root"></property>
<property name="password" value="287216"></property>
</bean>
</beans>
4、抽取公共代码制作成通知类
package com.uestc.utils;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
public class Logger {
/**
*前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知:Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
public void AfterReturingPrintLog(){
System.out.println("后置通知:Logger类中的AfterReturingPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知:Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知:Logger类中的afterPrintLog方法开始记录日志了。。。");
}
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码没有
* 解决:
* spring框架为我们提供了一个接口,ProceedingJoinPoint 该接口有一个方法proceed(),此方法就相当于明确调用切入点方法
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会我们提供该接口的实现类供我们使用
*spring 中的环绕通知
* 它是spring框架为我们提供的一种可以在代码里手动控制增强方法何时执行的方法
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue;
try{
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。前置");
Object [] args=pjp.getArgs();//得到方法执行需要的参数
rtValue=pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。最终");
}
}
}
5、把通知类用bean标签配置起来
<!--配置Logger类-->
<bean id="logger" class="com.uestc.utils.Logger"></bean>
6、使用aop:config声明aop配置
<aop:config>
<!--配置的代码都写在此处-->
</aop:config>
7、使用aop:aspect配置切面
<aop:config>
<!--配置的代码都写在此处-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型要写在此处-->
</aop:aspect>
</aop:config>
8、使用aop:pointcut配置切入点表达式
aop:pointcut
:
作用: 用于配置切入点表达式。就是指定对哪些类
的哪些方法
进行增强。
属性:
expression
:用于定义切入点表达式。id
:用于给切入点表达式提供一个唯一标
<aop:config>
<!--配置的代码都写在此处-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型要写在此处-->
<aop:pointcut id="pt1" expresseion="public void com.uestc.service.impl.AccountServiceImpl.saveAccout()">
</aop:pointcut>
</aop:aspect>
</aop:config>
9、使用aop:xxx配置对应的通知类型
<aop:config>
<!--切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.uestc.service.impl.*.*(..))"></aop:pointcut>
<!--配置的代码都写在此处-->
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型要写在此处-->
<!--配置前置通知的类型,在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!--配置后置通知的类型,在切入点方法正常之后执行,它和异常通知永远只能执行一个-->
<aop:after-returning method="afterReturingPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!--配置异常通知的类型,在切入点方法执行异常之后执行,它和异常通知永远只能执行一个-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最终通知的类型,并且建立通知方法和切入点方法关联-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
5.56 基于注解的AOP配置
1、在配置文件中导入context的名称空间
<?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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
2、在配置文件中指定spring要扫描的包
<!--配置spring 创建容器要扫描的包-->
<context:component-scan base-package="com.uestc"></context:component-scan>
3、将业务类用注解配置
package com.uestc.service.impl;
import com.uestc.service.IAccountService;
import org.springframework.stereotype.Service;
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
public void saveAccount() {
System.out.println("执行了保存");
}
public void updataAcccount(int i) {
System.out.println("执行了更新");
}
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
4、把通知类也使用注解配置
第一步:在通知类上使用@Aspect
注解声明为切面
@Aspect 作用: 把当前类声明为切面类。
第二步:在增强的方法上使用注解配置通知:
@Before
作用: 把当前方法看成是前置通知。
属性:
value
:用于指定切入点表达式,还可以指定切入点表达式的引用。
@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知:Logger类中的beforePrintLog方法开始记录日志了。。。");
}
@AfterReturning
作用: 把当前方法看成是后置通知。
属性:
value
:用于指定切入点表达式,还可以指定切入点表达式的引用。
@AfterReturning("pt1()")
public void AfterReturingPrintLog(){
System.out.println("后置通知:Logger类中的AfterReturingPrintLog方法开始记录日志了。。。");
}
@AfterThrowing
作用: 把当前方法看成是异常通知。
属性:
value
:用于指定切入点表达式,还可以指定切入点表达式的引用。
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知:Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
@After
作用: 把当前方法看成是异常通知。
属性:
value
:用于指定切入点表达式,还可以指定切入点表达式的引用。
@After("pt1()")
public void afterPrintLog(){
System.out.println("异常通知:Logger类中的afterPrintLog方法开始记录日志了。。。");
}
通知类注解
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
@Component("logger")
@Aspect //表示当前类是一个切面类
public class Logger {
//用@Pointcut注解声明频繁使用的切入点表达式
@Pointcut("execution(* com.uestc.service.impl.*.*(..))")
private void pt1(){}
/**
*前置通知
*/
@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知:Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
@AfterReturning("pt1()")
public void AfterReturingPrintLog(){
System.out.println("后置通知:Logger类中的AfterReturingPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知:Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
@After("pt1()")
public void afterPrintLog(){
System.out.println("最终通知:Logger类中的afterPrintLog方法开始记录日志了。。。");
}
5、 在spring配置文件中开启注解AOP的支持
<!--配置spring开启注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
6、 环绕通知注解配置
@Around
作用: 把当前方法看成是环绕通知。
属性:
value:用于指定切入点表达式,还可以指定切入点表达式的引用。
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知又明确的切入点方法调用,而我们的代码没有
* 解决:
* spring框架为我们提供了一个接口,ProceedingJoinPoint 该接口又一个方法proceed(),此方法就相当于明确调用切入点方法
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会我们提供该接口的实现类供我们使用
*spring 中的环绕通知
* 它时spring框架为我们提供的一种可以在代码李手动控制增强方法何时执行的方法
*/
//@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue;
try{
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。前置");
Object [] args=pjp.getArgs();//得到方法执行需要的参数
rtValue=pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。最终");
}
}
}
7、切入点表达式注解@Pointcut
作用: 指定切入点表达式
属性:value:指定表达式的内容
@Pointcut("execution(* com.uestc.service.impl.*.*(..))")
private void pt1(){}
5.57 AOP纯注解(不使用XML)
项目结构:

1、添加配置类 SpringConfiguration和JdbcConfiguration
//SpringConfiguration.java
package com.uestc.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan(basePackages="com.uestc")
@Import(JdbcConfiguration.class)
@EnableAspectJAutoProxy
public class SpringConfiguration {
}
//JdbcConfiguration.java
package com.uestc.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
@Configuration
@PropertySource("jdbcPro.properties")
public class JdbcConfiguration {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(name="dataSource")
public DataSource createSource(){
try {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Bean(name="queryRunner")
public QueryRunner createQueryRunner(DataSource ds){
QueryRunner queryRunner=new QueryRunner(ds);
return queryRunner;
}
}
2、新建数据库连接配置文件

3、 测试代码
package com.uestc.test;
import com.uestc.config.SpringConfiguration;
import com.uestc.service.IAccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestAspect {
public static void main(String[] args) {
ApplicationContext context=new AnnotationConfigApplicationContext(SpringConfiguration.class);
IAccountService service=(IAccountService)context.getBean("accountService");
service.transaction("ccc","FEI",1000);
}
}
5.6 案例: 使用aop事务的通知实现银行转账-基于XML配置
需求:不使用动态代理的方式,使用aop事务的通知实现银行转账
项目结构:

1、导入坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springlearning</artifactId>
<groupId>com.uestc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springday03</artifactId>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>
</dependencies>
</project>
2、持久层代码
package com.uestc.dao.impl;
import com.sun.org.apache.xpath.internal.SourceTree;
import com.uestc.dao.IAccountDao;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
import com.uestc.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao{
private QueryRunner runner;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public Account findAccountByName(String accountName) {
try{
List<Account> accounts= runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ?", new BeanListHandler<Account>(Account.class),accountName);
if(accounts==null || accounts.size()==0){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public List<Account> findAll() {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id= ?",new BeanHandler<Account>(Account.class), accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"insert into account(name, money) values(?,?)",account.getName(),account.getMoney());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"update account set name=?, money=? where id=?",account.getName(),account.getMoney(), account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer accountId) {
try{
runner.update(connectionUtils.getThreadConnection(),"delete account from account where id=?",accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
3、业务层代码
package com.uestc.service.impl;
import com.uestc.dao.IAccountDao;
import com.uestc.liufei.Account;
import com.uestc.service.IAccountService;
/**
* 账户的业务层实现类
* 曾经的xml的配置
*/
@Service(value = "accountService")
public class AccountServiceOld implements IAccountService {
//业务层需要调用持久层
private TransactionManager tsManager;
public void setTsManager(TransactionManager tsManager) {
this.tsManager = tsManager;
}
private IAccountDao dao=null;
public void setDao(IAccountDao dao) {
this.dao = dao;
}
public void transfer(String sourceName, String targetName, Float money) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
//2.1.根据名称查询转出账户
Account source=dao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target=dao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
dao.updateAccount(source);
int i=1/0;
//2.6更新转入账户
dao.updateAccount(target);
//3.提交事务
tsManager.commit();
}catch (Exception e){
//4.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//5.释放连接
tsManager.release();
}
}
public List<Account> findAll() {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
List<Account> accounts=dao.findAll();
//3.提交事务
tsManager.commit();
//4.返回结果
return accounts;
}catch (Exception e){
//5.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
tsManager.release();
}
}
public Account findAccountById(Integer accountId) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
Account account =dao.findAccountById(accountId);
//3.提交事务
tsManager.commit();
//4.返回结果
return account;
}catch (Exception e){
//5.回滚操作
tsManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
tsManager.release();
}
}
public void saveAccount(Account account) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
dao.saveAccount(account);
//3.提交事务
tsManager.commit();
}catch (Exception e){
//5.回滚操作
tsManager.rollback();
}finally {
//6.释放连接
tsManager.release();
}
dao.saveAccount(account);
}
public void updateAccount(Account account) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
dao.updateAccount(account);
//3.提交事务
tsManager.commit();
}catch (Exception e){
//5.回滚操作
tsManager.rollback();
}finally {
//6.释放连接
tsManager.release();
}
dao.updateAccount(account);
}
public void deleteAccount(Integer accountId) {
try{
//1.开启事务
tsManager.beginTransaction();
//2.执行操作
dao.deleteAccount(accountId);
//3.提交事务
tsManager.commit();
}catch (Exception e){
//5.回滚操作
tsManager.rollback();
}finally {
//6.释放连接
tsManager.release();
}
dao.deleteAccount(accountId);
}
}
4、把事务控制的公共代码抽取制作成通知类:
TransactionManager:
package com.uestc.utils;
/**
* 和事务管理相关的工具类,它包含了开启事务,提交事务,回滚事务,关闭事务
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try{
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try{
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try{
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 关闭事务
*/
public void release(){
try{
connectionUtils.getThreadConnection().close();//返回连接池
connectionUtils.removeConnction();//线程和连接解绑
}catch (Exception e){
e.printStackTrace();
}
}
}
ConnectionUtils:
package com.uestc.utils;
import javax.sql.DataSource;
import java.sql.Connection;
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
private ThreadLocal<Connection> tl=new ThreadLocal<Connection>();
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
private DataSource dataSource;
public Connection getThreadConnection(){
try{
//1.先从ThreadLocal上获取
Connection conn=tl.get();
//2.判断当前线程是否有连接
if(conn==null){
//3.从数据源中获取一个连接并且和线程绑定(存入threadlocal中)
conn=dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把线程和连接解绑
*/
public void removeConnction(){
tl.remove();
}
}
5、 IOC配置+AOP配置
IOC配置
<?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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置service-->
<bean id="accountService" class="com.uestc.service.impl.AccountServiceImpl">
<!-- 需要注入的dao对象-->
<property name="dao" ref="accountDao"></property>
</bean>
<!--配置dao对象 -->
<bean id="accountDao" class="com.uestc.dao.impl.AccountDaoImpl" >
<!--需要注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!--需要注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
</bean>
<!-- 配置connection的工具类ConnctionUtils -->
<bean id="connectionUtils" class="com.uestc.utils.ConnectionUtils">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mysql"></property>
<property name="user" value="root"></property>
<property name="password" value="287216"></property>
</bean>
<!--配置事务管理器-->
<bean id="tsManager" class="com.uestc.utils.TransactionManager">
<!--需要注入connectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
</beans>
AOP配置
<!--配置AOP-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.uestc.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="txAdvice" ref="tsManager">
<!--配置前置通知类型-->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!--配置后置通知类型-->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!--配置异常通知类型-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最终通知类型-->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
完整配置
<?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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置service-->
<bean id="accountService" class="com.uestc.service.impl.AccountServiceImpl">
<!-- 需要注入的dao对象-->
<property name="dao" ref="accountDao"></property>
</bean>
<!--配置dao对象 -->
<bean id="accountDao" class="com.uestc.dao.impl.AccountDaoImpl" >
<!--需要注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!--需要注入ConnectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
</bean>
<!-- 配置connection的工具类ConnctionUtils -->
<bean id="connectionUtils" class="com.uestc.utils.ConnectionUtils">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mysql"></property>
<property name="user" value="root"></property>
<property name="password" value="287216"></property>
</bean>
<!--配置事务管理器-->
<bean id="tsManager" class="com.uestc.utils.TransactionManager">
<!--需要注入connectionUtils-->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置AOP-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.uestc.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="txAdvice" ref="tsManager">
<!--配置前置通知类型-->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!--配置后置通知类型-->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!--配置异常通知类型-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最终通知类型-->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
5.7 案例:使用aop事务的通知实现银行转账-基于注解配置
项目结构:

1. 导入context注解的约束
<?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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
2、开启对AOP注解的支持
<!---告知spring在创建容器时要扫描的包,配置所需要的的标签不是在beans的约束中,而是
一个名为context名称空间和约束中-->
<context:component-scan base-package="com.uestc"></context:component-scan>
<!--开启spring对注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
3、删除bean.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!---告知spring在创建容器时要扫描的包,配置所需要的的标签不是在beans的约束中,而是
一个名为context名称空间和约束中-->
<context:component-scan base-package="com.uestc"></context:component-scan>
<!--配置QueryRunner对象-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mysql"></property>
<property name="user" value="root"></property>
<property name="password" value="287216"></property>
</bean>
<!--开启spring对注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
4、业务层实现类:
/**
* 账户的业务层实现类
* 曾经的xml的配置
*/
@Service(value = "accountService")
public class AccountServiceImpl implements IAccountService{
//业务层需要调用持久层
//使用注解注入,不需要set方法了
@Autowired
private IAccountDao dao=null;
public void transfer(String sourceName, String targetName, Float money) {
//1.根据名称查询转出账户
Account source=dao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target=dao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney()-money);
//4.转入账户加钱
target.setMoney(target.getMoney()+money);
//5.更新转出账户
dao.updateAccount(source);
int i=1/0;
//6.更新转入账户
dao.updateAccount(target);
}
public List<Account> findAll() {
return dao.findAll();
}
public Account findAccountById(Integer accountId) {
return dao.findAccountById(accountId);
}
public void saveAccount(Account account) {
dao.saveAccount(account);
}
public void updateAccount(Account account) {
dao.updateAccount(account);
}
public void deleteAccount(Integer accountId) {
dao.deleteAccount(accountId);
}
}
5、 持久层实现类
/**
* 持久层实现类
*/
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao{
@Autowired
private QueryRunner runner;
@Autowired
private ConnectionUtils connectionUtils;
public Account findAccountByName(String accountName) {
try{
List<Account> accounts= runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ?", new BeanListHandler<Account>(Account.class),accountName);
if(accounts==null || accounts.size()==0){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public List<Account> findAll() {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountById(Integer accountId) {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from account where id= ?",new BeanHandler<Account>(Account.class), accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void saveAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"insert into account(name, money) values(?,?)",account.getName(),account.getMoney());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void updateAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"update account set name=?, money=? where id=?",account.getName(),account.getMoney(), account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public void deleteAccount(Integer accountId) {
try{
runner.update(connectionUtils.getThreadConnection(),"delete account from account where id=?",accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
6、ConnectionUtils类
package com.uestc.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
/**
* 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
*/
@Component("connectionUtils")
public class ConnectionUtils {
private ThreadLocal<Connection> tl=new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
public Connection getThreadConnection(){
try{
//1.先从ThreadLocal上获取
Connection conn=tl.get();
//2.判断当前线程是否有连接
if(conn==null){
//3.从数据源中获取一个连接并且和线程绑定(存入threadlocal中)
conn=dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把线程和连接解绑
*/
public void removeConnction(){
tl.remove();
}
}
7、TransactionManager类
/**
* 和事务管理相关的工具类,它包含了开启事务,提交事务,回滚事务,关闭事务
*/
@Component
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
/**
* 切入点表达式
*/
@Pointcut("execution(* com.uestc.service.impl.*.*(..))")
public void pt1(){
}
/**
* 开启事务
*/
@Before("pt1()")
public void beginTransaction(){
try{
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
@AfterReturning("pt1()")
public void commit(){
try{
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
@AfterThrowing("pt1()")
public void rollback(){
try{
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 关闭事务
*/
@After("pt1()")
public void release(){
try{
connectionUtils.getThreadConnection().close();//返回连接池
connectionUtils.removeConnction();//线程和连接解绑
}catch (Exception e){
e.printStackTrace();
}
}
}
遇到的问题:
由于注解AOP的执行顺序是先前置通知->最终通知->后置通知(异常通知)
而在最终通知中是释放连接,和线程解绑,在后置通知进行提交的时候,获取连接会发现为null,就会创建一个新的连接,而这个连接并没有操作,因此会出现错误
解决:使用环绕通知
@Around("pt1()")
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue=null;
try{
//1.获取参数
Object [] args=pjp.getArgs();
//2.开启事务
this.beginTransaction();
//3.执行方法
rtValue=pjp.proceed(args);
//4.提交事务
this.commit();
return rtValue;
}catch (Throwable e){
this.rollback();
throw new RuntimeException(e);
}finally {
this.release();
}
}
6、Spring中的JdbcTemplate
6.1 简介
它是spring框架中提供的一个对象,是对原始Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类。
- 操作关系型数据的: JdbcTemplate、HibernateTemplate
- 操作nosql数据库的: RedisTemplate
- 操作消息队列的: JmsTemplate
我们需要导入spring-jdbc-5.0.2.RELEASE.jar和spring-tx-5.0.2.RELEASE.jar(事务相关的)
6.2 JdbcTemplate对象创建
源码:
public JdbcTemplate() { }
public JdbcTemplate(DataSource dataSource) {
setDataSource(dataSource);
afterPropertiesSet();
}
public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
setDataSource(dataSource);
setLazyInit(lazyInit);
afterPropertiesSet();
}
除了默认构造函数之外,都需要提供一个数据源。既然有set方法,依据我们之前学过的依赖注入,我们可以在配置文件中配置这些对象。
6.3 配置数据源
6.31 配置C3P0数据源
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/springTemplate"></property>
<property name="user" value="root"></property>
<property name="password" value="287216"></property>
</bean>
6.32 配置spring内置数据源
spring框架也提供了一个内置数据源,我们也可以使用spring的内置数据源,它就在spring-jdbc-5.0.2.REEASE.jar包中
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/springTemplate"></property>
<property name="username" value="root"></property>
<property name="password" value="287216"></property>
</bean>
6.33 将数据库连接的信息配置到属性文件中:
step1: 定义属性文件:jdbc.properties
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springTemplate
jdbc.username=root
jdbc.password=287216
step2: 引入外部的属性文件
<context:property-placeholder location="classpath:jdbc.properties">
或者
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"></property>
</bean>
step3: 配置数据源
<!--配置c3p0数据源-->
<bean id="dataSource class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置spring内置数据源-->
<bean id="dataSource class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
6.4 JdbcTemplate的增删改查操作
6.41 前期准备:
1、创建数据库和表:
create database springTemplate;
use springTemplate;
create table account(
id int primary key auto_increment,
name varchar(100),
money double
);
2、pom导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
</dependencies>
3、在spring配置文件中配置JdbcTemplate
<?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 http://www.springframework.org/schema/context/spring-context.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"></property>
</bean>
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
</beans>
6.42 具体操作
1、保存操作:
public class Demo2 {
public static void main(String[] args) {
//1.加载配置文件,获取容器
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
//2.获取jdbcTemplate对象
JdbcTemplate js=context.getBean("jdbcTemplate", JdbcTemplate.class);
//3.执行操作
js.execute("insert into account (name, money) values('ddd',4000)");
}
}
2、更新操作
public class Demo3 {
public static void main(String[] args) {
//1.加载配置文件,获取容器
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
//2.获取jdbcTemplate对象
JdbcTemplate js=context.getBean("jdbcTemplate", JdbcTemplate.class);
//3.执行操作
js.update("update account set name=?,money=? where id=? ","test",1500,1);
}
}
3、删除操作
public class Demo3 {
public static void main(String[] args) {
//1.加载配置文件,获取容器
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
//2.获取jdbcTemplate对象
JdbcTemplate js=context.getBean("jdbcTemplate", JdbcTemplate.class);
//3.执行操作
js.update("delete from account where id=?",5);
}
}
4、查询所有操作
public class Demo4 {
public static void main(String[] args) {
//1.加载配置文件,获取容器
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
//2.获取jdbcTemplate对象
JdbcTemplate js=context.getBean("jdbcTemplate", JdbcTemplate.class);
//3.执行操作
//查询所有
List<Account> accounts=js.query("select * FROM account where money>?", new BeanPropertyRowMapper<Account>(Account.class),1000);
for(Account account: accounts){
System.out.println(account);
}
}
或者:
public class Demo5 {
public static void main(String[] args) {
//1.加载配置文件,获取容器
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
//2.获取jdbcTemplate对象
JdbcTemplate js=context.getBean("jdbcTemplate", JdbcTemplate.class);
//3.执行操作
//查询所有
List<Account> accounts=js.query("select * FROM account where money>?", new AccountRowMapper(),1000);
for(Account account: accounts){
System.out.println(account);
}
}
public class AccountRowMapper implements RowMapper<Account>{
@Override public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
5、查询一个操作
public class Demo6 {
public static void main(String[] args) {
//1.加载配置文件,获取容器
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
//2.获取jdbcTemplate对象
JdbcTemplate js=context.getBean("jdbcTemplate", JdbcTemplate.class);
//3.执行操作
// 查询一个
List<Account> accountss=js.query("select * FROM account where id=?", new BeanPropertyRowMapper<Account>(Account.class),1);
System.out.println(accountss.isEmpty()?"没有内容":accounts.get(0));
}
6、查询返回一行一列操作
public class Demo7 {
public static void main(String[] args) {
//1.加载配置文件,获取容器
ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml");
//2.获取jdbcTemplate对象
JdbcTemplate js=context.getBean("jdbcTemplate", JdbcTemplate.class);
//3.执行操作
//查询返回一行一列(使用聚合函数,但不加group by 子句)
Integer count=js.queryForObject("select count(*) from account where money>?",Integer.class,1500);
System.out.println(count);
}
}
6.5 在dao中使用JdbcTemplate
6.51 前期准备
定义实体类
package com.uestc.domain;
import java.io.Serializable;
public class Account implements Serializable{
private Integer id;
private String name;
private double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
第一种方式:在dao中定义JdbcTemplate
dao接口:
public interface IAccountDao {
//根据id查询账户信息
public Account findAccountById(Integer accountId);
//根据名称查询账户信息
public Account findAccountByName(String accountName);
//更新账户信息
public void updateAccount(Account account);
}
dao实现类:
public class AccountDaoImpl implements IAccountDao{
private JdbcTemplate jdbcTemplate;
//需要给dao注入JdbcTemplate
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Account findAccountById(Integer accountId) {
List<Account> accounts=jdbcTemplate.query("select * from account where id=?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0) ;
}
public Account findAccountByName(String accountName) {
List<Account> accounts=jdbcTemplate.query("select * from account where name=?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?, money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
配置文件:
<?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 http://www.springframework.org/schema/context/spring-context.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"></property>
</bean>
<!--配置账户的持久层-->
<bean id="accountDao" class="com.uestc.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
</beans>
**问题:**dao有很多时,每个dao都有一些重复性的代码。下面就是重复代码:
private JdbcTemplate jdbcTemplate;
//需要给dao注入JdbcTemplate
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
第二种方式:让dao继承JdbcDaoSupport
JdbcDaoSupport是spring框架为我们提供的一个类,该类中定义了一个JdbcTemplate对象,我们可以直接获取使用,但是要想创建该对象,需要为其提供一个数据源
public class JdbcDaoSupport extends DaoSupport {
private JdbcTemplate jdbcTemplate;
//set方法注入数据源,判断是否注入了,注入了就创建JdbcTemplate
public void setDataSource(DataSource dataSource){
if(jdbcTemplate==null || dataSource!=this.jdbcTemplate.getDataSource()){
this.jdbcTemplate=createJdbcTemplate(dataSource);
initTemplateConfig();
}
}
//使用数据源创建JdcbTemplate
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
//当然,我们也可以通过注入JdbcTemplate对象
public final void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
initTemplateConfig();
}
//使用getJdbcTmeplate方法获取操作模板对象
public final JdbcTemplate getJdbcTemplate() {
return this.jdbcTemplate;
}
}
dao实现类:
只需要给它的父类注入一个数据源
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao{
public Account findAccountById(Integer accountId) {
//getJdbcTemplate()方法是从父类上继承下来的。
List<Account> accounts=super.getJdbcTemplate().query("select * from account where id=?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0) ;
}
public Account findAccountByName(String accountName) {
List<Account> accounts=super.getJdbcTemplate().query("select * from account where name=?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?, money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
配置文件:
<?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 http://www.springframework.org/schema/context/spring-context.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"></property>
</bean>
<!--配置账户的持久层-->
<bean id="accountDao" class="com.uestc.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
</beans>
两种方式的差异
第一种在Dao类中定义JdbcTemplate的方式,适用于所有配置方式(xml和注解都可以)。
第二种让Dao继承JdbcDaoSupport的方式,只能用于基于XML的方式,注解用不了
7、Spring中的事务控制
第一:JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
第二:spring框架为我们提供了一组事务控制的接口, 这组接口是在spring-tx-5.0.2.RELEASE.jar中。
第三:spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。
7.1 Spring中事务控制的API介绍
1、PlatformTransactionManager
此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法:
//获取事务的状态信息
TransactionStatus getTransation(TransactionDefinition definition)
//提交事务
void commit(TransationStutus status)
//回滚事务
void rollback(TransationStutus status)
其实现类:
真正管理事务的对象,使用Spring JDBC或iBatis 进行持久化数据时使用
org.springframework.jdbc.datasource.DataSourceTransactionManager
2、TransationDefinition
事务的定义信息对象,方法如下:
//获取事务对象名称
String getName()
//获取事务隔离级别
int getIsolationLevel()
//获取事务传播行为
int getPropagationBehavior()
//获取事务超时时间
int getTimeOut()
//获取事务是否只读:
//读写型事务:增加、删除、修改开启事务
//只读型事务:执行查询时,也会开启事务
boolean isReadOnly()
事务的隔离级别:
事务隔离级别反映了事务提交并发访问时的处理:
-ISOLATION_DEFAULT 默认级别,归属下列某一种
-ISOLATION_READ_UNCOMMITTED 可以读取未提交数据
-ISOLATION_READ_COMMITTED 只能读取已提交的数据,解决脏读问题(Oracle默认级别)
-ISOLATION_REPEATABLE_READ 是否读取其他事务提交修改后的数据,解决不可重读问题(Mysql默认级别)
-ISOLATION_SERIALIZABLE 是否读取其他书屋提交添加后的数据,解决幻读问题
事务的传播行为:
超时时间:
默认值是-1,没有超时限制。如果有,以秒为单位进行设置
是否是只读事务:
建议查询时设置为只读。
3、TransactionStatus
描述了某个时间点上事务的状态信息,此接口提供的是事务具体的运行状态:
//刷新事务
void flush()
//获取是否存在存储点
boolean hasSavepoint()
//获取事务是否完成
boolean isCompleted()
//获取事务是否为新的事务
boolean isNewTransaction()
//获取事务是否回滚
boolean isRollbackOnly()
//设置事务回滚
void setRollbackOnly()
7.2 基于XML的声明式事务控制
7.21 准备工作
工程目录:

step1: 导入pom依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
step2: 创建spring的配置文件并导入约束
注意:此处需要导入aop和tx两个名称空间
<?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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
step3: 准备数据库表和account实体类
package com.uestc.domain;
public class Account implements Serializable{
private Integer id;
private String name;
private double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
step4: 编写业务层接口和实现类
public interface IAccountService {
/**
* 根据id查询账户信息
* @param accountId
* @return
*/
public Account findById(Integer accountId);
/**
* 转账名称
* @param sourceName 转出账户名称
* @param targetName 转入账户名称
* @param money 转账金额
*/
public void transfer(String sourceName,String targetName,Float money);
}
package com.uestc.service.impl;
@Service
public class AccountServiceImpl implements IAccountService {
private IAccountDao dao;
public void setDao(IAccountDao dao) {
this.dao = dao;
}
public Account findById(Integer accountId) {
return dao.findByAccountId(accountId);
}
public void transfer(String sourceName, String targetName, Float money) {
//根据名字查找账户
Account sourceAccount = dao.findByAccountName(sourceName);
Account targetAccount = dao.findByAccountName(targetName);
//sourceAccount账户减去money
sourceAccount.setMoney(sourceAccount.getMoney()-money);
//targetAccount账户增加金额
targetAccount.setMoney(targetAccount.getMoney()+money);
//更新账户
dao.updateAccount(sourceAccount);
int i=10/0;
//更新账户
dao.updateAccount(targetAccount);
}
}
step5: 编写Dao接口和实现类
package com.uestc.dao;
import com.uestc.domain.Account;
public interface IAccountDao {
public Account findByAccountId(Integer accountId);
public Account findByAccountName(String accountName);
public void updateAccount(Account account);
}
package com.uestc.dao.impl;
import com.uestc.dao.IAccountDao;
import com.uestc.domain.Account;
public class AccountImplDao extends JdbcDaoSupport implements IAccountDao {
public Account findByAccountId(Integer accountId) {
List<Account> accounts= super.getJdbcTemplate().query("select * from account where id=?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
public Account findByAccountName(String accountName) {
List<Account> accounts= super.getJdbcTemplate().query("select * from account where name=?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId() );
}
}
step6: 在配置文件中配置业务层和持久层对
<bean class="com.uestc.service.impl.AccountServiceImpl">
<property name="dao" ref="accountDao"></property>
</bean>
<bean id="accountDao" class="com.uestc.dao.impl.AccountImplDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
7.22 配置步骤
step1: 配置事务管理器
!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
step2: 配置事务的通知引用事务管理器
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
</tx:advice>
step3:配置事务的属性
<!--配置事务的属性
-isolation:用于指定事务的隔离级别,表示使用数据库的默认隔离级别
-propagation:用于指定事务的传播行为,默认值是REQURED,表示一定会有事务,增删改的选择,查询方法可以选择SUPPORTS
-read-Only: 用于指定事务是否只读。只有查询方法才能设置为true,默认为false,表示读写
-timeout: 用于指定事务的超时时间,默认值为-1,表示永不超时,如果指定了数值,以秒为单位
-rollback-for: 用于指定一个异常,当产生该异常时,事务回滚,而产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚
-no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,而产生其他异常时事务回滚。没有默认值,表示任何异常都回滚
-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
step4: 配置AOP切入点表达式
<!--配置aop-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut expression="execution(* com.uestc.service.impl.*.*(..))" id="pt1" </aop:pointcut>
</aop:config>
step5: 配置切入点表达式和事务通知的对应关系
<!--配置aop-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut expression="execution(* com.uestc.service.impl.*.*(..))" id="pt1"></aop:pointcut>
<!--建立切入点表达式和事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
完整配置:
<?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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--加载jdbc配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--配置service-->
<bean class="com.uestc.service.impl.AccountServiceImpl">
<property name="dao" ref="accountDao"></property>
</bean>
<!--配置dao-->
<bean id="accountDao" class="com.uestc.dao.impl.AccountImplDao">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut expression="execution(* com.uestc.service.impl.*.*(..))" id="pt1"></aop:pointcut>
<!--建立切入点表达式和事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
7.3 基于注解的配置方式
7.31 准备工作
step1: 导入pom依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
step2: 创建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: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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置spring创建容器时要扫描的包 -->
<context:component-scan base-package="com.uestc"></context:component-scan>
<!--配置jdbc外部文件-->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--配置JdbcTemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置spring配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
step3: 创建业务层接口和实现类并使用注解让spring管理
@Service
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao dao;
public Account findById(Integer accountId) {
return dao.findByAccountId(accountId);
}
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void transfer(String sourceName, String targetName, Float money) {
//根据名字查找账户
Account sourceAccount = dao.findByAccountName(sourceName);
Account targetAccount = dao.findByAccountName(targetName);
//sourceAccount账户减去money
sourceAccount.setMoney(sourceAccount.getMoney()-money);
//targetAccount账户增加金额
targetAccount.setMoney(targetAccount.getMoney()+money);
//更新账户
dao.updateAccount(sourceAccount);
//int i=10/0;
//更新账户
dao.updateAccount(targetAccount);
}
step4: 创建Dao接口和实现类并使用注解让spring管理
@Repository
public class AccountImplDao implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Account findByAccountId(Integer accountId) {
List<Account> accounts= jdbcTemplate.query("select * from account where id=?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
public Account findByAccountName(String accountName) {
List<Account> accounts= jdbcTemplate.query("select * from account where name=?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId() );
}
}
7.32 配置步骤
step1: 配置事务管理器并注入数据源
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
step2: 在业务层使用@Transactional注解
该注解的属性和xml中的属性含义一致。该注解可以出现在接口上,类上和方法上。 出现接口上,表示该接口的所有实现类都有事务支持。 出现在类上,表示类中所有方法有事务支持 出现在方法上,表示方法有事务支持。 以上三个位置的优先级:方法>类>接口
package com.uestc.service.impl;
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao dao;
public Account findById(Integer accountId) {
return dao.findByAccountId(accountId);
}
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void transfer(String sourceName, String targetName, Float money) {
//根据名字查找账户
Account sourceAccount = dao.findByAccountName(sourceName);
Account targetAccount = dao.findByAccountName(targetName);
//sourceAccount账户减去money
sourceAccount.setMoney(sourceAccount.getMoney()-money);
//targetAccount账户增加金额
targetAccount.setMoney(targetAccount.getMoney()+money);
//更新账户
dao.updateAccount(sourceAccount);
//int i=10/0;
//更新账户
dao.updateAccount(targetAccount);
}
}
step3: 在配置文件中开启spring对注解事务的支持
<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
完整配置:
<?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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.uestc"></context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<!--配置JdbcTemplate-->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClass}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--spring中基于XML的声明式事务控制配置步骤
1、配置事务管理器
2、开启spring对注解事务的支持
3、在需要事务支持的地方使用@Transactional注解
-->
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
7.4 纯注解配置

jdbcConfig:
@Configuration
public class JdbcConfig {
@Value("${jdbc.driverClass}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建jdbcTemplate
* @param dataSource
* @return
*/
@Bean(name="jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean("dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds=new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
TransactionConfig:
package com.uestc.config;
public class TransactionConfig {
@Bean(name="transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
SpringConfiguration:
@Configuration
@ComponentScan(basePackages = {"com.uestc"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,TransactionConfig.class})
@EnableTransactionManagement
public class SpringConfiguration {
}