Spring AOP

参考:https://blog.csdn.net/mu_wind/article/details/102758005

一、 概念

     AOP(Aspect Oriented Programming),面向切面思想,是Spring的三大核心思想之一(两外两个:IOC-控制反转、DI-依赖注入)。

它是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。

在我们的程序中,经常存在一些系统性的需求,比如权限校验、日志记录、统计等,这些代码会散落穿插在各个业务逻辑中,非常冗余且不利于维护,如图1。

           

有多少业务操作,就要写多少重复的校验和日志记录代码,这显然是无法接受的。当然,用面向对象的思想,我们可以把这些重复的代码抽离出来,写成公共方法,如图2中:

这样,代码冗余和可维护性的问题得到了解决,但每个业务方法中依然要依次手动调用这些公共方法,也是略显繁琐。有没有更好的方式呢?有的,那就是AOP,AOP将权限校验、日志记录等非业务代码完全提取出来,与业务代码分离,并寻找节点切入业务代码中,如图3右:

aop 要做三类事:

  • where在哪里切入,也就是权限校验等非业务操作在哪些业务代码中执行。
  • when在什么时候切入,是业务代码执行前还是执行后。
  • what切入后做什么事,比如做权限校验、日志记录等

关键词:

Pointcut:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
Aspect:切面,即Pointcut和Advice。
Joint point:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。

二、注解

@Pointcut 注解,用来定义一个切点,即上文中所关注的某件事情的入口,切入点定义了事件触发时机。定义需要拦截的东西,这里介绍两个常用的表达式:一个是使用 execution(),另一个是使用 annotation()

execution(* com.mutest.controller..*.*(..))) 表达式为例:

第一个 * 号的位置:表示返回值类型,* 表示所有类型。
包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,在本例中指 com.mutest.controller包、子包下所有类的方法。
第二个 * 号的位置:表示类名,* 表示所有类。
*(..):这个星号表示方法名,* 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

annotation() 方式是针对某个注解来定义切点,比如我们对具有 @PostMapping 注解的方法做切面,可以如下定义切面:

@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
public void annotationPointcut() {}

@Around注解用于修饰Around增强处理,Around增强处理非常强大,表现在:

   @Around可以自由选择增强动作与目标方法的执行顺序,也就是说可以在增强动作前后,甚至过程中执行目标方法。这个特性的实现在于,调用ProceedingJoinPoint参数的procedd()方法才会执行目标方法。
   @Around可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值。
@Before 注解指定的方法在切面切入目标方法之前执行,可以做一些 Log 处理,也可以做一些信息的统计,比如获取用户的请求 URL 以及用户的 IP 地址等等,这个在做个人站点的时候都能用得到,都是常用的方法。

@After 注解和 @Before 注解相对应,指定的方法在切面切入目标方法之后执行,也可以做一些完成某方法之后的 Log 处理。

三、应用实例

aop+自定义注解

   自定义注解>解解注解>使用注解

自定义注解:

package com.clc.server.annotation;

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

@Target(value = {ElementType.TYPE, ElementType.METHOD})//使用位置(类,方法)
@Retention(RetentionPolicy.RUNTIME)//加载到jvm里运行
public @interface Clc {
    String value(); //注解的属性,如果只有一个属性,一般叫value
    String name() default ""; //属性,默认值"",可以不写
}

  解析注解:

package com.clc.server.aop;

import com.clc.server.annotation.Clc;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 解析clc注解使用
 */
@Aspect//来定义一个切面
@Component
public class ClcAop {

    //定义切入点
    @Pointcut("@annotation(com.clc.server.annotation.Clc)")
    public void auditAspect() {
        System.out.println("1221212132");
    }

    //通知
    @Before("auditAspect()")
    public void doBefore(JoinPoint joinPoint) {
        System.out.println("触发到 @Before(\"auditAspect()\")");
    }

    /**
     * 后置通知
     *
     * @param joinPoint 切点
     */
    @AfterReturning("auditAspect()")
    public void doAfrterReturning(JoinPoint joinPoint) {

        Object[] args = joinPoint.getArgs();
        System.out.println("触发 @AfterReturning(\"auditAspect()\")");
        System.out.println(args.length);
        getControllerMethodDescription(joinPoint);
    }

    /**
     * 获取注解中对方法的描述信息
     *
     * @param joinPoint 切点
     * @return 方法描述
     */
    public static void getControllerMethodDescription(JoinPoint joinPoint) {
        String targetName = joinPoint.getTarget().getClass().getName();    //获得执行方法的类名
        String methodName = joinPoint.getSignature().getName();            //获得执行方法的方法名
        Object[] arguments = joinPoint.getArgs();                          //获取切点方法的所有参数类型
        try {
            Class targetClass = Class.forName(targetName);

            Method[] methods = targetClass.getMethods();    //获取公共方法,不包括类私有的
            String value = "";
            String name = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class[] clazzs = method.getParameterTypes();     //对比方法中参数的个数
                    if (clazzs.length == arguments.length) {
                        value = method.getAnnotation(Clc.class).value();
                        name = method.getAnnotation(Clc.class).name();
                        break;
                    }
                }
            }
            System.out.println("value=" + value);
            System.out.println("name=" + name);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  使用注解:

   /**
     * 测试自定义注解
     */
    @Clc(value = "clc", name = "name")
    @RequestMapping(value = "/add2", method = RequestMethod.GET)
    public String add2() {
        //获取本服务的信息
        ServiceInstance instance = client.getLocalServiceInstance();
        Integer r = 2;
        String info = "/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + "结果:" + r;
        logger.info(info);
        return info;
    }

  

posted @ 2021-07-15 23:22  zhangtianhong511  阅读(55)  评论(0)    收藏  举报