网站建设外包公司,换空间网站备案,网站开发后服务费,wordpress 后台美化AOP 1、Spring AOP概述2、SpringAOP快速入门3、SpringAOP核心概念4、通知类型5、通知顺序6、切入点表达式6.1 execution方式6.2 annotation方式 7、连接点 1、Spring AOP概述
AOP#xff1a;Aspect Oriented Programming#xff0c;面向特定方法编程。 AOP是通过动态代理技术… AOP 1、Spring AOP概述2、SpringAOP快速入门3、SpringAOP核心概念4、通知类型5、通知顺序6、切入点表达式6.1 execution方式6.2 annotation方式 7、连接点 1、Spring AOP概述
AOPAspect Oriented Programming面向特定方法编程。 AOP是通过动态代理技术实现的。SpringAOP是Spring框架的高级技术旨在管理Bean对象的过程中主要通过底层的动态代理机制对特定的方法进行编程。
假设场景现在需要优化一个刚开发好的系统首先要记录每一个业务方法执行的时长也就是在业务开始时记录开始时间业务执行完毕时记录结束时间时间差就是该业务的执行时间。只要在每一个业务方法中增加这个操作就能记录所有业务方法的执行时间。但是由于业务可能非常多这样做相当繁琐工作量也大。这是可以通过AOP面向切面编程来优化该操作。
AOP会通过动态代理技术对原有方法进行改造。AOP首先会定义一个模板方法在模板方法内定义在业务方法执行前记录开始时间然后执行业务方法业务方法执行后记录结束时间获得时间差。这样就不需要对每个业务方法进行改造而是通过AOP来改造了。
2、SpringAOP快速入门
以记录业务方法执行时间为例。 SpringAOP的使用分一下几步
引入SpringAOP依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId
/dependency编写AOP程序。首先定义一个类在类上加Component注解与Aspect注解。Component注解表明将这个类交给IOC容器管理Aspect声明当前类为AOP类。然后定义一个方法模板在方法模板中记录开始时间调用原始方法再记录结束时间。注意给方法模板传入ProceedingJoinPoint类对象通过该对象的proceed()方法能够调用原始方法。给方法模板加切入点。指定哪些包里的那些接口和类中的方法需要通过AOP改造。
最后的简单实现如下
Slf4j
Component // 交给IOC容器管理
Aspect // 声明当前类为AOP类
public class TimeAspect {Around(execution(* com.wrj.controller.*.*(..))) // 切入点表达式。指定com.wrj.controller下的所有接口和类中的所有方法都被改造public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {// 1. 记录开始时间long begin System.currentTimeMillis();// 2. 调用原始方法运行Object result joinPoint.proceed();// 3. 记录结束时间计算方法耗时long end System.currentTimeMillis();long times end - begin;log.info(joinPoint.getSignature() 方法执行耗时{}, times ms);return result;}
}3、SpringAOP核心概念
SpringAOP中有五大核心概念
连接点JoinPoint可以被AOP控制的方法暗含方法执行时的相关信息通知Advice指哪些重复的逻辑也就是共性功能最终体现为一个方法切入点PointCut匹配连接点的条件通知仅会在切入点方法执行时被应用切面Aspect描述通知与切入点的对应关系通知切入点目标对象Target通知所应用的对象。
通过上面SpringAOP快速入门案例进行简单解释。 通知在上面的案例中需要记录方法的执行时间因此需要先记录开始时间再记录结束时间最后计算执行时长这三步共性代码就是通知。
切入点案例中方法模板上加了一个注解注解中写了一个切入点表达式。这个表达式是指定在com.wrj.controller包下的所有接口和类中的方法都被AOP改造。匹配的条件就是切入点。
Around(execution(* com.wrj.controller.*.*(..)))切面切面是切入点和通知的组合。描述的是切入点与通知的关系。即切入点匹配到的方法需要执行通知中的公共代码。
连接点在上面的案例中com.wrj.controller下的所有方法都是连接点因为都可以被AOP改造。
目标对象目标对象指通知应用的对象此处指的是com.wrj.controller包下的类对象或接口实现类对象。
4、通知类型
通知类型有5种
Around环绕通知此注解标注的通知方法在目标方法前、后都被执行Before前置通知此注解标注的通知方法在目标方法前被执行After后置通知此注解标注的通知方法在目标方法后被执行无论是否有异常都会执行AfterReturning返回后通知此注解标注的通知方法在目标方法后被执行有异常不会执行AfterThrowing异常后通知此注解标注的通知方法发生异常后执行。
在使用环绕通知Around时需注意注意
Around环绕通知需要自己调用 ProceedingJoinPoint.proceed()来让原始方法执行其他通知不需要考虑目标方法执行Around环绕通知方法的返回值必须指定为Object来接收原始方法的返回值。
5、通知顺序
如果有多个切面的切入点都匹配到了同一个目标方法目标方法运行时多个通知方法都会被执行。这时需要考虑通知顺序。
不同的切面类中默认按照切面类的类名字排序 目标方法前的通知方法字母排名靠前的先执行目标方法后的通知方法字母排名靠前的后执行 用Order(数字)加在切面类上来控制顺序 目标方法前的通知方法数字小的先执行目标方法后的通知方法数字小的后执行
6、切入点表达式
切入点表达式有两种形式execution方式与annotation方式
6.1 execution方式
execution方式主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配。 语法为
execution(访问修饰符 返回值 包名.类名.方法名(方法参数全类名) throws 异常)其中有几处可省略 · 访问权限修饰符可省略比如public、protected · 包名.类名.:可省略注意类名后面也有点不建议省略 · throws异常可省略注意是方法上声明抛出的异常不是实际抛出的异常
在切入点表达式中可以使用通配符描述切入点
* 单个独立的任意符号可以通配任意返回值、包名、类名、方法名、任意类型的一个参数也可以通配包、类、方法名的一部分。
.. 多个连续的任意符号可以通配任意层级的包或任意类型、任意个数的参数。在通知类型注解中通过切入点表达式设置切入点。
Component
Aspect
Slf4j
public class DemoAspect {Before(execution(* com.wrj.controller.*.*(..)))public void testBefore() {log.info(Before...前置通知);}Around(execution(* com.wrj.controller.*.*(..)))public Object testAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info(Around...环绕通知原始方法执行前逻辑);Object result joinPoint.proceed();log.info(Around...环绕通知原始方法执行后逻辑);return result;}After(execution(* com.wrj.controller.*.*(..)))public void testAfter() {log.info(Before...后置通知);}
}提取公共切入点。同样通过切入点表达式设置切入点但是提取公共切入点。可以通过定义一个空方法在空方法上加入注解PointCut指定要抽取的公共的切入点。
Component
Aspect
Slf4j
public class DemoAspect {// 1. 定义一个空方法加上PointCut注解抽取公共切入点Pointcut(execution(* com.wrj.controller.*.*(..)))public void pt() {}Before(pt()) // 2. 在通知类型注解中加入抽取的切入点public void testBefore() {log.info(Before...前置通知);}Around(pt())public Object testAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info(Around...环绕通知原始方法执行前逻辑);Object result joinPoint.proceed();log.info(Around...环绕通知原始方法执行后逻辑);return result;}After(pt())public void testAfter() {log.info(Before...后置通知);}
}引用其他切面类的切入点。前提是有一个切面类中已经抽取除了公共切入点。并且其他的切面类有足够的访问权限访问该设置了公共切入点的方法。直接在通知类型注解中通过 包名.类名.方法名() 的方式引用。
Slf4j
Component // 交给IOC容器管理
Aspect // 声明当前类为AOP类
public class TimeAspect {Around(com.wrj.aspect.DemoAspect.pt()) // 引用com.wrj.aspect包下DemoAspect类中的pt()方法上设置的切入点public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {// 1. 记录开始时间long begin System.currentTimeMillis();// 2. 调用原始方法运行Object result joinPoint.proceed();// 3. 记录结束时间计算方法耗时long end System.currentTimeMillis();long times end - begin;log.info(joinPoint.getSignature() 方法执行耗时{}, times ms);return result;}
}通过 ||、! 、 逻辑运算符连接多个切入点表达式。
Component
Aspect
Slf4j
public class DemoAspect {// 使用 || 运算连接两个切入点表达式Pointcut(execution(String com.wrj.controller.DemoController.demoFilter()) || execution(String com.wrj.controller.DemoController.demo()))public void pt() {}Before(pt())public void testBefore() {log.info(Before...前置通知);}
}6.2 annotation方式
自定义注解。在使用annotation方式前需要自定义一个注解。自定义注解为空注解即可不需要定义属性。
Retention(RetentionPolicy.RUNTIME) // Retention描述注解什么时候生效。RetentionPolicy.RUNTIME表示运行时生效
Target(ElementType.METHOD) // Target描述作用在哪些地方。ElementType.METHOD表示作用在方法上
public interface MyAnnotation {
}通过annotation方式编写切入点表达式。在annotation中写入自定义注解的全类名。含义是匹配加了自定义注解的方法。
Component
Aspect
Slf4j
public class DemoAspect {// 在annotation中写入自定义注解的全类名Pointcut(annotation(com.wrj.annotation.MyAnnotation))public void pt() {}Before(pt())public void testBefore() {log.info(Before...前置通知);}
}在需要被AOP改动的方法上加上自定义注解。
RestController
RequestMapping(/demo)
Slf4j
public class DemoController {MyAnnotation // 加自定义注解GetMappingpublic String demoFilter() {System.out.println(demoFilter接口执行...);return 执行结束;}MyAnnotation // 加自定义注解GetMappingpublic String demo() {System.out.println(demoFilter接口执行...);return 执行结束;}
}execution方式和annotation方式可以混用。
7、连接点
在Spring中用JoinPoint抽象了连接点用它可以获得方法执行时的相关信息如目标类名、方法名、方法参数等。
对于Around 通知获取连接点信息只能使用 ProceedingJoinPoint。
对于其他四种通知获取连接点信息只能使用JoinPoint它是ProceedingJoinPoint的父类型。
如要使用原始方法需要自己在模板方法形参中指定JoinPoint或ProceedingJoinPoint对象。
一些常用的方法
// 1. 获取 目标对象类名
String className joinPoint.getTarget().getClass().getName();
// 2. 获取 目标方法的方法名
String methodName joinPoint.getSignature().getName();
// 3. 获取 目标方法运行时传入的参数
Object[] args joinPoint.getArgs();
// 4. 放行 目标方法执行并且返回目标方法运行的返回值
Object result joinPoint.proceed();注意若模板方法需要返回原始方法返回的值则模板方法的返回值类型需定义为Object。