Spring之AOP面向切面编程

AOP(Aspect-Oriented Programming)面向切面编程,以前在做Android开发的时候,就有了解到,客户端请求网络判定网络状态,动态权限检查,从切面统一处理,就会解决很多麻烦事儿,代码高度解耦。原来其实都是从spring框架来的,看看Spring 中的AOP是什么样的。

前言

  1. 面向切面编程是一种编程范式,不是编程语言;
  2. 是一种解决特殊问题的方式,不能解决所有的问题
  3. 是面向对象编程OOP(封装、继承、堕胎)的一种补充

AOP好处

  1. DRY(Dont Repeat Yourself): 不要重复代码,高度解耦
  2. SOC(Separation of Contents): 分离内容
    • 水平分离:展示层-》服务层-》持久层
    • 垂直分离:模块的划分
    • 切面分离:切面编程是将功能性需求和非功能性需求分离出来
  3. 集中处理某一特定的逻辑
  4. 可侵入性少,可以增加代码的可读性和可维护性

使用场景

例如:权限控制,缓存控制,事务控制,审计日志,性能监控,异常处理,分布式追踪等基本上是非功能性的需求,下面的栗子就用一个权限校验来对比切面校验和普通校验。

初识Aspect(普通校验和切面校验)

  • 当然首先是maven导入aspectjr的包。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.9</version>
    </dependency>
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjtools</artifactId>
    <version>1.8.9</version>
    </dependency>
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.7.4</version>
    </dependency>
  • 普通权限校验的实现方式,在每次需要校验的前面加上固定的方法checkAccess()来校验用户是否合法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Test
    public void adminInsert() {
    CurrentUserHolder.set("admin");
    productService.insertWithOnlyCheck(1l);
    System.out.println("--------------Done---------------");
    }

    public void insertWithOnlyCheck(long id){
    authService.checkAccess();
    System.out.println("ProductService insert --- " + id);
    }

    public void checkAccess() {
    String user = CurrentUserHolder.get();
    if (!user.equals("admin")) {
    throw new RuntimeException("user is null or wrong");
    }
    }

    最终输出

    1
    2
    ProductService insert --- 1
    -------------Done------------
  • 切面校验

    1
    2
    3
    4
    5
    6
    @Test
    public void admiAnnotInsert() {
    CurrentUserHolder.set("Rocka");
    productService.insert(1l);
    System.out.println("------------Done--------------");
    }

    带有自定义的AdminOnly注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @AdminOnly
    public void delete(long id){
    System.out.println("ProductService only delete --- " + id);
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface AdminOnly {
    }

    @Aspect 注解自定义切面校验用户

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Aspect
    @Component
    public class SecurityAspect {

    @Autowired
    AuthService authService;

    //参数表达式,想切哪些东西,拦截标注有AdminOnly的注解
    @Pointcut("@annotation(AdminOnly)")
    public void adminOnly(){
    System.out.println("check start");
    }

    //在执行之前,执行一段共有逻辑
    @Before("adminOnly()")
    public void check(){
    System.out.println("adminOnly annotation check before");
    authService.checkAccess();
    }

    }

切面表达式

within表达式

  • 匹配包(拦截匹配类里的所有方法)

    @Pointcut(“within(com.rocka.service.ProductService)”)

  • 匹配类型(拦截包及子包下所有类的方法)

    @Pointcut(“within(com.rocka..*)”)

参数匹配

  • 匹配任何以find开头且只有一个Long参数的方法

    @Pointcut(“execution( ..find*(Long))”)

  • 匹配只有一个Long参数的方法

    @Pointcut(“args(Long)”)

  • 匹配任何以find开头而且第一个参数为Long的方法

    @Pointcut(“execution( ..find*(Long,..))”)

  • 匹配第一个参数为Long型的方法

    @Pointcut(“args(Long,..)”)

注解匹配

主要有匹配注解方法级别,类级别,参数级别的方法

  • 匹配方法标注有AdminOnly注解的方法

    @Pointcut(“@annotation(com.rocka.security.AdminOnly)”)

  • 匹配标注有bean类底下的方法,要求的annotation的RetentionPolicy的级别为CLASS

    @Pointcut(“@within(com.google.common.annotations.Beta)”)

  • 匹配标注有bean类底下的方法,要求的annotation的RetentionPolicy的级别为CLASS

    @Pointcut(“@target(com.springframework.stereotype.Repository)”)

  • 匹配传入的参数类标注有Repository注解的方法

    @Pointcut(“@target(com.springframework.stereotype.Repository)”)

execution表达式

  • @Pointcut(“execution(public com.rocka.service.Service.*(..))”)

    execution里面的表达式当做一个方法来看。

advice注解

  • @Before,前置通知
  • @After,后置通知,方法执行之后
  • @AfterReturning,返回通知,成功执行之后
  • @AfterThrowing,异常通知,抛出异常之后
  • @Around,环绕通知,可以替代上面所有的通知

相关代码地址

GitHub点我

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器