最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot之通過(guò)BeanPostProcessor動(dòng)態(tài)注入ID生成器案例詳解

 更新時(shí)間:2021年09月03日 11:39:17   作者:沉潛飛動(dòng)  
這篇文章主要介紹了SpringBoot之通過(guò)BeanPostProcessor動(dòng)態(tài)注入ID生成器案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下

在分布式系統(tǒng)中,我們會(huì)需要 ID 生成器的組件,這個(gè)組件可以實(shí)現(xiàn)幫助我們生成順序的或者帶業(yè)務(wù)含義的 ID。

目前有很多經(jīng)典的 ID 生成方式,比如數(shù)據(jù)庫(kù)自增列(自增主鍵或序列)、Snowflake 算法、美團(tuán) Leaf 算法等等,所以,會(huì)有一些公司級(jí)或者業(yè)務(wù)級(jí)的 ID 生成器組件的誕生。本文就是通過(guò) BeanPostProcessor 實(shí)現(xiàn)動(dòng)態(tài)注入 ID 生成器的實(shí)戰(zhàn)。

在 Spring 中,實(shí)現(xiàn)注入的方式很多,比如 springboot 的 starter,在自定義的 Configuration 中初始化 ID 生成器的 Bean,業(yè)務(wù)代碼中通過(guò)@AutoWired或者@Resource注入即可,開(kāi)箱即用。這種方式簡(jiǎn)單直接,但是缺點(diǎn)也是過(guò)于簡(jiǎn)單,缺少了使用方自定義的入口。

考慮一下實(shí)際場(chǎng)景,在同一個(gè)業(yè)務(wù)單據(jù)中,要保持 ID 的唯一,但是在不同單據(jù)中,可以重復(fù)。而且,這些算法在生成 ID 的時(shí)候,為了保持多線(xiàn)程返回結(jié)果唯一,都會(huì)鎖定共享資源。如果不同業(yè)務(wù),并發(fā)情景不同,可能低并發(fā)的業(yè)務(wù)被高并發(fā)的業(yè)務(wù)阻塞獲取 ID,造成一些性能的損失。所以,我們要考慮將 ID 生成器,根據(jù)業(yè)務(wù)隔離開(kāi),這樣 springboot 的 starter 就會(huì)顯得不夠靈活了。

實(shí)現(xiàn)

根據(jù)上面的需求,我們可以分幾步實(shí)現(xiàn)我們的邏輯:

  1. 自定義屬性注解,用于判斷是否需要注入屬性對(duì)象
  2. 定義 ID 生成器接口、實(shí)現(xiàn)類(lèi),以及工廠類(lèi),工廠類(lèi)是為了根據(jù)定義創(chuàng)建不同的 ID 生成器實(shí)現(xiàn)對(duì)象
  3. 定義 BeanPostProcessor,查找使用自定義注解定義的屬性,實(shí)現(xiàn)注入

自定義注解

首先自定義一個(gè)注解,可以定義一個(gè)value屬性,作為隔離業(yè)務(wù)的標(biāo)識(shí):

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface IdGeneratorClient {
    /**
     * ID 生成器名稱(chēng)
     *
     * @return
     */
    String value() default "DEFAULT";
}

定義 ID 生成器

定義 ID 生成器的接口:

public interface IdGenerator {
    String groupName();

    long nextId();
}

實(shí)現(xiàn) ID 生成器接口,偷懶使用AtomicLong實(shí)現(xiàn)自增,同時(shí)考慮 ID 生成器是分組的,通過(guò)ConcurrentHashMap實(shí)現(xiàn) ID 生成器的持有:

class DefaultIdGenerator implements IdGenerator {
    private static final Map<String, AtomicLong> ID_CACHE = new ConcurrentHashMap<>(new HashMap<>());
    private final String groupName;

    DefaultIdGenerator(final String groupName) {
        this.groupName = groupName;
        synchronized (ID_CACHE) {
            ID_CACHE.computeIfAbsent(groupName, key -> new AtomicLong(1));
        }
    }

    @Override
    public String groupName() {
        return this.groupName;
    }

    @Override
    public long nextId() {
        return ID_CACHE.get(this.groupName).getAndIncrement();
    }
}

如前面設(shè)計(jì)的,我們需要一個(gè)工廠類(lèi)來(lái)創(chuàng)建 ID 生成器,示例中使用最簡(jiǎn)單的實(shí)現(xiàn),我們真正使用的時(shí)候,還可以通過(guò)更加靈活的 SPI 實(shí)現(xiàn)(關(guān)于 SPI 的實(shí)現(xiàn),這里挖個(gè)坑,后面專(zhuān)門(mén)寫(xiě)一篇填坑):

public enum IdGeneratorFactory {
    INSTANCE;

    private static final Map<String, IdGenerator> ID_GENERATOR_MAP = new ConcurrentHashMap<>(new HashMap<>());

    public synchronized IdGenerator create(final String groupName) {
        return ID_GENERATOR_MAP.computeIfAbsent(groupName, key -> new DefaultIdGenerator(groupName));
    }
}

定義 BeanPostProcessor

前面都是屬于基本操作,這里才是擴(kuò)展的核心。我們的實(shí)現(xiàn)邏輯是:

  1. 掃描 bean 的所有屬性,然后找到定義了IdGeneratorClient注解的屬性
  2. 獲取注解的value值,作為 ID 生成器的分組標(biāo)識(shí)
  3. 使用IdGeneratorFactory這個(gè)工廠類(lèi)生成 ID 生成器實(shí)例,這里會(huì)返回新建的或已經(jīng)定義的實(shí)例
  4. 通過(guò)反射將 ID 生成器實(shí)例寫(xiě)入 bean
public class IdGeneratorBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
        parseFields(bean);
        return bean;
    }

    private void parseFields(final Object bean) {
        if (bean == null) {
            return;
        }
        Class<?> clazz = bean.getClass();
        parseFields(bean, clazz);

        while (clazz.getSuperclass() != null && !clazz.getSuperclass().equals(Object.class)) {
            clazz = clazz.getSuperclass();
            parseFields(bean, clazz);
        }
    }

    private void parseFields(final Object bean, Class<?> clazz) {
        if (bean == null || clazz == null) {
            return;
        }

        for (final Field field : clazz.getDeclaredFields()) {
            try {
                final IdGeneratorClient annotation = AnnotationUtils.getAnnotation(field, IdGeneratorClient.class);
                if (annotation == null) {
                    continue;
                }

                final String groupName = annotation.value();

                final Class<?> fieldType = field.getType();
                if (fieldType.equals(IdGenerator.class)) {
                    final IdGenerator idGenerator = IdGeneratorFactory.INSTANCE.create(groupName);
                    invokeSetField(bean, field, idGenerator);
                    continue;
                }

                throw new RuntimeException("未知字段類(lèi)型無(wú)法初始化,bean: " + bean + ",field: " + field);
            } catch (Throwable t) {
                throw new RuntimeException("初始化字段失敗,bean=" + bean + ",field=" + field, t);
            }
        }
    }

    private void invokeSetField(final Object bean, final Field field, final Object param) {
        ReflectionUtils.makeAccessible(field);
        ReflectionUtils.setField(field, bean, param);
    }
}

實(shí)現(xiàn)BeanPostProcessor接口需要完成postProcessBeforeInitializationpostProcessAfterInitialization兩個(gè)方法的定義。下圖是 Spring 中 Bean 的實(shí)例化過(guò)程:

Spring 中 Bean 的實(shí)例化過(guò)程圖示

從圖中可以知道,Spring 調(diào)用BeanPostProcessor的這兩個(gè)方法時(shí),bean 已經(jīng)被實(shí)例化,所有能注入的屬性都已經(jīng)被注入了,是一個(gè)完整的 bean。而且兩個(gè)方法的返回值,可以是原來(lái)的 bean 實(shí)例,也可以是包裝后的實(shí)例,這就要看我們的定義了。

測(cè)試我們的代碼

寫(xiě)一個(gè)測(cè)試用例,驗(yàn)證我們的實(shí)現(xiàn)是否生效:

@SpringBootTest
class SpringBeanPostProcessorApplicationTests {
    @IdGeneratorClient
    private IdGenerator defaultIdGenerator;
    @IdGeneratorClient("group1")
    private IdGenerator group1IdGenerator;

    @Test
    void contextLoads() {
        Assert.notNull(defaultIdGenerator, "注入失敗");
        System.out.println(defaultIdGenerator.groupName() + " => " + defaultIdGenerator.nextId());

        Assert.notNull(group1IdGenerator, "注入失敗");
        for (int i = 0; i < 5; i++) {
            System.out.println(defaultIdGenerator.groupName() + " => " + defaultIdGenerator.nextId());
            System.out.println(group1IdGenerator.groupName() + " => " + group1IdGenerator.nextId());
        }
    }

}

運(yùn)行結(jié)果為:

DEFAULT => 1
DEFAULT => 2
group1 => 1
DEFAULT => 3
group1 => 2
DEFAULT => 4
group1 => 3
DEFAULT => 5
group1 => 4
DEFAULT => 6
group1 => 5

可以看到,默認(rèn)的 ID 生成器與定義名稱(chēng)為 group1 的 ID 生成器是分別生成的,符合預(yù)期。

文末思考

我們實(shí)現(xiàn)了通過(guò)BeanPostProcessor實(shí)現(xiàn)自動(dòng)注入自定義的業(yè)務(wù)對(duì)象,上面的實(shí)現(xiàn)還比較簡(jiǎn)單,有很多可以擴(kuò)展的地方,比如工廠方法實(shí)現(xiàn),可以借助 SPI 的方式更加靈活的創(chuàng)建 ID 生成器對(duì)象。同時(shí),考慮到分布式場(chǎng)景,我們還可以在 ID 生成器實(shí)現(xiàn)類(lèi)中,通過(guò)注入 rpc 實(shí)例,實(shí)現(xiàn)遠(yuǎn)程 ID 生成邏輯。

玩法無(wú)限,就看我們的想象了。

源碼

附上源碼:https://github.com/howardliu-cn/effective-spring/tree/main/spring-beanpostprocessor

參考

推薦閱讀

到此這篇關(guān)于SpringBoot之通過(guò)BeanPostProcessor動(dòng)態(tài)注入ID生成器案例詳解的文章就介紹到這了,更多相關(guān)SpringBoot之通過(guò)BeanPostProcessor動(dòng)態(tài)注入ID生成器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java  中ThreadLocal實(shí)例分析

    java 中ThreadLocal實(shí)例分析

    這篇文章主要介紹了java 中ThreadLocal實(shí)例分析的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • Springboot 整合RabbitMq(用心看完這一篇就夠了)

    Springboot 整合RabbitMq(用心看完這一篇就夠了)

    這篇文章主要介紹了Springboot 整合RabbitMq(用心看完這一篇就夠了),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • Spring工作原理簡(jiǎn)單探索

    Spring工作原理簡(jiǎn)單探索

    這篇文章主要介紹了Spring工作原理簡(jiǎn)單探索,涉及Springaop與IOC,動(dòng)態(tài)代理靜態(tài)代理,反射等相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-11-11
  • Java多線(xiàn)程編程中使用Condition類(lèi)操作鎖的方法詳解

    Java多線(xiàn)程編程中使用Condition類(lèi)操作鎖的方法詳解

    Condition是java.util.concurrent.locks包下的類(lèi),提供了對(duì)線(xiàn)程鎖的更精細(xì)的控制方法,下面我們就來(lái)看一下Java多線(xiàn)程編程中使用Condition類(lèi)操作鎖的方法詳解
    2016-07-07
  • IDEA中make directory as的作用及說(shuō)明

    IDEA中make directory as的作用及說(shuō)明

    這篇文章主要介紹了IDEA中make directory as的作用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-09-09
  • java dom4j解析xml用到的幾個(gè)方法

    java dom4j解析xml用到的幾個(gè)方法

    這篇文章主要介紹了java dom4j解析xml用到的幾個(gè)方法,有需要的朋友可以參考一下
    2013-12-12
  • SpringBoot實(shí)現(xiàn)過(guò)濾器和攔截器的方法

    SpringBoot實(shí)現(xiàn)過(guò)濾器和攔截器的方法

    大家應(yīng)該都曉得實(shí)現(xiàn)過(guò)濾器需要實(shí)現(xiàn)?javax.servlet.Filter?接口,而攔截器會(huì)在處理指定請(qǐng)求之前和之后進(jìn)行相關(guān)操作,配置攔截器需要兩步,本文通過(guò)實(shí)例代碼給大家介紹SpringBoot?過(guò)濾器和攔截器的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2022-11-11
  • java枚舉的使用示例

    java枚舉的使用示例

    我們?cè)趯W(xué)習(xí)編程語(yǔ)言的時(shí)候都學(xué)過(guò)枚舉,現(xiàn)在就具體來(lái)看看java中的枚舉的使用
    2013-12-12
  • 使用Spring Cloud Feign作為HTTP客戶(hù)端調(diào)用遠(yuǎn)程HTTP服務(wù)的方法(推薦)

    使用Spring Cloud Feign作為HTTP客戶(hù)端調(diào)用遠(yuǎn)程HTTP服務(wù)的方法(推薦)

    在Spring Cloud中使用Feign, 我們可以做到使用HTTP請(qǐng)求遠(yuǎn)程服務(wù)時(shí)能與調(diào)用本地方法一樣的編碼體驗(yàn),開(kāi)發(fā)者完全感知不到這是遠(yuǎn)程方法,更感知不到這是個(gè)HTTP請(qǐng)求,具體內(nèi)容詳情大家參考下本文
    2018-01-01
  • IDEA配置Tomcat創(chuàng)建web項(xiàng)目的詳細(xì)步驟

    IDEA配置Tomcat創(chuàng)建web項(xiàng)目的詳細(xì)步驟

    Tomcat是一個(gè)Java?Web應(yīng)用服務(wù)器,實(shí)現(xiàn)了多個(gè)Java?EE規(guī)范(JSP、Java?Servlet等),這篇文章主要給大家介紹了關(guān)于IDEA配置Tomcat創(chuàng)建web項(xiàng)目的詳細(xì)步驟,需要的朋友可以參考下
    2023-12-12

最新評(píng)論

华坪县| 常宁市| 绥阳县| 江北区| 蕉岭县| 柏乡县| 犍为县| 泊头市| 保康县| 资兴市| 定结县| 龙游县| 云浮市| 古浪县| 抚远县| 株洲县| 峨眉山市| 武陟县| 神池县| 宁津县| 苏州市| 九江县| 定西市| 湟中县| 沙田区| 类乌齐县| 台北县| 和田县| 九寨沟县| 高碑店市| 沧州市| 札达县| 漳浦县| 桑日县| 醴陵市| 定南县| 天气| 张家港市| 陵水| 上饶县| 怀柔区|