Spring-AOP
AOP为
Aspect Oriented Programming
的缩写,意为:面向切面编程
,通过预编译方式和运行期间动态代理
实现程序功能的统一维护的一种技术。作用:在不惊动原始代码设计的情况下对功能做增强(无侵入式)
AOP常用的术语
- 切面(Aspect):
那些重复的公共的功能称为切面(用来封装通知方法的类,日志,权限)
切面由切点和增强组成,他既包含横切的定义,也包括了连接点的定义,SpringAOP就是负责实施切面的框架,他将切面定义为横切逻辑织入到切面所指定的连接点- 连接点(join Point):
程序执行的某个特定位置(抽取横切关注点的位置)
,比如(类开始初始化前,初始化后,方法前,方法后,异常后) 一个类或者一段程序代码拥有一些具有边界的特定点,这些特定点称为连接点- 切入点(pointcut):每个程序都拥有多个连接点,比如一个类有2个方法,这2个方法就是连接点,连接点就是程序中具体的事物,AOP 通过切点来定位特定的连接点,连接点相当于数据库中的记录,而切点相当于是查询记录的条件。
在程序中,切入点是连接点位置的集合
- 代理(Proxy):Spring AOP 中有
JDK 动态代理和 CGLib 代理
,目标对象实现了接口时采用 JDK 动态代理,反之采用 CGLib 代理。- 目标对象(Target):
代理的目标对象(想要增强的对象)
,指一个或多个切面所通知的对象。- 织入(weaving):就是
把增强添加到目标类具体的连接点上的过程
- 通知(Adivice):
- 指切面对于某个连接点所产生的动作,也就是
目标方法执行前后要进行的方法
,包括前置通知、后置通知、返回后通知、异常通知和环绕通知。
AOP理解
抽取
AOP的目标对象已经确定
,因为使用动态代理的方式,代理对象自动创建
从目标对象中,把非核心业务代码进行抽取出来,此处“非核心业务代码”就是横切关注点
抽出来之后,要把“非核心业务代码”放在另外一个类中,该类
就叫做切面Aspect
在切面类中,如何封装横切关注点?每一个横切关注点都是一个方法,这个方法就叫做通知Adivice
,因此切面是用来封装通知
织入
从哪里抽取出来的,就要在哪个位置进行织入,这个织入的地方叫做连接点
如何定位连接点
?要用到切入点
来进行定位
Spring-AOP代码实现
基于注解的AOP
导入依赖
<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
目标接口
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
目标对象
@Component
public class CalculatorPureImpl implements Calculator {
@Override
public int add(int i, int j) {
int result = i + j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int sub(int i, int j) {
int result = i - j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int mul(int i, int j) {
int result = i * j;
System.out.println("方法内部 result = " + result);
return result;
}
@Override
public int div(int i, int j) {
int result = i / j;
System.out.println("方法内部 result = " + result);
return result;
}
}
创建切面类并配置
将切面类和目标对象都交给Spring容器进行管理,配置注解扫描
<context:component-scan base-package="com.zengqh.aop">
</context:component-scan>
<!-- 开启基于基于注解的AOP-->
<aop:aspectj-autoproxy />
切面类通过注解@Aspect标识,表示这是一个切面类
@Component
@Aspect
public class LoggerAspect {
}
前置通知
@Component
@Aspect
public class LoggerAspect {
/**
*
* @Before()前置通知, 在目标方法(切入点)执行之前执行。
* value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式
* 注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知。
*/
@Before("execution(public int com.zengqh.aop.CalculatorPureImpl.add(int,int))")
public void beforeAdviceMethod(){
System.out.println("LoggerAspect-->前置通知");
}
}
测试
@Test public void test001(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-config.xml"); Calculator bean = ioc.getBean(Calculator.class); int add = bean.add(1, 1); }
切入点表达式语法
②语法细节
用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。
例如:*.Hello匹配com.Hello,不匹配com.atguigu.Hello
在包名的部分,使用“*..”表示包名任意、包的层次深度任意
在类名的部分,类名部分整体用*号代替,表示类名任意
在类名的部分,可以使用*号代替类名的一部分
例如:*Service匹配所有名称以Service结尾的类或接口
在方法名部分,可以使用*号表示方法名任意
在方法名部分,可以使用*号代替方法名的一部分
例如:*Operation匹配所有方法名以Operation结尾的方法
在方法参数列表部分,使用(..)表示参数列表任意
在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头
在方法参数列表部分,基本数据类型和对应的包装类型是不一样的
切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符
例如:execution(public int ..Service.*(.., int)) 正确
例如:execution(* int ..Service.*(.., int)) 错误
由于@Before("execution(public int com.zengqh.aop.CalculatorPureImpl.add(int,int))")
这个切入点表达式只对指定的这个方法进行切入,如果想要对同一个包下,同一个类下的所有方法都做切入可以使用*号
进行省略
//CalculatorPureImpl类的所有方法都可以切入
@Before("execution(* com.zengqh.aop.CalculatorPureImpl.*(..))")
public void beforeAdviceMethod(){
System.out.println("LoggerAspect-->前置通知");
}
//aop包下的类里面的所有方法都可以切入
@Before("execution(* com.zengqh.aop.*.*(..))")
public void beforeAdviceMethod() {
System.out.println("LoggerAspect-->前置通知");
}
获取连接点的信息
@Before("execution(* com.zengqh.aop.*.*(..))")
public void beforeAdviceMethod(JoinPoint joinPoint) {
//获取连接点的方法名
Signature signature = joinPoint.getSignature();
//获取连接点的方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->前置通知的方法名为:" + "[" + signature.getName() + "]" + "参数列表为" + Arrays.asList(args));
}
测试:
@Test public void test001(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-config.xml"); Calculator bean = ioc.getBean(Calculator.class); int add = bean.add(1, 1); int div = bean.div(1, 1); }
切入点表达式的复用
@Pointcut用来声明一个公共的切入点表达式
@Pointcut("execution(* com.zengqh.aop.*.*(..))")
public void pointCut() {
}
@After("pointCut()")
public void afterAdviceMethod(JoinPoint joinPoint){
}
AOP通知
前置通知:使用@Before注解标识,在被代理的目标方法前执行
返回通知:使用@AfterReturning注解标识,在被代理的目标方法成功结束后执行(寿终正寝)
异常通知:使用@AfterThrowing注解标识,在被代理的目标方法异常结束后执行(死于非命)
后置通知:使用@After注解标识,在被代理的目标方法最终结束后执行(盖棺定论)
环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕整个被代理的目标方法,包
括上面四种通知对应的所有位置
切面优先级
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
优先级高的切面:外面
优先级低的切面:里面
使用@Order注解可以控制切面的优先级:
@Order(较小的数):优先级高
@Order(较大的数):优先级低
基于XML的AOP(看看就行)
<context:component-scan base-package="com.atguigu.aop.xml"></context:componentscan>
<aop:config>
<!--配置切面类-->
<aop:aspect ref="loggerAspect">
<aop:pointcut id="pointCut" expression="execution(*
com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
<aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
<aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
<aop:after-returning method="afterReturningMethod" returning="result"
pointcut-ref="pointCut"></aop:after-returning>
<aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcutref="pointCut"></aop:after-throwing>
<aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
</aop:aspect>
<aop:aspect ref="validateAspect" order="1">
<aop:before method="validateBeforeMethod" pointcut-ref="pointCut">
</aop:before>
</aop:aspect>
</aop:config>