08.解析切面类并实现代理类逻辑


title: 08.解析切面类并实现代理类逻辑
tag: 笔记 手写SSM AOP


通能够生成代理对象后,我们就需要实现代理对象的调用逻辑了。

分析

在上一节我们实现了创建代理对象的工厂ProxyFactory,我们需要下面两个参数即可拿到原始Bean的代理对象。

  • 原始Bean对象
  • 一个处理代理对象方法逻辑的InvocationHandler

获取Beans

对于第一个条件,我们需要拿到IOC容器中所有Bean的实例,但我们AOP模块如何拿到IOC容器中的所有Bean呢?

这里参照Spring中的一系列Aware接口,当一个Bean注册到容器中,若这个Bean实现了某个Aware接口,那么在Bean实例化或者初始化时Spring会将与接口对应的信息注入到这个Bean中。

例如下面这个例子:

public class MyBean implements ApplicationContextAware {
    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void doSomething() {
        // 获取其他的Bean实例或其他的组件
        OtherBean otherBean = applicationContext.getBean("otherBean", OtherBean.class);
        // ...
    }
}

这个MyBean注册到IOC容器后,因为它实现了ApplicationContextAware接口,因此Spring会在初始化或者实例化时会调用接口的setApplicationContext方法将容器注入到这个类中,我们就可以在这个类中使用applicationContext

在我们现在的这个场景下,我们只需要拿到所有Bean,因此我们自定义一个接口,在IOC容器实例化时检测是否有Bean实现该接口,若实现则将Beans注入在这个Bean中。如此我们即可拿到容器中的全部Bean信息。

解析Aspect

拿到Bean之后,我们还需要找到Aspect的Bean,解析Aspect切面类的拦截规则。因为可能存在一个PointCut规则对应着多个Advice的情况,并且我们的PointCut只支持注解。我们可以使用一个Map结构来表达这个拦截规则,键是PointCut注解的名字,值是Advice的集合。

比如下面这个切面类:

@Aspect
@Component
public class MyAspect {

    /**
     * 拦截所有方法上携带  MyAopAnnotation 注解的方法
     * @param joinPoint
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @Around(targetAnno = target.class)
    public Object testAspect(ProceedingJoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {
        long startTime = System.currentTimeMillis();
        //方法放行
        Object proceed = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        System.out.println("总共用时:"+(endTime - startTime));
        return proceed;
    }
    /**
     * 拦截所有方法上携带  MyAopAnnotation 注解的方法
     * @param joinPoint
     * @return
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    @Around(targetAnno = target.class)
    public Object testAspect2(ProceedingJoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {
        long startTime = System.currentTimeMillis();
        //方法放行
        Object proceed = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        System.out.println("总共用时:"+(endTime - startTime));
        return proceed;
    }

    @Around(targetAnno = DAO.class)
    public Object DAOAdvice(ProceedingJoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException {
        System.out.println("DAO层调用开始");
        Object proceed = joinPoint.proceed();
        System.out.println("DAO层调用开始");
        return proceed;
    }

}

解析这个切面类,我们可以得到两条拦截规则:

  • @DAO: DAOAdvice
  • @target: testAspect,testAspect2.

代理类的方法调用逻辑

在解析了Aspect定义的拦截之后,我们就可以拦截需要生成代理的Bean为其生成代理对象了。这个时候我们就需要编写InvocationHandler来决定代理对象的方法调用逻辑了。

我们先来回顾一下Spring中如何编写一个通知:

@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {

    long beginTime = System.currentTimeMillis();
    Object result = point.proceed();
    long executeTime = System.currentTimeMillis() - beginTime;
    recordLog(point, executeTime,result);
    return result;
}

在进行环绕通知时,Spring需要我们在通知的签名中加入一个ProceedingJoinPoint来存储一些表示原始对象的一些信息,其中最重要的方法就是proceed方法,这个方法表示执行原始方法。因此我们需要将代理方法的执行转发到这个Advice通知中执行。

大致流程应当如下:

  1. 根据解析的拦截规则解析该方法存在的Advice链。若为0则说明不需要代理。
  2. 构建ProceedingJoinPoint参数并将参数传入Advice并递归调用Advice链。(重点)

其中第二步使用递归较为复杂。

使用BeanPostProcessor替换原始对象

在完成了如何创建代理之后,我们就可以使用BeanPostProcessor去替换IOC容器中原始的Bean了。

我们可以创建一个类,这个类实现两个接口:

  • BeanPostProcessor

创建Bean实例时创建代理对象替换原始的Bean,并且在注入时注入到原始的Bean

  • BeansAware

注入Beans时同时解析Aspect的拦截规则

实现

在上面的分析后,我们有了创建IOC容器中需要增强的Bean的代理类的思路了,下面我们可以使用代码来进行实现。

创建Around通知注解

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Around {

    /**
     * Invocation handler bean name.
     */
    Class<? extends Annotation> targetAnno();

}

Around注解只能标注到方法上,并且需要传入一个注解的参数。

创建表示Advice和ProceedingJoinPoint的类

/**
 * @author 白日
 * @create 2024/1/4 18:24
 * @description 通知方法
 */

public class Advice {
    /**
     * 切点的方法
     */
    private Method method;
    /**
     * 切点的对象
     */
    private Object target;
    /**
     * 切点的参数
     */
    private Object[] args;

    public Advice(Method method, Object target) {
        this.method = method;
        this.target = target;
    }

    public Object invoker() throws InvocationTargetException, IllegalAccessException {
        method.setAccessible(true);
        return method.invoke(target, args);
    }
}

这个类代表了用户定义的切点,有一个invoker方法调用这个切点方法,并且需要传入一个ProceedingJoinPoint作为参数。

/**
 * @author 白日
 * @create 2024/1/4 18:45
 * @description 代表了正在执行的方法的连接点(join point)
 */

public class ProceedingJoinPoint {
    /**
     * 目标方法的参数
     */
    private Object[] args;
    /**
     * 目标对象
     */
    private Object target;
    /**
     * 目标方法
     */
    private Method method;
    /**
     * 存在的调用链
     */
    private List<Advice> proxyChains = new ArrayList<>(8);
    /**
     * 当前调用链的指针位置
     */
    private int chainsIndex = 0;


    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target,args);
    }
}

这个类代表了正在执行的方法的连接点(join point),其中proceed方法会执行连接点的方法,并且这个类会作为一个Around坏绕通知的参数传入。

实现代理方法调用逻辑

这一步我们需要定义我们代理方法的执行逻辑:

  1. 根据解析的拦截规则解析该方法存在的Advice链。若为0则说明不需要代理。
  2. 构建ProceedingJoinPoint参数并将参数传入Advice并递归调用Advice链。(重点)
public class DynamicAopProxy implements InvocationHandler {
    Object target;//原始对象

    public Map<Class<? extends Annotation>, List<Advice>> proxyRule = new ConcurrentHashMap<>();//拦截规则
    List<Advice> proxyChains = new ArrayList<>(8); //当前Advice调用链
    int chainsIndex = 0; //调用链指针
    public DynamicAopProxy(Map<Class<? extends Annotation>, List<Advice>> proxyRule, Object target){
        this.proxyRule = proxyRule;
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(chainsIndex == 0){
            getCurProxyChains(method);
        }
        //递归中止条件
        if(proxyChains.size() == chainsIndex){
            chainsIndex = 0; //清空调用链
            proxyChains = new ArrayList<>(8); //清空调用链
            return method.invoke(target, args);//调用链执行完毕,调用原始方法。
        }
        //构造当前的Advice并调用
        Advice advice = getAdvice(proxy, method, args);
        return advice.invoker();//递归入口,在advice中调用method.invoke(proxy,args)仍然会转发到到invoke方法InvocationHandler的invoke方法。
    }

    private Advice getAdvice(Object proxy, Method method, Object[] args) {
        //构建ProceedingJoinPoint
        ProceedingJoinPoint proceedingJoinPoint = new ProceedingJoinPoint();
        proceedingJoinPoint.setArgs(args);
        proceedingJoinPoint.setMethod(method);
        proceedingJoinPoint.setProxyChains(proxyChains);
        proceedingJoinPoint.setTarget(proxy);//我们传入proxy代理对象
        //构建Advice
        Advice advice = proxyChains.get(chainsIndex++);//指针指向下一个Advice
        proceedingJoinPoint.setChainsIndex(chainsIndex);
        advice.setArgs(new Object[]{proceedingJoinPoint});
        return advice;
    }

    private void getCurProxyChains(Method method) {
        List<Advice> curProxyChains = new ArrayList<>(8);
        proxyRule.forEach((key, value) -> {
            if (target.getClass().isAnnotationPresent(key) ||
                    method.isAnnotationPresent(key)) {
                curProxyChains.addAll(value);
            }
        });
        this.proxyChains = curProxyChains;
    }
}

实现代理的BeanPostProcessor

@Component
public class AOPProxyCreator implements BeanPostProcessor, BeansAware {
    Map<String, Object> originBeans = new HashMap<>();
    public Map<String, BeanDefinition> beans;
    public final Map<Class<? extends Annotation>, List<Advice>> proxyRule = new ConcurrentHashMap<>();
    public List<Object> aspectInstance = new ArrayList<>(8);
    ProxyFactory proxyResolver = new ProxyFactory();
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        Class<?> beanClass = bean.getClass();
        if(checkJoinPoint(beanClass)){
            originBeans.put(beanName, bean);
            return proxyResolver.createProxy(bean, new DynamicAopProxy(proxyRule, bean));
        }
        return bean;

    }


    private boolean checkJoinPoint(Class<?> beanClass) {
        for (Annotation annotation : beanClass.getAnnotations()) {
            if(proxyRule.containsKey(annotation.annotationType())){
                return true;
            }
        }
        for (Method method : beanClass.getMethods()) {
            for (Annotation annotation : method.getAnnotations()) {
                if(proxyRule.containsKey(annotation.annotationType())){
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public Object postProcessOnSetProperty(Object bean, String beanName) {
        Object origin = this.originBeans.get(beanName);
        return origin != null ? origin : bean;
    }

    @Override
    public void setApplicationContext(Map<String, BeanDefinition> beans) {
        this.beans = beans;
        aspectInstance = getAspectInstance();
        parseAspectjClass();
        logger.debug("解析后的拦截规则为:{}", proxyRule);
    }

    private void parseAspectjClass(){
        for (Object aspect : aspectInstance) {
            for (Method method : aspect.getClass().getMethods()) {
                Around around = method.getAnnotation(Around.class);
                if(around != null){
                    Advice advice = new Advice(method, aspect);
                    Class<? extends Annotation> targetAnno = around.targetAnno();
                    if(proxyRule.containsKey(targetAnno)){
                        proxyRule.get(targetAnno).add(advice);
                    }else {
                        List<Advice> proxyChains = new ArrayList<>();
                        proxyChains.add(advice);
                        proxyRule.put(targetAnno, proxyChains);
                    }
                }
            }
        }
    }
    public List<Object> getAspectInstance() {
        List<BeanDefinition> aspectDef = beans.values().
                stream()
                .filter(definition -> definition.getBeanClass().isAnnotationPresent(Aspect.class))
                .toList();
        return aspectDef.stream().map(BeanDefinition::getInstance).toList();
    }
}
          }
        }
    }
}
public List<Object> getAspectInstance() {
    List<BeanDefinition> aspectDef = beans.values().
            stream()
            .filter(definition -> definition.getBeanClass().isAnnotationPresent(Aspect.class))
            .toList();
    return aspectDef.stream().map(BeanDefinition::getInstance).toList();
}

}

  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值