Spring AOP探秘

什么是AOP

1. AOP式一种编程范式,不是编程语言,如:面向对象编程 函数式编程 事件驱动编程 面向切面编程

2.解决特定问题,不能解决所有问题

3. 是oop的补充,不是替代

面向切面编程的目的:

1. 集中处理某一关注点/横切逻辑

2.可以很方便地添加/删除关注点

3.侵入性少,增强代码可读性及可维护性

AOP的应用场景:

1.权限控制

2.缓存控制

3.事务控制

4.审计控制

5.性能监控

6.分布式追踪

7.异常处理

实例:检查权限

如果要限定一些接口 只能超级管理员可以访问,那么,一般的做法就是在每个方法的开始检查权限,这样非常不灵活,更期待一个注解来完成,所以@Aspect注解就能快速定义这种校验注解

@Aspect三部曲
1. 建立普通注解


package com.asange.aop.security;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * com.asange.aop.security
 * icourt
 * 2018/4/6
 * author:asange
 * email:[email protected]
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminOnly {
}

2. @Aspect建立关联

package com.asange.aop.security;

import com.asange.aop.service.AuthService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * com.asange.aop.security
 * icourt
 * 2018/4/6
 * author:asange
 * email:[email protected]
 **/
@Aspect
@Component
public class SecurityAspect {

    @Pointcut("@annotation(AdminOnly)")
    public void adminOnly() {
    }
}


@Pointcut参数 模版:@anootation(xxx) xxx就是我们声明注解的名字

3. 设置切面关注点(在方法之前@Before,在方法之后 @After...)

package com.asange.aop.security;

import com.asange.aop.service.AuthService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * com.asange.aop.security
 * icourt
 * 2018/4/6
 * author:asange
 * email:[email protected]
 **/
@Aspect
@Component
public class SecurityAspect {
    @Autowired
    AuthService authService;

    @Pointcut("@annotation(AdminOnly)")
    public void adminOnly() {
    }

    @Before("adminOnly()")
    public void check() {
        authService.checkPermission();
    }

}

注意 @Before("xxx()") 是PointCut声明的方法名

在这里 我们模拟插入商品和删除商品 我们都关注这两个动作是否符合超级管理员权限

package com.asange.aop.service;

import com.asange.aop.security.AdminOnly;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * com.asange.aop.service
 * icourt
 * 2018/4/6
 * author:asange
 * email:[email protected]
 **/
@Service
public class ProductService2 {

    @Autowired
    AuthService authService;

    @AdminOnly
    public void insert() {
        System.out.println("insert success");
    }

    @AdminOnly
    public void delete(String id) {
        System.out.println("delete success id:" + id);
    }
}


用admin管理员账号测试:


用test用户测试:



本演示的例子代码点击

spring  AOP使用方式


Aop常见注解3个:


其中 @Aspect非常固定,重点介绍一下@Pointcut 切面表达式

分类 含义
designators指示器 execution ...
通配符 *  .. +等符号匹配
运算操作符 &&  ||  !等操作符

通配符:

1. * 匹配任意数量的字符

2. + 匹配指定类以及其子类

3 .. 两个点;一般用于匹配任意数的子包或参数

运算操作符:

1. && 与操作符号

2. || 或操作符号

3. ! 非操作符号


designators 指示器

1. 匹配方法  execution()

2. 匹配注解 @target() @args() @within() @annotation()

3. 匹配包/类型 within()

4. 匹配对象  this() bean() target()

5. 匹配参数 args()


匹配包/类型

    /**
     * 匹配ProductService2类里面的所有方法
     */
    @Pointcut("within(com.asange.aop.service.ProductService2)")
    public void adminService() {
    }

    /**
     * 匹配 com.asange.aop.service包及子包下所有类的方法
     */
    @Pointcut("within(com.asange.aop.service..*)")
    public void adminService2() {
    }

实例: 用within 校验ProductService所有方法检查用户权限!

package com.asange.aop.security;

import com.asange.aop.service.AuthService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * com.asange.aop.security
 * icourt
 * 2018/4/6
 * author:asange
 * email:[email protected]
 **/
@Aspect
@Component
public class SecurityAspect {
    @Autowired
    AuthService authService;
    /**
     * 匹配ProductService类里面的所有方法
     */
    @Pointcut("within(com.asange.aop.service.ProductService)")
    public void adminService() {
    }

 
    @Before("adminService()")
    public void checkClass() {
        authService.checkPermission();
    }

}
package com.asange.aop.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * com.asange.aop.service
 * icourt
 * 2018/4/6
 * author:asange
 * email:[email protected]
 **/
@Service
public class ProductService {

    @Autowired
    AuthService authService;

    public void insert() {
        System.out.println("insert success");
    }

    public void delete(String id) {
        System.out.println("delete success id:" + id);
    }
}

测试:

用admin账号测试


用非admin账号测试


在这里ProductService的方法上并为添加注解
可以发现@PointCut("within("类的全路径"))可以校验这个指定的类的所有方法 检查我们指定的逻辑

匹配对象

 /**
     * 匹配AOP对象的目标对象为指定类型的方法,即UserDao的aop代理对象的方法
     */
    @Pointcut("this(com.asange.aop.dao.UserDao)")
    public void thisDao() {

    }

    /**
     * 匹配IUserDao接口的目标对象(而不是aop代理后的对象)的方法,这里即UserDao
     */
    @Pointcut("target(com.asange.aop.dao.IUserDao)")
    public void targetDao()
    {
    }

    /**
     * 匹配所有以Service结尾的bean里面的方法
     */
    @Pointcut("bean(*Service)")
    public void beanDemo()
    {
    }


实例:关注log打印开始

package com.asange.aop;

/**
 * com.asange.aop.service
 * icourt
 * 2018/4/6
 * author:asange
 * email:[email protected]
 **/
public interface Loggable {

    void d(String s);
}
package com.asange.aop.service;

import com.asange.aop.Loggable;
import org.springframework.stereotype.Component;

/**
 * com.asange.aop.service
 * icourt
 * 2018/4/6
 * author:asange
 * email:[email protected]
 **/
@Component
public class LoggerService implements Loggable {
    @Override
    public void d(String s) {
        System.out.println("==========>" + s);
    }
}

package com.asange.aop.service;

import com.asange.aop.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * com.asange.aop.service
 * icourt
 * 2018/4/6
 * author:asange
 * email:[email protected]
 **/
@Service
public class ProductService3 {

    @Autowired
    AuthService authService;
    @Autowired
    LoggerService loggerService;

    public void insert() {
        loggerService.d("insert success");
    }
}

aspct配置:

package com.asange.aop.security;

/**
 * com.asange.aop.security
 * icourt
 * 2018/4/6
 * author:asange
 * email:[email protected]
 **/
@Aspect
@Component
public class SecurityAspect {
   
    @Pointcut("this(com.asange.aop.Loggable)")
    public void log() {
    }

    @Before("log()")
    public void logBefore() {
        Log.log("------------>logBefore");
    }
}

测试结果:


可以发现在注入的loggerservice 在打印插入成功的时候,先打印的logbefore

匹配参数

   /**
     * 匹配任何以find开头并且只有一个Long参数的方法
     */
    @Pointcut("execution(* *..find*(Long))")
    public void argsDemo1() {

    }

    /**
     * 匹配任何只有一个Long参数的方法
     */
    @Pointcut("args(Long)")
    public void argsDemo2() {

    }

    /**
     * 匹配任何以find开头的并且第一个参数为Long型的方法
     */
    @Pointcut("execution(* *..find*(Long,..))")
    public void argsDemo3() {
    }

    /**
     * 匹配第一个参数为Long型的方法
     */
    @Pointcut("args(Long,..)")
    public void argsDemo4() {

    }

实例:关注某个类中带Long型的参数的方法

package com.asange.aop.match_param;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * com.asange.aop.match_param
 * icourt
 * 2018/4/7
 * author:asange
 * email:[email protected]
 **/
@Aspect
@Component
public class MatchAspect {
    @Pointcut("args(Long)&& within(com.asange.aop.match_param.MatchParamService)")
    public void argsMatchs() {
    }

    @Before("argsMatchs()")
    public void argsMatchsBind() {
        System.out.println("\n==========只有一个Long参数的方法执行了\n");
    }

    @Pointcut("args(Long,..)&& within(com.asange.aop.match_param.MatchParamService)")
    public void argsMatchs2() {
    }

    @Before("argsMatchs()")
    public void argsMatchsBind2() {
        System.out.println("\n==========带Long参数的方法执行了\n");
    }
}


package com.asange.aop.match_param;

import org.springframework.stereotype.Service;

/**
 * com.asange.aop.match_param
 * icourt
 * 2018/4/7
 * author:asange
 * email:[email protected]
 **/
@Service
public class MatchParamService {

    public void sayNum(Long num) {
        System.out.println("\n=======sayNum:" + num + "\n");
    }
}

测试结果:都观察到了MatchParamService中的带Long参数的方法



注解匹配

 /**
     * 匹配方法标注有AdminOnly的注解的方法
     */
    @Pointcut("@annotation(com.asange.aop.security.AdminOnly)")
    public void annoDemo() {
    }

    /**
     * 匹配标注有Beta的类底下的方法,要求的annotation的RetentionPolcy级别为Class
     */
    @Pointcut("@within(com.google.common.annotations.beta)")
    public void annoWithinDemo() {
    }

    /**
     * 匹配标注有repository的类底下的方法,要求annotation的RetentionPolicy级别为RUNTIME
     */
    @Pointcut("@target(org.springframework.stereotype.Repository)")
    public void annoTargetDemo() {
    }

    /**
     * 匹配传入的参数类标注有Repository注解的方法
     */
    @Pointcut("@args(org.springframework.stereotype.Repository)")
    public void annoArgsDemo() {

    }

execution表达式

execution(* com.xxx.service..*.*(..)

符号 含义
execution() 表达式主体
第一个 “*”号 代表返回值的任意类型
com.xxx.service aop所切的服务的包名
包名后面的“.."

表示当前包及子包

第二个"*" 表示类名,* 表示所有类
.*(..) 表示任何方法名,括号表示参数,两个点表示任何参数类型
   
execution的基本语法格式为:

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

除了返回类型模式,方法名模式和参数模式外,其他都是可选的。

1. 通过方法签名定义切点:

execution(public * *(..))

匹配所有目标类的public方法,第一个*代表返回类型,第二个*代表方法名,而..表示任意入参的方法

package com.asange.aop.execution;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/**
 * com.asange.aop.execution
 * icourt
 * 2018/4/7
 * author:asange
 * email:[email protected]
 **/
@Service
public class ExecutionService {

    public void sayHello() {
        System.out.println("========>hello");
    }
}
package com.asange.aop.execution;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * com.asange.aop.execution
 * icourt
 * 2018/4/7
 * author:asange
 * email:[email protected]
 **/
@Aspect
@Component
public class ExecutionAspect {
    @Pointcut("execution(public * *(..)) && within(ExecutionService)")
    public void executiondemo() {
    }

    @Before("executiondemo()")
    public void executionBind() {
        System.out.println("========>execution(* *(..))");
    }
}


变种版本,如:execution(**(String))

package com.asange.aop.execution;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * com.asange.aop.execution
 * icourt
 * 2018/4/7
 * author:asange
 * email:[email protected]
 **/
@Aspect
@Component
public class ExecutionAspect {

    @Pointcut("execution(* *(String)) && within(ExecutionService)")
    public void executiondemo2() {
    }

    @Before("executiondemo2()")
    public void executionBind2() {
        System.out.println("========>execution(* *(String))");
    }
}

package com.asange.aop.execution;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/**
 * com.asange.aop.execution
 * icourt
 * 2018/4/7
 * author:asange
 * email:[email protected]
 **/
@Service
public class ExecutionService {

    public void sayHello(String str) {
        System.out.println("========>"+str);
    }
}


可以看出 execution表达式组合太多, 如execution(* say(String,int)) 那么只会关注  say(String xx,int xx)的方法,

又如exection(* *To(..)) 则会关注 convertToxx(xx)等类似方法,

又如execution(* com.xx.xservice.*(..)) 匹配xservice下面的的所有方法
太多组合,参考官方文档

Advice注解

1. @Before 前置通知

2. @After(finally),后置通知,成功执行之后

3. @AfterReturning, 返回通知,成功执行之后

4. @AfterThrowing 异常通知,抛出异常之后

5. @Around,环绕通知


本演示的例子代码点击

猜你喜欢

转载自blog.csdn.net/axuanqq/article/details/79834599
今日推荐