1.概述
AOP全称Aspect Orient Programming,即:面向切面编程。将通知织入到切入点,形成切面(Aspect)的过程,就叫做:面向切面编程。
1.1基本概念
(1)Joinpoint(连接点):在应用执行过程中,可以被拦截到、并插入切面的点。在Spring中,这些点指的是方法(Spring只支持方法类型的连接点)。个人理解:连接点理论上都可以插入切面,它们相当于“候选人”。
(2)Pointcut(切入点):指定我们要对哪些连接点进行拦截,切入点表达式会匹配通知所要织入的一个或者多个连接点。个人理解:定义哪些连接点会被插入通知,相当于从“候选人”中脱颖而出。
(3)Advice(通知):指拦截到连接点之后所要做的事情,通常是对方法的增强。个人理解:指定对切入点做什么。
(4)Weaving(织入):把通知应用到目标对象来创建新的代理对象的过程。切面在指
定的切入点被织入到目标对象中,在目标对象的生命周期中有多个点可以织入。
1.2通知的类型
Spring AOP通知分成五类:
(1)前置通知:aop: before,在切入点前面执行,前置通知不会影响切入点的执行,除非此处抛出异常。
(2)后置通知:aop: after-returning,在切入点正常执行完成后执行。如果切入点抛出异常,则不会执行。
(3)异常通知:aop: after-throwing,在切入点方法抛出异常后执行。
(4)最终通知:aop: after (finally),在切入点执行完成后执行。不管是正常执行完成,还是抛出异常,都会执行最终通知中的内容。
(5)环绕通知:aop: around,环绕通知围绕在切入点前后,比如:一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知要决定是否执行切入点方法(调用ProceedingJoinPoint的proceed( )方法)。
1.3 AOP的好处
AOP最大的好处就是:可以在不修改源码的情况下,对已有逻辑功能进行增强。以后,当我们遇到这样的场景时,就可以用AOP去实现。
注意:Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
2.原理
AOP的实现原理:动态地生成代理类,在代理类中对被代理类的方法进行增强。
Spring中AOP的有两种实现方式:JDK动态代理、Cglib动态代理。采用哪一种方式的判断标准就是被切面的类是否有其实现的接口,如果有对应的接口,则采用 JDK 动态代理,否则采用CGlib字节码生成机制动态代理方式。
按照代理的创建时期,代理类可以分为两种:
静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
动态:在程序运行时运用反射机制动态创建而成。
3.使用方法
Demo的说明:AccountServiceImpl类中有三个业务方法:
saveAccount( ) -- 无返回值+无参
updateAccount(int i) -- 无返回值+无参
deleteAccount( ) -- 无返回值+无参
目标:在上述方法执行时,增加打印日志的功能。
3.1环境准备
3.1.1导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
3.1.2准备必要的代码
主要是service层(实现类、接口)、logger(通知功能的类)。为了演示方便,这里业务逻辑就直接打印一句话,省去了dao层。
(1)IAccountService.java
package com.qyl.service;
public interface IAccountService {
void saveAccount();
void updateAccount(int i);
int deleteAccount();
}
(2)AccountServiceImpl.java
package com.qyl.service.impl;
import com.qyl.service.IAccountService;
public class AccountServiceImpl implements IAccountService {
/**
* 无返回值+无参
*/
@Override
public void saveAccount() {
System.out.println("执行了保存");
}
/**
* 无返回值+有参
*/
@Override
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
/**
* 有返回值+无参
*/
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
(3)Logger.java
public class Logger {
public void printLog(){
System.out.println("Logger类中的printLog方法开始记录日志了");
}
//环绕通知
public Object aroundPringLog(ProceedingJoinPoint pjp) {
Object returnValue = null;
try {
Object[] args = pjp.getArgs();
System.out.println("方法执行前,Logger类中的aroundPringLog开始记录日志了....");
returnValue = pjp.proceed(args);
System.out.println("方法执行后,Logger类中的aroundPringLog开始记录日志了....");
} catch (Throwable e) {
System.out.println("方法执行出现异常,Logger类中的aroundPringLog开始记录日志了....");
throw new RuntimeException(e);
} finally {
System.out.println("finally,Logger类中的aroundPringLog开始记录日志了....");
}
return returnValue;
}
}
3.1.3 AOP配置
3.1.3.1基于xml文件配置
(1)导入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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
(2)配置spring的IOC:
<!--配置spring的IOC,把service对象配置进来-->
<bean id="accountService" class="com.qyl.service.impl.AccountServiceImpl"></bean>
(3)配置logger类
<!--配置logger类-->
<bean id="logger" class="com.qyl.utils.Logger"></bean>
(4)配置AOP
这里以前置通知、环绕通知为例:
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,并建立通知方法和切入点方法的关联-->
<!--前置通知-->
<aop:before method="printLog" pointcut="execution( * com.qyl.service.impl.*.*(..))"/>
<!--环绕通知-->
<aop:around method="aroundPringLog" pointcut="execution( * com.qyl.service.impl.*.saveAccount(..))"/>
</aop:aspect>
</aop:config>
这里有5种通知类型,分别是:前置通知(aop:before)、后置通知(aop:after-returning)、异常通知(aop:after-throwing)、最终通知(aop:after)、环绕通知(aop:around)
注意:前4种使用方式类似,环绕通知特殊一点,会单独列出来
3.1.3.2基于注解配置
(1)导入相应的约束
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.qyl"/>
<aop:aspectj-autoproxy/>
</beans>
(2)配置@Service、@Repository等注解
业务逻辑实现类:@Service
本例中没有持久层,所以不涉及@Repository
(3)配置Logger类:
@Component("logger")
@Aspect//表明这是一个切面类
public class Logger {
//前置通知
@Before("execution( * com.qyl.service.impl.*.*(..))")
public void printLog(){
System.out.println("Logger类中的printLog方法开始记录日志了");
}
//环绕通知
@Around("execution( * com.qyl.service.impl.*.*(..))")
public Object aroundPringLog(ProceedingJoinPoint pjp) {
Object returnValue = null;
try {
Object[] args = pjp.getArgs();
System.out.println("方法执行前,Logger类中的aroundPringLog开始记录日志了....");
returnValue = pjp.proceed(args);
System.out.println("方法执行后,Logger类中的aroundPringLog开始记录日志了....");
} catch (Throwable e) {
System.out.println("方法执行出现异常,Logger类中的aroundPringLog开始记录日志了....");
throw new RuntimeException(e);
} finally {
System.out.println("finally,Logger类中的aroundPringLog开始记录日志了....");
}
return returnValue;
}
}
运行结果:
在这里插入图片描述
3.1.3.3 切点表达式
表达式语法:execution( [修饰符] 返回值类型 包名.类名.方法名(参数) ),其中:
(1)访问修饰符:可以省略
(2)返回值:可以使用*号,表示任意返回值
(3)包名:a.可以使用*号,表示任意包,但是有几级包,需要写几个*
b.使用..来表示当前包及其子包
(4)类名:可以使用*号,表示任意类
(5)方法名:可以使用*号,表示任意方法
(6)参数列表:a.参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参
数 b.参数列表可以使用..表示有无参数均可,有参数可以是任意类型
特别的,全通配形式:* *..*.*(..)
3.1.4环绕通知(单独列出)
它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed( ),下图中的pjp.proceed( )方法就相当于明确调用切入点方法。该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们调用。
public Object aroundPringLog(ProceedingJoinPoint pjp) {
Object returnValue = null;
try {
Object[] args = pjp.getArgs();
System.out.println("方法执行前,Logger类中的aroundPringLog开始记录日志了....");
returnValue = pjp.proceed(args);
System.out.println("方法执行后,Logger类中的aroundPringLog开始记录日志了....");
return returnValue;
} catch (Throwable e) {
System.out.println("方法执行出现异常,Logger类中的aroundPringLog开始记录日志了....");
throw new RuntimeException(e);
} finally {
System.out.println("finally,Logger类中的aroundPringLog开始记录日志了....");
}
}
4.应用场景
Spring的AOP主要有以下应用场景:
场景一: 记录日志
场景二: 监控方法运行时间 (监控性能)
场景三: 权限控制
场景四: 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
场景五: 事务管理 (调用方法前开启事务, 调用方法后提交关闭事务 )
网友评论