Spring-AOP
第一节 初识AOP
1.1 AOP概述
AOP:全称是Aspect Oriented Programming。即:面向切面编程。
它是把我们业务逻辑中的各个部分进行隔离,使每个部分独立开来,在需要用到某个部分的时候,运用预编译和运 行期动态代理技术把增强的代码加入到我们的业务逻辑中,组成完整的功能。
它是一种编程思想,一种设计理念,是OOP的一种延续。运用AOP编程思想,可以提高代码的可重用性,使编码 更加简洁,更易于维护。
OOP:面向对象编程 。核心思想:封装,继承,多态. OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础 上,对我们的已有方法进行增强。
1.2AOP的使用场景
1.2.1案例问题回顾
在Spring的注解loC课程的案例中,我们通过账户操作的综合案例把loC课程内容串联起来了,但是当我们加入了 转账功能后,代码变得不再简洁。我们的故事就从这里开始。
代码节选如下:(只保留了业务层代码,主要问题就在业务层实现类中)
/**
* 账户的业务层接口
*/
public interface AccountService {
/**
* 转账
*/
void transfer(String resource,String target,double money);
/**
* 保存
*/
void save(Account account);
/**
* 根据id删除
*/
void delete(Integer id);
/**
* 更新账户
*/
void update(Account account);
/**
* 根据id查询
*/
Account findById(Integer id);
/**
* 根据名称查询账户
*/
Account findByName(String name);
/**
* 查询所有
*/
List<Account> findAll();
}
账户业务层接口实现类
@Service
public class AccountServiceImpl implements AccountService {
// 依赖注入 通过依赖注入可以实现service调用dao的增删改查方法 通过方法进行增删改查的调用
// 调用dao的时候要有接口所对应的SQL文件
// 依赖注入
@Autowired
private AccountDao accountDao;
@Autowired
private TransactionManager transactionManager;
/*public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}*/
// 转帐
@Override
public void transfer(String resource, String target, double money) {
try {
// 开启事务
transactionManager.begin();
/*Connection conn = dataSource.getConnection();
System.out.println("conn = " + conn);
conn.setAutoCommit(false);*/
// 转账业务
// 1.根据name 获取 账户对象
Account resourceAccount = accountDao.findByName(resource);
// 当前代码执行所在的线程对象
System.out.println("当前线程:"+Thread.currentThread().getName());
Account targetAccount = accountDao.findByName(target);
// 2.更新账户对象中 金额
resourceAccount.setMoney(resourceAccount.getMoney() - money);
targetAccount.setMoney(targetAccount.getMoney() + money);
// 3.更新数据库中 账户对象的金额
accountDao.update(resourceAccount);
// 当前代码执行所在的线程对象
System.out.println("当前线程:"+Thread.currentThread().getName());
// int i = 1/0;
accountDao.update(targetAccount);
// 提交事务
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
}finally {
// 释放连接
transactionManager.close();
}
}
@Override
public void save(Account account) {
try {
// 开启事务
transactionManager.begin();
accountDao.save(account);
// 提交事务
transactionManager.commit();
}catch (Exception e){
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
}finally {
// 释放链接
}
}
@Override
public void delete(Integer id) {
try {
// 开启事务
transactionManager.begin();
accountDao.delete(id);
// 提交事务
transactionManager.commit();
}catch (Exception e){
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
}finally {
// 释放链接
}
}
@Override
public void update(Account account) {
try {
// 开启事务
transactionManager.begin();
accountDao.update(account);
// 提交事务
transactionManager.commit();
}catch (Exception e){
e.printStackTrace();
// 回滚事务
transactionManager.rollback();
}finally {
// 释放链接
}
}
// 下边三个try/catch 省略了
@Override
public Account findById(Integer id) {
return accountDao.findById(id);
}
@Override
public Account findByName(String name) {
return accountDao.findByName(name);
}
@Override
public List<Account> findAll() {
return accountDao.findAll();
}
}
红框代码不一样所以无法抽取父类公共方法,面向对象(竖向)继承的思想解决不了这个问题。所以要用AOP思想(横向)抽取,把重复代码抽取到工具类中。
1.3解决思路分析
1.3.1明确目标
我们要明确解决什么问题?
我们要解决的是大量的重复代码导致开发效率下降,同时对后期维护造成不便的问题。换句话说就是对业务层瘦身(去除重复代码),但是必须保证业务层代码还有事务的支持((不能因为去重,而导致没有事务了)。
1.3.2技术选择
业务层中方法的代码需要解耦,实现把事务和具体业务剥离,在执行业务层方法时,通过对方法进行增强,从而实 现业务层方法的事务支持。其核心思想就是对业务层方法增强。
既然是对业务层方法增强,同时又不希望针对每个方法独立编写事务,那么就有两种技术可供选用了。 第一个是采用装饰者模式的设计理念,采用创建业务层对象的包装类,从而实现对方法增强,加入事务的支持(装 饰者模式也称为静态代理)。
简单举个例子 对原有方法进行增强
InputStream read() 一个一个字节读取
FileReader 一个个字符读取
BufferedReader readLine() 一次读取一行
new BufferedReader(new FileReader("a.txt"))
第二个是采用代理模式的设计理念,运用动态代理技术,通过运行期创建代理对象,对业务层方法增强,加入事务 的支持。
1)静态代理—装饰者模式
- 动态代理 - 代理模式
通过上面两张图的回顾,我们得知,静态代理需要为每个业务层都要创建一个包装类,那么此时其实并没有解决手 们现在的问题,因为一旦到了项目中,我们的业务层实现类一多起来,对每个实现类都创建包装类的话,同样会产 生大量的重复代码。但是装饰者模式的优势是,它可以针对不同租户进行不同的包装(增强),也就是说支持可足 制化,让我们的每个包装类都具有独立的特点。最明显的例子就是早期我们学的IO流对。只不过,这个优势在目 前我们的问题中反倒成了劣势。
由此,我们得出结论,我们需要选用动态代理技术解决案例中的问题。
1.4动态代理技术
动态代理作用:重复代码的抽取
作用:
在不修改源码的基础上,对已有方法增强。
动态代理特征
特征:
字节码(.class)是随用随创建,随用随加载。
动态代理分类
动态代理根据创建代理对象的类型,分为两类。
第一类:基于接口的动态代理,即创建的代理对象和被代理对象实现了相同的接口
第二类:基于子类的动态代理,即创建的代理对象是被代理对象的子类
1.4.1基于接口的动态代理
提供者
JDK官方ProxyI
使用要求
被代理类最少实现一个接口
创建代理对象的类
java.lang.reflect.Proxy
创建代理对象的方法
public static object newProxyInstance(classLoader loader,
class<?>[]interfaces,
InvocationHandler h)
方法参数的含义
参数/说明 | 是什么 | 做什么 | 些什么 |
---|---|---|---|
ClassLoaderloader | 类加载器 | 用于加载代理对象的字节码 | 和被代理对象使用相同的类加载器 |
Class<?>[] interfaces | 字节码数组 | 用于让代理对象和被代理对象具有相同的行为(方法) | 要根据被代理对象区别对待。如果被代理对象是一个普通类,那么写的就是它实现的接口数组,通常就是用被代理类字节码对象调用getInterfaces();方法。如果被代理对象本身就是一个接口,那么就直接创建一个字节码数组,把接口的字节码传入。通常就是new class[]{被代理接口的字节码}。Sq1Session.getMapper([UserDao.class] ) |
InvocationHandlerh | 一个接口 | 用于给被代理对象方法提供增强代码的 | 编写InvocationHanlder的实现类,重写invoke方法。在方法中提供要增强的代码。 |
InvocationHandler的invoke方法
/**
* @param proxy创建的代理对象的引用
* @param method 当前执行的被代理对象的方法
* @param args当前被代理对象方法执行所需的参数
* @Return object 当前方法执行的返回值void 被看成Void类型
*/
public object invoke(object proxy, Method method, object[] args)
throws Throwable;
1.4.2基于子类的动态代理
提供者
第三方开源项目CGLIB (Code Generation Library)(代码生产库)
使用要求
被代理类不能是最终类
创建代理对象的类
net. sf. cglib. proxy. Enhancer
<!-- https : //mvnrepository. com/ artifact/cglib/cglib -->
<dependency>
<groupId>cg1ib</ groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</ dependency>
创建代理对象的方法
public static object create(Class type, Callback callback)
方法参数的含义
参数/说明 | 是什么 | 做什么 | 写什么 |
---|---|---|---|
Classtype | 字节码 | 用于创建被代理的子类代理对象,同时用于得到加载代理对象的类加载器 | 被代理对象的字节码 |
Callbackcallback | 一个接口 | 用于给被代理对象方法提供增强代码的。打开源码发现,此接口中没有任何方法。因为callback/接口是用于定义规范的标记接口,在实际开发中,我们一般使用它的子接口WethodInterceptor。 | 编写MethodInterceptor的实现类,重写intercept方法,在方法中提供要增强的代码。 |
MethodInterceptor的intercept方法
*@param obj创建的代理对象的引用
*@param method当前执行的被代理对象的方法
*@param args当前被代理对象方法执行所需的参数
*@param proxy当前执行被代理对象方法的代理对象。它也可以用于执行被代理对象方法,效率比 method.inovke略高。
*/
public object intercept(Dbject obj,java.lang.reflect.Method method,Object[] args,
MethodProxy proxy)throws Throwable;
1.5案例问题的解决
基于JDK的动态代理(proxy)
/**
* 基于JDK的动态代理(proxy)
* 作用:用于创建AccountService 动态代理对象的工厂类
*/
@Component
public class ProxyAccountServiceFactory {
// 指定被代理对象(AccountServiceImpl对象)
@Autowired
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
// 用于创建AccountService 动态代理对象
@Bean("proxyAccountService")
public AccountService createAccountServiceProxy(){
// 创建AccountService 的动态代理对象
/**
* 参数一:被代理对象的类加载器
* 参数二:被代理对象 所实现的所有接口
* 参数三:指定原有方法 如何进行增强
*/
AccountService proxyAccountService = (AccountService) Proxy.newProxyInstance(
accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* invoke 方法,就是对原有功能方法的增强
* @param proxy 就是创建出来的动态代理对象的引用
* @param method 被代理对象 所要执行的方法
* @param args 被代理对象 所要执行的方法 所接收的实际参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("被代理对象="+ accountService);
System.out.println("被增强的方法="+method.getName());
System.out.println("被增强的方法 所接收的实际参数值= "+ Arrays.toString(args));
Object rtValue = null;
try {
// 开启事务(增强)
transactionManager.begin();
// 调用 被代理对象 的原有方法
rtValue = method.invoke(accountService,args);
// 提交事务(增强)
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务(增强)
transactionManager.rollback();
} finally {
// 释放连接(增强)
transactionManager.close();
}
return proxy;
}
}
);
return proxyAccountService;
}
基于CgLib的动态代理(第三方jar包)
/**
* 基于CgLib的动态代理(第三方jar包)
* 作用:用于创建AccountService 动态代理对象的工厂类
*/
@Component
public class CglibAccountServiceFactory {
// 指定被代理对象(AccountServiceImpl对象)
@Autowired
@Qualifier("accountServiceImpl")
private AccountService accountService;
@Autowired
private TransactionManager transactionManager;
// 用于创建AccountService 动态代理对象
@Bean("proxyAccountService")
public AccountService createAccountServiceProxy(){
// 创建AccountService 的动态代理对象
AccountService cgLibAccountService = (AccountService) Enhancer.create(
accountService.getClass(),
new MethodInterceptor() {
/**
* intercept 方法, 就是对原有功能方法的增强
* @param proxy 就是创建出来的动态代理对象的引用
* @param method 被代理对象 所要执行的方法
* @param objects 被代理对象 所要执行的方法 所接收的实际参数
* @param methodProxy 所要执行的方法的代理对象 method.invoke()
*/
@Override
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("被代理对象="+ accountService);
System.out.println("被增强的方法="+method.getName());
System.out.println("被增强的方法 所接收的实际参数值= "+ Arrays.toString(objects));
Object rtValue = null;
try {
// 开启事务(增强)
transactionManager.begin();
// 调用 被代理对象 的原有方法
rtValue = method.invoke(accountService,objects);
// 提交事务(增强)
transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
// 回滚事务(增强)
transactionManager.rollback();
} finally {
// 释放连接(增强)
transactionManager.close();
}
return proxy;
}
});
return cgLibAccountService;
}
案例小结
我们在本示例中,只是生成了一个AccountService的实现,有些同学会觉得getProxyAccountService()这个方法 的返回值限定死了,只能生成AccountService类型的代理对象,其实不然,因为我们已经学过了Spring的loC,只 要把创建的代理对象传入进来就行了,只是我们目前案例只有一个Service的实现,因此把它写成固定的了。
第二节 AOP的相关概念
2.1 AOP的基础知识
AOP优势
运用AOP编程思想,具有以下优势:
1.提高代码的独立性
⒉.减少重复代码,提高开发效率
3.易于维护
AOP实现原理分析
通过《1.1AOP概述》小节中的概念介绍,我们得知AOP编程是通过预编译和运行期动态代理技术实现的对重复代 码进行统一的管理和调用。
AOP应用场景说明
在实际开发中,有很多地方都可以看到AOP编程思想的身影。我们第一次接触到它的地方,就是学习Filter过滤器 的时候,过滤器就是AOP思想的具体应用场景之一。
当然,在我们实际开发中,有很多具体的需求,都可以借助AOP编程思想来实现。
例如:记录访问日志,统计方法执行效率,判断用户的访问权限等等,这些场景都可以使用AOP编程思想。
2.2 Spring中的AOP
2.2.1 AOP相关术语
1)业务主线
在讲解AOP术语之前,我们先来看一下下面这两张图,它们就是第一章节案例中的需求,只不过把它完善成了使用 三层架构实现账户的操作:
上图描述的就是未采用AOP思想设计的程序,当我们红色框中圈定的方法时,会带来大量的重复劳动。程序中充斥 着大量的重复代码,使我们程序的独立性很差。而下图中是采用了AOP思想设计的程序,它把红框部分的代码抽取 出来的同时,运用动态代理技术,在运行期对需要使用的业务逻辑方法进行增强。
2)AOP术语
名词 | 解释 |
---|---|
Joinpoint(连接点) | 它指的是那些可以用于把增强代码加入到业务主线中的点,那么由上图中红色字体我们可以看出,这些点指的就是方法。在方法执行的前后通过动态代理技术加入增强的代码。在Spring框架AOP思想的技术实现中,也只支持方法类型的连接点。 |
Pointcut(切入点) | 它指的是那些已经把增强代码加入到业务主线进来之后的连接点。由上图中,我们看出表现层transfer方法就只是连接点,因为判断访问权限的功能并没有对其增强。 |
Aspect(切面) | 它指定是增强的代码所关注的方面,把这些相关的增强代码定义到一个类中,这个类就是切面类。例如,事务切面,它里面定义的方法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务无关的方法。我们前面的案例中TrasnactionManager就是一个切面。 |
Advice(通知/增强) | 它指的是切面类中用于提供增强功能的方法。并且不同的方法增强的时机是不一样的。比如,开启事务肯定要在业务方法执行之前执行;提交事务要在业务方法正常执行之后执行,而回滚事务要在业务方法执行产生异常之后执行等等。那么这些就是通知的类型。其分类有:前置通知后置通知异常通知最终通知环绕通知。 |
Target(目标对象) | 它指的是代理的目标对象。即被代理对象。 |
Proxy(代理) | 它指的是一个类被AOP织入增强后,产生的代理类。即代理对象。 |
Weaving(织入) | 它指的是把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织 入, 而Aspect采用编译期织入和类装载期织入。 |
lntroduction(引 介) | 它指的是在不修改类代码的前提下,运行期为目标对象动态地添加一些方法或字段。 |
2.2.2 Spring中AOP的代理选择
默认情况下,Spring会根据被代理对象是酋实现接口来选择使用DK还是CGLIB。当被代理对象没有实现任何接口 时,Spring会选择CGLIB。当被代理对象实现了接口,Spring会选择DK官方的代理技术,不过我们可以通过配置 的方式,让Spring强制使用CGLIB。关于配置的方式,我们在接下来的课程中给大家讲解。
2.2.3 Spring中AOP的配置方式
在Spring的AOP配置中,也和loC配置一样,支持3类配置方式。
第一类:使用XML配置
第二类:使用XML+注解组合配置
第三类:使用纯注解配置
2.2.4学习spring中的AOP要明确的事
1)开发阶段(我们做的)
编写核心业务代码((开发主线)∶大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知:AOP编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。︰AOP编程人员来做。
2)运行阶段(Spring框架完成的)
Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理 对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
第三节 Spring中应用AOP
3.1基于XML配置的入门案例
3.1.1案例需求介绍
在业务层方法执行之前,输出记录日志的语句。
3.1.2导入坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
3.1.3 编写基础代码
// 账户业务层接口
public interface AccountService {
/*
* 模拟保存账户的方法
* */
void save();
}
// 账户接口的实现类
public class AccountServiceImpl implements AccountService {
@Override
public void save() {
// 打印一句话,日志(前置增强)
System.out.println("保存账户");
}
}
/**
* 日志切面类,提供了AccountServiceImpl 类 增强方法
* */
public class LogUtils {
// 打印日志(增强方法)
public void printLog(){
System.out.println("增强方法printLog 执行了");
}
}
3.1.4 配置Spring的IoC
3.1.5 配置Spring的AOP
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置service 对象 -->
<bean id="accountService" class="com.zhuxu.service.impl.AccountServiceImpl"/>
<!-- 配置LogUtil 切面类(增强功能)对象 -->
<bean id="logUtils" class="com.zhuxu.utils.LogUtils"/>
<!-- 配置AOP 实现了对service 中的方法 进行增强(LogUtil) -->
<aop:config>
<!-- 配置切面 -->
<aop:aspect id="logUtil" ref="logUtils">
<!-- 配置切入点表达式 目的是为了让我们service层中的方法和我们要增强的功能代码结合在一起 -->
<aop:before method="printLog" pointcut="execution(public void com.zhuxu.service.impl.AccountServiceImpl.save())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
3.1.6 编写测试类代码
public class SpringAOPTest {
public static void main(String[] args) {
// 1.创建Spring容器对象
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml");
// 2.获取AccountService对象
AccountService accountService = applicationContext.getBean(AccountService.class);
// 3.调用方法
accountService.save();
}
}
3.2基于XML的AOP配置细节
3.2.1关于切入点表达式
在入门案例中,我们实现了对AccountServiceImpl的save方法进行增强,在其执行之前,输出了记录日志的语 句。这里面,我们接触了一个比较陌生的名称:切入点表达式,它是做什么的呢?我们往下看。
1)概念及作用
切入点表达式,也称之为Aspect]切入点表达式,指的是遵循特定语法结构的字符串,其作用是用于对符合语法格 式的连接点进行增强。,它是AspectJ表达式的一部分。
回2)关于AspectJ
Aspect是一个基于Java语言的AOP框架,Spring框架从2.0版本之后集成了AspectJ框架中切入点表达式的部分, 开始支持AspectJ切入点表达式。
3)表达式中的关键字
关键字 | 说明 |
---|---|
execution | 用于匹配方法执行的连接点 |
4)切入点表达式的使用示例
全限定方法名
访问修饰符返回值包名.包名.包名.类名.方法名(参数列表)
全匹配方式:
public void com. itheima. service . impl . AccountServiceImpl. saveAccount()
访问修饰符可以省略
void com. itheima . service . impl. AccountServiceImpl . saveAccount( )
返回值可以使用*,表示任意返回值
* com. itheima. service . impl . AccountServiceImpl . saveAccount()
包名可以使用" .. "表示当前包及其子包
* com. itheima. service. . AccountServiceImpl. saveAccount( )
类名和方法名,都可以使用*,表示任意类,任意方法
* com. itheima . service. imp1.*.*()
参数列表,可以使用具体类型
基本类型直接写类型名称: int
引用类型必须写全限定类名: java.1ang . String
参数列表可以使用*,表示任意参数类型,但是必须有参数
com. itheima . service . impl . AccountServiceImpl. saveAccount(*)
参数列表可以使用...表示有无参数均可。有参数可以是任意类型
* com. itheima. service . impl . AccountServiceImpl. saveAccount(..)
全通配方式:
* *..*.*(..)
开发中常用的方法
* com. itheima.service..*.*(..)
3.2.2 入门案例的标签
1)aop:config标签
<!--
作用:
用于表示开始aop的配置
出现位置:
写在beans标签的内部
属性:
proxy-target-class:用于指定代理方式。默认值是false。当取值为true时, 采用cglib的代理方
expose-proxy:用于指定是否暴露代理对象,通过AopContext可以进行访问代理对象。
-->
<aop : config proxy-target-class="false" expose- proxy= "false"></aop:config>
2)aop:aspect标签
<!--
作用:
用于配置切面
出现位置:
aop:config标签内部
属性:
id:用于指定切面的唯一标识。
ref:用于指定引用bean的id。
order:用于指定多个切面中,相同通知类型的执行顺序。取值是个整数,数值越小优先级越高
-->
< aop:aspect id="logAdvice" ref="logUtil1" order="1">< /aop: aspect>
3)aop:pointcut标签
<!--
作用:
用于配置通用切入点表达式
出现位置:
aop : config标签内部,当出现在此处时,要求必须在所有aop: aspect标签之前。它可以供所有切面使用
aop : aspect标签内部,当出现在此处时,它没有顺序要求,但只能供当前切面使用。
-->
<aop : pointcut id="pointcut1"
expression="execution(public * com. itheima . service. impl . AccountServiceImpl.save())">
</ aop: pointcut>
3.2.3配置SpringAOP使用Cglib代理模式
在前面我们已经说了,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择的。被代理对象实现了 接口,则采用基于接口的动态代理。当被代理对象没有实现任何接口的时候,Spring会自动切换到基于子类的动态 代理方式。|
但是我们都知道,无论被代理对象是否实现接口,只要不是final修饰的类都可以采用cglib提供的方式创建代理对 象。所以Spring也考虑到了这个情况,提供了配置的方式实现强制使用基于子类的动态代理〈(即cglib的方式)。 配置的方式有两种。
1)第一种:使用aop:config标签配置
<aop:config proxy-target-class="true">
2)第二种:使用aop:aspectj-autoproxy标签配置
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
3.3五种通知类型
3.3.1前置通知
配置方式: aop:before标签
<!--
作用:
用于配置前置通知
出现位置:
它只能出现在aop:aspect标签内部
属性:
method:用于指定前置通知的方法名称
pointcut:用于指定切入点表达式
pointcut-ref:用于指定切入点表达式的引用
-->
<aop : before method="printLog" pointcut-ref-" pointcut1"></aop: before>
执行时机
前置通知永远都会在切入点方法(业务核心方法)执行之前执行。
细节
前置通知可以获取切入点方法的参数,并对其进行增强。
3.3.2 后置通知
配置方式
<!--
作用:
用于配置后置通知
出现位置:
它只能出现在aop: aspect标签内部
属性:
method:用于指定后置通知的方法名称
pointcut:用于指定切入点表达式
pointcut-ref:用于指定切入点表达式的引用
returning:用于指定返回值类型
-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
执行时机
通知就不再执行了,而是执行异常通知。
细节
后置通知既可以获取到切入点方法的参数,也可以获取切入点方法的返回值。
3.3.3异常通知
配置方式
<!--
作用:
用于配置异常通知。
出现位置:
它只能出现在aop:aspect标签内部
属性:
method:用于指定异常通知的方法名称
pointcut:用于指定切入点表达式
pointcut-ref:用于指定切入点表达式的引用
arg-names:用于指定通知方法的参数名称,要求表达式中必须有描述args的语句
throwing:用于指定异常通知中异常的变量名称
-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1" ></aop:after-throwing>
执行时机
异常通知的执行时机是在切入点方法(业务核心方法))执行产生异常之后,异常通知执行。如果切入点方法执行没有产生异常,则异常通知不会执行。
细节
异常通知不仅可以获取切入点方法执行的参数,也可以获取切入点方法执行产生的异常信息。
3.3.4 最终通知
配置方式
<!--
作用:
用于指定最终通知。
出现位置:
它只能出现在aop: aspect标签内部
属性:
method:用于指定最终通知的方法名称
pointcut:用于指定切入点表达式
pointcut-ref:用于指定切入点表达式的引用
arg-names:用于指定通知方法的参数名称,要求表达式中必须有描述args的语句
-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
执行时机
切入点方法执行是否产生异常,它都会在返回之前执行。
细节
最终通知执行时,可以获取到通知方法的参数。同时它可以做一些清理操作。
最终通知的执行时机是在切入点方法(业务核心方法)执行完成之后,切入点方法返回之前执行。换句话说,无论
3.3.5 环绕通知
配置方式
<!--
作用:
用于配置环绕通知。
出现位置:
它只能出现在aop:aspect标签的内部
属性:
method:用于指定环绕通知的方法名称
pointcut:用于指定切入点表达式
pointcut-ref:用于指定切入点表达式的引用
arg-names:用于指定环绕通知方法的参数名称,要求表达式中必须有描述args的语句
-->
<aop: around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
特别说明
环绕通知,它是有别于前面四种通知类型外的特殊通知。前面四种通知(前置,后置,异常和最终)它们都是指定何时增强的通知类型。而环绕通知,它是Spring框架为我们提供的一种可以通过编码的方式,控制增强代码何时执行的通知类型。它里面借助的ProceedingloinPoint接口及其实现类,实现手动触发切入点方法的调用。
Proceeding]oinPoint接口介绍
3.3.6 五种通知类型的完整案例
第一步:导入坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
第二步:基础代码
账户的业务层接口
// 账户的业务层接口
public interface AccountService {
/**
* 模拟保存操作
*/
void save();
/**
* 模拟更新操作
* @param i
*/
void update(int i);
/**
* 模拟删除操作
* @return
*/
int delete();
}
账户业务层接口实现类
// 账户业务层接口实现类
public class AccountServiceImpl implements AccountService {
@Override
public void save() {
System.out.println("保存账户.....");
// 异常测试
int i = 1/0;
}
@Override
public void update(int i) {
System.out.println("更新账户....."+i);
}
@Override
public int delete() {
System.out.println("删除账户...0");
return 999;
}
}
日志的切面类(包含了 增强方法)
public class LogUtil {
// 配置 前置增强:执行实现 在切入点方法执行前执行
public void beforePrintLog(JoinPoint joinPoint) throws Throwable {
// 获取切入点方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置增强beforePrintLog="+ Arrays.toString(args));
}
// 配置 后置增强:执行时机 在切入点方法执行完毕后,执行;若切入点方法执行过程中产生了异常,后置增强不执行
public void afterReturningPrintLog(JoinPoint joinPoint,Object rtValue) throws Throwable {
System.out.println("后置增强afterReturningPrintLog="+ rtValue);
}
// 配置 异常增强:执行时机 在切入点方法执行过程中 抛出了异常
public void afterThrowingPrintLog(JoinPoint joinPoint,Exception e) throws Throwable {
System.out.println("异常增强:afterThrowingPrintLog="+e);
}
// 配置 最终增强:执行时机 在切入点方法执行完毕后,执行(无论是否有异常,最终增强都执行)
public void afterPrintLog(JoinPoint joinPoint) throws Throwable {
System.out.println("最终增强:afterPrintLog");
}
// 配置 环绕通知:包含了(前置增强,后置增强,异常增强,最终增强)
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object rtValue = null;
try {
// 配置 前置增强
System.out.println("前置增强");
// 获取方法执行时,所需的实际参数
Object[] args = joinPoint.getArgs();
// 原有的方法执行
rtValue=joinPoint.proceed(args);
// 配置 后置增强
System.out.println("后置增强");
} catch (Throwable throwable) {
throwable.printStackTrace();
// 配置 异常增强
System.out.println("异常增强");
} finally {
// 配置 最终增强
System.out.println("最终增强");
}
return rtValue;
}
}
Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置service -->
<bean id="accountService" class="com.zhuxu.service.impl.AccountServiceImpl"/>
<!-- 配置切面类(增强) -->
<bean id="logUtil" class="com.zhuxu.utils.LogUtil"/>
<!-- 配置AOP -->
<aop:config>
<!-- 配置 通用的 切入点表达式 -->
<aop:pointcut id="pc" expression="execution(* com.zhuxu.service..*.*(..))"></aop:pointcut>
<!-- 配置切面(织入) -->
<aop:aspect id="logUtil" ref="logUtil">
<!-- 配置 前置增强:执行时机 在切入点方法执行前执行 -->
<aop:before method="beforePrintLog" pointcut-ref="pc"></aop:before>
<!-- 配置 后置增强:执行时机 在切入点方法执行完毕后,执行;若切入点方法执行过程中产生了异常,后置增强不执行-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pc" returning="rtValue"></aop:after-returning>
<!-- 配置 异常增强:执行时机 在切入点方法执行过程中 抛出了异常,执行-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pc" throwing="e"></aop:after-throwing>
<!-- 配置 最终增强:执行时机 在切入点方法执行完毕后,执行(无论是否有异常,最终增强都执行)-->
<aop:after method="afterPrintLog" pointcut-ref="pc"></aop:after>
</aop:aspect>
<!-- 上边四种增强或环绕增强 二选一 -->
<!-- 配置 环绕通知:包含了(前置增强,后置增强,异常增强,最终增强)-->
<aop:around method="around" pointcut-ref="pc"></aop:around>
</aop:config>
</beans>
测试类
package com.zhuxu.service;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Lucky
* @date 2020/8/31
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:ApplicationContext.xml"})
public class AccountServiceTest {
// 自动注入
@Autowired
private AccountService accountService;
@Test
public void testSave() {
accountService.save();
}
@Test
public void testUpdate() {
accountService.update(123);
}
@Test
public void testDelete() {
accountService.delete();
}
}
第四节 基于注解的AOP配置
注解方式的AOP一定要采用环绕通知,否则发生报错后,最终增强在前,异常增强和后置增强在后,发生位置混乱
比如事务控制,先开启事务,然后转账,转账后先提交事务,再释放连接,如果不用环绕注解,刚转完帐就关闭了,无法提交
4.1 入门案例
4.1.1 导入坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
</dependencies>
4.1.2 基础代码
账户业务层接口
public interface AccountService {
/**
* 模拟保存操作
*/
void save();
/**
* 模拟更新操作
* @param i
*/
void update(int i);
/**
* 模拟删除操作
* @return
*/
int delete();
}
账户业务层接口实现类
@Service
public class AccountServiceImpl implements AccountService {
@Override
public void save() {
System.out.println("保存账户.....");
int i = 1/0;
}
@Override
public void update(int i) {
System.out.println("更新账户....."+i);
}
@Override
public int delete() {
System.out.println("删除账户...");
return 999;
}
}
4.1.3 日志的切面类
@Component
// 设置切面类
@Aspect
/**
* 替换掉的spring.xml代码
* <!-- 配置切面类(增强) -->
* <bean id="logUtil" class="com.zhuxu.utils.LogUtil"/>
* */
public class LogUtil {
// 配置通用的切入点表达式
@Pointcut("execution(* com.zhuxu.service..*.*(..))")
public void pointcut(){}
// 配置 前置增强:执行实现 在切入点方法执行前执行
// @Before("pointcut()")
public void beforePrintLog(JoinPoint joinPoint) throws Throwable {
// 获取切入点方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置增强beforePrintLog="+ Arrays.toString(args));
}
// 配置 后置增强:执行时机 在切入点方法执行完毕后,执行;若切入点方法执行过程中产生了异常,后置增强不执行
// @AfterReturning("pointcut()")
public void afterReturningPrintLog(JoinPoint joinPoint) throws Throwable {
System.out.println("后置增强afterReturningPrintLog=");
}
// @AfterThrowing("pointcut()")
// 配置 异常增强:执行时机 在切入点方法执行过程中 抛出了异常
public void afterThrowingPrintLog(JoinPoint joinPoint) throws Throwable {
System.out.println("异常增强:afterThrowingPrintLog=");
}
// 配置 最终增强:执行时机 在切入点方法执行完毕后,执行(无论是否有异常,最终增强都执行)
// @After("pointcut()")
public void afterPrintLog(JoinPoint joinPoint) throws Throwable {
System.out.println("最终增强:afterPrintLog");
}
// 配置 环绕通知:包含了(前置增强,后置增强,异常增强,最终增强)
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object rtValue = null;
try {
// 配置 前置增强
System.out.println("前置增强");
// 获取方法执行时,所需的实际参数
Object[] args = joinPoint.getArgs();
// 原有的方法执行
rtValue=joinPoint.proceed(args);
// 配置 后置增强
System.out.println("后置增强");
} catch (Throwable throwable) {
throwable.printStackTrace();
// 配置 异常增强
System.out.println("异常增强");
} finally {
// 配置 最终增强
System.out.println("最终增强");
}
return rtValue;
}
}
4.1.4 spring配置文件
<!-- 开启SpringIOC容器的注解扫描 -->
<context:component-scan base-package="com.zhuxu"></context:component-scan>
<!--代替的代码
配置service
<bean id="accountService" class="com.zhuxu.service.impl.AccountServiceImpl"/>
-->
<!-- 开启SpringAOP的注解扫描-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4.1.5 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:ApplicationContext.xml"})
public class AccountServiceTest {
// 自动注入
@Autowired
private AccountService accountService;
@Test
public void testSave() {
accountService.save();
}
@Test
public void testUpdate() {
accountService.update(123);
}
@Test
public void testDelete() {
accountService.delete();
}
}
4.2注解驱动开发AOP的使用
4.2.1写在最前
<!--开启spring对注解aop的支持-->
<aop:aspectj-autoproxy/>
4.2.2创建配置类
@Configuration
@ComponentScan( "com.itheima")
public class SpringConfiguration {
}
4.2.3注解配置Spring对注解AOP的支持
在使用注解驱动开发aop时,我们要明确的就是,是注解替换掉配置文件中的下面这行配置:
4.3注解详解
4.3.1用于开启注解AOP支持的
1)@EnableAspectJAutoProxy
4.3.2用于配置切面的
1)@Aspect
4.3.3用于配置切入点表达式的
- @Pointcut
4.3.4用于配置通知的
1)@Before
2)@AfterReturning
3)@AfterThrowing
4)@After
5) @Around