首页 > 编程语言 >AOP面向切面编程

AOP面向切面编程

时间:2023-12-24 20:33:07浏览次数:45  
标签:通知 void 编程 com 切面 AOP execution 方法 public

AOP

简介

  • AOP是什么?

AOP(面向切面编程),也就是面向特定方法编程

AOP并不是一门技术,而是一种思想。

列如我们需要统计每个接口方法的执行耗时时,我们可以通过AOP技术来进行编写

将重复的逻辑编写在单独的AOP文件中,我们在切入点表达式写入特定方法的全类名来定位方法。

优点: 减少重复代码,代码无侵入,提高开发效率,维护方便

开发前提

引入AOP的依赖

pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

样式案例

AOP程序:TimeAspect

@Component
@Aspect //当前类为切面类
@Slf4j
public class TimeAspect {

    @Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        //记录方法执行开始时间
        long begin = System.currentTimeMillis();

        //执行原始方法
        Object result = pjp.proceed();

        //记录方法执行结束时间
        long end = System.currentTimeMillis();

        //计算方法执行耗时
        log.info("方法执行耗时: {}毫秒",end-begin);
        return result;
    }
}

我们通过AOP完成业务方法执行耗时的统计

其实AOP的功能不止如此,比如

  • 记录系统的操作日志
  • 权限管理
  • 事务管理

优势

  • 代码无侵入:没有修改原始的业务方法,就已经对原始的业务方法进行了功能的增强或者是功能的改变

  • 减少了重复代码

  • 提高开发效率

  • 维护方便

AOP核心概念

下面我们来了解一下AOP中的核心名词,也是AOP面向切面编程的开发方式

  1. 连接点:可以被AOP控制的方法,列如所有的业务方法都是可以被aop控制的方法
  2. 通知:指的是那些重复的逻辑,也就是共性的功能,在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的一部分重复的逻辑,也就是共性的功能
  3. 切入点:匹配连接点的条件,通知仅会在切入点方法执行时被应用
  4. 切面:描述的是通知和切入点的对应关系(通知+切入点);当通知和切入点结合在一起,就形成了一个切面,通过切面就能描述当前aop程序需要针对哪个原始方法。切面所在的类,我们一般称为切面类,(被@Aspect注解标识的类)
  5. 目标对象:通知所应用的对象

在Spring的AOP底层是基于动态代理技术来实现的,也就是说在程序运行的时候,会自动的基于动态代理技术为目标对象生成一个对应的代理对象,在代理对象当中就会对目标对象当中的原始方法进行功能的增强

SpringAOP旨在管理bean对象的过程中,主要通过底层 的动态代理机制,对特定的方法进行编程

AOP进阶

通知类型

Spring中AOP的通知类型:

  • @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 ...");
    }
}

我们发现每一个注解中都指定了切入点表达式,但是这些切入点表达式都一模一样,我们通常会通过 抽取的方式来使用@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 ...");
    }
}

通知顺序

在项目开发当中,我们定义了多个切面类 ,而当多个切面类中多个切入点都匹配到了同一个目标方法,此时当目标方法在运行的时候,这多个切面类中的这些通知方法都会运行。那么他们的运行顺序是怎么样的呢?

在不同的切面类中,默认按照切面类的类名字母排序

  • 目标方法前的通知方法:字母排名靠前的先执行
  • 目标方法后的通知方法:字母排名靠前的后执行

那么我们可以通过以下两种方式来控制通知的执行顺序

  1. 修改切面类的类名
  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 ...");
    }
}

切入点表达式

切入点表达式:

  • 描述切入点方法的一种表达式

  • 作用:主要用来决定项目中的哪些方法需要加入通知

  • 常见通知:

    1. @annotation:根据注解匹配
    2. execution:根据方法的签名来匹配

execution

execution主要根据方法的返回值,包名,类名,方法参数来进行匹配

语法

execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)

其中带?的表示可以省略的部分

  • 访问修饰符:可省略(比如: public、protected)

  • 包名.类名: 可省略

  • throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")

可以使用通配符描述切入点

  • * :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分

  • .. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数

切入点表达式示例

  • 省略方法的修饰符号

    execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
    
  • 使用*代替返回值类型

    execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
    
  • 使用*代替包名(一层包使用一个*

    execution(* com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))
    
  • 使用..省略包名

    execution(* com..DeptServiceImpl.delete(java.lang.Integer))    
    
  • 使用*代替类名

    execution(* com..*.delete(java.lang.Integer))   
    
  • 使用*代替方法名

    execution(* com..*.*(java.lang.Integer))   
    
  • 使用 * 代替参数

    execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))
    
  • 使用..省略参数

    execution(* com..*.*(..))
    

@annotation

也就是自定义注解 MyLog

实现步骤:

  1. 编写自定义注解
  2. 在业务类要做为连接点的方法上添加自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}

业务类:DeptServiceImpl

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Override
    @MyLog //自定义注解(表示:当前方法属于目标方法)
    public List<Dept> list() {
        List<Dept> deptList = deptMapper.list();
        //模拟异常
        //int num = 10/0;
        return deptList;
    }

    @Override
    @MyLog  //自定义注解(表示:当前方法属于目标方法)
    public void delete(Integer id) {
        //1. 删除部门
        deptMapper.delete(id);
    }


    @Override
    public void save(Dept dept) {
        dept.setCreateTime(LocalDateTime.now());
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.save(dept);
    }

    @Override
    public Dept getById(Integer id) {
        return deptMapper.getById(id);
    }

    @Override
    public void update(Dept dept) {
        dept.setUpdateTime(LocalDateTime.now());
        deptMapper.update(dept);
    }
}

切面类

@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 ...");
    }
}
  • execution切入点表达式
    • 根据我们所指定的方法的描述信息来匹配切入点方法,这种方式也是最为常用的一种方式
    • 如果我们要匹配的切入点方法的方法名不规则,或者有一些比较特殊的需求,通过execution切入点表达式描述比较繁琐
  • annotation 切入点表达式
    • 基于注解的方式来匹配切入点方法。这种方式虽然多一步操作,我们需要自定义一个注解,但是相对来比较灵活。我们需要匹配哪个方法,就在方法上加上对应的注解就可以了

连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名,方法名,方法参数等

  • 对应@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型
  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型

标签:通知,void,编程,com,切面,AOP,execution,方法,public
From: https://www.cnblogs.com/yifan0820/p/17924821.html

相关文章

  • 05. 函数式编程
    目录1、前言2、什么是函数式编程2.1、函数是一等公民2.2、避免状态和可变数据3、函数式编程的核心概念3.1、高阶函数3.2、Lambda(匿名函数)3.3、递归&尾递归优化3.4、functools模块3.4.1、partial3.4.2、reduce3.4.3、lru_cache3.4.4、wraps4、函数式编程的实际应用4.1、函数式编程......
  • Go编程基础教程:Go容器化技术
    作者:禅与计算机程序设计艺术1.背景介绍目前互联网服务开发已经从单体应用模式升级到微服务架构模式。在微服务架构模式下,服务之间会相互调用,为了更好地管理和调优这些分布式系统,需要对其进行容器化,使得各个服务可以独立部署、资源分配、隔离等方面更加灵活高效。本文将通过Go语言......
  • Python网络编程:掌握urllib包的妙用
    在Python的世界里,处理网络请求是日常任务之一。不论是爬取网页数据,还是调用网络API,一个好用的HTTP客户端库是必不可少的。Python标准库中的urllib包就是这样一个强大的工具,它提供了一个简单的界面来与网上资源互动。本文将带你深入了解urllib包,包括它的主要模块,以及如何使用它们完......
  • 设计规范,编程规范
     编程规范:名称GB/T39412-2020信息安全技术—代码安全审计规范 MISRAC2012 GB/T34944-2017Java语言源代码缺陷测试规范 GB/T34946-2017C#语言源代码缺陷测试规范 GB/T34943-2017C/C++语言源代码缺陷测试规范 SJ/T11682-2017C/C++语言源代码缺陷控制与测试规范......
  • 常见HTTP状态码与常见编程语言应用
    常见HTTP状态码与常见编程语言应用     我之前问过一些初中级工程师,他们并不是每一个人都清楚。但确实很重要。您应该知道的HTTP状态代码HTTP的响应代码分为五类:以下是一些常见的HTTP状态码及其含义:1xx(信息性状态码):这些状态码表示请求已被接收,继续处理。100Continue:服......
  • Spring编程式事务控制
    目录Spring编程式事务控制代码实现测试Spring编程式事务控制实际中很少使用代码实现pom.xml<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&......
  • golang快速入门:并发编程(一)
    进程、线程和协程进程:是操作系统中的一个执行实体,它拥有独立的内存空间和系统资源。每个进程都是独立运行的,它们之间相互隔离,通过进程间通信(IPC)来进行数据交换。每个进程都有自己的地址空间、堆栈和文件描述符等。进程之间的切换开销较大,因为需要保存和恢复整个进程的状态。线程:是......
  • Spring基于注解的AOP事务控制
    Spring基于注解的AOP事务控制源码代码测试pom.xml<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schem......
  • Spring基于XML AOP事务控制
    Spring基于XMLAOP事务控制源码代码测试pom.xml<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:sche......
  • Spring 基于注解的AOP面向切面编程
    Spring基于注解的AOP面向切面编程源码代码实现pom.xml<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:sc......