«Это 23-й день моего участия в ноябрьском испытании обновлений. Подробную информацию об этом событии см.:Вызов последнего обновления 2021 г."
Эта статья была«Весенняя загрузка в действии»Коллекция столбцов.
Привет, я смотрю на гору.
В распределенной системе нам понадобится компонент генератора идентификаторов, который можно реализовать, чтобы помочь нам генерировать последовательные или важные для бизнеса идентификаторы.
В настоящее время существует множество классических методов генерации идентификаторов, таких как столбец автоинкремента базы данных (первичный ключ или последовательность автоинкремента), алгоритм Snowflake, алгоритм Meituan Leaf и т. д. Следовательно, будет какой-то уровень компании или бизнес-уровня. Компоненты генератора ID. Эта статья представляет собой настоящую борьбу с динамическим внедрением генератора идентификаторов через BeanPostProcessor.
В Spring существует множество способов реализации инъекции, например, запуск Springboot, bean-компонент генератора идентификаторов инициализируется в пользовательской конфигурации, а бизнес-код передается через@AutoWired
или@Resource
Просто введите его прямо из коробки. Этот метод прост и прямолинеен, но недостатком является то, что он слишком прост и не имеет пользовательского ввода.
Рассмотрим реальный сценарий: в одном и том же бизнес-документе необходимо сохранить уникальный идентификатор, но в разных документах он может повторяться. Более того, когда эти алгоритмы генерируют идентификаторы, чтобы сохранить уникальные результаты, возвращаемые несколькими потоками, они блокируют общие ресурсы. Если разные службы имеют разные сценарии параллелизма, службы с низким уровнем параллелизма могут быть заблокированы службами с высоким уровнем параллелизма для получения идентификаторов, что приводит к некоторым потерям производительности. Поэтому мы должны рассмотреть возможность изоляции генератора идентификаторов в соответствии с бизнесом, чтобы стартер Springboot не был достаточно гибким.
выполнить
Согласно вышеуказанным требованиям, мы можем реализовать нашу логику в несколько шагов:
- Аннотация пользовательского свойства, используемая для определения необходимости внедрения объекта свойства.
- Определите интерфейс генератора идентификаторов, класс реализации и класс фабрики, класс фабрики предназначен для создания различных объектов реализации генератора идентификаторов в соответствии с определением.
- Определите BeanPostProcessor, найдите свойства, определенные с помощью пользовательских аннотаций, и внедрите внедрение
пользовательская аннотация
Сначала настройте аннотацию, вы можете определитьvalue
Атрибут, как идентификатор изолированного бизнеса:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface IdGeneratorClient {
/**
* ID 生成器名称
*
* @return
*/
String value() default "DEFAULT";
}
Определить генератор идентификаторов
Определите интерфейс для генератора идентификаторов:
public interface IdGenerator {
String groupName();
long nextId();
}
Реализуйте интерфейс генератора идентификаторов и используйте его ленивоAtomicLong
Реализовать автоинкремент с учетом того, что генератор ID сгруппирован поConcurrentHashMap
Реализуйте генератор идентификаторов, содержащий:
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();
}
}
Как было задумано ранее, нам нужен фабричный класс для создания генератора идентификаторов.В примере используется самая простая реализация.Когда мы на самом деле используем его, мы также можем использовать более гибкую реализацию SPI (для реализации SPI мы будем копать отверстие здесь, а позже напишите специальный кусок, чтобы заполнить яму):
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
Все первые являются базовыми операциями, а вот ядро расширения. Наша логика реализации такова:
- Сканирует все свойства бина и находит
IdGeneratorClient
Аннотированные свойства - получать аннотации
value
значение, как идентификатор группы для генератора идентификаторов - использовать
IdGeneratorFactory
Этот фабричный класс генерирует экземпляры генератора идентификаторов, которые будут возвращать новые или уже определенные экземпляры. - Записать экземпляр генератора идентификаторов в 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("未知字段类型无法初始化,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);
}
}
выполнитьBeanPostProcessor
Интерфейс надо делатьpostProcessBeforeInitialization
иpostProcessAfterInitialization
Определение двух методов. На следующем рисунке показан процесс создания экземпляров Beans в Spring:
Схема процесса инстанцирования bean-компонентов в Spring
Как видно из рисунка, Spring вызываетBeanPostProcessor
При использовании этих двух методов создается экземпляр bean-компонента, внедряются все внедряемые свойства, и он является законченным bean-компонентом. И возвращаемое значение двух методов может быть исходным экземпляром компонента или обернутым экземпляром, что зависит от нашего определения.
проверить наш код
Напишите тестовый пример, чтобы убедиться, что наша реализация работает:
@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());
}
}
}
Текущий результат:
DEFAULT => 1
DEFAULT => 2
group1 => 1
DEFAULT => 3
group1 => 2
DEFAULT => 4
group1 => 3
DEFAULT => 5
group1 => 4
DEFAULT => 6
group1 => 5
Как видите, генератор идентификаторов по умолчанию и генератор идентификаторов с именем определения group1 генерируются отдельно, как и ожидалось.
Мысли в конце статьи
Мы достигли черезBeanPostProcessor
Чтобы добиться автоматического внедрения пользовательских бизнес-объектов, приведенная выше реализация относительно проста, и есть много мест, которые можно расширить, например, реализация фабричного метода, который может более гибко создавать объекты генератора идентификаторов с помощью SPI. В то же время, учитывая распределенный сценарий, мы также можем реализовать логику удаленной генерации идентификатора, внедрив экземпляры rpc в класс реализации генератора идентификаторов.
Геймплей ничем не ограничен, все зависит от нашего воображения. Вы можете обратить внимание на публичный аккаунт «Хижина Горы» и ответить «весне», чтобы получить исходный код.
Рекомендуемое чтение
- SpringBoot Combat: элегантный ответ для достижения результата
- Борьба с SpringBoot: как изящно обрабатывать исключения
- Бои SpringBoot: динамическая инъекция генератора идентификаторов через BeanPostProcessor
- SpringBoot Combat: пользовательский фильтр элегантно получает параметры запроса и результаты ответа
- Бои SpringBoot: элегантное использование параметров перечисления
- Бои SpringBoot: элегантное использование параметров перечисления (принцип)
- Бой SpringBoot: элегантное использование параметров перечисления в RequestBody
- Бой SpringBoot: элегантное использование параметров перечисления в RequestBody (принцип)
- SpringBoot бой: JUnit5+MockMvc+Mockito проводят модульное тестирование
- SpringBoot бой: загрузка и чтение содержимого файла ресурсов
Привет, я смотрю на гору. Плавайте в мире кода, играйте и наслаждайтесь жизнью. Если статья была вам полезна, ставьте лайк, добавляйте в закладки и подписывайтесь. Приглашаем обратить внимание на паблик-аккаунт «Глядя на горную хижину» и открыть для себя другой мир.