在分布式系統(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)我們的邏輯:
- 自定義屬性注解,用于判斷是否需要注入屬性對象
- 定義 ID 生成器接口、實現(xiàn)類,以及工廠類,工廠類是為了根據(jù)定義創(chuàng)建不同的 ID 生成器實現(xiàn)對象
- 定義 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)邏輯是:
-
掃描 bean 的所有屬性,然后找到定義了
IdGeneratorClient
注解的屬性 -
獲取注解的
value
值,作為 ID 生成器的分組標識 -
使用
IdGeneratorFactory
這個工廠類生成 ID 生成器實例,這里會返回新建的或已經(jīng)定義的實例 - 通過反射將 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
接口需要完成postProcessBeforeInitialization
和postProcessAfterInitialization
兩個方法的定義。下圖是 Spring 中 Bean 的實例化過程:
從圖中可以知道,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
|
@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()); } } } |
運行結(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
參考
推薦閱讀
- SpringBoot 實戰(zhàn):一招實現(xiàn)結(jié)果的優(yōu)雅響應(yīng)
- SpringBoot 實戰(zhàn):如何優(yōu)雅的處理異常
- SpringBoot 實戰(zhàn):通過 BeanPostProcessor 動態(tài)注入 ID 生成器
- SpringBoot 實戰(zhàn):自定義 Filter 優(yōu)雅獲取請求參數(shù)和響應(yīng)結(jié)果
- SpringBoot 實戰(zhàn):優(yōu)雅的使用枚舉參數(shù)
- SpringBoot 實戰(zhàn):優(yōu)雅的使用枚舉參數(shù)(原理篇)
- SpringBoot 實戰(zhàn):在 RequestBody 中優(yōu)雅的使用枚舉參數(shù)
- SpringBoot 實戰(zhàn):在 RequestBody 中優(yōu)雅的使用枚舉參數(shù)(原理篇)
到此這篇關(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/