美文网首页程序员
手撕Spring AOP,让你成为完全理解AOP的作用

手撕Spring AOP,让你成为完全理解AOP的作用

作者: 夏年城丶凌 | 来源:发表于2020-05-07 23:30 被阅读0次

AOP基本概念

面向对象编程 [官网介绍]: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop
白话文:增强/加强一系列的类/方法

定义几个简单的接口

public interface ServiceForKTV {

    void kty();
}
public interface ServiceForSPA {

    void spa();
}

具体实现逻辑可以自己写

定义代理逻辑

public interface Advice {

    //定义一个方法
    //用户提供增加逻辑
    /**
     *
     * @param target
     * @param method
     * @param args
     * @return
     */
    public Object invoke(Object target, Method method,Object[] args) throws Exception;
}
public class TimeCsAdvice implements Advice {
    @Override
    public Object invoke(Object target, Method method, Object[] args) throws Exception {
        long startTime = System.currentTimeMillis();
        Object ret = method.invoke(target,args);
        long endTime = System.currentTimeMillis();
        System.out.println("类名【"+target.getClass().getName()+"】");
        System.out.println("方法名【"+method.getName()+"】");
        System.out.println("耗时【"+(endTime - startTime)+"】");
        return null;
    }
}

这里是统计接口耗时和打印一些参数

到这里的话,基本上用户的在逻辑都写完了,接下来就要完成一些框架需要干的事了

定义切入点和切入方法

@Data
public class Pointcut {

    private String classPattern;

    private String methodPattern;
}

java是能识别正则表达式的,这里我们用正则。

定义一个类来封装切点和Advice

public class Aspect {

    private Advice advice;

    private Pointcut pointcut;
}

定义AOP核心处理类InvocationHandler

public class AopInvocationHandler implements InvocationHandler {

    private Aspect aspect;

    private Object bean;

    public AopInvocationHandler(Aspect aspect, Object bean) {
        this.aspect = aspect;
        this.bean = bean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (bean.getClass().getName().matches(aspect.getPointcut().getMethodPattern())){
            return aspect.getAdvice().invoke(bean,method,args);
        }
        return method.invoke(bean,args);
    }
}

增强需要被代理的类

构造简易的Spring上下文对象

public interface IApplicationContext {

    Object getBean(String beanName) throws Exception;

    void registerBeanDefinition(String beanName,Class<?> beanClass) throws Exception;

    void setAspect(Aspect aspect);
}
public class MyApplicationContext implements IApplicationContext {

    private Map<String,Class<?>> beanMap = new ConcurrentHashMap<>();

    private Aspect aspect;

    @Override
    public Object getBean(String beanName) throws Exception {
        Object bean = createInstance(beanName);
        //返回一个增加的方法
        bean = proxyEnhance(bean);
        return bean;
    }

    private Object proxyEnhance(Object bean) {
        if (aspect != null && (bean.getClass().getName()).matches(aspect.getPointcut().getClassPattern()) ){
            return Proxy.newProxyInstance(bean.getClass().getClassLoader(),
                    bean.getClass().getInterfaces(),
                    new AopInvocationHandler(aspect,bean));
        }
        return bean;
    }

    private Object createInstance(String beanName) throws Exception {
        return beanMap.get(beanName).newInstance();
    }

    @Override
    public void registerBeanDefinition(String beanName, Class<?> beanClass) throws Exception{
        this.beanMap.put(beanName,beanClass);
    }

    @Override
    public void setAspect(Aspect aspect) {
        this.aspect = aspect;
    }

}

解释&说明

  • beanMap 模拟Spring容器,存放实例化的Bean对象

  • getBean(String beanName) 获取Bean实例。如果对象都被增强,则返回增强过后的类,在实例化出来的时候,已经是被增强过的,可以参考Spring 提供的源码。

  • proxyEnhance(Object bean) 简易的增强逻辑,通过正则匹配,是否需要被增强。这里有使用到动态代理。java给我们提供了两种便捷的API,分别是JDK提供的动态代理CGLIB提供的

  • registerBeanDefinition(String beanName, Class<?> beanClass) 简易的加入逻辑,将Bean放入到Spring容器中。可以通过XML或是注解的形式将Bean放入到Spring中。这里简化了IOC处理的流程,具体IOC相关的知识,可以参考

  • createInstance(String beanName) Bean 的实例化

到这里的话,已经是大功告成。

功成身退

public class AopMain {

    public static void main(String[] args) throws Exception{
        Advice advice = new TimeCsAdvice();
        Pointcut pointcut = new Pointcut();
        pointcut.setClassPattern("com\\.lynn\\.pay\\.imooc\\.demo\\.aop\\.service\\..*");
        pointcut.setMethodPattern(".*impl");
        Aspect aspect = new Aspect();
        aspect.setAdvice(advice);
        aspect.setPointcut(pointcut);

        //创建容器
        IApplicationContext applicationContext = new MyApplicationContext();
        applicationContext.setAspect(aspect);
        applicationContext.registerBeanDefinition("ktv", KTVimpl.class);
        applicationContext.registerBeanDefinition("spa", SPAimpl.class);

        ServiceForSPA spa = (ServiceForSPA)applicationContext.getBean("spa");
        ServiceForKTV ktv = (ServiceForKTV)applicationContext.getBean("ktv");

        spa.spa();
        ktv.kty();
    }
}

运行结果如下图,只对KTV服务进行了匹配处理。

image.png

总结

Sping已经为我们封装好了AOP,我们只需要定义好以下几个点就可以使用了

  1. Pointcut(切入点)
  2. Advice (通知)
  3. invoke(增强逻辑)

当然,在实际生产中也有许多作用,比如参数验签,TOKEN校验,统一的脱敏处理,重复请求处理等。方便我们用的同时,不会入侵到原有代码,非常高效。
这里是参考网易云免费直播课Tony老师的课学习的。

相关文章

网友评论

    本文标题:手撕Spring AOP,让你成为完全理解AOP的作用

    本文链接:https://www.haomeiwen.com/subject/effgghtx.html