优雅编码技巧之Spring AOP

371 阅读4分钟

一、AOP概念

AOP(Aspect Oriented Programming)是一种面向切面的编程思想,是基于面向对象编程(OOP)的一种补充。SpringAOP是Spring提供的标准易用的AOP框架,依托Spring的IOC容器、代理技术实现对代码的增强。

AOP概念定义

  • Aspect(切面):切面是通知和切入点的结合,在切面中会包含着一些Pointcut以及相应的Advice。
  • Joint Point(连接点):表示在程序中能够插入切面的明确定义的点。
  • Pointcut(切点):表示一组特殊的Joint Point,这些Joint Point或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的Advice将要发生的地方。
  • Advice(通知):Advice定义了在Pointcut里面定义的程序点具体要做的操作,有Before(前置通知)、After(后置通知)、AfterThrowing(异常通知)、AfterReturning(返回通知)和Around(环绕通知)几种类型。
  • Introduction(引入):允许我们向现有的类添加新方法属性。
  • Weaving(织入):将Aspect和其他对象连接起来, 并创建代理对象的过程。

二、应用场景

记录日志、权限校验、效率检查、实现事务、统一异常处理等等

三、编码实现

接下来以开发过程中经常出现的权限校验场景为例,用SpringAOP结合自定义注解来简单实现。

案例中的用到的是JDK环境为JDK1.8,Spring Boot环境为2.6.3。

  1. 自定义注解
@Target({ElementType.METHOD})       //说明注解的作用目标
@Retention(RetentionPolicy.RUNTIME) //定义被它所注解的注解保留多久
@Documented//如果一个注解@PermissionCheck,被@Documented标注,那么被@PermissionCheck修饰的类,生成文档时,会显示@PermissionCheck
public @interface PermissionCheck {

    /**
     * 管理员角色编码
     */
    String ADMIN_CODE = "ADMIN";

    /**
     * 访客角色编码
     */
    String GUEST_CODE = "GUEST";

    /**
     * 权限编码组  默认管理员
     *
     * @return String[]
     */
    String[] permissionCode() default {ADMIN_CODE};
}
  1. 编写AOP
@Aspect             //把当前类标识为一个切面供容器读取
@Order(value = -99) //切面执行顺序
@Component          //bean注入
public class PermissionCheckAop {

    private static final Logger LOGGER = LoggerFactory.getLogger(PermissionCheckAop.class);

    /**
     * 定义切点,声明在ExecuteLog注解的方法上拦截
     */
    @Pointcut(value = "@annotation(com.shuaijie.config.annotation.PermissionCheck)")
    public void pointcut() {
    }

    /**
     * 前置通知,目标方法调用前被调用
     *
     * @param joinPoint
     */
    @Before("pointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        LOGGER.info("验证票据通过");
        // 获取允许的权限编码组
        // 此处是从方法上获取注解,@Target({ElementType.METHOD}) 只作用在方法上
        // 作用于类上的注解可通过joinPoint.getTarget().getClass().getAnnotation(PermissionCheck.class).permissionCode()获取
        String[] permissionCode = ((MethodSignature) joinPoint
                .getSignature())
                .getMethod()
                .getAnnotation(PermissionCheck.class).permissionCode();
        LOGGER.info("匹配权限通过");
    }

    /**
     * 后置通知,目标方法执行完之后执行
     */
    @After("pointcut()")
    public void afterAdvice() {
    }

    /**
     * 后置异常通知
     * 定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法;
     * throwing 只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行,
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void afterThrowingAdvice(JoinPoint joinPoint, Exception e) {
    }

    /**
     * 后置返回通知
     * 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
     * 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
     * returning 只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行
     *
     * @param joinPoint
     * @param keys
     */
    @AfterReturning(value = "pointcut()", returning = "keys")
    public void afterReturningAdvice(JoinPoint joinPoint, String keys) {
    }


    /**
     * 环绕通知
     * 环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
     * 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     *
     * @param proceedingJoinPoint
     */
    @Around(value = "pointcut()")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        return proceedingJoinPoint.proceed();
    }
}
  1. 注解使用
@Service
public class ISelectServiceImpl implements ISelectService {

    @PermissionCheck
    @Override
    public Integer selectLoginNum() {
        return new Integer(1);
    }
}
  1. 执行结果
@Test
void selectLoginNum() {
    Integer integer = iSelectService.selectLoginNum();
    LOGGER.info("查询系统登录数为:{}", integer);
}

image.png

四、SpringAOP总结

  • SpringAOP是基于设计模式中的代理模式实现的。
  • SpringAOP使用方便、灵活、优雅、便于理解,可以减少重复代码,提高开发效率。
  • SpringAOP可以在不改变原有代码的情况下,加入新的逻辑代码。
  • SpringAop缺点是逻辑不连贯,代码阅读体验相对来说差一点。