Spring AOP-编码实现拦截

1. 实现方式概述

Spring AOP除了通过AspectJ的注解实现拦截之外,还可以通过编码方式实现拦截。这里介绍如下几种方式:

● 方式一

实现AbstractAutoProxyCreator类,在getAdvicesAndAdvisorsForBean方法返回Advisor或Advice实现拦截,而Advisor由Advice组成,所以最终都是Advice完成拦截增强动作。

另外AbstractAutoProxyCreator实现类要作为一个Spring Bean才能起作用,因此可以加上@Component注解,或者是通过@Bean定义成Bean,甚至@Service也可以。

● 方式二

直接将Advisor定义为一个Spring Bean,在Advisor中返回Advice则可。

● 方式三

通过ProxyFactory注入业务实现类,然后注入Advice或Advisor,接着返回业务类接口代理对象,调用业务方法。

2. AbstractAutoProxyCreator实现类方式

2.1 类结构

首先选几个要实现的Advice:

选择一个要实现的Advisor:

最后的类结构如下:

描述
MyAutoProxyInterceptor 示例在AutoProxyCreator中返回Advice,实现比较常用的MethodInterceptor接口,它也是Advice。
MyAutoProxyAfterAdvice 示例被Advisor创建的场景,在方法调用后增加处理。
MyAutoProxyAdvisor 示例在AutoProxyCreator中返回Advisor,创建MyAutoProxyAfterAdvice。
MyAutoProxyCreator AbstractAutoProxyCreator的实现类。

2.2 代码

2.2.1 MyAutoProxyInterceptor

拦截目标类方法调用,在目标方法调用前做处理,这里没有加条件要拦截哪些类,在autoproxy类中加判断条件:

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

@Slf4j
public class MyAutoProxyInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        log.info("MyInterceptorForAutoProxy be called.");
        // 注:也可以在调用后处理
        return invocation.proceed();
    }
}

2.2.2 MyAutoProxyAfterAdvice

拦截目标类方法调用,在目标方法调用成功后处理:

java 复制代码
import com.kengcoder.springframeawsome.aop.service.BookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.support.AopUtils;

import java.lang.reflect.Method;

@Slf4j
public class MyAutoProxyAfterAdvice implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        // 注意这里要使用正确的判断条件
        if (AopUtils.getTargetClass(target).isAssignableFrom(BookService.class)) {
            log.info("MyAutoProxyAfterAdvice be called.");
        }
    }
}

2.2.3 MyAutoProxyAdvisor

Advisor类,包装Advice类。

java 复制代码
import com.kengcoder.springframeawsome.aop.service.IBookService;
import org.aopalliance.aop.Advice;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;

import java.lang.reflect.Method;

public class MyAutoProxyAdvisor extends StaticMethodMatcherPointcutAdvisor {
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        // 注意匹配条件要正确
        return method.getDeclaringClass().isAssignableFrom(IBookService.class);
    }

    @Override
    public Advice getAdvice() {return new MyAfterReturningAdvice();}

    // 可以定义advisor的执行顺序
    @Override
    public int getOrder() {return 3;}
}

2.2.4 MyAutoProxyCreator

自动代理类创建者类:

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.TargetSource;
import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator;
import org.springframework.beans.BeansException;
import org.springframework.stereotype.Component;

@Slf4j
// 必须定义为Spring Bean,通过其他方式如@Bean,@Service也可以,但这个场景通常是使用@Component或@Bean
@Component
public class MyAutoProxyCreator extends AbstractAutoProxyCreator {
    @Override
    protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) throws BeansException {
        // 注意正确的判断条件
        if (beanName.equals("bookService")) {
            log.info("MyAutoProxyCreator: beanName " + beanName);
            // 返回Advice或Advisor组成的数组
            return new Object[]{new MyAutoProxyInterceptor(),
                    new MyAutoProxyAdvisor()};
        } else {
            // 条件不匹配则返回null
            return null;
        }
    }
}

Book相关类很简单,不再赘述。

2.3 调用日志

调用IBookService的queryBook方法,输出日志如下:

java 复制代码
2023-12-23 23:31:14 INFO  MyAutoProxyInterceptor:11 - MyAutoProxyInterceptor be called.
2023-12-23 23:31:14 INFO  BookService:22 - queryBook be called.
2023-12-23 23:31:14 INFO  MyAutoProxyAfterAdvice:16 - MyAutoProxyAfterAdvice be called.

3. Advisor直接定义Bean方式

3.1 类结构

类结构如下:

这里实现了两个Advisor,各自返回一个Advice做拦截。

描述
MyAdvisorBeforeAdvice 示例在Advisor中返回Advice,在方法调用前增加处理。
MyAdviceAdvisor 示例Advisor Bean,创建MyAdvisorBeforeAdvice。
MyAdvisorInterceptor 示例在Advisor中返回Advice,实现比较常用的MethodInterceptor接口,它也是Advice。
MyInterceptorAdvisor 示例Advisor Bean,创建MyAdvisorInterceptor。

3.2 代码

3.2.1 MyAdvisorBeforeAdvice

拦截目标类方法调用,在目标方法调用前处理:

java 复制代码
import com.kengcoder.springframeawsome.aop.service.BookService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.support.AopUtils;

import java.lang.reflect.Method;

@Slf4j
public class MyAdvisorBeforeAdvice implements MethodBeforeAdvice {
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        // 注意正确的匹配条件
        if (AopUtils.getTargetClass(target).isAssignableFrom(BookService.class)) {
            log.info("MyAdvisorBeforeAdvice be called.");
        }
    }
}

3.2.2 MyAdviceAdvisor

Advisor类,包装Advice类。

java 复制代码
import com.kengcoder.springframeawsome.aop.service.BookService;
import org.aopalliance.aop.Advice;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

// 必须定义为Spring Bean,通过其他方式如@Bean,@Service也可以,但这个场景通常是使用@Component或@Bean
@Component
public class MyAdviceAdvisor extends StaticMethodMatcherPointcutAdvisor {
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        return true;
    }

    @Override
    public ClassFilter getClassFilter() {
        // 注意正确的匹配条件
        return clazz -> clazz.isAssignableFrom(BookService.class);
    }

    @Override
    public Advice getAdvice() {return new MyAdvisorBeforeAdvice();}

    @Override
    public int getOrder() {return 1;}
}

3.2.3 MyAdvisorInterceptor

拦截目标类方法调用,在目标方法调用前处理:

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

@Slf4j
public class MyAdvisorInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        log.info("MyAdvisorInterceptor be called.");
        // 注:也可以在调用后处理
        return invocation.proceed();
    }
}

3.2.4 MyInterceptorAdvisor

Advisor类,包装Advice类。

java 复制代码
import com.kengcoder.springframeawsome.aop.service.BookService;
import org.aopalliance.aop.Advice;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

// 必须定义为Spring Bean,通过其他方式如@Bean,@Service也可以,但这个场景通常是使用@Component或@Bean
@Component
public class MyInterceptorAdvisor extends StaticMethodMatcherPointcutAdvisor {
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        return true;
    }

    @Override
    public ClassFilter getClassFilter() {
        return clazz -> clazz.isAssignableFrom(BookService.class);
    }

    @Override
    public Advice getAdvice() {return new MyAdvisorInterceptor();}

    @Override
    public int getOrder() {return 2;}
}

3.3 调用日志

java 复制代码
2023-12-23 23:43:36 INFO  MyAdvisorBeforeAdvice:16 - MyAdvisorBeforeAdvice be called.
2023-12-23 23:43:36 INFO  MyAdvisorInterceptor:11 - MyAdvisorInterceptor be called.
2023-12-23 23:43:36 INFO  BookService:22 - queryBook be called.

4. ProxyFactory方式

ProxyFactory方式很简单,直接看代码。

4.1 代码

java 复制代码
    @Autowired
    private IBookService bookService;
	
    void test() {
        ProxyFactory factory = new ProxyFactory(bookService);
        // 可以注入多个Advice和Advisor,如果Advisor已经包含了某个Advice,重复添加该Advice,只会执行一次
        factory.addAdvice(new MyAdvisorBeforeAdvice());
        factory.addAdvisor(new MyAdviceAdvisor()); // 包含MyAdvisorBeforeAdvice,只会执行一次
        IBookService proxyBookService = (IBookService) factory.getProxy();
        Book book = proxyBookService.queryBook("Effective Java");
    }

4.2 调用日志

java 复制代码
2023-12-25 16:18:46 INFO  MyAdvisorBeforeAdvice:23 - MyAdvisorBeforeAdvice[default] be called.
2023-12-25 16:18:46 INFO  BookService:22 - queryBook be called.

5. 调用顺序

5.1 调用顺序验证

注:以下关于调用顺序不包含ProxyFactory方式。

到现在为止,我们实现了3个Advisor,都通过getOrder()方法返回了顺序如下:

Advisor类 包装Advice Order
MyAdviceAdvisor MyAdvisorBeforeAdvice 1
MyInterceptorAdvisor MyAdvisorInterceptor 2
MyAutoProxyAdvisor MyAutoProxyAfterAdvice 3

输出日志:

erlang 复制代码
2023-12-23 23:46:37 INFO  MyAutoProxyInterceptor:11 - MyAutoProxyInterceptor be called.
2023-12-23 23:46:37 INFO  MyAdvisorBeforeAdvice:16 - MyAdvisorBeforeAdvice be called.
2023-12-23 23:46:37 INFO  MyAdvisorInterceptor:11 - MyAdvisorInterceptor be called.
2023-12-23 23:46:37 INFO  BookService:22 - queryBook be called.
2023-12-23 23:46:37 INFO  MyAutoProxyAfterAdvice:16 - MyAutoProxyAfterAdvice be called.

可以看到顺序似乎正常,但其实有两点并不能确定:

● MyAutoProxyAfterAdvice是After拦截器,其他都是Before拦截器,所以必然在最后,看起来是对的,但是未必就真是这个逻辑。

● 自动代理的MyAutoProxyInterceptor在最前,虽然它不是Advisor,但和MyAutoProxyAdvisor一样,都是在MyAutoProxyCreator类中返回的,它为什么排在最前?

为了验证这些疑点,我们在MyAutoProxyAdvisor中也返回MyAdvisorBeforeAdvice,并给MyAdvisorBeforeAdvice一个name为autoproxy,用于识别是MyAutoProxyAdvisor返回的:

Advisor类 包装Advice Order Name
MyAdviceAdvisor MyAdvisorBeforeAdvice 1 default
MyInterceptorAdvisor MyAdvisorInterceptor 2
MyAutoProxyAdvisor MyAdvisorBeforeAdvice 3 autoproxy

输出日志:

yaml 复制代码
2023-12-24 00:29:04 INFO  MyAdvisorBeforeAdvice:23 - MyAdvisorBeforeAdvice[autoproxy] be called.
2023-12-24 00:29:04 INFO  MyAutoProxyInterceptor:11 - MyAutoProxyInterceptor be called.
2023-12-24 00:29:04 INFO  MyAdvisorBeforeAdvice:23 - MyAdvisorBeforeAdvice[default] be called.
2023-12-24 00:29:04 INFO  MyAdvisorInterceptor:11 - MyAdvisorInterceptor be called.

可以看到虽然MyAdvisorBeforeAdvice的order=3,但是只要是MyAutoProxyInterceptor返回的,还是排在最前,大致可以说明AutoProxyCreator实现类方式比直接定义Advisor为Bean的方式优先级高。

查看AutoProxyCreator的父类ProxyProcessorSupport实现了Ordered接口,并且order默认值为:Integer.MAX_VALUE,这样足以证明上面的说法了,因为值越小优先级越高,而这里定义的是Integer最大值还能排在最前面:

另外Advisor Bean方式的顺序没啥问题,再看看autoproxy方式内部顺序如何,将所有Advisor都让autoproxy返回,并且按照order逆序排列在返回数组中:

java 复制代码
                return new Object[]{new MyAutoProxyAdvisor(), new MyInterceptorAdvisor(),
                        new MyAdviceAdvisor(), new MyAutoProxyInterceptor()
                };

输出日志:

java 复制代码
2023-12-24 00:43:05 INFO  MyAdvisorBeforeAdvice:23 - MyAdvisorBeforeAdvice[autoproxy] be called.
2023-12-24 00:43:05 INFO  MyAdvisorInterceptor:11 - MyAdvisorInterceptor be called.
2023-12-24 00:43:05 INFO  MyAdvisorBeforeAdvice:23 - MyAdvisorBeforeAdvice[default] be called.
2023-12-24 00:43:05 INFO  MyAutoProxyInterceptor:11 - MyAutoProxyInterceptor be called.
2023-12-24 00:43:05 INFO  BookService:22 - queryBook be called.

可以看到advisor的order字段并没有起作用,调用顺序就是数组元素的排列顺序,这也可以理解,因为该数组可以同时返回advice和advisor,而advice没有实现Orderd接口,无法定义顺序,在混排时想控制顺序,只能靠定义返回数组时排列数组元素顺序。

5.2 调用顺序小结

● autoproxy方式优先级始终高于Advisor Bean方式。

● autoproxy内部顺序是返回Advisor和Advice的数组元素排列顺序。

● Advisor Bean方式的顺序可以通过实现Orderd的getOrder()方法决定。

因此,在两种方式同时混排的场景下要考虑到上述规则,或者不要混排,统一用一种方式实现。

6. 总结

本文介绍了Spring AOP通过编码实现拦截的几种方式,以及调用顺序的注意点,编码方式虽然看起来比注解方式复杂一些,但也提供了更大的灵活性,可以自定义一些控制逻辑,两种方式比较如下:

实现方式 优点 适用场景
编码方式 更灵活,可以通过编码自定义复杂逻辑。 偏底层框架
注解方式 使用起来更简单。 偏上层业务

可以结合实际业务场景进行选择。


其他阅读:

如何编写软件设计文档
Spring Cache架构、机制及使用
布隆过滤器适配Spring Cache及问题与解决策略
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(五)函数式接口-复用,解耦之利刃

相关推荐
智慧老师5 分钟前
Spring基础分析13-Spring Security框架
java·后端·spring
lxyzcm7 分钟前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
V+zmm1013440 分钟前
基于微信小程序的乡村政务服务系统springboot+论文源码调试讲解
java·微信小程序·小程序·毕业设计·ssm
Oneforlove_twoforjob1 小时前
【Java基础面试题025】什么是Java的Integer缓存池?
java·开发语言·缓存
xmh-sxh-13141 小时前
常用的缓存技术都有哪些
java
搬码后生仔1 小时前
asp.net core webapi项目中 在生产环境中 进不去swagger
chrome·后端·asp.net
凡人的AI工具箱1 小时前
每天40分玩转Django:Django国际化
数据库·人工智能·后端·python·django·sqlite
AiFlutter1 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter
J不A秃V头A2 小时前
IntelliJ IDEA中设置激活的profile
java·intellij-idea
DARLING Zero two♡2 小时前
【优选算法】Pointer-Slice:双指针的算法切片(下)
java·数据结构·c++·算法·leetcode