广州建设厅网站首页,网站建设公司有哪几家,wordpress设置显示为英文,厦门企业制作网站方案一、概述
AOP#xff1a;Aspect Oriented Programming#xff08;面向切面编程、面向方面编程#xff09;#xff0c;其实就是面向特定方法编程
那什么又是面向方法编程呢#xff0c;为什么又需要面向方法编程呢#xff1f;来我们举个例子做一个说明#xff1a;
比如…一、概述
AOPAspect Oriented Programming面向切面编程、面向方面编程其实就是面向特定方法编程
那什么又是面向方法编程呢为什么又需要面向方法编程呢来我们举个例子做一个说明
比如我们这里有一个项目项目中开发了很多的业务功能。
场景
案例部分功能运行较慢我们需要定位执行耗时较长的业务方法此时需要统计每一个业务方法的执行耗时 此时我们就需要统计当前这个项目当中每一个业务方法的执行耗时。那么统计每一个业务方法的执行耗时该怎么实现
可能多数人首先想到的就是在每一个业务方法运行之前记录这个方法运行的开始时间。在这个方法运行完毕之后再来记录这个方法运行的结束时间。拿结束时间减去开始时间不就是这个方法的执行耗时吗
而AOP面向方法编程就可以做到在不改动这些原始方法的基础上针对特定的方法进行功能的增强。 AOP的作用在程序运行期间在不修改源代码的基础上对已有方法进行增强无侵入性: 解耦
那面向这样的指定的一个或多个方法进行编程我们就称之为 面向切面编程。
二、快速入门
导入依赖在pom.xml中导入AOP的依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId
/dependency
编写AOP程序针对于特定方法根据业务需要进行编程
Component
Aspect //当前类为切面类
Slf4j
public class TimeAspect {Around(execution(* com.itheima.service.*.*(..))) public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {//记录方法执行开始时间long begin System.currentTimeMillis();//执行原始方法Object result pjp.proceed();//记录方法执行结束时间long end System.currentTimeMillis();//计算方法执行耗时log.info(pjp.getSignature()执行耗时: {}毫秒,end-begin);return result;}
} 三、核心概念
连接点JoinPoint可以被AOP控制的方法暗含方法执行时的相关信息
连接点指的是可以被aop控制的方法。例如入门程序当中所有的业务方法都是可以被aop控制的方法。 通知Advice指哪些重复的逻辑也就是共性功能最终体现为一个方法
在AOP面向切面编程当中我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑也就是共性的功能。
切入点PointCut匹配连接点的条件通知仅会在切入点方法执行时被应用
在通知当中我们所定义的共性功能到底要应用在哪些方法上此时就涉及到了切入点pointcut概念。切入点指的是匹配连接点的条件。通知仅会在切入点方法运行时才会被应用。在aop的开发当中我们通常会通过一个切入点表达式来描述切入点(后面会有详解)。 切面Aspect描述通知与切入点的对应关系通知切入点
当通知和切入点结合在一起就形成了一个切面。通过切面就能够描述当前aop程序需要针对于哪个原始方法在什么时候执行什么样的操作。 目标对象Target通知所应用的对象 四、进阶
4.1. 通知类型 Around环绕通知此注解标注的通知方法在目标方法前、后都被执行 Before前置通知此注解标注的通知方法在目标方法前被执行 After 后置通知此注解标注的通知方法在目标方法后被执行无论是否有异常都会执行 AfterReturning 返回后通知此注解标注的通知方法在目标方法后被执行有异常不会执行 AfterThrowing 异常后通知此注解标注的通知方法发生异常后执行 下面我们通过代码演示来加深对于不同通知类型的理解
Slf4j
Component
Aspect
public class MyAspect1 {//前置通知Before(execution(* com.itheima.service.*.*(..)))public void before(JoinPoint joinPoint){log.info(before ...);}//环绕通知Around(execution(* com.itheima.service.*.*(..)))public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {log.info(around before ...);//调用目标对象的原始方法执行Object result proceedingJoinPoint.proceed();//原始方法如果执行时有异常环绕通知中的后置代码不会在执行了log.info(around after ...);return result;}//后置通知After(execution(* com.itheima.service.*.*(..)))public void after(JoinPoint joinPoint){log.info(after ...);}//返回后通知程序在正常执行的情况下会执行的后置通知AfterReturning(execution(* com.itheima.service.*.*(..)))public void afterReturning(JoinPoint joinPoint){log.info(afterReturning ...);}//异常通知程序在出现异常的情况下执行的后置通知AfterThrowing(execution(* com.itheima.service.*.*(..)))public void afterThrowing(JoinPoint joinPoint){log.info(afterThrowing ...);}
}
在使用通知时的注意事项 Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行其他通知不需要考虑目标方法执行 Around环绕通知方法的返回值必须指定为Object来接收原始方法的返回值否则原始方法执行完毕是获取不到返回值的。
另外 Spring提供了PointCut注解该注解的作用是将公共的切入点表达式抽取出来需要用到时引用该切入点表达式即可。
Slf4j
Component
Aspect
public class MyAspect1 {//切入点方法公共的切入点表达式Pointcut(execution(* com.itheima.service.*.*(..)))private void pt(){}//前置通知引用切入点Before(pt())public void before(JoinPoint joinPoint){log.info(before ...);}//环绕通知Around(pt())public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {log.info(around before ...);//调用目标对象的原始方法执行Object result proceedingJoinPoint.proceed();//原始方法在执行时发生异常//后续代码不在执行log.info(around after ...);return result;}//后置通知After(pt())public void after(JoinPoint joinPoint){log.info(after ...);}//返回后通知程序在正常执行的情况下会执行的后置通知AfterReturning(pt())public void afterReturning(JoinPoint joinPoint){log.info(afterReturning ...);}//异常通知程序在出现异常的情况下执行的后置通知AfterThrowing(pt())public void afterThrowing(JoinPoint joinPoint){log.info(afterThrowing ...);}
}
需要注意的是当切入点方法使用private修饰时仅能在当前切面类中引用该表达式 当外部其他切面类中也要引用当前类中的切入点表达式就需要把private改为public而在引用的时候具体的语法为全类名.方法名()
4.2. 通知顺序
当在项目开发当中我们定义了多个切面类而多个切面类中多个切入点都匹配到了同一个目标方法。此时当目标方法在运行的时候这多个切面类当中的这些通知方法都会运行。
此时我们就有一个疑问这多个通知方法到底哪个先运行哪个后运行 在不同切面类中默认按照切面类的类名字母排序 目标方法前的通知方法字母排名靠前的先执行 目标方法后的通知方法字母排名靠前的后执行
如果我们想控制通知的执行顺序有两种方式 修改切面类的类名这种方式非常繁琐、而且不便管理 使用Spring提供的Order注解
使用Order注解控制通知的执行顺序
Slf4j
Component
Aspect
Order(2) //切面类的执行顺序前置通知数字越小先执行; 后置通知数字越小越后执行
public class MyAspect2 {//前置通知Before(execution(* com.itheima.service.*.*(..)))public void before(){log.info(MyAspect2 - before ...);}//后置通知 After(execution(* com.itheima.service.*.*(..)))public void after(){log.info(MyAspect2 - after ...);}
}
Slf4j
Component
Aspect
Order(3) //切面类的执行顺序前置通知数字越小先执行; 后置通知数字越小越后执行
public class MyAspect3 {//前置通知Before(execution(* com.itheima.service.*.*(..)))public void before(){log.info(MyAspect3 - before ...);}//后置通知After(execution(* com.itheima.service.*.*(..)))public void after(){log.info(MyAspect3 - after ...);}
}
Slf4j
Component
Aspect
Order(1) //切面类的执行顺序前置通知数字越小先执行; 后置通知数字越小越后执行
public class MyAspect4 {//前置通知Before(execution(* com.itheima.service.*.*(..)))public void before(){log.info(MyAspect4 - before ...);}//后置通知After(execution(* com.itheima.service.*.*(..)))public void after(){log.info(MyAspect4 - after ...);}
}
4.3. 切入点表达式 4.3.1 execution
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配语法为 execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?) 其中带?的表示可以省略的部分 访问修饰符可省略比如: public、protected 包名.类名 可省略 throws 异常可省略注意是方法上声明抛出的异常不是实际抛出的异常
示例
Before(execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer)))
可以使用通配符描述切入点 * 单个独立的任意符号可以通配任意返回值、包名、类名、方法名、任意类型的一个参数也可以通配包、类、方法名的一部分 .. 多个连续的任意符号可以通配任意层级的包或任意类型、任意个数的参数
切入点表达式的语法规则
1. 方法的访问修饰符可以省略 2. 返回值可以使用*号代替任意返回值类型 3. 包名可以使用*号代替代表任意包一层包使用一个* 4. 使用..配置包名标识此包以及此包下的所有子包 5. 类名可以使用*号代替标识任意类 6. 方法名可以使用*号代替表示任意方法 7. 可以使用 * 配置参数一个任意类型的参数 8. 可以使用.. 配置参数任意个任意类型的参数
切入点表达式示例 注意事项
- 根据业务需要可以使用 且、或||、非! 来组合比较复杂的切入点表达式。 4.2.2 annotation
已经学习了execution切入点表达式的语法。那么如果我们要匹配多个无规则的方法比如list()和 delete()这两个方法。这个时候我们基于execution这种切入点表达式来描述就不是很方便了。而在之前我们是将两个切入点表达式组合在了一起完成的需求这个是比较繁琐的。
我们可以借助于另一种切入点表达式annotation来描述这一类的切入点从而来简化切入点表达式的书写。注解的全类名
实现步骤
1. 编写自定义注解
Target(ElementType.METHOD)
Retention(RetentionPolicy.RUNTIME)
public interface MyLog {
}
2. 在业务类要做为连接点的方法上添加自定义注解
Slf4j
Service
public class DeptServiceImpl implements DeptService {Autowiredprivate DeptMapper deptMapper;OverrideMyLog //自定义注解表示当前方法属于目标方法public ListDept list() {ListDept deptList deptMapper.list();//模拟异常//int num 10/0;return deptList;}OverrideMyLog //自定义注解表示当前方法属于目标方法public void delete(Integer id) {//1. 删除部门deptMapper.delete(id);}Overridepublic void save(Dept dept) {dept.setCreateTime(LocalDateTime.now());dept.setUpdateTime(LocalDateTime.now());deptMapper.save(dept);}Overridepublic Dept getById(Integer id) {return deptMapper.getById(id);}Overridepublic void update(Dept dept) {dept.setUpdateTime(LocalDateTime.now());deptMapper.update(dept);}
}
3.切面类
Slf4j
Component
Aspect
public class MyAspect6 {//针对list方法、delete方法进行前置通知和后置通知//前置通知Before(annotation(com.itheima.anno.MyLog))public void before(){log.info(MyAspect6 - before ...);}//后置通知After(annotation(com.itheima.anno.MyLog))public void after(){log.info(MyAspect6 - after ...);}
}
4.4. 连接点
讲解完了切入点表达式之后接下来我们再来讲解最后一个部分连接点。我们前面在讲解AOP核心概念的时候我们提到过什么是连接点连接点可以简单理解为可以被AOP控制的方法。
我们目标对象当中所有的方法是不是都是可以被AOP控制的方法。而在SpringAOP当中连接点又特指方法的执行。
在Spring中用JoinPoint抽象了连接点用它可以获得方法执行时的相关信息如目标类名、方法名、方法参数等。
- 对于Around通知获取连接点信息只能使用ProceedingJoinPoint类型
- 对于其他四种通知获取连接点信息只能使用JoinPoint它是ProceedingJoinPoint的父类型
示例代码
Slf4j
Component
Aspect
public class MyAspect7 {Pointcut(annotation(com.itheima.anno.MyLog))private void pt(){}//前置通知Before(pt())public void before(JoinPoint joinPoint){log.info(joinPoint.getSignature().getName() MyAspect7 - before ...);}//后置通知Before(pt())public void after(JoinPoint joinPoint){log.info(joinPoint.getSignature().getName() MyAspect7 - after ...);}//环绕通知Around(pt())public Object around(ProceedingJoinPoint pjp) throws Throwable {//获取目标类名String name pjp.getTarget().getClass().getName();log.info(目标类名{},name);//目标方法名String methodName pjp.getSignature().getName();log.info(目标方法名{},methodName);//获取方法执行时需要的参数Object[] args pjp.getArgs();log.info(目标方法参数{}, Arrays.toString(args));//执行原始方法Object returnValue pjp.proceed();return returnValue;}
}