手撕Spring AOP,讓你成為完全理解AOP的作用

AOP基本概念

面向切面編程 [官網(wǎng)介紹]: https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop
白話文:增強(qiáng)/加強(qiáng)一系列的類/方法

定義幾個(gè)簡(jiǎn)單的接口

public interface ServiceForKTV {

    void kty();
}
public interface ServiceForSPA {

    void spa();
}

具體實(shí)現(xiàn)邏輯可以自己寫(xiě)

定義代理邏輯

public interface Advice {

    //定義一個(gè)方法
    //用戶提供增加邏輯
    /**
     *
     * @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("耗時(shí)【"+(endTime - startTime)+"】");
        return null;
    }
}

這里是統(tǒng)計(jì)接口耗時(shí)和打印一些參數(shù)

到這里的話,基本上用戶的在邏輯都寫(xiě)完了,接下來(lái)就要完成一些框架需要干的事了

定義切入點(diǎn)和切入方法

@Data
public class Pointcut {

    private String classPattern;

    private String methodPattern;
}

java是能識(shí)別正則表達(dá)式的,這里我們用正則。

定義一個(gè)類來(lái)封裝切點(diǎn)和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);
    }
}

增強(qiáng)需要被代理的類

構(gòu)造簡(jiǎn)易的Spring上下文對(duì)象

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);
        //返回一個(gè)增加的方法
        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;
    }

}

解釋&說(shuō)明

  • beanMap 模擬Spring容器,存放實(shí)例化的Bean對(duì)象

  • getBean(String beanName) 獲取Bean實(shí)例。如果對(duì)象都被增強(qiáng),則返回增強(qiáng)過(guò)后的類,在實(shí)例化出來(lái)的時(shí)候,已經(jīng)是被增強(qiáng)過(guò)的,可以參考Spring 提供的源碼。

  • proxyEnhance(Object bean) 簡(jiǎn)易的增強(qiáng)邏輯,通過(guò)正則匹配,是否需要被增強(qiáng)。這里有使用到動(dòng)態(tài)代理。java給我們提供了兩種便捷的API,分別是JDK提供的動(dòng)態(tài)代理CGLIB提供的

  • registerBeanDefinition(String beanName, Class<?> beanClass) 簡(jiǎn)易的加入邏輯,將Bean放入到Spring容器中??梢酝ㄟ^(guò)XML或是注解的形式將Bean放入到Spring中。這里簡(jiǎn)化了IOC處理的流程,具體IOC相關(guān)的知識(shí),可以參考

  • createInstance(String beanName) Bean 的實(shí)例化

到這里的話,已經(jīng)是大功告成。

功成身退

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);

        //創(chuàng)建容器
        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();
    }
}

運(yùn)行結(jié)果如下圖,只對(duì)KTV服務(wù)進(jìn)行了匹配處理。

image.png

總結(jié)

Sping已經(jīng)為我們封裝好了AOP,我們只需要定義好以下幾個(gè)點(diǎn)就可以使用了

  1. Pointcut(切入點(diǎn))
  2. Advice (通知)
  3. invoke(增強(qiáng)邏輯)

當(dāng)然,在實(shí)際生產(chǎn)中也有許多作用,比如參數(shù)驗(yàn)簽,TOKEN校驗(yàn),統(tǒng)一的脫敏處理,重復(fù)請(qǐng)求處理等。方便我們用的同時(shí),不會(huì)入侵到原有代碼,非常高效。
這里是參考網(wǎng)易云免費(fèi)直播課Tony老師的課學(xué)習(xí)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容