spring - aop

本文就介绍一下 AOP 在 spring 环境下的应用。

专有名词

编码过程中,会遇到很多类似的名词。

  • concerns:关注点,就是字面意思,一个我们感兴趣的点;

  • cross-cutting concerns:横切关注点,跨越多个模块的关注点称之为横切关注点。例:日志就是许多模块共同的关注点。日常交流也称为“横向切面”;

  • Aspect:切面,切面就像一个平面,在业务流中插入新的功能。代码上,就是一个类,内部包含切入点、植入的新代码等内容;

  • JoinPoint:连接点,程序执行过程中的任意一点。代码上,就是对拦截函数的二次封装,如下列案例中ProceedingJoinPoint;

  • Pointcut:切入点,对拦截方式的定义,对应于 spring 中的 @Pointcut 注解;

  • Advice:通知,程序执行到关注点要做某些事,通知就是将要做的事情封装成代码。常用的有:前置、后置、异常、返回、环绕通知五类;

  • Target:目标对象:对方法进行拦截时,方法的所属对象;

  • Weave:织入,将通知应用到目标对象上,并导致代理对象被创建的过程;

@Aspect

在 springboot 环境下,手写 aop 的次数非常少,大部分的需求,都有专门的接口可用。

能想到的也只有写日志了,下面提供一个案例:

/**
 * 声明一个注解,如果一个方法上有这个注解,说明要记录日志
 */
@Documented
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionLogger {
}

/**
 * 类上面要写 @Aspect,表示当前类是一个 AOP 切面
 */
@Aspect
@Configuration
public class ServiceLogAspect {

    /**
     * 方法上 @Around 注解,声明我们要做一个环绕切面,
     * 其中 @annotation(cn.seaboot.admin.logger.manager.ActionLogger),指的是拦截所有带 @ActionLogger 的函数
     *
     * ProceedingJoinPoint 连接点,包含我们编码所需的各种参数。
     *
     * @param joinPoint 连接点,包含很多反射所需的对象
     * @return 程序运行结果
     * @throws Throwable 异常
     */
    @Around("@annotation(cn.seaboot.admin.logger.manager.ActionLogger)")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // TODO: write logs before invoke method

        Object ret = joinPoint.proceed();

        // TODO: write logs after invoke method
        return ret;
    }
}

其它常用的切面:

  • @Before 前置切面
  • @After 后置切面
  • @AfterReturning 方法返回结果之后
  • @AfterThrowing 抛出异常之后
  • @Around 环绕切面

古老的案例

非常古老的案例,用主函数就能调用。

能用主函数调用,意味着拥有超高的代码自由度,你可以发挥自己的想象,封装出各种自己所需的代码。

spring 中有无数个切面,但是学会这 5 个,基本就能满足所有需求了。

前置切面,准确地翻译叫 “前置通知”,也有地叫做 “前置增强”,不确定为什么叫 “增强”,日常沟通的时候,这些名词都可能出现。

  • 前置切面:org.springframework.aop.BeforeAdvice,空的接口,可以用实现类 MethodBeforeAdvice;
  • 后置切面:org.springframework.aop.AfterReturningAdvice;
  • 异常切面:org.springframework.aop.ThrowsAdvice;
  • 方法拦截(环绕切面):org.aopalliance.intercept.MethodInterceptor,方法拦截;
  • 调用拦截:org.springframework.aop.IntroductionInterceptor,Java反射相关拦截。
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jetbrains.annotations.NotNull;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;

import java.lang.reflect.Method;

/**
 * 前置切面
 *
 * @author Mr.css on 2017-01-01
 */
class MyMethodBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object @NotNull [] args, Object target) throws Throwable {
        System.out.println(method.getName() + "方法执行之前");
    }
}

/**
 * 后置切面
 *
 * @author Mr.css on 2017-01-01
 */
class MyAfterReturningAdvice implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object @NotNull [] args, Object target) throws Throwable {
        System.out.println(method.getName() + "方法执行结束");
    }
}

/**
 * 异常切面,接口是空的,方法要按照规范写
 *
 * @author Mr.css on 2017-01-01
 */
class MyThrowsAdvice implements ThrowsAdvice {
    /**
     * 笼统地使用了Exception,可以做具体的异常捕捉
     */
    public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable {
        System.out.println(method.getName() + "出现异常");
    }
}

/**
 * 调用拦截,可以控制 Java 反射相关的内容
 *
 * @author Mr.css on 2017-01-01
 */
class MyIntroductionInterceptor extends DelegatingIntroductionInterceptor {
    private static final long serialVersionUID = 2582891122340903718L;

    public Object invoke(@NotNull MethodInvocation methodInvocation) throws Throwable {
        System.out.println("引介增强启动拦截");
        return super.invoke(methodInvocation);
    }
}


/**
 * 方法拦截,代码中很常见
 *
 * @author Mr.css on 2017-01-01
 */
class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        System.out.println("MyMethodInterceptor执行");
        // TODO 方法执行前要做的事情

        //如果说,不执行下面这一行代码,则目标方法将不执行,其他的增强效果也全部失效
        Object result = methodInvocation.proceed();

        // TODO 方法执行后要做的事情
        return result;
    }
}

/**
 * 测试用接口
 *
 * @author Mr.css on 2017-01-01
 */
interface BaseDao {
    long queryId();
}

/**
 * 测试用Dao
 *
 * @author Mr.css on 2017-01-01
 */
class UserDao implements BaseDao {
    public long queryId() {
        System.out.println("query complete!");
        // 下面这一行肯定会报错
        return 2 / 0;
    }
}

public class Test {

    public static void main(String[] args) {
        // 用主函数就能调用,超高的自由度,意味着能写任何你想写的内容

        ProxyFactory factory = new ProxyFactory();
        // 把我们写的四种代理全部放进去
        factory.addAdvice(new MyMethodInterceptor());
        factory.addAdvice(new MyMethodBeforeAdvice());
        factory.addAdvice(new MyAfterReturningAdvice());
        factory.addAdvice(new MyThrowsAdvice());

        // 调用拦截比较特殊,需要提供接口
        factory.addAdvice(new MyIntroductionInterceptor());
        factory.setInterfaces(BaseDao.class.getInterfaces());

        // 需要代理的那个类
        factory.setTarget(new UserDao());

        try {
            BaseDao dao = (UserDao) factory.getProxy();
            System.out.println(dao.queryId());
        } catch (Exception ignore) {
        }
    }
}

posted on 2017-01-02 01:10  疯狂的妞妞  阅读(298)  评论(0编辑  收藏  举报

导航