🌱 Spring Framework — Üretim Odaklı Rehber

Bu bölümde Spring dünyasının çekirdeği (IoC/DI), Spring Boot, MVC/REST, Data JPA, Security, Cloud ve üretim ortamı pratikleri detaylı şekilde ele alınır. Kod kesitleri, püf noktaları ve “best practice” odaklıdır.

🔎 Genel Bakış

  • IoC/DI: Gevşek bağlılık, test edilebilirlik; bean yaşam döngüsü ve kapsamları.
  • Boot: Auto-configuration, starter’lar, dışarıdan konfigürasyon, Actuator.
  • MVC/REST: Controller, DTO, validasyon, global hata yönetimi.
  • Data JPA: Repositories, query metodlar, JPQL/Native, performans.
  • Security: Filter chain, yetkilendirme, JWT, method-level security.
  • Cloud: Config, Gateway, Discovery, Feign, Resilience4j, Tracing.

🧠 IoC/DI & Bean Yönetimi

📦 Bean Tanımlama & Enjeksiyon

@Configuration
public class AppConfig {
  @Bean
  public ObjectMapper objectMapper() {
    return new ObjectMapper().findAndRegisterModules();
  }
}

@Service
public class ReportService {
  private final ObjectMapper mapper;         // ✅ Constructor injection (önerilir)
  public ReportService(ObjectMapper mapper) { this.mapper = mapper; }
}
  • Constructor injectionnull-safety, test kolaylığı.
  • @Primary / @Qualifier → çoklu bean senaryoları.
  • Scopes: singleton (varsayılan), prototype, web: request, session.

🪄 Yaşam Döngüsü İpuçları

  • @PostConstruct yerine SmartLifecycle / ApplicationRunner tercih edilebilir.
  • Konfigürasyon değerleri için @ConfigurationProperties → tip güvenli, test edilebilir.
@ConfigurationProperties(prefix = "app.mail")
public record MailProps(String host, int port, String from) {}
// application.yml: app: mail: host: smtp ...

⚙️ Spring Boot Esasları

🚀 Starter’lar & Auto-Configuration

  • Starter: kütüphane setlerini hazır getirir (web, data-jpa, security …).
  • Auto-config: sınıf yolundaki bağımlılıklara göre varsayılan bean’ler.
  • application.yml ile dışarıdan konfigürasyon ve profiller.
server:
  port: 8080
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/app
    username: app
    password: secret
  jpa:
    hibernate:
      ddl-auto: validate
    properties:
      hibernate:
        format_sql: true
logging:
  level:
    org.hibernate.SQL: DEBUG
    org.hibernate.orm.jdbc.bind: TRACE

🧪 Devtools & Config Tips

  • spring-boot-devtools ile hızlı geliştirme; prod’da devtools ekleme.
  • YAML hiyerarşisi okunabilirlik sağlar; gizli bilgiler için Vault/KMS.

🌐 Spring MVC & REST

🛣️ Controller + DTO

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
  private final UserService userService;

  @GetMapping
  public Page<UserView> list(Pageable pageable) {
    return userService.list(pageable);
  }

  @PostMapping
  public ResponseEntity<UserView> create(@Valid @RequestBody UserCreate dto) {
    return ResponseEntity.status(HttpStatus.CREATED).body(userService.create(dto));
  }
}
  • DTO kullan, Entity’yi dışarı açma.
  • Pageable otomatik bağlanır: ?page=0&size=20&sort=name,asc
  • İçerik türleri: produces/consumes.

🧭 Path/Query Var.

@GetMapping("/{id}")
public UserView get(@PathVariable Long id, @RequestParam(defaultValue="false") boolean withRoles) {
  return service.get(id, withRoles);
}

✅ Validasyon & Global Hata Yönetimi

🧾 Bean Validation

public record UserCreate(
  @NotBlank String username,
  @Email String email,
  @Size(min=8) String password
) {}

🧯 @ControllerAdvice

@RestControllerAdvice
public class ApiExceptionHandler {
  @ExceptionHandler(MethodArgumentNotValidException.class)
  ResponseEntity<Map<String,Object>> handleValidation(MethodArgumentNotValidException ex) {
    Map<String,Object> body = new LinkedHashMap<>();
    body.put("error", "validation");
    body.put("details", ex.getBindingResult().getFieldErrors().stream()
        .map(f -> Map.of("field", f.getField(), "msg", f.getDefaultMessage()))
        .toList());
    return ResponseEntity.badRequest().body(body);
  }
}

💾 Spring Data JPA

📚 Repository Desenleri

@Entity class User { @Id @GeneratedValue Long id; String username; String email; }

public interface UserRepo extends JpaRepository<User, Long> {
  Optional<User> findByUsername(String username);
  @Query("select u from User u where lower(u.email) like lower(concat('%', :q, '%'))")
  Page<User> search(@Param("q") String q, Pageable p);
}
  • Query method kolay, karmaşıkta @Query (JPQL/Native).
  • Projection: Interface/DTO ile minimal veri.
  • EntityGraph ve fetch join ile N+1 azalt.

🧠 İpuçları

  • save() hem insert hem update yapabilir; id null → insert.
  • deleteInBatch / deleteAllInBatch bulk siler; cascade ve orphanRemoval davranışlarını bil.

🔐 Transaction Yönetimi

⚖️ @Transactional

@Transactional(readOnly = true)
public Page<UserView> list(Pageable pageable) { /* only reads */ }

@Transactional
public UserView create(UserCreate dto) { /* write ops */ }

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void audit(AuditEvent e) { /* bağımsız log */ }
  • readOnly=true → gereksiz dirty checking yok.
  • Propagation: REQUIRED, REQUIRES_NEW, MANDATORY
  • İzolasyon: READ_COMMITTED çoğu ortamda yeterli; yarışta daha katı.

🧪 Test Stratejileri

🔹 Dilimler & Spring Boot Test

@DataJpaTest      // Sadece JPA bileşenleri
class UserRepoTest { /* ... */ }

@WebMvcTest(UserController.class) // Controller + MVC slice
class UserControllerTest { /* MockMvc ... */ }

@SpringBootTest // Tüm konteyner (yavaş, entegre)
class AppIT { /* ... */ }

🐳 Testcontainers (önerilir)

@Testcontainers
class RepoIT {
  @Container static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:15");
  // spring.datasource.url = db.getJdbcUrl() ...
}

🛡️ Spring Security (5/6+)

🧱 Filter Chain Config (Java DSL)

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  return http
    .csrf(csrf -> csrf.disable())
    .authorizeHttpRequests(auth -> auth
        .requestMatchers("/actuator/**").permitAll()
        .requestMatchers(HttpMethod.POST, "/api/auth/**").permitAll()
        .anyRequest().authenticated())
    .oauth2ResourceServer(oauth -> oauth.jwt()) // JWT resource server
    .build();
}

@Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
  • Method-level: @EnableMethodSecurity + @PreAuthorize("hasRole('ADMIN')")
  • Stateless API: session yok, JWT ile kimlik doğrulama.

☁️ Spring Cloud Esasları

🔌 OpenFeign & Resilience4j

@FeignClient(name="order", url="${services.order.url}", configuration = FeignConfig.class)
public interface OrderClient {
  @GetMapping("/api/orders/{id}") OrderDto get(@PathVariable Long id);
}
@Service
public class OrderGateway {
  private final OrderClient client;
  public OrderGateway(OrderClient client) { this.client = client; }

  @CircuitBreaker(name="order", fallbackMethod="fallback")
  @Retry(name="order")
  public OrderDto get(Long id) { return client.get(id); }

  OrderDto fallback(Long id, Throwable t) { return new OrderDto(id, "N/A"); }
}

🧭 Discovery, Gateway, Config

  • Service Discovery (Eureka/Consul): client-side load balancing.
  • API Gateway (Spring Cloud Gateway): route, rate limit, auth.
  • Config Server: merkezi konfig, spring.cloud.config.

📊 Actuator & İzleme

📈 Health, Metrics, Info

management:
  endpoints:
    web:
      exposure:
        include: "health,info,metrics,env,threaddump,httpexchanges"
  endpoint:
    health:
      show-details: "always"
@Component
public class AppHealth implements HealthIndicator {
  public Health health() {
    return Health.up().withDetail("service", "ok").build();
  }
}
  • Micrometer + Prometheus/Grafana ile metrik toplama.
  • Tracing: Micrometer Tracing + OpenTelemetry/Zipkin/Jaeger.

⚡ Caching / Async / Scheduling

🧠 Cache

@EnableCaching
@SpringBootApplication
class App {}

@Service
class ProductService {
  @Cacheable(cacheNames="product", key="#id")
  Product get(Long id) { /* DB call */ }
  @CacheEvict(cacheNames="product", key="#id")
  void evict(Long id) {}
}

🧵 @Async & ⏰ @Scheduled

@EnableAsync @EnableScheduling
@Configuration class AsyncCfg {
  @Bean Executor taskExecutor() { return Executors.newVirtualThreadPerTaskExecutor(); /* veya ThreadPoolTaskExecutor */ }
}

@Service
class Mailer {
  @Async public void sendMail(...) { /* async */ }
  @Scheduled(cron = "0 0 * * * *") public void hourlyTask() { /* ... */ }
}

🧩 Config & Profiles

🌿 Profiller & Dış Konfig

spring:
  profiles:
    active: "dev"
---
spring:
  config:
    activate:
      on-profile: "prod"
server:
  port: 8080
  • @Profile("prod") ile koşullu bean yükleme.
  • Gizli bilgiler → ENV veya Config Server / Vault.

🔭 Logging & Gözlemlenebilirlik

📝 Logback & MDC

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} %-5level [%X{traceId:-} %X{spanId:-}] %logger - %msg%n</pattern>
    </encoder>
  </appender>
  <root level="INFO"><appender-ref ref="STDOUT"/></root>
</configuration>
  • MDC ile korelasyon id’si; Micrometer Tracing otomatik doldurur.
  • Log seviyelerini logging.level ile yönet.

🚀 Performans & Antipattern

❌ Kaçınılması Gerekenler

  • Controller’da Entity döndürmek (döngüsel referans, alan sızıntısı).
  • Geniş object graph’ı EAGER ile yüklemek → bellek/sorgu patlaması.
  • @Transactional sınırlarını servis katmanı dışında tanımlamak.
  • OFFSET/FETCH ile devasa sayfa atlamak → keyset pagination kullan.

✅ İyi Pratikler

  • Constructor injection + final alanlar.
  • DTO Projections & EntityGraph ile hedefli veri çekimi.
  • Actuator + Metrics + Tracing ile görünürlük.
  • Resilience4j ile dış çağrılarda circuit breaker/timeout/bulkhead.

❓ Mülakat Notları (Kısa Cevaplı)

1) IoC nedir?

Kontrolün tersine çevrilmesi: nesnelerin oluşturulması ve bağımlılıklarının yönetimini konteyner üstlenir.

2) @Component vs @Bean farkı?

@Component sınıf seviyesinde taranır; @Bean ise @Configuration içinde fabrika metodu olarak tanımlanır.

3) @Transactional readOnly ne sağlar?

Kirlenme kontrolünü azaltır; bazı sürücülerde optimizasyon; yazma girişimleri hata üretebilir (motor davranışına bağlı).

4) Spring Security 6’da WebSecurityConfigurerAdapter?

Kaldırıldı. Yerine SecurityFilterChain @Bean yaklaşımı kullanılır.

5) Feign + Resilience4j birlikte nasıl?

Feign client’ı bir servisle wrap et; metodlarda @CircuitBreaker/@Retry kullan, fallback metodları tanımla.