Springboot-AOP面向切面编程,上部:详解AOP切面技术!

0、前言

本文分上下两部分,上部为AOP切面技术基础,学习Spring-boot-AOP切面技术。

下部为实践,利用AOP切面技术为主流RESTFul-API接口打造一个统一的访问日志。

1、AOP切面编程简介

有这样一个场景,某公司有一个主数据管理系统目前已经上线运行,但是系统运行不稳定,有时候运行很慢,为了检测到底是哪里出了问题,开发人员需要监控每一个方法的执行时间,再判断问题所在。当问题解决后,还需要把监控移除掉。因为系统已经上线运行,如果手动修改程序方法,那么工作量会非常大,而且这些监控方法以后还要移除掉,费时费力。如果能够在系统运行时动态添加代码,就能很好的解决这个需求。目标:不修改源代码,也能实现监控输出,这时AOP切面编程就是最佳选择。

在系统运行时动态添加代码的方式称为“面向切面编程AOP”。它有非常多的应用场景:

A:系统监控,执行情况分析,性能统计。

B:设置日志,记录登录、执行、参数等信息。

C:拦截,通过AOP快速实现精细化拦截,安全控制。

D:事务处理、统一异常处理。

简单来说,AOP就像给程序照CT,将执行过程进行切片管理。

2、AOP常见概念

  • Joinpoint:连接点,类里可以被增强的方法,例如你想修改哪个方法的功能,那么这就是一个连接点。
  • Pointcut:切入点,即对Joinpoint进行拦截的定义即为切入点,如拦截所有select开始的方法,这个定义就是一个切入点。
  • Advice:通知,拦截到Joinpoint之后所要做的事情就是通知,例如打印日志、发送邮件、写入监控等。
  • Aspect:切面,Pointcut和Advice的结合。
  • Target:要增强的类称为Target。

3、Spring-boot AOP切面技术基础

3.1添加Maven依赖

        
        
            org.springframework.boot
            spring-boot-starter-aop
        

项目结构如下图所示:

Springboot-AOP面向切面编程,上部:详解AOP切面技术!_第1张图片

3.2创建一个UserService服务类,一个UserController接口实现类用于调用UserService服务类。

我们在com.example.demohelloworld.AOP.Service包下创建UserService服务类,代码如下:

package com.example.demohelloworld.AOP.Service;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    //定义一个简单的查询方法
    public String getUserById(Integer id){
        System.out.println("get>>>"+id);
        return "getuser";
    }
    //定义一个简单的删除方法
    public void deleteUserById(Integer id){
        System.out.println("delete>>>");
    }
}

我们在com.example.demohelloworld.AOP包下创建UserController接口类,代码如下:

package com.example.demohelloworld.AOP;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
//用于调用UserService和前台执行测试
@RestController
public class UserController {
    @Autowired
    UserService userservice;
    @GetMapping("/getuser/{id}")
    public String getUserById(@PathVariable Integer id){
        return userservice.getUserById(id);
    }
    @GetMapping("/deleteuser")
    public void deleteUserById(Integer id){
        userservice.deleteUserById(id);
    }
}

3.3接下来创建AOP切面类AspectLog

我们在com.example.demohelloworld.AOP包下创建AspectLog切面类,代码如下:

package com.example.demohelloworld.AOP;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;

@Component
//@Aspect注解表明这是一个切面类
@Aspect
public class AspectLog {
    //定义切入点,execution 中的第一个*表示方法返回任意值,第二个*表示AOP包下的任意类,第三个*表示类中的任意方法,括号中的两个点表示方法参数任意值
    @Pointcut("execution(* com.example.demohelloworld.AOP.Service.*.*(..))")
    public void pointcutvoid(){
    }
    //@Before注解表示这是一个前置通知,该方法在目标方法执行之前执行。通过JoinPoint参数可以获取目标方法的方法名、修饰符等信息
    @Before(value = "pointcutvoid()")
    public  void before(JoinPoint joinPoint){
        //获取传入参数
        Object[] args =joinPoint.getArgs();
        System.out.println(joinPoint.getSignature().getName()+"方法开始执行...传入参数为:"+Arrays.deepToString(args));
    }
    //@After注解表示这是一个后直通知,该方法在目标方法执行之后执行
    @After(value = "pointcutvoid()")
    public void after(JoinPoint joinPoint){
        System.out.println(joinPoint.getSignature().getName()+"方法执行结束...");
    }
    //@AfterReturning注解表示这是一个返回通知,在该方法中可以获取目标方法的返回值,returning参数是指返回值的变量名,对应方法的参数。在方法参数中定义了result的类型为Object,表示目标方法的返回值可以是任意类型,若result参数的类型为Long,则该方法只能处理目标方法返回值为Long的情况
    @AfterReturning(value = "pointcutvoid()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        System.out.println(joinPoint.getSignature().getName()+"方法返回值为:"+result);
    }
    //@AfterThrowing注解表示这是一个异常通知,即当目标方法发生异常时,该方法会被调用,异常类型为Exception表示所有异常都会进入该方法中执行,若异常类型为ArithmeticException,则表示只有目标方法抛出的ArithmeticException异常才会进入该方法中处理
    @AfterThrowing(value = "pointcutvoid()",throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint,Exception ex){
        System.out.println(joinPoint.getSignature().getDeclaringTypeName()+joinPoint.getSignature().getName()+"方法异常了,异常是:"+ex.getMessage());
    }
    //@Around注解表示这是一个环绕通知,绕通知是所有通知里功能最为强大的通知,可以实现前置通知、后置通知、异常通知以及返回通知的功能。目标方法进入环绕通知后,通过调用ProceedingJoinPoint对象的proceed方法使目标方法继续执行,开发者可以在此修改目标方法的执行参数、返回值等,并且可以在此处理目标方法的异常
    @Around("pointcutvoid()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
        return proceedingJoinPoint.proceed();
    }
}

3.3.1  @Aspect注解表明这是一个切面类。

3.3.2  pointcutvoid()方法使用了@Pointcut注解,表明这是一个切入点。

本文中切入点为@Pointcut("execution(* com.example.demohelloworld.AOP.Service.*.*(..))")

execution 中的第一个*表示方法返回任意值,第二个*表示Service包下的任意类,第三个*表示类中的任意方法,括号中的两个点表示方法参数任意值。

我们可以根据需要,从包、类、方法三个层次去定义所要进行切面的内容。

3.3.3  我们还需要先了解一下切面注解的含义:

切面注解 注解含义 详细含义
@Before 前置 表示这是一个前置通知,该方法在目标方法执行之前执行。通过JoinPoint参数可以获取目标方法的方法名、修饰符等信息
@AfterThrowing 异常抛出 表示这是一个异常通知,即当目标方法发生异常时,该方法会被调用,异常类型为Exception表示所有异常都会进入该方法中执行,若异常类型为ArithmeticException,则表示只有目标方法抛出的ArithmeticException异常才会进入该方法中处理
@After 后置 表示这是一个后直通知,该方法在目标方法执行之后执行
@AfterReturning 后置增强,执行顺序在@After之后 表示这是一个返回通知,在该方法中可以获取目标方法的返回值,returning参数是指返回值的变量名,对应方法的参数。在方法参数中定义了result的类型为Object,表示目标方法的返回值可以是任意类型,若result参数的类型为Long,则该方法只能处理目标方法返回值为Long的情况
@Around 环绕 表示这是一个环绕通知,绕通知是所有通知里功能最为强大的通知,可以实现前置通知、后置通知、异常通知以及返回通知的功能。目标方法进入环绕通知后,通过调用ProceedingJoinPoint对象的proceed方法使目标方法继续执行,开发者可以在此修改目标方法的执行参数、返回值等,并且可以在此处理目标方法异常

3.3.4  连接点JoinPoint对象封装了AOP切面方法的执行信息(这个就是我们需要提炼的信息,可以作为监控、日志等),所以我们还需了解一下连接点JoinPoint中getSignature()常用方法的含义

方法 方法含义
joinPoint.getSignature().getDeclaringTypeName()

获取被切面类的路径及名称,例如在本文中为:com.example.demohelloworld.AOP.UserService

joinPoint.getSignature().getName() 获取被切面类中方法的名称,例如在本文中为:getUserById或者deleteUserById
joinPoint.getArgs() 获取传入目标方法的参数对象
joinPoint.getTarget() 获取被代理的对象

 3.4启动项目开始测试切面效果

打开浏览器输入http://localhost:8080/getuser/1,对getUserById进行AOP切面,后台效果如下:

Springboot-AOP面向切面编程,上部:详解AOP切面技术!_第2张图片

输入http://localhost:8080/deleteuser,对deleteUserById进行AOP切面,后台效果如下:

Springboot-AOP面向切面编程,上部:详解AOP切面技术!_第3张图片

4、 AOP总结

AOP面向切面编程,指扩展功能且不修改源代码,将功能代码从业务逻辑代码中分离出来。进而实现日志记录,性能统计,安全控制,事务处理,异常处理等功能。

切面技术的应用大大提升了程序的可扩展性、安全性、操作便捷性,且对开发者非常友好,使用较少的代码就能完成监控、日志、拦截等功能,大大提升开发效率。

下一讲,我将通过一个实际案例:利用AOP切面技术为RESTFul-API接口打造一个统一的登录日志而不修改任何原接口代码,让大家进一步了解AOP切面技术的优势。

你可能感兴趣的:(Spring-boot,aop,spring,boot,java)