Custom Spring extensions

01.07.2026 | Muallif: Jaxongir a.k.a | Kategoriya: Spring Boot | 26 daqiqa o'qish

Custom Spring extensions - Spring Boot’ni faqat ishlatish emas, balki uni o‘z loyihangiz yoki kompaniya platformangizga moslab kengaytirish demakdir.

Bu maqola quyidagilarni qamrab oladi: custom auto-configuration, custom starter, custom Actuator endpoint, custom annotation + AOP, ApplicationListener, SmartLifecycle.


1. Custom Spring extensions nima?

Oddiy developer Spring Boot’dan tayyor holda foydalanadi:

spring-boot-starter-web
spring-boot-starter-data-jpa
spring-boot-starter-security

Senior developer esa kerak bo‘lsa o‘zi ham shunday reusable extension yozadi:

company-security-starter
company-observability-starter
company-multitenancy-starter
company-kafka-starter
company-audit-starter

Maqsad:

Bir xil config va boilerplate kodni har projectda qayta yozmaslik.

Masalan, kompaniyada 20 ta microservice bor. Har birida quyidagilar kerak:

JWT security config
Tracing config
Audit logging
Tenant filter
Kafka error handler
Actuator custom endpoint
Common exception handler

Bularni har service’da copy-paste qilish yomon. Yaxshiroq yechim:

company-platform-starter

2. Qachon custom extension kerak?

Custom extension kerak bo‘ladi:

[+] Bir xil config 3+ projectda takrorlansa
[+] Platform team umumiy standart bermoqchi bo‘lsa
[+] Security/observability/kafka config markazlashtirilsa
[+] Internal library reusable bo‘lishi kerak bo‘lsa
[+] Teamlar bir xil convention bilan ishlashi kerak bo‘lsa

Kerak emas:

[-] Faqat bitta project uchun bo‘lsa
[-] Oddiy 2-3 ta bean uchun ortiqcha abstraction bo‘lsa
[-] Extension projectni tushunishni qiyinlashtirsa
[-] Team hali Spring internals’ni yaxshi bilmasa

Senior qoida:

Abstraction foyda bersa yoziladi. Faqat "chiroyli ko‘rinsin" deb starter yozilmaydi.

3. Custom auto-configuration

Auto-configuration nima?

Spring Boot auto-configuration classpath’dagi dependency va mavjud beanlarga qarab avtomatik config qiladi. Rasmiy hujjatda Spring Boot auto-configuration application’ni qo‘shilgan jar dependency’lar asosida avtomatik sozlashga urinishi aytiladi. (Home)

Masalan:

spring-boot-starter-data-jpa bor
↓
DataSource kerak
↓
Hibernate kerak
↓
JpaRepository kerak
↓
Spring Boot ko‘p config’ni o‘zi qiladi

Custom auto-configuration ham xuddi shunday:

company-audit-starter dependency qo‘shildi
↓
AuditLogger bean avtomatik yaratildi
↓
AuditAspect avtomatik yoqildi

Oddiy custom auto-configuration misol

Tasavvur qilamiz, bizda audit logging library bor.

1. Properties class

package uz.company.audit;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "company.audit")
public class AuditProperties {

    private boolean enabled = true;
    private String applicationName = "unknown-service";

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public String getApplicationName() {
        return applicationName;
    }

    public void setApplicationName(String applicationName) {
        this.applicationName = applicationName;
    }
}

2. Audit service

package uz.company.audit;

public class AuditLogger {

    private final AuditProperties properties;

    public AuditLogger(AuditProperties properties) {
        this.properties = properties;
    }

    public void log(String action, String userId) {
        System.out.printf(
                "audit app=%s action=%s userId=%s%n",
                properties.getApplicationName(),
                action,
                userId
        );
    }
}

3. Auto-configuration class

package uz.company.audit.autoconfigure;

import uz.company.audit.AuditLogger;
import uz.company.audit.AuditProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

@AutoConfiguration
@EnableConfigurationProperties(AuditProperties.class)
@ConditionalOnProperty(
        prefix = "company.audit",
        name = "enabled",
        havingValue = "true",
        matchIfMissing = true
)
public class AuditAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public AuditLogger auditLogger(AuditProperties properties) {
        return new AuditLogger(properties);
    }
}

Spring Boot custom auto-configuration yaratish bo‘yicha rasmiy hujjatda auto-configuration starter bilan bog‘lanishi, starter esa auto-configuration code va kerakli library’larni olib kelishi tushuntiriladi. (Home)


4. AutoConfiguration.imports

Spring Boot 3'da auto-configuration class’ni e’lon qilish uchun odatda quyidagi file ishlatiladi:

src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Ichida:

uz.company.audit.autoconfigure.AuditAutoConfiguration

Shundan keyin boshqa project dependency qo‘shsa, Spring Boot bu auto-configuration’ni ko‘radi.


5. Conditional annotations

Custom extension yozishda eng muhim narsa - shartli ishlash.

Yomon starter:

Dependency qo‘shilishi bilan hamma narsani majburan yaratadi.

Yaxshi starter:

Faqat kerakli class/property/bean bo‘lsa ishlaydi.
User o‘z bean’ini bersa, starter chekinadi.

Muhim condition annotationlar

Annotation

Vazifasi

@ConditionalOnClass

Classpath’da class bo‘lsa ishlaydi

@ConditionalOnMissingClass

Class yo‘q bo‘lsa ishlaydi

@ConditionalOnBean

Bean bor bo‘lsa ishlaydi

@ConditionalOnMissingBean

Bean yo‘q bo‘lsa default bean yaratadi

@ConditionalOnProperty

Property bo‘yicha yoqadi/o‘chiradi

@ConditionalOnWebApplication

Web app bo‘lsa ishlaydi

@ConditionalOnNotWebApplication

Web app bo‘lmasa ishlaydi

Misol:

@Bean
@ConditionalOnMissingBean
public AuditLogger auditLogger(AuditProperties properties) {
    return new AuditLogger(properties);
}

Bu nima degani?

Agar user o‘zi AuditLogger bean bermagan bo‘lsa,
starter default AuditLogger yaratadi.

Bu extension yozishda juda muhim odob:

User override qila olishi kerak.

6. Custom starter

Starter nima?

Starter - dependencylarni va auto-configuration’ni bir joyga yig‘adigan library.

Masalan:

<dependency>
    <groupId>uz.company</groupId>
    <artifactId>company-audit-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

Shuni qo‘shgan project avtomatik quyidagilarga ega bo‘ladi:

AuditProperties
AuditLogger
AuditAspect
Audit actuator endpoint
Audit metrics

Starter project structure

company-audit-spring-boot-starter/
  src/main/java/
    uz/company/audit/
      AuditLogger.java
      AuditProperties.java
      AuditAspect.java

    uz/company/audit/autoconfigure/
      AuditAutoConfiguration.java

  src/main/resources/
    META-INF/spring/
      org.springframework.boot.autoconfigure.AutoConfiguration.imports

  pom.xml

Starter pom.xml

<project>
    <groupId>uz.company</groupId>
    <artifactId>company-audit-spring-boot-starter</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </dependency>
    </dependencies>
</project>

spring-boot-configuration-processor foydasi:

application.yml da autocomplete va metadata beradi.

Masalan:

company:
  audit:
    enabled: true
    application-name: order-service

7. Starter ishlatish

Boshqa microservice’da faqat dependency qo‘shiladi:

<dependency>
    <groupId>uz.company</groupId>
    <artifactId>company-audit-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

Config:

company:
  audit:
    enabled: true
    application-name: order-service

Service’da:

@Service
@RequiredArgsConstructor
public class OrderService {

    private final AuditLogger auditLogger;

    public void cancelOrder(Long orderId, String userId) {
        auditLogger.log("ORDER_CANCELLED", userId);
    }
}

Bitta starter bilan 20 ta service’da bir xil audit standard ishlaydi.


8. Custom annotation + AOP

Muammo

Audit logni har joyda qo‘lda chaqirish ham boilerplate:

auditLogger.log("ORDER_CANCELLED", userId);

Yaxshiroq:

@Auditable(action = "ORDER_CANCELLED")
public void cancelOrder(Long orderId) {
    ...
}

Custom annotation

package uz.company.audit;

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Auditable {
    String action();
}

Aspect

package uz.company.audit;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class AuditAspect {

    private final AuditLogger auditLogger;
    private final CurrentUserProvider currentUserProvider;

    public AuditAspect(
            AuditLogger auditLogger,
            CurrentUserProvider currentUserProvider
    ) {
        this.auditLogger = auditLogger;
        this.currentUserProvider = currentUserProvider;
    }

    @Around("@annotation(auditable)")
    public Object audit(
            ProceedingJoinPoint joinPoint,
            Auditable auditable
    ) throws Throwable {
        String userId = currentUserProvider.getCurrentUserId();

        try {
            Object result = joinPoint.proceed();

            auditLogger.log(auditable.action() + "_SUCCESS", userId);

            return result;
        } catch (Throwable ex) {
            auditLogger.log(auditable.action() + "_FAILED", userId);
            throw ex;
        }
    }
}

Auto-config’da aspect qo‘shamiz:

@Bean
@ConditionalOnMissingBean
public AuditAspect auditAspect(
        AuditLogger auditLogger,
        CurrentUserProvider currentUserProvider
) {
    return new AuditAspect(auditLogger, currentUserProvider);
}

Ishlatish

@Service
public class OrderService {

    @Auditable(action = "ORDER_CANCELLED")
    public void cancelOrder(Long orderId) {
        // business logic
    }
}

Natija:

Method success bo‘lsa → ORDER_CANCELLED_SUCCESS
Exception bo‘lsa → ORDER_CANCELLED_FAILED

9. Custom annotation yozishda ehtiyot bo‘lish

1. Self-invocation muammosi

Spring AOP proxy orqali ishlaydi.

@Service
public class OrderService {

    public void methodA() {
        methodB(); // self call
    }

    @Auditable(action = "METHOD_B")
    public void methodB() {
    }
}

Bu holatda methodB() annotation’i ishlamasligi mumkin, chunki chaqiriq proxy’dan o‘tmaydi.

Yechimlar:

- Annotated methodni boshqa bean’ga chiqarish
- AspectJ weaving ishlatish
- Self-invocation’dan qochish

2. Private methodlarga AOP ishlamasligi

Yomon:

@Auditable(action = "PRIVATE")
private void doWork() {
}

Spring AOP odatda public/proxy orqali chaqiriladigan methodlarda ishlaydi.


3. Annotation business logic’ni yashirib yubormasin

Annotation juda ko‘payib ketsa:

@Auditable
@Retryable
@Transactional
@Cacheable
@PreAuthorize

methodning real behavior’ini tushunish qiyinlashadi.

Senior qoida:

Annotation abstraction bo‘lsa ham, behavior aniq va predictable bo‘lsin.

10. Custom Actuator endpoint

Nima uchun kerak?

Spring Boot Actuator application’ni kuzatish va boshqarish uchun endpointlar beradi; rasmiy hujjatda Actuator ichki endpointlardan tashqari custom endpoint qo‘shish imkonini ham berishi aytiladi. (Home)

Custom endpoint misollar:

/actuator/features
/actuator/tenants
/actuator/build-info
/actuator/kafka-status
/actuator/outbox

Custom endpoint misol

package uz.company.platform.actuator;

import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;

import java.util.Map;

@Endpoint(id = "features")
public class FeatureFlagsEndpoint {

    private final FeatureFlagService featureFlagService;

    public FeatureFlagsEndpoint(FeatureFlagService featureFlagService) {
        this.featureFlagService = featureFlagService;
    }

    @ReadOperation
    public Map<String, Boolean> features() {
        return featureFlagService.getAllFlags();
    }
}

Auto-config:

@Bean
@ConditionalOnBean(FeatureFlagService.class)
public FeatureFlagsEndpoint featureFlagsEndpoint(
        FeatureFlagService featureFlagService
) {
    return new FeatureFlagsEndpoint(featureFlagService);
}

Config:

management:
  endpoints:
    web:
      exposure:
        include: health,info,features

Endpoint:

GET /actuator/features

Response:

{
  "paymentV2": true,
  "newCheckout": false
}

Custom Actuator endpoint xavfsizligi

Actuator endpointlar production’da ehtiyotkorlik bilan ochiladi.

Yomon:

management:
  endpoints:
    web:
      exposure:
        include: "*"

Yaxshi:

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus,features

Security config:

@Bean
SecurityFilterChain actuatorSecurity(HttpSecurity http) throws Exception {
    return http
            .securityMatcher("/actuator/**")
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers("/actuator/health").permitAll()
                    .requestMatchers("/actuator/prometheus").hasRole("MONITORING")
                    .anyRequest().hasRole("ADMIN")
            )
            .build();
}

Senior qoida:

Actuator endpoint operational data beradi. Uni public ochmang.

11. Custom HealthIndicator

Agar sizda muhim dependency bo‘lsa, custom health indicator yozish mumkin.

Masalan, payment provider status:

@Component
public class PaymentProviderHealthIndicator implements HealthIndicator {

    private final PaymentProviderClient client;

    public PaymentProviderHealthIndicator(PaymentProviderClient client) {
        this.client = client;
    }

    @Override
    public Health health() {
        try {
            boolean ok = client.ping();

            if (ok) {
                return Health.up()
                        .withDetail("provider", "payment-provider")
                        .build();
            }

            return Health.down()
                    .withDetail("provider", "payment-provider")
                    .build();
        } catch (Exception ex) {
            return Health.down(ex).build();
        }
    }
}

Lekin ehtiyot:

Har dependency’ni liveness ichiga qo‘shmang.
Critical dependency readiness’da bo‘lishi mumkin.

12. Application events

Spring ichida event publish/listen qilish mumkin.

Event class

public record OrderCreatedEvent(
        Long orderId,
        Long userId
) {}

Publish qilish

@Service
@RequiredArgsConstructor
public class OrderService {

    private final ApplicationEventPublisher eventPublisher;

    @Transactional
    public void createOrder(CreateOrderRequest request) {
        Order order = saveOrder(request);

        eventPublisher.publishEvent(
                new OrderCreatedEvent(order.getId(), order.getUserId())
        );
    }
}

Listen qilish

@Component
public class OrderCreatedListener {

    @EventListener
    public void handle(OrderCreatedEvent event) {
        System.out.println("Order created: " + event.orderId());
    }
}

13. Transactional event listener

Oddiy @EventListener transaction ichida darhol ishlashi mumkin.

Agar eventni faqat DB commit bo‘lgandan keyin ishlatmoqchi bo‘lsangiz:

@Component
public class OrderCreatedListener {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handle(OrderCreatedEvent event) {
        // DB commit bo‘lgandan keyin ishlaydi
    }
}

Bu muhim.

Yomon flow:

Order save qilindi
Event publish qilindi
Listener notification yubordi
Keyin DB transaction rollback bo‘ldi

Natija:

User notification oldi, lekin order DB’da yo‘q.

Yaxshi:

DB commit
↓
AFTER_COMMIT listener
↓
Notification

14. @Async event listener

Event listener’ni async qilish mumkin:

@Component
public class OrderCreatedListener {

    @Async("eventExecutor")
    @EventListener
    public void handle(OrderCreatedEvent event) {
        // background processing
    }
}

Executor:

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean
    public Executor eventExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(1000);
        executor.setThreadNamePrefix("event-");
        executor.initialize();
        return executor;
    }
}

Ehtiyot:

Async event transaction boundary va error handlingni murakkablashtiradi.

Agar event juda muhim bo‘lsa:

Spring event emas, outbox + Kafka ishlating.

15. ApplicationListener

@EventListener o‘rniga class-based listener ham yozish mumkin.

@Component
public class OrderCreatedApplicationListener
        implements ApplicationListener<OrderCreatedEvent> {

    @Override
    public void onApplicationEvent(OrderCreatedEvent event) {
        System.out.println("Order created: " + event.orderId());
    }
}

Qachon kerak?

- Framework-level extension yozilganda
- Generic listener kerak bo‘lsa
- Starter ichida explicit contract kerak bo‘lsa

16. Spring Boot lifecycle events

Spring Boot application start paytida turli eventlar chiqaradi.

Ko‘p ishlatiladiganlari:

Event

Qachon ishlaydi

ApplicationStartingEvent

Juda erta

ApplicationEnvironmentPreparedEvent

Environment tayyor

ApplicationPreparedEvent

Context tayyorlanmoqda

ApplicationStartedEvent

Context refresh bo‘ldi

ApplicationReadyEvent

App request qabul qilishga tayyor

ApplicationFailedEvent

Startup fail bo‘ldi

Masalan:

@Component
public class StartupLogger {

    @EventListener(ApplicationReadyEvent.class)
    public void onReady() {
        System.out.println("Application is ready");
    }
}

ApplicationReadyEvent - warmup yoki startup tugaganidan keyingi ishlar uchun qulay.

Lekin og‘ir ishni shu yerda sync bajarish ham app readiness’ga ta’sir qilishi mumkin.


17. SmartLifecycle

Nima?

SmartLifecycle - Spring context start/stop jarayonida komponentni boshqarish uchun ishlatiladi.

Masalan:

Kafka consumerni custom start qilish
Background worker boshlash
Scheduler-like processor ishga tushirish
External connection ochish
Graceful shutdown’da to‘xtatish

Spring Framework SmartLifecycle interface’i Lifecycle'ni kengaytiradi va auto-startup, phase kabi imkoniyatlar beradi. (Home)


SmartLifecycle misol

@Component
public class OutboxWorkerLifecycle implements SmartLifecycle {

    private final OutboxWorker worker;
    private volatile boolean running = false;

    public OutboxWorkerLifecycle(OutboxWorker worker) {
        this.worker = worker;
    }

    @Override
    public void start() {
        worker.start();
        running = true;
    }

    @Override
    public void stop() {
        worker.stop();
        running = false;
    }

    @Override
    public boolean isRunning() {
        return running;
    }

    @Override
    public boolean isAutoStartup() {
        return true;
    }

    @Override
    public int getPhase() {
        return 100;
    }
}

getPhase() nima?

Phase start/stop tartibini boshqaradi.

Kichik phase → oldin start
Katta phase → keyin start

Stop paytida aksincha:
Katta phase → oldin stop
Kichik phase → keyin stop

Masalan:

Database connection manager phase=0
Outbox worker phase=100

Start:

DB avval
Worker keyin

Stop:

Worker avval
DB keyin

Bu graceful shutdown uchun juda foydali.


18. Custom BeanPostProcessor

Nima?

BeanPostProcessor bean yaratilgandan keyin uni o‘zgartirish yoki tekshirish imkonini beradi.

Bu advanced Spring internals mavzusi.

Misol: @Sensitive annotationli beanlarni startup’da tekshirish.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveComponent {
}

Processor:

@Component
public class SensitiveBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(
            Object bean,
            String beanName
    ) throws BeansException {

        if (bean.getClass().isAnnotationPresent(SensitiveComponent.class)) {
            System.out.println("Sensitive bean registered: " + beanName);
        }

        return bean;
    }
}

Ehtiyot:

BeanPostProcessor noto‘g‘ri yozilsa, butun application context behavior’i buzilishi mumkin.

Buni faqat haqiqiy zaruratda ishlating.


19. Custom annotation for validation

Spring extension faqat AOP emas. Bean Validation uchun ham custom annotation yoziladi.

Annotation

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface ValidUzPhone {

    String message() default "Invalid Uzbekistan phone number";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Validator

public class PhoneNumberValidator
        implements ConstraintValidator<ValidUzPhone, String> {

    private static final Pattern PATTERN =
            Pattern.compile("^\\+998\\d{9}$");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null || value.isBlank()) {
            return true;
        }

        return PATTERN.matcher(value).matches();
    }
}

Ishlatish

public record RegisterRequest(
        @NotBlank String name,
        @ValidUzPhone String phone
) {}

20. Custom meta-annotation

Ba’zida bir nechta annotationni bitta annotationga yig‘ish mumkin.

Masalan:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasAuthority('SCOPE_orders.write')")
@Transactional
@Auditable(action = "ORDER_WRITE")
public @interface OrderWriteOperation {
}

Ishlatish:

@OrderWriteOperation
public void cancelOrder(Long orderId) {
    ...
}

Ehtiyot:

Meta-annotation behaviorni yashirib yuborishi mumkin.
Team buni yaxshi tushunsa ishlating.

21. Custom starter uchun test

Starter yozsangiz, uni albatta test qilish kerak.

Spring Boot’da ApplicationContextRunner custom auto-configuration testlari uchun juda qulay.

class AuditAutoConfigurationTest {

    private final ApplicationContextRunner contextRunner =
            new ApplicationContextRunner()
                    .withConfiguration(
                            AutoConfigurations.of(AuditAutoConfiguration.class)
                    );

    @Test
    void shouldCreateAuditLoggerByDefault() {
        contextRunner.run(context -> {
            assertThat(context).hasSingleBean(AuditLogger.class);
        });
    }

    @Test
    void shouldDisableAuditWhenPropertyFalse() {
        contextRunner
                .withPropertyValues("company.audit.enabled=false")
                .run(context -> {
                    assertThat(context).doesNotHaveBean(AuditLogger.class);
                });
    }

    @Test
    void shouldBackOffWhenUserProvidesAuditLogger() {
        contextRunner
                .withBean(AuditLogger.class, () -> new CustomAuditLogger())
                .run(context -> {
                    assertThat(context).hasSingleBean(AuditLogger.class);
                });
    }
}

Test qilinadigan narsalar:

[ ] Default bean yaratiladimi?
[ ] Property orqali o‘chadimi?
[ ] User custom bean bersa back off qiladimi?
[ ] Kerakli class yo‘q bo‘lsa ishlamayaptimi?
[ ] Web/non-web context farqi to‘g‘rimi?

22. Versioning va compatibility

Internal starter ham product kabi versionlanadi.

Yomon:

company-starter:latest

Yaxshi:

company-starter:1.4.2

Semantic versioning:

Version

Ma’nosi

1.4.3

Bug fix

1.5.0

Backward-compatible feature

2.0.0

Breaking change

Senior qoida:

Starter yangilanishi 20 ta service’ni buzmasligi kerak.

23. Real example: company observability starter

Kompaniyada har microservice’da bir xil observability kerak:

traceId loglarda
common MDC filter
custom metrics
standard actuator exposure
correlation id
exception metrics

Starter:

company-observability-starter

Ichida:

CorrelationIdFilter
MdcLoggingFilter
CommonTagsCustomizer
CustomHealthIndicator
Actuator endpoint
AutoConfiguration
Properties

Service’da faqat:

<dependency>
    <groupId>uz.company</groupId>
    <artifactId>company-observability-starter</artifactId>
    <version>1.0.0</version>
</dependency>

Config:

company:
  observability:
    correlation-id-header: X-Correlation-Id
    service-name: order-service

Natija:

Hamma service bir xil log format
Hamma service bir xil metrics tag
Hamma service bir xil correlation id handling

Bu custom Spring extension’ning haqiqiy foydasi.


24. Common mistakes

1. Starter juda ko‘p ish qiladi

Yomon:

company-platform-starter
  security
  kafka
  database
  redis
  actuator
  tracing
  exception
  migration
  cache

Bunday starter juda og‘ir bo‘ladi.

Yaxshiroq:

company-security-starter
company-kafka-starter
company-observability-starter
company-multitenancy-starter

2. Override qilish imkoni yo‘q

Yomon:

@Bean
public AuditLogger auditLogger() {
    return new AuditLogger();
}

Yaxshi:

@Bean
@ConditionalOnMissingBean
public AuditLogger auditLogger() {
    return new AuditLogger();
}

3. Property bilan o‘chirib bo‘lmaydi

Har starterda disable option bo‘lsin:

company:
  audit:
    enabled: false

4. Internal API documentation yo‘q

Starterda README bo‘lishi kerak:

- Nima qiladi?
- Qanday dependency qo‘shiladi?
- Qanday config bor?
- Default behavior nima?
- Qanday override qilinadi?
- Qanday troubleshoot qilinadi?

5. Auto-config haddan tashqari agressiv

Starter user project’da kutilmagan bean yaratmasligi kerak.


25. Custom Spring extensions checklist

[ ] Extension haqiqatan reusablemi?
[ ] Auto-configuration shartli ishlaydimi?
[ ] @ConditionalOnMissingBean ishlatilganmi?
[ ] Property orqali enable/disable bormi?
[ ] ConfigurationProperties bor-mi?
[ ] Metadata/autocomplete uchun configuration processor bormi?
[ ] AutoConfiguration.imports to‘g‘ri yozilganmi?
[ ] Actuator endpointlar secure qilinganmi?
[ ] AOP self-invocation muammosi hisobga olinganmi?
[ ] Lifecycle start/stop tartibi aniqmi?
[ ] ApplicationContextRunner bilan test qilinganmi?
[ ] Versioning strategy bormi?
[ ] README va config example bormi?
[ ] Breaking change release’da aniq yozilganmi?

26. Senior interview javob

Suhbatda so‘rasa:

Custom Spring starter yoki auto-configuration yozganmisiz? Qanday ishlaydi?

Javob:

Custom Spring starter umumiy platform config’ni reusable qilish uchun yoziladi.
Masalan, company-observability-starter yoki company-security-starter.

Starter ichida auto-configuration class bo‘ladi, u @AutoConfiguration bilan belgilanadi va
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports file orqali Spring Boot’ga tanitiladi.

Auto-configuration majburan bean yaratmasligi kerak. Shuning uchun @ConditionalOnClass,
@ConditionalOnProperty, @ConditionalOnMissingBean kabi condition annotationlar ishlatiladi.
User o‘z bean’ini bersa, starter back off qilishi kerak.

ConfigurationProperties orqali application.yml’dan type-safe config olinadi.
Agar kerak bo‘lsa custom annotation + AOP, custom Actuator endpoint, HealthIndicator,
ApplicationListener yoki SmartLifecycle qo‘shiladi.

Bunday starter ApplicationContextRunner bilan test qilinadi:
default bean yaratilishi, property bilan disable bo‘lishi, user custom bean bersa override ishlashi tekshiriladi.

27. Qisqa xulosa

Custom Spring extensions Senior developer uchun Spring ekotizimini chuqur tushunishni ko‘rsatadi.

Qisqa xulosa qilsak:

Custom extension - takrorlanadigan platform kodni reusable, configurable va override qilinadigan Spring module’ga aylantirish.

Eng muhim tushunchalar:

Mavzu

Esda qoladigan qoida

Auto-configuration

Dependency/property/bean asosida avtomatik config

Custom starter

Dependency + auto-config + common code

AutoConfiguration.imports

Spring Boot 3 auto-config registration

@ConditionalOnMissingBean

User override qila olsin

@ConfigurationProperties

Type-safe config

Custom annotation + AOP

Cross-cutting concern uchun

Custom Actuator endpoint

Operational visibility uchun

ApplicationListener

Spring eventlarga javob berish

SmartLifecycle

Start/stop tartibini boshqarish

ApplicationContextRunner

Starter testlari uchun