Spring中使用AOP来面向切面编程解决复杂业务场景

Scroll Down

来源:NanCheung`s Blog
链接:https://blog.nancheung.com /archives/752013
商业转载请联系作者获得授权,非商业转载请注明出处。



简介

AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待。

一. Spring中AOP的基本概念

Aspect(切面)

Spring可以基于Aspect实现AOP模式,通常是一个类,在这个类里面定义切入点和各种通知(通知见第3点)

Pointcut(切入点)

就是带有通知的连接点,在程序中主要体现为书写切入点表达式,可指定需要切入的点

JointPoint(连接点)

程序执行过程中,当切入点触发时,我们定义的切面方法可以获取到这个切入点的信息,其中JointPoint的常用方法如下:

  1. java.lang.Object[] getArgs()

    获取连接点方法的入参列表;

  2. Signature getSignature()

    获取连接点的方法签名对象,通过此可获得方法名等;

  3. java.lang.Object getTarget()

    获取连接点所在的目标对象 ,可通过getClass()得到类对象;

  4. java.lang.Object getThis()

    获取代理对象本身;

  5. java.lang.Object proceed() throws java.lang.Throwable

    通过反射执行目标对象的连接点处的方法;

  6. java.lang.Object proceed(java.lang.Object[] args) throws java.lang.Throwable

    通过反射执行目标对象连接点处的方法,不过使用新的参数替换原来的参数。

5、6两个方法是ProceedingJoinPoint继承JoinPoint子接口,它新增了两个用于执行连接点方法的方法,如需5、6方法,则应该定义ProceedingJoinPoint对象

Advice(通知)

AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around

  1. Before:前置通知

    会在切入点之前执行

  2. AfterReturning:后置通知

    会在切入点之后执行

  3. AfterThrowing:异常通知

    切入点抛出异常执行,捕捉异常

  4. After: 最终后置通知

    不管有无异常、都会执行的后置通知

  5. Around: 环绕通知

    可定义前置后置方法,环绕切入点

实例演练

下面写一个切面类,在里面定义几种通知,供大家参考学习
/**
* 日志切入类
*
* @author NanCheung
*/
@Aspect  //基于Aspect实现AOP
@Component  //持久化Bean层,如果类不归属于@Controller, @Service, @Repository则可使用此注解
public class LoggerAspect {
    @Autowired  //自动装配
    private LogMapper logMapper;
    private Logger logger = LoggerFactory.getLogger(LoggerAspect.class);

    /**
     * 配置切入点
     * 匹配controller包下的所有类中的方法
     */
    @Pointcut("execution(* com.blog.controller.*.*(..))")

    public void exceptionLog() {

    }

    /**
     * 异常切入方法
     * 打印异常
     *
     * @param joinPoint
     * @param e
     */
     @Order(5)  //当切点有几种通知时,那么很容易起冲突,@Order()可以指定切面的执行顺序,括号数字越小优先级越高
    @AfterThrowing(pointcut = "exceptionLog()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Throwable e) {
        System.out.println("AfterThrowing logger!");
        logger.error("error", e);
    }

    /**
     * 前置通知
     * 在接口执行之前打印接口信息及传入的参数
     *
     * @param jp 切点信息
     */
    @Order(6)  //这个6比上个方法的5要大,说明上一个异常切入会执行得早一点
    @Before(value = "exceptionLog()")     //当定义了一个切入点之后,我们只需要引用那个切入点即可
    public void logBeforeMethod(JoinPoint jp) {   //JoinPoint得到连接点信息

        // 获取传入接口的参数
        Object[] args = jp.getArgs();

        //所有参数信息
        StringBuilder params = new StringBuilder();

        //如果连接点的入参是一个对象的话,那么必须手动解析它
        for (Object arg : args) {
            //反射获取对象
            Field[] fields = arg.getClass().getDeclaredFields();

            for (Field field : fields) {
                //设置private对象为public
                field.setAccessible(true);

                // 获取字段名称
                String fieldName = field.getName();
                params.append(fieldName).append(":");

                try {
                    // 获取字段值
                    params.append(field.get(arg)).append("\t");
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

        //获取目标类名
        String className = jp.getTarget().getClass().getName();
        //获取目标方法签名
        Signature signature = jp.getSignature();
        //获取目标方法名字
        String methodName = signature.getName();

        System.out.println("Cut into the log:\nClassName:" + className + "\nMethodName:" + methodName + "\nParams:{" + params + "}");
    }
}

以上是一个简易的切面类,书写了几个常用的通知。

Pointcut常用的配置方法:

//表示匹配所有方法
@Pointcut("execution(* com.blog.controller.*.*(..))")
public void exceptionLog() {}
//表示匹配com.savage.server.UserService中所有的公有方法
@Pointcut("execution(public * com. savage.service.UserService.*(..))")
public void exceptionLog() {}
//表示匹配com.savage.server包及其子包下的所有方法

@Pointcut("execution(* com.savage.server..*.*(..))")
public void exceptionLog() {}