一、Spring AOP简介
AOP的全称是Aspect-Oriented Programming,即面向切面编程
(也称面向方面编程)。它是面向对象编程(OOP)
的一种补充,目前已成为一种比较成熟的编程方式。
在业务处理代码中,通常都会进行事务处理
、日志记录
等操作。虽然使用OOP可以通过组合或者继承的方式来达到代码的重用,但有时为了实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率,更重要的是降低了代码的可维护性。
为了解决这一问题,AOP思想随之产生。AOP采取横向抽取机制
,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码织入到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用
。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充。
类与切面的关系
AOP的使用,使开发人员在编写业务逻辑时可以专心于核心业务,而不用过多的关注于其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性。
二、AOP术语
连接点:可以被增强的方法
切入点:实际被增强的方法
通知(增强):实际增强的逻辑部分称为通知(增强)
前置通知
后置通知
环绕通知
异常通知
最终通知
切面:是动作,把通知应用到切入点过程
Proxy(代理)
:将通知应用到目标对象之后,被动态创建的对象。Weaving(织入)
:将切面代码插入到目标对象上,从而生成代理对象的过程。
三、JDK动态代理(基于接口实现)
3.1、先抛出问题
3.1、使用JDK动态代理解决问题(实际开发不常使用,学习了解原理)
JDK动态代理是通过java.lang.reflect.Proxy 类
来实现的,我们可以调用Proxy类的newProxyInstance()方法
来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。
newProxyInstance()方法
方法有三个参数:
第一参数:类加载器
第二参数:增强方法所在的类,这个类实现的接口,支持多个接口
第三参数:实现这个接口InvocationHandler,创建代理对象,写增强的部分。
步骤:
1、创建接口类
2、创建接口的实现类
3、使用Proxy创建接口代理对象
代理的作用:附加功能由动态代理来做,核心功能的业务处理由service来实现
四、AspectJ开发
4.1、AspectJ简介(不是spring组成部分)
AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。Spring 2.0以后
,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。新版本的Spring框架,也建议使用AspectJ来开发AOP。
使用AspectJ实现AOP有两种方式:
一种是基于XML
的声明式AspectJ,另一种是基于注解
的声明式AspectJ。
4.2、AspectJ开发准备
下载AspectJ的jar包https://mvnrepository.com/artifact/org.aspectj/aspectjweaver
需要导入的jar包
4.3、通知的类型
按照通知在目标类方法的连接点位置,主要可以分为以下类型:
1、环绕通知:在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。
2、前置通知:在目标方法执行前实施增强,可以应用于权限管理等功能。
3、返回通知:在目标方法成功执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
4、后置(最终)通知:在目标方法执行后实施增强,不论是否发生异常,该通知都要执行,该类通知可用于释放资源。
5、异常抛出通知:在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
4.4、基于注解的AspectJ
4.4.1、AspectJ注解
AspectJ框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码。AspectJ的注解及其描述如下所示:
4.4.2、切入点表达式
execution(modifiers-pattern ret-type-pattern declaring-type-pattern name-pattern(param-pattern) throws-pattern)
(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强)
语法结构:,
execution([权限修饰符][返回类型][类全路径][方法名称]([参数列表]))
举例1:对com.dgut.dao.BookDao类里面的add进行增强
execution(* com.dgut.dao.BookDao.add(..))
举例2:对 com.dgut.dao.BookDao类里面的所有的方法进行增强
vexecution(* com.dgut.daa.BookDao.*(..))
举例3:对com.dgut.dao包里面所有类,类里面所有方法进行增强
execution(*com.dgut.dao.*.*(..))
4.4.、基于注解的AspectJ内容
获取方法的方法名
获取方法的参数
获取方法的返回结果
处理特定异常
环绕通知用法
重用切入点表达式
切面的优先级------->@Order(2) // 第二个切面,后使用
4.4.、实例(实现接口情况)
MyAspect .java
package com.dgut.spring.aop.aspectj;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
//创建一个切面
@Component
@Aspect//声明这是一个切面类
@Order(2) // 第二个切面,后使用
public class MyAspect {
// 重用切入点表达式
@Pointcut("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.*(..))")
public void myPointCut() {}
// 前置通知
// 切CalculatorImpl类的add方法参数任意..,返回值任意*
@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
public void beforeAnnouncement(JoinPoint jp) {
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
}
// 返回通知
// 切CalculatorImpl类的所有方法参数任意..,返回值为res和参数Object res同名,表示两个是一致的
@AfterReturning(value = "myPointCut()", returning = "res")
public void afterReturningAnnouncement(JoinPoint jp, Object res) {
String methodName = jp.getSignature().getName();
System.out.println("AfterReturning Announcement===> " + "要切入的方法名:" + methodName + "() ,返回结果:" + res);
}
// 后置(最终)通知
// 切CalculatorImpl类的所有方法参数任意..,返回值任意*
@After("myPointCut()")
public void afterAnnouncement(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("After Announcement===> " + "要切入的方法名:" + methodName + "()");
}
// 异常抛出通知
// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
@AfterThrowing(value = "myPointCut()", throwing = "ex")
public void exceptionAnnouncement(JoinPoint jp, Exception ex) {
String methodName = jp.getSignature().getName();
System.out.println("Exception Announcement===> " + "要切入的方法名:" + methodName + "()" + " throwing Exception:" + ex);
}
// 环绕通知
// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
@Around("myPointCut()")
public Object aroundAnnouncement(ProceedingJoinPoint pjp) {
Object res = null;
try {
// 前置通知
System.out.println("前置通知");
res = pjp.proceed();
// 返回通知
System.out.println("返回通知");
} catch (Throwable e) {
// 异常抛出通知
System.out.println("异常抛出通知");
e.printStackTrace();
} finally {
// 后置(最终)通知
System.out.println("后置(最终)通知");
}
return res;
}
}
OtherAspect .java
package com.dgut.spring.aop.aspectj;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
//创建一个切面
@Component
@Aspect
@Order(1) // 第一个切面,先使用
public class OtherAspect {
// 前置通知
// 切CalculatorImpl类的add方法参数任意..,返回值任意*
@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
public void beforeAnnouncement(JoinPoint jp) {
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("<===Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
}
}
4.4.、实例(没有实现接口情况)
步骤:
(1)开启注解扫描
(2)使用注创建CalculatorImpl和MyAspect(OtherAspect 可选)对象
(3)在增强类上面添加注解@Aspect
(4)在spring配置文件中开启生成代理对象
(4)在增强类的里面,在作为通知方法上面添加通知类型注解,使用切入点表达式配置.
MyAspect .java
package com.dgut.spring.aop.aspectj;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
//创建一个切面
@Component
@Aspect
@Order(2) // 第二个切面,后使用
public class MyAspect {
// 重用切入点表达式
@Pointcut("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.*(..))")
public void myPointCut() {
}
// 前置通知
// 切CalculatorImpl类的add方法参数任意..,返回值任意*
@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
public void beforeAnnouncement(JoinPoint jp) {
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
}
// 返回通知
// 切CalculatorImpl类的所有方法参数任意..,返回值为res和参数Object res同名,表示两个是一致的
@AfterReturning(value = "myPointCut()", returning = "res")
public void afterReturningAnnouncement(JoinPoint jp, Object res) {
String methodName = jp.getSignature().getName();
System.out.println("AfterReturning Announcement===> " + "要切入的方法名:" + methodName + "() ,返回结果:" + res);
}
// 后置(最终)通知
// 切CalculatorImpl类的所有方法参数任意..,返回值任意*
@After("myPointCut()")
public void afterAnnouncement(JoinPoint jp) {
String methodName = jp.getSignature().getName();
System.out.println("After Announcement===> " + "要切入的方法名:" + methodName + "()");
}
// 异常抛出通知
// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
@AfterThrowing(value = "myPointCut()", throwing = "ex")
public void exceptionAnnouncement(JoinPoint jp, Exception ex) {
String methodName = jp.getSignature().getName();
System.out.println("Exception Announcement===> " + "要切入的方法名:" + methodName + "()" + " throwing Exception:" + ex);
}
// 环绕通知
// 切CalculatorImpl类的所有方法参数任意..,异常返回值ex和接收参数Exception ex同名,表示两个是一致的
@Around("myPointCut()")
public Object aroundAnnouncement(ProceedingJoinPoint pjp) {
Object res = null;
try {
// 前置通知
System.out.println("前置通知");
res = pjp.proceed();
// 返回通知
System.out.println("返回通知");
} catch (Throwable e) {
// 异常抛出通知
System.out.println("异常抛出通知");
e.printStackTrace();
} finally {
// 后置(最终)通知
System.out.println("后置(最终)通知");
}
return res;
}
}
OtherAspect .java
package com.dgut.spring.aop.aspectj;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
//创建一个切面
@Component
@Aspect
@Order(1) // 第一个切面,先使用
public class OtherAspect {
// 前置通知
// 切CalculatorImpl类的add方法参数任意..,返回值任意*
@Before("execution(* com.dgut.spring.aop.aspectj.CalculatorImpl.add(..))")
public void beforeAnnouncement(JoinPoint jp) {
String methodName = jp.getSignature().getName();
Object[] args = jp.getArgs();
System.out.println("<===Before Announcement===> " + "要切入的方法名:" + methodName + "() ,对应的参数:" + Arrays.asList(args));
}
}
4.5、基于XML的AspectJ(作为了解)
基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>
元素内。
4.5.1、XML文件中常用元素的配置方式如下: