title: SSM-Spring author: LF_Tech top: false cover: false toc: true mathjax: false tags:
Spring框架 categories:
SSM abbrlink: 20680 date: 2020-12-10 20:02:52 img: coverImg: password: summary:
Spring 1、Spring 框架体系结构
1.1 Core Container(核心容器) 核心容器由 spring-core,spring-beans,spring-context,spring-context-support和spring-expression (SpEL,Spring 表达式语言,Spring Expression Language)等模块组成
导入的jar包:
1 2 3 4 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包
1 2 3 4 5 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包
1 2 3 4 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包
1 2 spring-aop-5.0 .2 .RELEASE.jar spring-aspects-5.0 .2 .RELEASE.jar
完整依赖关系 完整依赖关系如下图所示:
2、早期代码开发(不使用ioc)
2.1 新建实体类User 2.2 新建dao接口以及实现类 1 2 3 4 5 package com.uestc.dao;import com.uestc.domain.User;public interface IUserDao { User findAll () ; }
dao实现类UserDaoMysql
1 2 3 4 5 6 7 8 9 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
1 2 3 4 5 6 7 8 9 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 ; } }
2.3 新建service及其实现类 1 2 3 4 5 package com.uestc.service;import com.uestc.domain.User;public interface IUserService { User findAll () ; }
1 2 3 4 5 6 7 8 9 10 11 12 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(); } }
2.4 新建测试类 1 2 3 4 5 6 7 8 9 10 11 12 13 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注入方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; public void setUserDao (IUserDao userDao) { this .userDao = userDao; } public User findAll () { return userDao.findAll(); } }
更改测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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(); } }
注意:
这种思想从本质上解决了问题,我们程序员不用再去管理对象的创建了。系统的耦合性大大降低,可以更加专注的在业务的实现上,这是IOC的原型!
3、HelloSpring 注意:我们以spring基于xml配置为入门程序
3.1、 导入Spring相关jar包(pom文件导入坐标) 注:spring需要导入commons-logging进行日志记录,我们利用maven会自动下载依赖
1 2 3 4 5 6 7 <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 5.2.7.RELEASE</version > </dependency > </dependencies >
可以看到相关jar包已经导入
3.2、 编写HelloSpring实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 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); } }
3.3、 创建 bean 的配置文件bean.xml
1 2 3 4 5 6 <?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>
可以在官网中找到:
3.4、创建测试类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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) { ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml" ); HelloSpring helloSpring=(HelloSpring) context.getBean("hello" ); 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 案例准备
4.211 创建业务层接口和实现类 业务层接口:
1 2 3 4 5 6 7 8 9 10 package com.uestc.service;public interface IAccountService { void saveAccount () ; }
业务层接口实现类:
1 2 3 4 5 6 7 8 9 10 11 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.saveAccount(); } }
4.212 创建持久层接口和实现类 持久层接口 :
1 2 3 4 5 6 7 package com.uestc.dao;public interface IAccountDao { void saveAccount () ; }
持久层接口实现类:
1 2 3 4 5 6 7 8 9 10 package com.uestc.dao.impl;import com.uestc.dao.IAccountDao;public class AccountDaoImpl implements IAccountDao { public void saveAccount () { System.out.println("保存账户" ); } }
4.22 基于XML的配置入门案例 第一步:在resources文件中创建bean.xml
文件
第二步:导入约束
1 2 3 4 <?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <bean id ="accountDao" class ="com.uestc.dao.impl.AccountDaoImpl" > </bean > <bean id ="accountService" class ="com.uestc.service.impl.AccountService" > </bean > </beans >
第四步: 测试配置是否成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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) { ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml" ); 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方法时候创建多实例对象
示例
1 <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的销毁的方法(需要进行配置销毁的方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 <bean id ="accountService" class ="com.itheima.service.impl.AccountServiceImpl" scope ="prototype" init-method ="init" destroy-method ="destroy" > </bean >
4.334 Bean实例化(创建对象)三种方式 第一种方式:使用默认无参构造函数
1 2 3 4 5 <bean id ="accountService" class ="com.itheima.service.impl.AccountServiceImpl" > </bean >
第二种方法:spring管理实例工厂-使用实例工厂的方法创建对象
1 2 3 4 5 6 7 8 9 10 11 package com.uestc.factory;import com.uestc.service.IAccountService;import com.uestc.service.impl.AccountServiceImpl;public class InstanceFactory { public IAccountService getAccountService () { return new AccountServiceImpl(); } }
1 2 3 4 5 6 7 8 9 <bean id ="instanceFactory" class ="com.itheima.factory.InstanceFactory" > </bean > <bean id ="accountService" factory-bean ="instanceFactory" factory-method ="getAccountService" > </bean > -->
第三种方式:spring管理静态工厂-使用静态工厂的方法创建对象
1 2 3 4 5 6 7 8 9 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(); } }
1 2 3 4 5 6 <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中的属性传值
要求:类中需要提供一个对应参数列表的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 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; }
1 2 3 4 5 6 7 8 9 <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没有执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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; } }
1 2 3 4 5 6 7 <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>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package com.uestc.service.impl;import java.util.*;import com.uestc.service.IAccountService;public class AccountServiceImpl implements IAccountService { private String [] myStrs; private List<String> myList; private Set<String> mySet; private Map<String,String> myMap; 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); } }
注入数组
1 2 3 4 5 6 7 8 9 <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集合
1 2 3 4 5 6 7 8 9 <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集合
1 2 3 4 5 6 7 8 9 <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集合
1 2 3 4 5 6 7 8 <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
1 2 3 4 5 6 <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配置文件
1 2 3 4 5 6 7 8 9 10 11 12 <?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 > </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对象类型和要注入的变量类型匹配,就可以注入成功
出现的位置 :可以是变量 上,也可以是方法 上
细节:在使用注解注入时,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案例 4.41 需求:
使用 spring的 IoC的实现账户的 CRUD
4.42 技术要求
使用 spring 的 IoC 实现对象的管理
使用 DBAssit
作为持久层解决方案
使用 c3p0
数据源
4.43 实现
第一步:在pom文件添加依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <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 >
第二步:创建数据库和编写实体类 1 2 3 4 5 6 7 8 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 );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 + '}' ; } }
第三步:编写持久层代码 持久层接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.uestc.dao;import com.uestc.liufei.Account;import java.util.List;public interface IAccountDao { public List<Account> findAll () ; public Account findAccountById (Integer accountId) ; public void saveAccount (Account account) ; public void updateAccount (Account account) ; public void deleteAccount (Integer accountId) ; }
持久层实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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 { 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); } } }
第四步:编写业务层代码 : 业务层接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.uestc.service;import com.uestc.liufei.Account;import java.util.List;public interface IAccountService { public List<Account> findAll () ; public Account findAccountById (Integer accountId) ; public void saveAccount (Account account) ; public void updateAccount (Account account) ; public void deleteAccount (Integer accountId) ; }
业务层实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 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;@Service (value = "accountService" )public class AccountServiceImpl implements IAccountService { private IAccountDao dao=null ; 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); } }
第五步:创建并编写配置文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?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" > <bean id ="accountService" class ="com.uestc.service.impl.AccountServiceImpl" > <property name ="dao" ref ="accountDao" > </property > </bean > <bean id ="accountDao" class ="com.uestc.dao.impl.AccountDaoImpl" > <property name ="runner" ref ="runner" > </property > </bean > <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 >
第六步:测试类代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 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 () { ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml" ); IAccountService as=ac.getBean("accountService" ,IAccountService.class ) ; List<Account> accounts=as.findAll(); for (Account account: accounts){ System.out.println(account); } } @Test public void testFindOne () { ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml" ); IAccountService as=ac.getBean("accountService" ,IAccountService.class ) ; Account account=as.findAccountById(1 ); System.out.println(account); } @Test public void testSave () { ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml" ); IAccountService as=ac.getBean("accountService" ,IAccountService.class ) ; Account account=new Account(); account.setMoney(9999.9f ); account.setName("liufei" ); as.saveAccount(account); } @Test public void testUpdata () { ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml" ); IAccountService as=ac.getBean("accountService" ,IAccountService.class ) ; Account account=as.findAccountById(4 ); account.setMoney(1119999.9f ); as.updateAccount(account); } @Test public void testDelete () { ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml" ); IAccountService as=ac.getBean("accountService" ,IAccountService.class ) ; as.deleteAccount(1 ); } }
4.44 分析测试中的问题 通过上面的测试类,我们可以看出,每个测试方法都重新获取了一次spring的核心容器,造成了不必要的重复代码,增加了我们开发的工作量。这种情况,在开发中应该避免发生。 有些同学可能想到了,我们把容器的获取定义到类中去。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 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 () { ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml" ); 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案例 4.51 第一步:配置xml文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?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 > <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 >
4.52 持久层实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 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; 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); } } }
4.53 业务层实现: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 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;@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); } }
4.54 测试类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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 () { ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml" ); 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,所有配置都用注解来实现呢?
待改造的问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <context:component-scan base-package ="com.uestc" > </context:component-scan > <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对象创建的参数时,该注解可以不写
1 2 @Configuration public class SpringConfiguration {}
我们已经把配置文件用类来代替了,但是如何配置创建容器时要扫描的包呢? 请看下一个注解。
@ComponentScan 作用 :用于通过注解指定spring在创建容器时要扫描的包属性 :values
它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包
我们使用了此注解就等同于在xml中配置了:<context:component-scan base-package="com.uestc"></context:component-scan>
1 2 3 @Configuration @ComponentScan (basePackages = {"com.uestc" ,"config" })public class SpringConfiguration {}
我们已经配置好了要扫描的包,但是数据源和JdbcTemplate对象如何从配置文件中移除呢? 请看下一个注解。
@Bean 作用 :
该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。 用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性 :name
用于指定bean的id,当不写的时候默认值是当前方法的名称
细节 :
当我们用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象 查找的方式和Autowired注解的作用一致
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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 { @Bean ("runner" ) @Scope ("prototype" ) public QueryRunner createQueryRunner (DataSource dataSource) { return new QueryRunner(dataSource); } @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
类
1 2 3 4 5 6 7 8 9 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
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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; @Bean ("runner" ) @Scope ("prototype" ) public QueryRunner createQueryRunner (DataSource dataSource) { return new QueryRunner(dataSource); } @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[]
:
用于指定其他配置类的字节码。
1 2 3 4 5 @ComponentScan (basePackages = {"com.uestc" ,"config" })@Import (JdbcConfig.class ) @PropertySource("classpath:jdbcConfig.properties") public class SpringConfiguration { }
注意: 我们已经把要配置的都配置好了,但是新的问题产生了,由于没有配置文件了,如何获取容器呢?
通过注解获取容器 1 ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class ) ;
4.7 Spring 整合 Junit 4.71 测试类中的问题和解决思路 问题:
在测试类中,每个测试方法都有以下两行代码:
1 2 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
注解替换原有运行器
1 2 @RunWith (SpringJUnit4ClassRunner.class ) public class AccountServiceTest { }
第三步:使用@ContextConfiguration
指定 spring 配置文件的位置
1 2 3 4 5 6 @RunWith (SpringJUnit4ClassRunner.class ) @ContextConfiguration (classes =SpringConfiguration.class ) public class AccountServiceTest { private ApplicationContext ac=null ; IAccountService as=null ; }
第四步:使用@Autowired
给测试类中的变量注入数据
1 2 3 4 5 6 7 8 @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类创建代理对象 场景使用一个演员的例子: 在很久以前,演员和剧组都是直接见面联系的。没有中间人环节。 而随着时间的推移,产生了一个新兴职业:经纪人(中间人),这个时候剧组再想找演员就需要通过经纪人来找了
第一种:使用匿名内部类创建代理对象
包结构
接口类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.uestc.proxy;public interface IActor { void basicPerform (float money) ; void dangerPerform (float money) ; }
被代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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+"钱,开始危险表演。。" ); } }
使用匿名内部类创建代理对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 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 () { 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" )){ if (money>2000 ){ rtValue=method.invoke(actor,money/2 ); } } if (name.equals("dangerPerform" )){ if (money>5000 ){ rtValue=method.invoke(actor,money/2 ); } } return rtValue; } }); proxy_actor.basicPerform(8000f ); proxy_actor.dangerPerform(50000f ); } }
第二种:创建代理对象
包结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.uestc.proxy;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class ActorProxy implements InvocationHandler { private Object object; public ActorProxy (Object object) { this .object = object; } 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" )){ if (money>2000 ){ rtValue=method.invoke(object,money/2 ); } } if (name.equals("dangerPerform" )){ if (money>5000 ){ rtValue=method.invoke(object,money/2 ); } } return rtValue; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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包
1 2 3 4 5 <dependency > <groupId > cglib</groupId > <artifactId > cglib</artifactId > <version > 3.2.10</version > </dependency >
2、Actor类
1 2 3 4 5 6 7 8 9 10 11 12 13 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、代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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() { public Object intercept (Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object rtValue=null ; Float money =(Float)args[0 ]; if (method.getName().equals("basicPerform" )){ if (money>2000 ){ rtValue=method.invoke(actor,money/2 ); } } if (method.getName().equals("dangerPerform" )){ if (money>5000 ){ rtValue=method.invoke(actor,money/2 ); } } return rtValue; } }); cglibProducer.basicPerform(10000f ); cglibProducer.dangerPerform(20000f ); } }
5.3 银行转账案例 5.31 遇到的问题 我们在业务层中多加入一个方法transfer
: 当我们执行时,由于执行有异常,转账失败。但是因为我们是每次执行持久层方法都是独立事务,导致无法实现事务控制(不符合事务的一致性 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public void transfer (String sourceName, String targetName, Float money) { Account source=dao.findAccountByName(sourceName); Account target=dao.findAccountByName(targetName); source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); dao.updateAccount(source); int i=1 /0 ; dao.updateAccount(target); }
5.32 问题的解决
解决办法: 让业务层来控制事务的提交和回滚。
两个工具类 ConnectionUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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 { Connection conn=tl.get(); if (conn==null ){ conn=dataSource.getConnection(); tl.set(conn); } return conn; }catch (Exception e){ throw new RuntimeException(e); } } public void removeConnction () { tl.remove(); } }
TransactionManager
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 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(); } } }
持久层代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 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); } } }
配置文件加入: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <bean id ="accountDao" class ="com.uestc.dao.impl.AccountDaoImpl" > <property name ="runner" ref ="runner" > </property > <property name ="connectionUtils" ref ="connectionUtils" > </property > <bean id ="runner" class ="org.apache.commons.dbutils.QueryRunner" scope ="prototype" > </bean > <bean id ="connectionUtils" class ="com.uestc.utils.ConnectionUtils" > <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" > <property name ="connectionUtils" ref ="connectionUtils" > </property > </bean >
业务层改进后的代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 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;@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 { tsManager.beginTransaction(); Account source=dao.findAccountByName(sourceName); Account target=dao.findAccountByName(targetName); source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); dao.updateAccount(source); int i=1 /0 ; dao.updateAccount(target); tsManager.commit(); }catch (Exception e){ tsManager.rollback(); throw new RuntimeException(e); }finally { tsManager.release(); } } public List<Account> findAll () { try { tsManager.beginTransaction(); List<Account> accounts=dao.findAll(); tsManager.commit(); return accounts; }catch (Exception e){ tsManager.rollback(); throw new RuntimeException(e); }finally { tsManager.release(); } } public Account findAccountById (Integer accountId) { try { tsManager.beginTransaction(); Account account =dao.findAccountById(accountId); tsManager.commit(); return account; }catch (Exception e){ tsManager.rollback(); throw new RuntimeException(e); }finally { tsManager.release(); } } public void saveAccount (Account account) { try { tsManager.beginTransaction(); dao.saveAccount(account); tsManager.commit(); }catch (Exception e){ tsManager.rollback(); }finally { tsManager.release(); } dao.saveAccount(account); } public void updateAccount (Account account) { try { tsManager.beginTransaction(); dao.updateAccount(account); tsManager.commit(); }catch (Exception e){ tsManager.rollback(); }finally { tsManager.release(); } dao.updateAccount(account); } public void deleteAccount (Integer accountId) { try { tsManager.beginTransaction(); dao.deleteAccount(accountId); tsManager.commit(); }catch (Exception e){ tsManager.rollback(); }finally { tsManager.release(); } dao.deleteAccount(accountId); } }
5.33 新的问题 上一小节的代码,通过对业务层改造,已经可以实现事务控制了,但是由于我们添加了事务控制,也产生了一个新的问题: 业务层方法变得臃肿了,里面充斥着很多重复代码。并且业务层方法和事务控制方法耦合了。 试想一下,如果我们此时提交,回滚,释放资源中任何一个方法名变更,都需要修改业务层的代码,况且这还只是一个业务层实现类,而实际的项目中这种业务层实现类可能有十几个甚至几十个。
5.4 用动态代理解决银行转账 用于创建service 的代理对象的工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 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;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; } public IAccountService getAccountService () { IAccountService iAccountService= (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { Object returnValue=null ; try { tsManager.beginTransaction(); returnValue=method.invoke(accountService,args); tsManager.commit(); return returnValue; }catch (Exception e){ tsManager.rollback(); throw new RuntimeException(e); }finally { tsManager.release(); } } }); return iAccountService; } }
最终的配置文件: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <bean id ="proxyAccountService" factory-bean ="beanfactory" factory-method ="getAccountService" > </bean > <bean id ="beanfactory" class ="com.uestc.factory.BeanFactory" > <property name ="accountService" ref ="accountService" > </property > <property name ="tsManager" ref ="tsManager" > </property > </bean > <bean id ="accountService" class ="com.uestc.service.impl.AccountServiceImpl" > <property name ="dao" ref ="accountDao" > </property > </bean > <bean id ="accountDao" class ="com.uestc.dao.impl.AccountDaoImpl" > <property name ="runner" ref ="runner" > </property > <property name ="connectionUtils" ref ="connectionUtils" > </property > </bean > <bean id ="runner" class ="org.apache.commons.dbutils.QueryRunner" scope ="prototype" > </bean > <bean id ="connectionUtils" class ="com.uestc.utils.ConnectionUtils" > <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" > <property name ="connectionUtils" ref ="connectionUtils" > </property > </bean >
修改后的业务层代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 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;@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) { Account source=dao.findAccountByName(sourceName); Account target=dao.findAccountByName(targetName); source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); dao.updateAccount(source); int i=1 /0 ; 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
(表达式)表达式语法 : 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法 :
1 public void com.uestc.service.impl.AccountServiceImpl.saveAccout()
1.访问修饰符可以省略
1 void com.uestc.service.impl.AccountServiceImpl.saveAccout()
2.返回值可以使用通配符*
,表示任意返回值
1 * com.uestc.service.impl.AccountServiceImpl.saveAccout()
3.1包名可以使用通配符*
,表示任意包,但是有几级就需要写几个*
1 * *.*.*.*.Account.saveAccout()
3.2 包名可以使用..
表示当前包及其子包:
1 * com..AccountServiceImpl.saveAccout()
4.类名和方法名都可以使用通配*
5.参数列表 5.1 可以直接写数据类型
5.2 通配符*
通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类下的所有方法:
1 * com.uestc.service.impl.*.*(..)
5.553 通知类型说明 aop:before
(前置配置)作用 :用于前置配置
通知。指定增强的方法在切入点方法之前执行
属性 :
method
:用于指定通知类中的增强方法名称
ponitcut-ref
:用于指定切入点的表达式的引用
poinitcut
:用于指定切入点表达式
执行时间点 : 切入点方法执行之前执行
1 2 3 4 5 6 7 8 9 <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
:指定切入点表达式的引用
执行时间点 : 切入点方法正常执行之后。它和异常通知只能有一个执行
1 2 3 4 5 6 7 8 9 <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
:指定切入点表达式的引用
执行时间点 : 切入点方法执行产生异常后执行。它和后置通知只能执行一个
1 2 3 4 5 6 7 8 9 <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
(最终通知)作用 :用于配置最终通知
。无论切入点方法执行时是否有异常,它都会在其后面执行属性 :
执行时间点 :无论切入点方法执行时是否有异常,它都会在其后面执行
1 2 3 4 5 6 7 8 9 <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框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。 注意: 通常情况下,环绕通知都是独立使用的
1 2 3 4 5 6 7 <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包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <?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的约束
1 2 3 4 5 6 7 8 <?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?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" > <bean id ="accountService" class ="com.uestc.service.impl.AccountService" > <property name ="accountDao" ref ="accountDao" > </property > </bean > <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、抽取公共代码制作成通知类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 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方法开始记录日志了。。。" ); } 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标签配置起来
1 2 <bean id ="logger" class ="com.uestc.utils.Logger" > </bean >
6、使用aop:config声明aop配置
1 2 3 <aop:config > </aop:config >
7、使用aop:aspect配置切面
1 2 3 4 5 6 <aop:config > <aop:aspect id ="logAdvice" ref ="logger" > </aop:aspect > </aop:config >
8、使用aop:pointcut配置切入点表达式
aop:pointcut
:作用 : 用于配置切入点表达式。就是指定对哪些类
的哪些方法
进行增强。属性 :
expression
:用于定义切入点表达式。
id
:用于给切入点表达式提供一个唯一标
1 2 3 4 5 6 7 8 <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配置对应的通知类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <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的名称空间 1 2 3 4 5 6 7 8 9 10 11 <?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要扫描的包 1 2 <context:component-scan base-package ="com.uestc" > </context:component-scan >
3、将业务类用注解配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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
:用于指定切入点表达式,还可以指定切入点表达式的引用。
1 2 3 4 @Before ("pt1()" )public void beforePrintLog () { System.out.println("前置通知:Logger类中的beforePrintLog方法开始记录日志了。。。" ); }
@AfterReturning 作用 : 把当前方法看成是后置通知。属性 :value
:用于指定切入点表达式,还可以指定切入点表达式的引用。
1 2 3 4 @AfterReturning ("pt1()" )public void AfterReturingPrintLog () { System.out.println("后置通知:Logger类中的AfterReturingPrintLog方法开始记录日志了。。。" ); }
@AfterThrowing 作用 : 把当前方法看成是异常通知。属性 :value
:用于指定切入点表达式,还可以指定切入点表达式的引用。
1 2 3 4 @AfterThrowing ("pt1()" )public void afterThrowingPrintLog () { System.out.println("异常通知:Logger类中的afterThrowingPrintLog方法开始记录日志了。。。" ); }
@After 作用 : 把当前方法看成是异常通知。属性 :value
:用于指定切入点表达式,还可以指定切入点表达式的引用。
1 2 3 4 @After ("pt1()" )public void afterPrintLog () { System.out.println("异常通知:Logger类中的afterPrintLog方法开始记录日志了。。。" ); }
通知类注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package com.uestc.utils;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.springframework.stereotype.Component;@Component ("logger" )@Aspect public class Logger { @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的支持 1 2 <aop:aspectj-autoproxy > </aop:aspectj-autoproxy >
6. 环绕通知注解配置 @Around
作用 : 把当前方法看成是环绕通知。属性 : value:用于指定切入点表达式,还可以指定切入点表达式的引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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:指定表达式的内容
1 2 @Pointcut ("execution(* com.uestc.service.impl.*.*(..))" )private void pt1 () {}
5.57 AOP纯注解(不使用XML) 项目结构:
1. 添加配置类 SpringConfiguration和JdbcConfiguration 1 2 3 4 5 6 7 8 9 10 11 12 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 {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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. 测试代码 1 2 3 4 5 6 7 8 9 10 11 12 13 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. 导入坐标 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <?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. 持久层代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 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. 业务层代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 package com.uestc.service.impl;import com.uestc.dao.IAccountDao;import com.uestc.liufei.Account;import com.uestc.service.IAccountService;@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 { tsManager.beginTransaction(); Account source=dao.findAccountByName(sourceName); Account target=dao.findAccountByName(targetName); source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); dao.updateAccount(source); int i=1 /0 ; dao.updateAccount(target); tsManager.commit(); }catch (Exception e){ tsManager.rollback(); throw new RuntimeException(e); }finally { tsManager.release(); } } public List<Account> findAll () { try { tsManager.beginTransaction(); List<Account> accounts=dao.findAll(); tsManager.commit(); return accounts; }catch (Exception e){ tsManager.rollback(); throw new RuntimeException(e); }finally { tsManager.release(); } } public Account findAccountById (Integer accountId) { try { tsManager.beginTransaction(); Account account =dao.findAccountById(accountId); tsManager.commit(); return account; }catch (Exception e){ tsManager.rollback(); throw new RuntimeException(e); }finally { tsManager.release(); } } public void saveAccount (Account account) { try { tsManager.beginTransaction(); dao.saveAccount(account); tsManager.commit(); }catch (Exception e){ tsManager.rollback(); }finally { tsManager.release(); } dao.saveAccount(account); } public void updateAccount (Account account) { try { tsManager.beginTransaction(); dao.updateAccount(account); tsManager.commit(); }catch (Exception e){ tsManager.rollback(); }finally { tsManager.release(); } dao.updateAccount(account); } public void deleteAccount (Integer accountId) { try { tsManager.beginTransaction(); dao.deleteAccount(accountId); tsManager.commit(); }catch (Exception e){ tsManager.rollback(); }finally { tsManager.release(); } dao.deleteAccount(accountId); } }
4. 把事务控制的公共代码抽取制作成通知类: TransactionManager:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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 { Connection conn=tl.get(); if (conn==null ){ conn=dataSource.getConnection(); tl.set(conn); } return conn; }catch (Exception e){ throw new RuntimeException(e); } } public void removeConnction () { tl.remove(); } }
5. IOC配置+AOP配置 IOC配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <?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" > <bean id ="accountService" class ="com.uestc.service.impl.AccountServiceImpl" > <property name ="dao" ref ="accountDao" > </property > </bean > <bean id ="accountDao" class ="com.uestc.dao.impl.AccountDaoImpl" > <property name ="runner" ref ="runner" > </property > <property name ="connectionUtils" ref ="connectionUtils" > </property > </bean > <bean id ="runner" class ="org.apache.commons.dbutils.QueryRunner" scope ="prototype" > </bean > <bean id ="connectionUtils" class ="com.uestc.utils.ConnectionUtils" > <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" > <property name ="connectionUtils" ref ="connectionUtils" > </property > </bean > </beans >
AOP配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <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 >
完整配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 <?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" > <bean id ="accountService" class ="com.uestc.service.impl.AccountServiceImpl" > <property name ="dao" ref ="accountDao" > </property > </bean > <bean id ="accountDao" class ="com.uestc.dao.impl.AccountDaoImpl" > <property name ="runner" ref ="runner" > </property > <property name ="connectionUtils" ref ="connectionUtils" > </property > </bean > <bean id ="runner" class ="org.apache.commons.dbutils.QueryRunner" scope ="prototype" > </bean > <bean id ="connectionUtils" class ="com.uestc.utils.ConnectionUtils" > <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" > <property name ="connectionUtils" ref ="connectionUtils" > </property > </bean > <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注解的约束 1 2 3 4 5 6 7 8 9 10 11 <?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注解的支持 1 2 3 4 5 <context:component-scan base-package ="com.uestc" > </context:component-scan > <aop:aspectj-autoproxy > </aop:aspectj-autoproxy >
3. 删除bean.xml中的配置并替换成相应的注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?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" > <context:component-scan base-package ="com.uestc" > </context:component-scan > <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 > <aop:aspectj-autoproxy > </aop:aspectj-autoproxy > </beans >
4. 业务层实现类: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 @Service (value = "accountService" )public class AccountServiceImpl implements IAccountService { @Autowired private IAccountDao dao=null ; public void transfer (String sourceName, String targetName, Float money) { Account source=dao.findAccountByName(sourceName); Account target=dao.findAccountByName(targetName); source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); dao.updateAccount(source); int i=1 /0 ; 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. 持久层实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 @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类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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 { Connection conn=tl.get(); if (conn==null ){ conn=dataSource.getConnection(); tl.set(conn); } return conn; }catch (Exception e){ throw new RuntimeException(e); } } public void removeConnction () { tl.remove(); } }
7. TransactionManager类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 @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,就会创建一个新的连接,而这个连接并没有操作,因此会出现错误
解决:使用环绕通知 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Around ("pt1()" ) public Object aroundAdvice (ProceedingJoinPoint pjp) { Object rtValue=null ; try { Object [] args=pjp.getArgs(); this .beginTransaction(); rtValue=pjp.proceed(args); 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对象创建 源码:
1 2 3 4 5 6 7 8 9 10 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数据源 1 2 3 4 5 6 7 <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包中
1 2 3 4 5 6 7 <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
1 2 3 4 jdbc.driverClass =com.mysql.jdbc.Driver jdbc.url =jdbc:mysql://localhost:3306/springTemplate jdbc.username =root jdbc.password =287216
step2: 引入外部的属性文件
1 2 3 4 5 <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: 配置数据源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <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 > <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、创建数据库和表:
1 2 3 4 5 6 7 create database springTemplate;use springTemplate;create table account ( id int primary key auto_increment, name varchar (100 ), money double );
2、pom导入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?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 ="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 2 3 4 5 6 7 8 9 10 public class Demo2 { public static void main (String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml" ); JdbcTemplate js=context.getBean("jdbcTemplate" , JdbcTemplate.class ) ; js.execute("insert into account (name, money) values('ddd',4000)" ); } }
6.43 更新操作 1 2 3 4 5 6 7 8 9 10 public class Demo3 { public static void main (String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml" ); JdbcTemplate js=context.getBean("jdbcTemplate" , JdbcTemplate.class ) ; js.update("update account set name=?,money=? where id=? " ,"test" ,1500 ,1 ); } }
6.44 删除操作 1 2 3 4 5 6 7 8 9 10 public class Demo3 { public static void main (String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml" ); JdbcTemplate js=context.getBean("jdbcTemplate" , JdbcTemplate.class ) ; js.update("delete from account where id=?" ,5 ); } }
6.45 查询所有操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Demo4 { public static void main (String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml" ); JdbcTemplate js=context.getBean("jdbcTemplate" , JdbcTemplate.class ) ; List<Account> accounts=js.query("select * FROM account where money>?" , new BeanPropertyRowMapper<Account>(Account.class ),1000) ; for (Account account: accounts){ System.out.println(account); } }
或者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Demo5 { public static void main (String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml" ); JdbcTemplate js=context.getBean("jdbcTemplate" , JdbcTemplate.class ) ; 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; }
6.46 查询一个操作 1 2 3 4 5 6 7 8 9 10 11 public class Demo6 { public static void main (String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml" ); JdbcTemplate js=context.getBean("jdbcTemplate" , JdbcTemplate.class ) ; 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.47 查询返回一行一列操作 1 2 3 4 5 6 7 8 9 10 11 12 public class Demo7 { public static void main (String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("bean.xml" ); JdbcTemplate js=context.getBean("jdbcTemplate" , JdbcTemplate.class ) ; Integer count=js.queryForObject("select count(*) from account where money>?" ,Integer.class ,1500) ; System.out.println(count); } }
6.5 在dao中使用JdbcTemplate 前期准备 定义实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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接口:
1 2 3 4 5 6 7 8 public interface IAccountDao { public Account findAccountById (Integer accountId) ; public Account findAccountByName (String accountName) ; public void updateAccount (Account account) ; }
dao实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class AccountDaoImpl implements IAccountDao { private JdbcTemplate 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()); } }
配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?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 > <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都有一些重复性的代码。下面就是重复代码:
1 2 3 4 5 private JdbcTemplate jdbcTemplate;public void setJdbcTemplate (JdbcTemplate jdbcTemplate) { this .jdbcTemplate = jdbcTemplate; }
让dao继承JdbcDaoSupport JdbcDaoSupport是spring框架为我们提供的一个类,该类中定义了一个JdbcTemplate对象,我们可以直接获取使用,但是要想创建该对象,需要为其提供一个数据源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class JdbcDaoSupport extends DaoSupport { private JdbcTemplate jdbcTemplate; public void setDataSource (DataSource dataSource) { if (jdbcTemplate==null || dataSource!=this .jdbcTemplate.getDataSource()){ this .jdbcTemplate=createJdbcTemplate(dataSource); initTemplateConfig(); } } public JdbcTemplate createJdbcTemplate (DataSource dataSource) { return new JdbcTemplate(dataSource); } public final void setJdbcTemplate (JdbcTemplate jdbcTemplate) { this .jdbcTemplate = jdbcTemplate; initTemplateConfig(); } public final JdbcTemplate getJdbcTemplate () { return this .jdbcTemplate; } }
dao实现类:
只需要给它的父类注入一个数据源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao { public Account findAccountById (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 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()); } }
配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?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 >
两种方式的差异
7、Spring中的事务控制
第一:JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
第二:spring框架为我们提供了一组事务控制的接口, 这组接口是在spring-tx-5.0.2.RELEASE.jar中。
第三:spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式 实现。
7.1 Spring中事务控制的API介绍 1、PlatformTransactionManager
此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法:
1 2 3 4 5 6 TransactionStatus getTransation (TransactionDefinition definition) void commit (TransationStutus status) void rollback (TransationStutus status)
其实现类:
1 2 真正管理事务的对象,使用Spring JDBC或iBatis 进行持久化数据时使用 org.springframework.jdbc.datasource.DataSourceTransactionManager
2、TransationDefinition
事务的定义信息对象,方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 String getName () int getIsolationLevel () int getPropagationBehavior () int getTimeOut () boolean isReadOnly ()
事务的隔离级别 :
事务隔离级别反映了事务提交并发访问时的处理:
1 2 3 4 5 -ISOLATION_DEFAULT 默认级别,归属下列某一种 -ISOLATION_READ_UNCOMMITTED 可以读取未提交数据 -ISOLATION_READ_COMMITTED 只能读取已提交的数据,解决脏读问题(Oracle默认级别) -ISOLATION_REPEATABLE_READ 是否读取其他事务提交修改后的数据,解决不可重读问题(Mysql默认级别) -ISOLATION_SERIALIZABLE 是否读取其他书屋提交添加后的数据,解决幻读问题
事务的传播行为:
超时时间 :
默认值是-1,没有超时限制。如果有,以秒为单位进行设置
是否是只读事务 :
建议查询时设置为只读。
3、TransactionStatus
描述了某个时间点上事务的状态信息,此接口提供的是事务具体的运行状态:
1 2 3 4 5 6 7 8 9 10 11 12 void flush () boolean hasSavepoint () boolean isCompleted () boolean isNewTransaction () boolean isRollbackOnly () void setRollbackOnly ()
7.2 基于XML的声明式事务控制 准备工作 工程目录:
step1: 导入pom依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <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两个名称空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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: 编写业务层接口和实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface IAccountService { public Account findById (Integer accountId) ; public void transfer (String sourceName,String targetName,Float money) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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.setMoney(sourceAccount.getMoney()-money); targetAccount.setMoney(targetAccount.getMoney()+money); dao.updateAccount(sourceAccount); int i=10 /0 ; dao.updateAccount(targetAccount); } }
step5: 编写Dao接口和实现类
1 2 3 4 5 6 7 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) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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: 在配置文件中配置业务层和持久层对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <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 >
配置步骤 step1: 配置事务管理器
1 2 3 4 !--配置事务管理器--> <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" > </property > </bean >
step2: 配置事务的通知引用事务管理器
1 2 3 <tx:advice id ="txAdvice" transaction-manager ="transactionManager" > </tx:advice >
step3:配置事务的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <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切入点表达式
1 2 3 4 5 <aop:config > <aop:pointcut expression="execution(* com.uestc.service.impl.*.*(..))" id="pt1" </aop:pointcut> </aop:config >
step5: 配置切入点表达式和事务通知的对应关系
1 2 3 4 5 6 7 <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 >
完整配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?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:property-placeholder location ="classpath:jdbc.properties" > </context:property-placeholder > <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 > <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: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 基于注解的配置方式 准备工作 step1: 导入pom依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <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的配置文件导入约束并配置扫描的包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?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 > <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 >
step3: 创建业务层接口和实现类并使用注解让spring管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @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.setMoney(sourceAccount.getMoney()-money); targetAccount.setMoney(targetAccount.getMoney()+money); dao.updateAccount(sourceAccount); dao.updateAccount(targetAccount); }
step4: 创建Dao接口和实现类并使用注解让spring管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @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() ); } }
配置步骤 step1: 配置事务管理器并注入数据源
1 2 3 4 <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" > </property > </bean >
step2: 在业务层使用@Transactional注解
该注解的属性和xml中的属性含义一致。该注解可以出现在接口上,类上和方法上。 出现接口上,表示该接口的所有实现类都有事务支持。 出现在类上,表示类中所有方法有事务支持 出现在方法上,表示方法有事务支持。 以上三个位置的优先级:方法>类>接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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.setMoney(sourceAccount.getMoney()-money); targetAccount.setMoney(targetAccount.getMoney()+money); dao.updateAccount(sourceAccount); dao.updateAccount(targetAccount); } }
step3: 在配置文件中开启spring对注解事务的支持
1 2 <tx:annotation-driven transaction-manager ="transactionManager" > </tx:annotation-driven >
完整配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <?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 > <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 > <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="dataSource" > </property > </bean > <tx:annotation-driven transaction-manager ="transactionManager" > </tx:annotation-driven > </beans >
7.4 纯注解配置
jdbcConfig:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @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; @Bean (name="jdbcTemplate" ) public JdbcTemplate createJdbcTemplate (DataSource dataSource) { return new JdbcTemplate(dataSource); } @Bean ("dataSource" ) public DataSource createDataSource () { DriverManagerDataSource ds=new DriverManagerDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); return ds; } }
TransactionConfig:
1 2 3 4 5 6 7 package com.uestc.config;public class TransactionConfig { @Bean (name="transactionManager" ) public PlatformTransactionManager createTransactionManager (DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
SpringConfiguration:
1 2 3 4 5 6 7 @Configuration @ComponentScan (basePackages = {"com.uestc" })@PropertySource ("classpath:jdbc.properties" )@Import ({JdbcConfig.class ,TransactionConfig .class }) @EnableTransactionManagement public class SpringConfiguration {}