一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - SpringBoot之通過BeanPostProcessor動態(tài)注入ID生成器案例詳解

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

2021-12-16 12:57沉潛飛動 Java教程

這篇文章主要介紹了SpringBoot之通過BeanPostProcessor動態(tài)注入ID生成器案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下

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

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

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

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

實現(xiàn)

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

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

自定義注解

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

?
1
2
3
4
5
6
7
8
9
10
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface IdGeneratorClient {
    /**
     * ID 生成器名稱
     *
     * @return
     */
    String value() default "DEFAULT";
}

定義 ID 生成器

定義 ID 生成器的接口:

?
1
2
3
4
5
public interface IdGenerator {
    String groupName();
 
    long nextId();
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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è)計的,我們需要一個工廠類來創(chuàng)建 ID 生成器,示例中使用最簡單的實現(xiàn),我們真正使用的時候,還可以通過更加靈活的 SPI 實現(xiàn)(關(guān)于 SPI 的實現(xiàn),這里挖個坑,后面專門寫一篇填坑):

?
1
2
3
4
5
6
7
8
9
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

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

  1. 掃描 bean 的所有屬性,然后找到定義了IdGeneratorClient注解的屬性
  2. 獲取注解的value值,作為 ID 生成器的分組標識
  3. 使用IdGeneratorFactory這個工廠類生成 ID 生成器實例,這里會返回新建的或已經(jīng)定義的實例
  4. 通過反射將 ID 生成器實例寫入 bean
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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("未知字段類型無法初始化,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);
    }
}

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

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

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

測試我們的代碼

寫一個測試用例,驗證我們的實現(xiàn)是否生效:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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());
        }
    }
 
}

運行結(jié)果為:

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

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

文末思考

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

玩法無限,就看我們的想象了。

源碼

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

參考

推薦閱讀

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

原文鏈接:https://www.howardliu.cn/spring-beanpostprocessor/

延伸 · 閱讀

精彩推薦
  • Java教程20個非常實用的Java程序代碼片段

    20個非常實用的Java程序代碼片段

    這篇文章主要為大家分享了20個非常實用的Java程序片段,對java開發(fā)項目有所幫助,感興趣的小伙伴們可以參考一下 ...

    lijiao5352020-04-06
  • Java教程Java BufferWriter寫文件寫不進去或缺失數(shù)據(jù)的解決

    Java BufferWriter寫文件寫不進去或缺失數(shù)據(jù)的解決

    這篇文章主要介紹了Java BufferWriter寫文件寫不進去或缺失數(shù)據(jù)的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望...

    spcoder14552021-10-18
  • Java教程小米推送Java代碼

    小米推送Java代碼

    今天小編就為大家分享一篇關(guān)于小米推送Java代碼,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧...

    富貴穩(wěn)中求8032021-07-12
  • Java教程Java使用SAX解析xml的示例

    Java使用SAX解析xml的示例

    這篇文章主要介紹了Java使用SAX解析xml的示例,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下...

    大行者10067412021-08-30
  • Java教程xml與Java對象的轉(zhuǎn)換詳解

    xml與Java對象的轉(zhuǎn)換詳解

    這篇文章主要介紹了xml與Java對象的轉(zhuǎn)換詳解的相關(guān)資料,需要的朋友可以參考下...

    Java教程網(wǎng)2942020-09-17
  • Java教程升級IDEA后Lombok不能使用的解決方法

    升級IDEA后Lombok不能使用的解決方法

    最近看到提示IDEA提示升級,尋思已經(jīng)有好久沒有升過級了。升級完畢重啟之后,突然發(fā)現(xiàn)好多錯誤,本文就來介紹一下如何解決,感興趣的可以了解一下...

    程序猿DD9332021-10-08
  • Java教程Java實現(xiàn)搶紅包功能

    Java實現(xiàn)搶紅包功能

    這篇文章主要為大家詳細介紹了Java實現(xiàn)搶紅包功能,采用多線程模擬多人同時搶紅包,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙...

    littleschemer13532021-05-16
  • Java教程Java8中Stream使用的一個注意事項

    Java8中Stream使用的一個注意事項

    最近在工作中發(fā)現(xiàn)了對于集合操作轉(zhuǎn)換的神器,java8新特性 stream,但在使用中遇到了一個非常重要的注意點,所以這篇文章主要給大家介紹了關(guān)于Java8中S...

    阿杜7482021-02-04
主站蜘蛛池模板: 国产一区日韩二区欧美三区 | 亚洲精品一区二区三区在线看 | 亚洲国产精品福利片在线观看 | 好湿好紧太硬了我太爽了网站 | 国产成人愉拍精品 | 国产福利不卡视频在免费 | 日本性生活大片 | 国产精品午夜久久 | 视频在线网站 | 欧美粗黑巨大gay | yellow视频免费观看播放 | 蜜桃88av| 欧美一区二区三区gg高清影视 | 人性本色| 亚洲国产精品久久无套麻豆 | 久草在线福利视频在线播放 | 免费在线观看小视频 | 国产成人激烈叫床视频 | 亚久久伊人精品青青草原2020 | 日韩精品视频在线播放 | 好姑娘完整版在线观看中文 | 成人国产精品一区二区不卡 | 色婷婷婷丁香亚洲综合不卡 | 亚洲国产成人久久99精品 | 久久伊人久久 | 青青草99久久精品国产综合 | 久久国产加勒比精品无码 | 亚洲v成人天堂影视 | 亚洲精品在线网址 | 欧美一级裸片又黄又裸 | 亚洲 在线 日韩 欧美 | 亚洲 综合 自拍 精品 在线 | 精品日韩欧美一区二区三区 | 久久影院中文字幕 | 亚洲精品成人AV在线观看爽翻 | 天天做天天爰夜夜爽 | 京东热dj6666 | 国产精品www夜色影视 | 四虎精品永久免费 | 高清不卡免费一区二区三区 | 女人爽到喷水的视频免费 |