欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?Bean名稱(chēng)不會(huì)被代理的命名技巧

 更新時(shí)間:2023年11月06日 11:51:04   作者:江南一點(diǎn)雨  
Spring Bean一些使用小細(xì)節(jié)就是在不斷的源碼探索中逐步發(fā)現(xiàn)的,今天就來(lái)和小伙伴們聊一下通過(guò) beanName 的設(shè)置,可以讓一個(gè) bean 拒絕被代理

1. 代碼實(shí)踐

假設(shè)我有如下一個(gè)切面:

@Aspect
@EnableAspectJAutoProxy
@Component
public class LogAspect {
    @Pointcut("execution(* org.javaboy.demo.service.*.*(..))")
    public void pc() {
    }
    @Before("pc()")
    public void before(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println(name + " 方法開(kāi)始執(zhí)行了...");
    }
}

這個(gè)切面要攔截的方法是 org.javaboy.demo.service 包下的所有類(lèi)的所有方法,現(xiàn)在,這個(gè)包下有一個(gè) BookService 類(lèi),內(nèi)容如下:

@Service("org.javaboy.demo.service.BookService.ORIGINAL")
public class BookService {
    public void hello() {
        System.out.println("hello bs");
    }
}

這個(gè) BookService 的 beanName 我沒(méi)有使用默認(rèn)的 beanName,而是自己配置了一個(gè) beanName,這個(gè) beanName 的配置方式是 類(lèi)名的完整路徑+.ORIGINAL。

當(dāng)我們按照這樣的規(guī)則給 bean 取名之后,那么即使當(dāng)前 bean 已經(jīng)包含在切點(diǎn)所定義的范圍內(nèi),這個(gè) bean 也不會(huì)被代理了。

這是 Spring5.1 開(kāi)始的新玩法。

這種寫(xiě)法的原理是什么呢?

2. 原理分析

在 Spring 創(chuàng)建 Bean 的時(shí)候,小伙伴們都知道,bean 創(chuàng)建完成之后會(huì)去各種后置處理器(BeanPostProcessor)中走一圈,所以一般我們認(rèn)為 BeanPostProcessor 是在 bean 實(shí)例創(chuàng)建完成之后執(zhí)行。但是,BeanPostProcessor 中有一個(gè)特例 InstantiationAwareBeanPostProcessor,這個(gè)接口繼承自 BeanPostProcessor,但是在 BeanPostProcessor 的基礎(chǔ)之上,增加了額外的能力:

  • 在 bean 實(shí)例化之前先做一些預(yù)處理,例如直接創(chuàng)建代理對(duì)象,代替后續(xù)的 bean 生成。
  • 在 bean 實(shí)例化之后但是屬性填充之前,可以自定義一些屬性注入策略。

大致上就是這兩方面的能力。

具體到代碼上,就是在創(chuàng)建 bean 的 createBean 方法中:

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
        throws BeanCreationException {
    //省略。。。
    try {
        // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
        Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
        if (bean != null) {
            return bean;
        }
    }
    catch (Throwable ex) {
        throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
                "BeanPostProcessor before instantiation of bean failed", ex);
    }
    try {
        Object beanInstance = doCreateBean(beanName, mbdToUse, args);
        if (logger.isTraceEnabled()) {
            logger.trace("Finished creating instance of bean '" + beanName + "'");
        }
        return beanInstance;
    }
    //省略。。。
}

小伙伴們看,這里的 resolveBeforeInstantiation 方法就是給 BeanPostProcessor 一個(gè)返回代理對(duì)象的機(jī)會(huì),在這個(gè)方法中,最終就會(huì)觸發(fā)到 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 方法,我們來(lái)看看這里涉及到的跟 AOP 相關(guān)的 AbstractAutoProxyCreator#postProcessBeforeInstantiation 方法:

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
    Object cacheKey = getCacheKey(beanClass, beanName);
    if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
        if (this.advisedBeans.containsKey(cacheKey)) {
            return null;
        }
        if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return null;
        }
    }
    // Create proxy here if we have a custom TargetSource.
    // Suppresses unnecessary default instantiation of the target bean:
    // The TargetSource will handle target instances in a custom fashion.
    TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
    if (targetSource != null) {
        if (StringUtils.hasLength(beanName)) {
            this.targetSourcedBeans.add(beanName);
        }
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
        Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
        this.proxyTypes.put(cacheKey, proxy.getClass());
        return proxy;
    }
    return null;
}

方法作用

這個(gè)方法實(shí)際上干了兩件事:

  • 檢查當(dāng)前 bean 是否需要代理,如果不需要代理,那么就存入到一個(gè) map 集合 advisedBeans 中,key 是 bean 的名字,value 如果為 true 則表示這個(gè) bean 是需要代理的,value 為 false,則表示這個(gè) bean 是不需要代理的。
  • 如果有我們有自定義的 TargetSource,則根據(jù)自定義的 TargetSource 去創(chuàng)建代理對(duì)象。

這里我要和大家說(shuō)的是第一點(diǎn)。

在判斷一個(gè) bean 是否需要代理的時(shí)候,主要依據(jù)兩個(gè)方法:

  • isInfrastructureClass:這個(gè)方法主要是檢查當(dāng)前 bean 是否是 Advice/Advisor/Pointcut 等類(lèi)型,或者這個(gè)類(lèi)上是否有 @Aspect 注解,這個(gè)松哥在之前的文章中其實(shí)和大家介紹過(guò)了:聽(tīng)說(shuō) Spring Bean 的創(chuàng)建還有一條捷徑?。
  • shouldSkip:如果 isInfrastructureClass 方法返回 false,那么就要執(zhí)行 shouldSkip 了,我們來(lái)仔細(xì)看下 shouldSkip 方法。
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
    // TODO: Consider optimization by caching the list of the aspect names
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    for (Advisor advisor : candidateAdvisors) {
        if (advisor instanceof AspectJPointcutAdvisor pointcutAdvisor &&
                pointcutAdvisor.getAspectName().equals(beanName)) {
            return true;
        }
    }
    return super.shouldSkip(beanClass, beanName);
}

這里首先找到系統(tǒng)中所有的切面,找到之后挨個(gè)遍歷,遍歷的時(shí)候判斷如果當(dāng)前要?jiǎng)?chuàng)建的 bean 剛好就是切面,那切面肯定是不需要代理的,直接返回 true。否則就會(huì)去調(diào)用父類(lèi)的 shouldSkip 方法,我們?cè)賮?lái)瞅一眼父類(lèi)的 shouldSkip 方法:

protected boolean shouldSkip(Class<?> beanClass, String beanName) {
    return AutoProxyUtils.isOriginalInstance(beanName, beanClass);
}
static boolean isOriginalInstance(String beanName, Class<?> beanClass) {
    if (!StringUtils.hasLength(beanName) || beanName.length() !=
            beanClass.getName().length() + AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX.length()) {
        return false;
    }
    return (beanName.startsWith(beanClass.getName()) &&
            beanName.endsWith(AutowireCapableBeanFactory.ORIGINAL_INSTANCE_SUFFIX));
}

父類(lèi) shouldSkip 方法主要是調(diào)用了一個(gè)靜態(tài)工具方法 isOriginalInstance 來(lái)判斷當(dāng)前 bean 是否是一個(gè)不需要代理的 bean,這個(gè)具體的判斷邏輯就是檢查這個(gè) beanName 是否按照 類(lèi)名的完整路徑+.ORIGINAL 的方式命名的,如果是則返回 true。

當(dāng) shouldSkip 方法返回 true 的時(shí)候,就會(huì)進(jìn)入到 postProcessBeforeInstantiation 方法的 if 分支中,該分支將當(dāng)前 beanName 存入到 advisedBeans 集合中,存儲(chǔ)的 key 就是 beanName,value 則是 false,然后將方法 return。

當(dāng) bean 創(chuàng)建完成之后,再進(jìn)入到 AbstractAutoProxyCreator#postProcessAfterInitialization 方法中處理的時(shí)候,就會(huì)發(fā)現(xiàn)這個(gè) bean 已經(jīng)存入到 advisedBeans 集合中,并且 value 是 false,這就意味著這個(gè) bean 不需要代理,那么就針對(duì)該 bean 就不會(huì)進(jìn)行 AOP 處理了,直接 return 即可。

以上就是Spring Bean名稱(chēng)不會(huì)被代理的命名技巧的詳細(xì)內(nèi)容,更多關(guān)于Spring Bean命名的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringCloud?Eureka應(yīng)用全面介紹

    SpringCloud?Eureka應(yīng)用全面介紹

    Eureka是Netflix開(kāi)發(fā)的服務(wù)發(fā)現(xiàn)框架,本身是一個(gè)基于REST的服務(wù),主要用于定位運(yùn)行在AWS域中的中間層服務(wù),以達(dá)到負(fù)載均衡和中間層服務(wù)故障轉(zhuǎn)移的目的
    2022-09-09
  • Java類(lèi)初始化順序詳解

    Java類(lèi)初始化順序詳解

    這篇文章主要介紹了Java類(lèi)初始化順序詳解,java語(yǔ)言在使用過(guò)程中最先開(kāi)始就是初始化,在工作中如果遇到什么問(wèn)題需?要定位往往到最后也可能是初始化的問(wèn)題,因此掌握初始化的順序很重要,需要的朋友可以參考下
    2023-08-08
  • SpringMVC 中文亂碼的解決方案

    SpringMVC 中文亂碼的解決方案

    這篇文章主要介紹了SpringMVC 中文亂碼的解決方案,幫助大家更好的理解和學(xué)習(xí)使用SpringMVC,感興趣的朋友可以了解下
    2021-04-04
  • spring與mybatis三種整合方法

    spring與mybatis三種整合方法

    這篇文章主要介紹了spring與mybatis三種整合方法,需要的朋友可以參考下
    2017-04-04
  • 深入分析JAVA 建造者模式

    深入分析JAVA 建造者模式

    這篇文章主要介紹了JAVA 建造者模式的的相關(guān)資料,文中講解非常詳細(xì),代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • Spring整合redis的操作代碼

    Spring整合redis的操作代碼

    這篇文章主要介紹了Spring整合redis的操作代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2022-02-02
  • Java中的布隆過(guò)濾器你真的懂了嗎

    Java中的布隆過(guò)濾器你真的懂了嗎

    經(jīng)常會(huì)聽(tīng)到大家說(shuō)起布隆過(guò)濾器,但是很多人都只是聽(tīng)過(guò)名字,卻并不知道其是怎么實(shí)現(xiàn)的。下面將詳細(xì)介紹一下布隆過(guò)濾器,并且使用簡(jiǎn)單的代碼演示
    2023-04-04
  • Java 詳解如何獲取網(wǎng)絡(luò)接口信息

    Java 詳解如何獲取網(wǎng)絡(luò)接口信息

    讀萬(wàn)卷書(shū)不如行萬(wàn)里路,只學(xué)書(shū)上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)踐中才能獲得能力的提升,本篇文章手把手帶你用Java獲取網(wǎng)絡(luò)接口的信息,大家可以在過(guò)程中查缺補(bǔ)漏,提升水平
    2021-11-11
  • Java中使用BeanMap將對(duì)象轉(zhuǎn)為Map詳解

    Java中使用BeanMap將對(duì)象轉(zhuǎn)為Map詳解

    這篇文章主要介紹了Java中使用BeanMap將對(duì)象轉(zhuǎn)為Map詳解,BeanMap?是?Apache?Commons?BeanUtils?庫(kù)中的一個(gè)類(lèi),BeanMap?可以將?Java?對(duì)象的屬性作為鍵,屬性值作為對(duì)應(yīng)的值,存儲(chǔ)在一個(gè)?Map?中,它提供了一種將?Java?對(duì)象轉(zhuǎn)換為?Map?的方式,需要的朋友可以參考下
    2024-01-01
  • 用Java代碼實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)的基本方法歸納

    用Java代碼實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)的基本方法歸納

    這篇文章主要介紹了用Java代碼實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)的基本方法歸納,各種算法的實(shí)現(xiàn)也是ACM上經(jīng)常出現(xiàn)的題目,是計(jì)算機(jī)學(xué)習(xí)的基本功,需要的朋友可以參考下
    2015-08-08

最新評(píng)論