一、Spring AOP前瞻知识——代理模式
1.代理模式
AOP本身是基于动态代理模式实现的,所以掌握代理模式使我们学好AOP的一个重要前提条件
1.1 静态代理模式
若代理类在程序运行前就已经存在,那么这种代理方式被称为静态代理,这种情况下的代理类通常都是我们在Java代码中定义的。通常情况下,静态代理的代理类和目标类会实现统一接口或者是派生自相同的父类。
1.1.1 创建公共接口
package Service;
/**
* 代理模式中的 公共接口
*
*/
public interface SomeService {
String doSome(String msg);
}
1.1.2 目标对象
package Service.impl;
import Service.SomeService;
/**
* 代理模式 中的目标对象
*/
public class SomeServiceImpl implements SomeService {
/**
* 目标对象实现自己的核心功能业务
* @param msg
* @return
*/
@Override
public String doSome(String msg) {
System.out.println("目标对象执行了..."+msg);
return "dance"+msg;
}
}
1.1.3 创建代理对象
package Proxy;
import Service.SomeService;
/**
* 静态代理类
*/
public class SomeProxy implements SomeService {
//目标类对象
private SomeService target;
//构造方法引入目标对象
public SomeProxy(SomeService target){
this.target=target;
}
@Override
public String doSome(String msg) {
System.out.println("目标方法执行之前");
//让目标对象来执行核心业务
String res=target.doSome(msg);
System.out.println("目标方法执行之后");
return res;
}
}
1.1.4 测试
import Proxy.SomeProxy;
import Service.SomeService;
import Service.impl.SomeServiceImpl;
public class AppAction {
public static void main(String[] args) {
//获得目标对象
SomeServiceImpl target = new SomeServiceImpl();
//获得代理对象
SomeProxy someProxy = new SomeProxy(target);
//通过代理对象执行目标对象的方法
System.out.println(someProxy.doSome("baby"));
}
}
1.2 动态代理
代理类在程序运行时被创建的代理方式被称为动态代理。也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行中根据我们在代码中的“指示”动态生成的。
代理类型 | 使用场景 |
---|---|
JDK动态代理 | 如果目标对象实现了接口,采用JDK的动态代理 |
CGLIB动态代理 | 如果目标对象没有实现接口,必须采用CGLIB动态代理 |
1.2.1 动态定理模式之JDK代理
1.2.1.1 声明接口
1.2.1.2 创建目标类
1.2.1.3 实现代理
1.2.2 动态代理模式实现CGLIB代理
1.2.3 测试
2.AOP概念
AOP,即面向切面编程,可以说是OOP(面向对象编程)的补充和完善。
面向切面是面向对象的一种方式而已。在代码执行过程中,动态嵌入其他代码,叫做面向切面编程。
面向切面编程的几个核心概念
概念 | 说明 |
---|---|
IOC/DI | 本质就是Java反射+XML解释 |
AOP | 本质就是Java动态代理 |
切点 | 要添加代码的地方叫切点 |
切面 | 切点+通知 |
通知(增强) | 向切点插入的代码称为通知(Advice) |
连接点 | 切点的定义 |
面向切面编程,就是将交叉业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志等。若不使用AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样,会使主业务逻辑变得混杂不清。
AOP术语介绍
术语 | 说明 |
---|---|
切面(Aspect) | 切面泛指交叉业务逻辑。比如事务处理、日志处理就可以理解为切面。常用的切面有通知和顾问。实际就是对主业务逻辑的一种增强(通知和切入点之间的关系) |
织入(Weaving) | 织入是将切面代码插入到目标对象的过程 |
连接点(Joinpoint) | 连接点指切面具体可以织入的位置 |
切入点(PointCut) | 切入点指切面具体织入的位置 |
通知(Advice) | 通知是切面的一种实现,可以完成简单的织入功能(织入功能就是在这里完成的)。通知定义了增强代码切入到代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。(被抽取的共性功能的代码逻辑) |
顾问(Advisor) | 顾问是切面的另一种实现方式,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂的装配器。不仅指定了切入时间点,还可以指定具体的切入点 |
目标对象(Target Object) | 有切入点方法的对象 |
代理对象(Proxy Object) | Spring代理目标对象也就是AOP代理对象 |
3.AOP的具体实现
3.1 环境准备
3.2 基于纯代理的AOP
基于纯代理方式实现的AOP中具有的几种通知类型
通知类型 | 说明 |
---|---|
前置通知(MethodBeforeAdvice) | 目标方法执行之前调用 |
后置通知(AfterReturningAdvice) | 目标方法执行完成之后调用 |
环绕通知(MethodInterceptor) | 目标方法前后都会调用方法,且能增强结果 |
异常处理通知(ThrowsAdvice) | 目标方法出现异常调用 |
3.3 前置通知
步骤
3.3.1 创建接口,即目标类要实现的接口
3.3.2 创建接口实现类,即目标类(实现核心方法)
3.3.3 创建前置通知类(抽取的公共代码)
3.3.4 配置文件(在里面配置代理对象)
3.3.5 测试代码
3.4 后置通知
和前置通知一样,只是通知类实现不同的接口
后置通知是在目标方法执行之后执行的通知
3.4.1 创建后置通知类
3.4.2 配置文件配置
3.4.3 测试
3.5 环绕通知
基本实现和前置通知、后置通知一样,但是他可以修改返回的结果
环绕通知就是在切入点方法的前后都会执行的方式,而且环绕通知相比于前置通知和后置通知来说可以修改返回结果
3.5.1 配置环绕通知类
3.5.2 在配置文件中配置(和前面一样)
3.5.3 测试
3.6 异常通知
当切入点方法(目标对象的方法)执行抛异常后会触发的通知
步骤和前面一样,就是通知类实现的接口不同
3.6.1 创建通知类实现接口
3.6.2 配置文件和前面一样
3.6.3 测试
3.7 基于AspectJ的AOP
对于AOP这种编程思想,有许多框架都进行了实现。Spring就是其中一种,可以完成面向切面编程。然而,AspectJ也实现了AOP的功能,且其实现方式更为简便,而且还支持注解式开发。所以,Spring又将AspectJ的对于AOP的实现也引入到了自己的框架中。在Spring中使用AOP开发时,一般使用AspectJ的实现方式
AspectJ的通知类型
通知类型 | 说明 |
---|---|
前置通知 | 目标方法执行之前调用 |
后置通知 | 目标方法执行之后调用 |
环绕通知 | 目标方法执行前后都会调用方法 |
异常处理通知 | 目标方法出现异常调用 |
最终通知 | 无论程序执行是否正常,该通知都会执行。类似于try…catch中的finally代码块 |
AspectJ的切入点表达式
execution(
[modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
[declaring-type-pattern] 全限定性类名
name-pattern(param-pattern) 方法名(参数名)
[throws-pattern] 抛出异常类型
)
切入点表达式要匹配的对象就是目标方法的方法名。所以,execution表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可以省略,各部分要用空格分开。在其中可以使用以下符号
符号 | 意义 |
---|---|
* | 0至多个字符 |
… | 方法参数中表示任意多个参数,用在包名后表示当前包及其子包路径 |
+ | 用在类名后表示当前类及子类,用在接口后表示接口及实现类 |
基于注解的实现
和之前的区别就是不用通知类,而是用一个切面类(Aspect)并加上@Aspect注解。
然后在里面写方法,若是前置通知,就在此方法上面加@Before(value=execution表达式);
若是后置通知,就在此方法上面加@AfterReturning(value=execution表达式);
若是环绕通知,就在此方法上面加@Around(value=execution表达式);
若是异常通知,就在此方法上面加@AfterThrowing(value=execution表达式);
若是最终通知,就在此方法上面加@After(value=execution表达式)。
execution表达式有什么作用呢?切入点表达式要匹配的对象就是目标方法的方法名,使通知和切入点关联
3.7.1 前置通知
步骤
3.7.1.1 创建对应的接口
3.7.1.2 创建目标类实现接口
3.7.1.5 创建切面类
3.7.1.4 配置文件
3.7.1.5 也可以用Java配置类的方式
3.7.1.5 测试
3.7.2 后置通知
3.7.2.1 和前面一样,创建接口和创建目标类
3.7.2.2 创建切面类
3.7.2.3 配置文件一样
3.7.3 环绕通知
3.7.3.1切面类
3.7.4 异常通知
3.7.4.1 切面类
3.7.5 最终通知
3.7.5.1 切面类
3.8 基于配置文件的实现
以前置通知为例
3.8.1 创建接口和目标类(如前面一样)
3.8.2 切面类
3.8.3 配置文件配置
4.Spring 事务管理
4.1 JDBCTemplate
简化JDBC操作的步骤、模板化数据库操作
4.2 基本操作
4.2.1 导入相关的依赖
4.2.2 配置JdbcTemplate
在配置文件中配置JdbcTemplate相关信息
4.2.3 在DAO实现类中利用JdbcTemplate进行增删查改
4.2.3.1 增加
userDAOImpl里
4.2.3.2 删除
4.2.3.5 修改记录
4.2.3.4 查询多个记录
4.2.3.5 查询一个记录
4.2.3.6 测试代码
public class AppStart {
@Test
public void testadd(){
ApplicationContext ap=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDao = ap.getBean(UserDaoImpl.class);
User user=new User();
user.setUsername("李四");
user.setAddress("湖南");
user.setGender("男");
userDao.addUser(user);
}
@Test
public void testUpdate(){
ApplicationContext ap=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDao = ap.getBean(UserDaoImpl.class);
User user=new User();
user.setId(1);
user.setUsername("晓丽");
user.setAddress("云南");
user.setGender("女");
userDao.updateUser(user);
}
@Test
public void testDelete(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl UserDao = ac.getBean(UserDaoImpl.class);
UserDao.deleteUser(3);
}
@Test
public void queryList(){
ApplicationContext ap=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDao = ap.getBean(UserDaoImpl.class);
List<User> users = userDao.queryList();
for (User u:
users) {
System.out.println(u);
}
}
@Test
public void queryUser(){
ApplicationContext ap=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDao = ap.getBean(UserDaoImpl.class);
userDao.deleteUser(3);
}
}
4.3 事务相关概念介绍
数据库事务,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
属性 | 说明 |
---|---|
原子性(Atomic) | 事务内的操作要内全部成功,要么全部失败,不会在中间的某个环节结束 |
一致性(Consistency) | 数据库在一个事务执行之前和执行之后,数据库都必须处于一致性状态。如果事务执行失败,那么需要自动滚回到原始状态 |
隔离性(Isolation) | 在并发环境下,不同的事务同时修改相同的数据时,一个未完成的事务不会影响另外一个未完成的事务 |
持久性(Durability) | 事务一旦提交,其修改的数据永久保存到数据库中,其改变是永久的 |
步骤
4.3.1 有数据库的操作方法
public class AppStart {
@Test
public void testadd(){
ApplicationContext ap=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDao = ap.getBean(UserDaoImpl.class);
User user=new User();
user.setUsername("李四");
user.setAddress("湖南");
user.setGender("男");
userDao.addUser(user);
}
@Test
public void testUpdate(){
ApplicationContext ap=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDao = ap.getBean(UserDaoImpl.class);
User user=new User();
user.setId(1);
user.setUsername("晓丽");
user.setAddress("云南");
user.setGender("女");
userDao.updateUser(user);
}
@Test
public void testDelete(){
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl UserDao = ac.getBean(UserDaoImpl.class);
UserDao.deleteUser(3);
}
@Test
public void queryList(){
ApplicationContext ap=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDao = ap.getBean(UserDaoImpl.class);
List<User> users = userDao.queryList();
for (User u:
users) {
System.out.println(u);
}
}
@Test
public void queryUser(){
ApplicationContext ap=new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDao = ap.getBean(UserDaoImpl.class);
userDao.deleteUser(3);
}
}
4.3.1 在service层提供复杂的业务处理方法
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private UserDaoImpl userDao;
@Override
public int fun1() {
//添加数据、
User user=new User();
user.setUsername("王五123456");
user.setAddress("长沙123456");
user.setGender("男");
//添加用户信息
userDao.addUser(user);
user.setId(2);
userDao.updateUser(user);
return 0;
}
}
4.3.2 在配置文件中配置事务
Spring中使用XML配置事务有三大步骤
1、创建事务管理器
2、配置事务方法
3、配置AOP
Spring事务处理之注解
然后在被事务管理的方法的头部添加@Transcational即可
4.4 事务的传播属性
Spring中的7个事务传播行为:
事务行为 | 说明 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,假设当前没有事务。就新建一个事务 |
PROPAGATION_SUPPORTS | 支持当前事务,假设当前没有事务,就以非事务方式运行 |
PROPAGATION_MANDATORY | 支持当前事务,假设当前没有事务,就抛出异常 |
PROPAGATION_REQUIRES_NEW | 新建事务,假设当前存在事务。把当前事务挂起 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式运行,假设当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
举例说明
案例代码
ServiceA
ServiceA {
void methodA() {
ServiceB.methodB();
}
}
ServiceB
ServiceB {
void methodB() {
}
}