spring 实现切面的方法

时间:2024-07-20 07:02:07

AOP切面实现方法

  • 问题导入
  • 实现方法
    • 使用 @Aspect 注解实现
      • 依赖
      • 定义需要切入的注解
      • 定义切面类
    • 继承AbstractPointcutAdvisor类
      • 1. 定义需要切入的注解
      • 2. 定义拦截器
      • 3. 配置Advisor
      • 4. 注册 Advisor

问题导入

AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。

想象下面的场景,开发中在多个模块间有某段重复的代码,我们通常是怎么处理的?显然,没有人会靠“复制粘贴”吧。在传统的面向过程编程中,我们也会将这段代码,抽象成一个方法,然后在需要的地方分别调用这个方法,这样当这段代码需要修改时,我们只需要改变这个方法就可以了。然而需求总是变化的,有一天,新增了一个需求,需要再多出做修改,我们需要再抽象出一个方法,然后再在需要的地方分别调用这个方法,又或者我们不需要这个方法了,我们还是得删除掉每一处调用该方法的地方。实际上涉及到多个地方具有相同的修改的问题我们都可以通过 AOP 来解决。

实现方法

使用 @Aspect 注解实现

@Aspect注解底层的实现原理是基于AspectJ框架。当使用@Aspect注解标记一个类时,Spring会使用AspectJ编译器进行解析和增强。AspectJ是一个功能强大的AOP框架,它提供了更丰富的切点表达式和切面功能。

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义需要切入的注解

package com.space.aspect.anno;
 
import java.lang.annotation.*;
 
/**
 * 定义系统日志注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
    String value() default "";
}

定义切面类

/**
 * 系统日志切面
 */
@Aspect  // 使用@Aspect注解声明一个切面
@Component
public class SysLogAspect {
 
    @Autowired
    private SysLogService sysLogService;
 
    /**
     * 这里我们使用注解的形式
     * 当然,我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method
     * 切点表达式:   execution(...)
          *
     * execution(public * *(..)) 任意的公共方法
     * execution(* set*(..)) 以set开头的所有的方法
     * execution(* com.LoggerApply.*(..))com.LoggerApply这个类里的所有的方法
     * execution(* com.annotation.*.*(..))com.annotation包下的所有的类的所有的方法
     * execution(* com.annotation..*.*(..))com.annotation包及子包下所有的类的所有的方法
     * execution(* com.annotation..*.*(String,?,Long)) com.annotation包及子包下所有的类的有三个参数,第一个参数为String类型,第二个参数为任意类型,第三个参数为Long类型的方法
     * execution(@annotation(com.lingyejun.annotation.Lingyejun))
     */
    @Pointcut("@annotation(com.space.aspect.anno.SysLog)")
    public void logPointCut() {}
 
    /**
     * 环绕通知 @Around  , 当然也可以使用 @Before (前置通知)  @After (后置通知)
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        Object result = point.proceed();
        long time = System.currentTimeMillis() - beginTime;
        try {
            saveLog(point, time);
        } catch (Exception e) {
        }
        return result;
    }
 
    /**
     * 保存日志
     */
    private void saveLog(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SysLogBO sysLogBO = new SysLogBO();
        sysLogBO.setExeuTime(time);
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        sysLogBO.setCreateDate(dateFormat.format(new Date()));
        SysLog sysLog = method.getAnnotation(SysLog.class);
        if(sysLog != null){
            //注解上的描述
            sysLogBO.setRemark(sysLog.value());
        }
        //请求的 类名、方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLogBO.setClassName(className);
        sysLogBO.setMethodName(methodName);
        //请求的参数
        Object[] args = joinPoint.getArgs();
        try{
            List<String> list = new ArrayList<String>();
            for (Object o : args) {
                list.add(new Gson().toJson(o));
            }
            sysLogBO.setParams(list.toString());
        }catch (Exception e){ }
        sysLogService.save(sysLogBO);
    }
}

继承AbstractPointcutAdvisor类

AbstractPointcutAdvisor是Spring框架提供的抽象类,用于实现切面的定义。它提供了一种更灵活的方式来定义切点和增强逻辑。
AbstractPointcutAdvisor类是Spring AOP框架中的一部分,用于支持自定义的切面。你可以创建自己的切面类,继承AbstractPointcutAdvisor并实现必要的方法,以定义切点和增强逻辑。

1. 定义需要切入的注解

2. 定义拦截器

//你也可以继承或实现你的业务类
public class SysLogAnnotationInterceptor implements MethodInterceptor {
    
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //获取目标类
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
        //获取指定方法
        Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
        //获取真正执行的方法,可能存在桥接方法
        final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);

        //获取方法上注解
        SysLog sysLog = AnnotatedElementUtils.findMergedAnnotation(userDeclaredMethod, SysLog.class);
        if (sysLog == null) {
            sysLog = AnnotatedElementUtils.findMergedAnnotation(userDeclaredMethod.getDeclaringClass(), SysLog.class);
        }

        //获取返回类型
        Class<?> returnType = invocation.getMethod().getReturnType();
        //返回类型判断
        if (User.class.isAssignableFrom(returnType)) {

            return null;
        }

        //执行具体业务逻辑
        return invocation.proceed();
        // ..... 后置增强
    }

}

3. 配置Advisor

@Getter
@EqualsAndHashCode(callSuper = true)
public class SysLogAnnotationAdvisor extends AbstractPointcutAdvisor {

    private final Advice advice;

    private final Pointcut pointcut;

    public SysLogAnnotationAdvisor() {
        this.advice = new SysLogAnnotationInterceptor();
        this.pointcut = this.buildPointcut();
    }

    protected Pointcut buildPointcut() {
        Pointcut classPointcut = new AnnotationMatchingPointcut(SysLog.class, true);
        Pointcut methodPointcut = new AnnotationMatchingPointcut(null, SysLog.class, true);
        return new ComposablePointcut(classPointcut).union(methodPointcut);
    }

}

4. 注册 Advisor

配置类

@AutoConfiguration
public class SyslogPermissionAutoConfiguration {


    @Bean
    public SyslogAnnotationAdvisor syslogAnnotationAdvisor() {
        return new SyslogAnnotationAdvisor();
    }

}

相关文章