1、AOP
1.1、AOP概念
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
可以说OOP是类级别上的纵向扩展,也就是通过继承多态实现对类的增强。而AOP则是方法级别上的横向扩展,通过动态代理,实现在方法的执行前后执行一段增强代码,实现在不改变代理类的源码的情况下的增强。
1.2、AOP术语
- Joinpoint(连接点):连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。简而言之,连接点就是代理类所有的方法,因为这些类理论上都可以被增强!
- Pointcut(切入点):切入点是指我们要对哪些 Joinpoint 进行拦截的定义。换句话说,只有被增强的连接点才是切入点,也就是说被增强的方法才是切入点。切入点一定是连接点,而连接点不一定是切入点。
- Advice(通知/增强):通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
- Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或属性。
- Target(目标对象):代理的目标对象。
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
- Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
- Aspect(切面):是切入点和通知(引介)的结合。
2、入门Demo
2.1、前言
要配置AOP的核心就是配置切面,而切面是由切入点和通知组成的。所以我们要配置AOP,就必须完成以下:
- 通知类以及通知类中的方法
- 将通知类和带有切入点的类加入Spring容器中
- 配置切面的通知
- 配置切面的切入点
maven依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.8.9</version>
</dependency>
</dependencies>
2.2、定义通知类
因为通知分为五种:前置通知,后置通知,异常通知,最终通知,环绕通知,所以我们可以定义五种类型的通知!环绕通知后面再写,用法是一样的。
package utils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;
/**
* @author RuiMing Lin
* @date 2020-04-14 15:54
*/
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知beforePrintLog方法开始记录日志了");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("后置通知afterReturningPrintLog方法开始记录日志了");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知afterThrowingPrintLog方法开始记录日志了");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知afterPrintLog方法开始记录日志了。。。");
}
}
2.3、配置bean
<!-- 配置srping的Ioc,把service对象配置进来-->
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger类 -->
<bean id="logger" class="utils.Logger"></bean>
2.4、配置通知和切入点
<!--配置AOP-->
<aop:config>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
<aop:before method="beforePrintLog" pointcut="execution(* service.impl.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
2.5、测试
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.IAccountService;
/**
* 测试AOP的配置
*/
public class AOPTest {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
as.updateAccount(1);
as.deleteAccount();
}
}
结果输出:
3、AOP标签
3.1、aop:config
作用: 用于声明开始aop的配置
3.2、aop:aspect
作用: 用于配置切面。
属性:
- id:给切面提供一个唯一标识
- ref:引用配置好的通知类bean的id
3.3、aop:pointcut
作用: 用于配置切入点表达式
属性:
- expression:用于定义切入点表达式
- id:用于给切入点表达式提供一个唯一标识
3.4、aop:before
作用: 用于配置前置通知
属性:
- method:指定通知中方法的名称。
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
3.5、aop:after-returning
作用:用于配置后置通知,出现异常不调用
属性:
- method:指定通知中方法的名称
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
3.6、aop:after-throwing
作用:用于配置异常通知
属性:
- method:指定通知中方法的名称。
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
3.7、aop:after
作用:用于配置最终通知
属性:
- method:指定通知中方法的名称。
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
3.8、aop:around
作用:用于配置环绕通知
属性:
- method:指定通知中方法的名称。
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
4、切入点表达式
execution(表达式)
表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明:
- 全匹配方式:
public void service.impl.AccountServiceImpl.saveAccount(domain.Account)
- 访问修饰符可以省略:
void service.impl.AccountServiceImpl.saveAccount(domain.Account)
- 返回值可以使用*号,表示任意返回值
*com.itheima.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
- 包名可以使用*号,表示任意包,但是有几级包,需要写几个
*. *.AccountServiceImpl.saveAccountdomain.Account)
- 使用 . . 来表示当前包,及其子包
service. . AccountServiceImpl.saveAccount(domain.Account)
- 类名可以使用*号,表示任意类
service. .*.saveAccount(com.itheima.domain.Account)
- 方法名可以使用*号,表示任意方法
service . ..( com.itheima.domain.Account)
- 参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
service. . * . * (*)
- 参数列表可以使用…表示有无参数均可,有参数可以是任意类型
service….(…)
- 全通配方式:
/ * * … .(…)
5、通知参数
5.1、JoinPoint类
org.aspectj.lang.JoinPointJoinPoint:提供访问当前被通知方法的目标对象、代理对象、方法参数等数据:
public interface JoinPoint {
String toString(); //连接点所在位置的相关信息
String toShortString(); //连接点所在位置的简短相关信息
String toLongString(); //连接点所在位置的全部相关信息
Object getThis(); //返回AOP代理对象,如果想要使用这个方法的话,最好使用this连接点,这样可以获得最佳的性能。
Object getTarget(); //返回目标对象,如果想要使用这个方法的话,最好使用target连接点,这样可以获得最佳的性能。
Object[] getArgs(); //返回被通知方法参数列表
Signature getSignature(); //返回当前连接点签名
SourceLocation getSourceLocation(); //返回连接点方法所在类文件中的位置
String getKind(); //连接点类型
StaticPart getStaticPart(); //返回连接点静态部分
// getKind 方法的返回值
static String METHOD_EXECUTION = "method-execution";
static String METHOD_CALL = "method-call";
static String CONSTRUCTOR_EXECUTION = "constructor-execution";
static String CONSTRUCTOR_CALL = "constructor-call";
static String FIELD_GET = "field-get";
static String FIELD_SET = "field-set";
static String STATICINITIALIZATION = "staticinitialization";
static String PREINITIALIZATION = "preinitialization";
static String INITIALIZATION = "initialization";
static String EXCEPTION_HANDLER = "exception-handler";
static String SYNCHRONIZATION_LOCK = "lock";
static String SYNCHRONIZATION_UNLOCK = "unlock";
static String ADVICE_EXECUTION = "adviceexecution";
}
5.2、ProceedingJoinPoint类
ProceedingJoinPoint:用于环绕通知,使用proceed()方法来执行目标方法:
public interface ProceedingJoinPoint extends JoinPoint {
void set$AroundClosure(AroundClosure arc); // 这是个内部方法,不应该直接调用
public Object proceed() throws Throwable; // 执行目标函数,以默认的参数执行
public Object proceed(Object[] args) throws Throwable; // 执行目标函数,并传入所需的参数
}
5.3、实例应用
package utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* @author RuiMing Lin
* @date 2020-04-14 15:54
*/
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(JoinPoint joinPoint){
System.out.println(joinPoint.getSignature());
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(JoinPoint joinPoint){
System.out.println(joinPoint.getSignature());
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(JoinPoint joinPoint){
System.out.println(joinPoint.getSignature());
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
public void afterPrintLog(JoinPoint joinPoint){
System.out.println(joinPoint.getSignature());
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
/**
* 环绕通知
*/
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
6、自定义AOP实现日志记录
6.1、定义通知类:
package utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
* @author RuiMing Lin
* @date 2020-04-14 15:54
*/
public class Logger {
/**
* 记录日志的方法
* @param methodName
* @param flag
* @param time
* @throws IOException
*/
private void log(String methodName,String flag,String time) throws IOException {
File file = new File("log.txt");
if (!file.exists()){
file.createNewFile();
}
try (FileWriter fw = new FileWriter("log.txt",true);){
fw.write(new String(time + methodName + flag) + '\n');
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 前置通知
*/
public void beforePrintLog(JoinPoint joinPoint) throws IOException{
// 获取执行 方法名
String signature = joinPoint.getSignature().toString();
int index = signature.lastIndexOf(".");
String methodName = signature.substring(index);
// 获取当前时间
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = df.format(new Date());
// 记录日志
log(methodName,"开始执行", time);
}
/**
* 后置通知
*/
public void afterReturningPrintLog(JoinPoint joinPoint) throws IOException{
// 获取执行方法名
String signature = joinPoint.getSignature().toString();
int index = signature.lastIndexOf(".");
String methodName = signature.substring(index);
// 获取当前时间
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = df.format(new Date());
// 记录日志
log(methodName,"结束执行", time);
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(JoinPoint joinPoint) throws IOException{
// 获取执行方法 名
String signature = joinPoint.getSignature().toString();
System.out.println(signature);
int index = signature.lastIndexOf(".");
System.out.println(index);
String methodName = signature.substring(index);
// 获取当前时间
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = df.format(new Date());
// 记录日志
log(methodName,"出现异常", time);
}
/**
* 最终通知
*/
public void afterPrintLog(JoinPoint joinPoint){
// 获取执行方法名
String signature = joinPoint.getSignature().toString();
int index = signature.lastIndexOf(".");
String methodName = signature.substring(index);
// 获取当前时间
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = df.format(new Date());
// 记录日志
log(methodName, "最终通知", time);
}
/**
* 环绕通知
*/
public Object aroundPrintLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
6.2、配置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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置srping的Ioc,把service对象配置进来-->
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger类 -->
<bean id="logger" class="utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置切入点 -->
<aop:pointcut id="servicePT" expression="execution(* service.impl.*.*(..))"/>
<!-- 配置通知,并且建立通知方法和切入点方法的关联-->
<aop:before method="beforePrintLog" pointcut-ref="servicePT"></aop:before>
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="servicePT"></aop:after-returning>
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="servicePT"></aop:after-throwing>
<aop:after method="afterPrintLog" pointcut-ref="servicePT"></aop:after>
<aop:around method="aroundPrintLog" pointcut-ref="servicePT"></aop:around>
</aop:aspect>
</aop:config>
</beans>
6.3、测试类
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.IAccountService;
/**
* 测试AOP的配置
*/
public class AOPTest {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
as.updateAccount(1);
as.deleteAccount();
}
}