# 30 Spring Boot Mülakat Sorusu: Java Geliştiricileri için Eksiksiz Kılavuz
> Auto-configuration, starter'lar, Spring Data JPA, güvenlik ve test konularını kapsayan 30 temel soruyla Spring Boot mülakatlarına hazırlanın.
- Published: 2026-02-07
- Updated: 2026-04-28
- Author: SharpSkill
- Tags: spring boot, java, interview, backend, java interview
- Reading time: 18 min
---
Spring Boot, kurumsal Java geliştirmenin başvuru çatısı haline geldi. Teknik mülakatlar; iç mekanizmaları, iyi uygulamaları ve Spring ekosistemini anlama düzeyini ölçer. Bu kılavuz; temel kavramlardan ileri konulara uzanan en sık sorulan 30 soruyu kapsar.
> **Hazırlık ipucu**
>
> Mülakatçılar, her özelliğin arkasındaki "neden"i anlayan adayları takdir eder. Sözdiziminin ötesine geçip Spring Boot'un hangi sorunları çözdüğünü açıklamak gerekir.
## Spring Boot temelleri
### 1. Spring ile Spring Boot arasındaki fark nedir?
Spring; bağımlılık enjeksiyonu, işlem yönetimi ve birçok teknolojiyle entegrasyon sunan modüler bir çatıdır. Spring Boot ise yapılandırmayı ve uygulamanın başlatılmasını basitleştiren, Spring üzerine bir soyutlama katmanıdır.
```java
// Application.java
// With Spring Boot: a single annotation to start
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// SpringApplication configures and starts the Spring context
SpringApplication.run(Application.class, args);
}
}
```
Spring Boot; auto-configuration, bağımlılık starter'ları, gömülü sunucu ve Actuator üzerinden üretim metrikleri sağlar. Amaç, fikirden çalışan koda dakikalar içinde geçmektir.
### 2. Auto-configuration nasıl çalışır?
Auto-configuration, classpath'i analiz eder ve gerekli bean'leri otomatik olarak yapılandırır. Mekanizma, `@EnableAutoConfiguration` anotasyonuna (`@SpringBootApplication` içinde gelir) ve auto-configuration sınıflarında tanımlanan koşullara dayanır.
```java
// CustomAutoConfiguration.java
@Configuration
@ConditionalOnClass(DataSource.class) // Activates if DataSource is on classpath
@ConditionalOnMissingBean(DataSource.class) // Only acts if no DataSource exists
public class CustomAutoConfiguration {
@Bean
@ConditionalOnProperty(name = "app.datasource.enabled", havingValue = "true")
public DataSource dataSource() {
// Default DataSource configuration
return DataSourceBuilder.create()
.driverClassName("org.h2.Driver")
.url("jdbc:h2:mem:testdb")
.build();
}
}
```
Spring Boot, hangi bean'lerin oluşturulacağını belirlemek için koşulları (`@ConditionalOn*`) inceler. Bu yaklaşım çakışmaları önler ve varsayılan yapılandırmaların kolayca geçersiz kılınmasına izin verir.
### 3. Starter nedir ve nasıl oluşturulur?
Starter, bir özellik için gerekli bağımlılıkları ve yapılandırmaları bir araya getiren Maven/Gradle modülüdür. Örneğin `spring-boot-starter-web`; Spring MVC, Jackson, gömülü Tomcat ve validation içerir.
```xml
my-company-starter
org.springframework.boot
spring-boot-starter
com.mycompany
logging-utils
```
Özel bir starter oluşturmak için auto-configuration sınıfları tanımlanır ve `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` dosyasında referans edilir.
### 4. application.properties ile application.yml karşılaştırması
Her iki format da yapılandırmayı dış kaynağa taşır. YAML, iç içe yapılar için daha okunaklı bir sözdizimi sunarken properties, düz yapılandırmalar için daha basit kalır.
```properties
# application.properties
server.port=8080
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=admin
spring.jpa.hibernate.ddl-auto=update
```
```yaml
# application.yml - clear hierarchical structure
server:
port: 8080
spring:
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: admin
jpa:
hibernate:
ddl-auto: update
```
Profiller, yapılandırmayı ortama göre uyarlar: `application-dev.yml`, `application-prod.yml`. Etkinleştirme `spring.profiles.active` üzerinden yapılır.
### 5. @SpringBootApplication nasıl çalışır?
Bu anotasyon, Spring Boot uygulamasının davranışını şekillendiren üç temel anotasyonu birleştirir.
```java
// DemoApplication.java
// @SpringBootApplication is equivalent to these three annotations:
@SpringBootConfiguration // Equivalent to @Configuration
@EnableAutoConfiguration // Activates auto-configuration
@ComponentScan // Scans components in package and sub-packages
public class DemoApplication {
public static void main(String[] args) {
// Creates context, runs configurations, and starts server
ConfigurableApplicationContext context =
SpringApplication.run(DemoApplication.class, args);
// The context contains all configured beans
UserService userService = context.getBean(UserService.class);
}
}
```
Bu sınıfı paketin kökünde tutmak çok önemlidir: `@ComponentScan` bu paketi ve tüm alt paketleri tarayarak bileşenleri bulur.
## Spring Data ve kalıcılık
### 6. JpaRepository, CrudRepository ve PagingAndSortingRepository arasındaki fark nedir?
Bu arabirimler, veri erişiminde artan yetenekler sunan bir hiyerarşi oluşturur.
```java
// UserRepository.java
// CrudRepository: basic CRUD operations
public interface UserCrudRepository extends CrudRepository {
// save(), findById(), findAll(), deleteById(), count(), existsById()
}
// PagingAndSortingRepository: adds pagination and sorting
public interface UserPagingRepository extends PagingAndSortingRepository {
// Inherits from CrudRepository + findAll(Sort), findAll(Pageable)
}
// JpaRepository: complete JPA features
public interface UserRepository extends JpaRepository {
// Inherits from previous + flush(), saveAndFlush(), deleteInBatch()
// Derived query methods
List findByEmailContaining(String email);
Optional findByUsernameAndActiveTrue(String username);
}
```
Çoğu durumda `JpaRepository` önerilen seçimdir, çünkü JPA uygulaması geliştirmek için gereken tüm özellikleri sunar.
### 7. @Query ile özel sorgular nasıl tanımlanır?
`@Query` anotasyonu, türetilmiş metodlar yetersiz kaldığında JPQL veya yerel SQL sorguları yazmaya izin verir.
```java
// OrderRepository.java
public interface OrderRepository extends JpaRepository {
// JPQL with named parameters
@Query("SELECT o FROM Order o WHERE o.customer.id = :customerId AND o.status = :status")
List findCustomerOrdersByStatus(
@Param("customerId") Long customerId,
@Param("status") OrderStatus status
);
// Native SQL query for specific needs
@Query(value = "SELECT * FROM orders WHERE created_at > NOW() - INTERVAL '7 days'",
nativeQuery = true)
List findRecentOrders();
// Modifying query with @Modifying
@Modifying
@Transactional
@Query("UPDATE Order o SET o.status = :status WHERE o.id IN :ids")
int updateOrderStatuses(@Param("ids") List ids, @Param("status") OrderStatus status);
}
```
JPQL sorguları veritabanları arasında taşınabilirken, yerel sorgular DBMS'e özgü özelliklere erişim sağlar.
### 8. @Transactional ile işlem yönetimi
Bu anotasyon, bir metot veya sınıfın işlem sınırlarını tanımlar. Spring; commit, rollback ve propagation işlemlerini otomatik olarak yönetir.
```java
// PaymentService.java
@Service
public class PaymentService {
private final AccountRepository accountRepository;
private final TransactionLogRepository logRepository;
// Transaction with custom configuration
@Transactional(
propagation = Propagation.REQUIRED, // Creates or joins a transaction
isolation = Isolation.READ_COMMITTED, // Isolation level
timeout = 30, // Timeout in seconds
rollbackFor = PaymentException.class // Rollback on this exception
)
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
Account from = accountRepository.findById(fromId)
.orElseThrow(() -> new AccountNotFoundException(fromId));
Account to = accountRepository.findById(toId)
.orElseThrow(() -> new AccountNotFoundException(toId));
from.debit(amount); // Throws exception if insufficient balance
to.credit(amount);
accountRepository.save(from);
accountRepository.save(to);
// Log will be rolled back with everything else if an exception occurs
logRepository.save(new TransactionLog(fromId, toId, amount));
}
}
```
Propagation seviyeleri (`REQUIRED`, `REQUIRES_NEW`, `NESTED` vb.) iç içe işlem çağrılarında işlemlerin nasıl yuvalanacağını tanımlar.
> **Sık karşılaşılan tuzak**
>
> `@Transactional` yalnızca Spring proxy üzerinden geçen çağrılar için çalışır. Aynı sınıfta yapılan dahili çağrı proxy'yi atlar ve anotasyonu yok sayar.
### 9. Lazy ve Eager Loading ilişkileri nasıl yönetilir?
İlişkilerin yüklenme şekli performansı doğrudan etkiler. Lazy Loading erişime kadar yüklemeyi erteler, Eager Loading ise hemen yükler.
```java
// User.java
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// Eager: roles are loaded with the user
@ManyToMany(fetch = FetchType.EAGER)
private Set roles;
// Lazy: orders are only loaded on access
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List orders;
}
```
```java
// UserService.java
@Service
public class UserService {
@Transactional(readOnly = true)
public UserDTO getUserWithOrders(Long userId) {
User user = userRepository.findById(userId).orElseThrow();
// Accessing orders triggers an additional SQL query
// This works because the session is open (@Transactional)
List orderDTOs = user.getOrders().stream()
.map(this::toDTO)
.collect(Collectors.toList());
return new UserDTO(user, orderDTOs);
}
// Alternative: query with JOIN FETCH to avoid N+1
public UserDTO getUserWithOrdersOptimized(Long userId) {
User user = userRepository.findByIdWithOrders(userId);
// Orders are already loaded, no additional query
return new UserDTO(user, user.getOrders());
}
}
```
N+1 problemi, lazy loading'in koleksiyonun her elemanı için bir sorgu üretmesi durumunda ortaya çıkar. `JOIN FETCH` veya projeksiyonlar bu sorunu çözer.
### 10. N+1 problemi nedir ve nasıl çözülür?
N+1 problemi; ilk sorgudan (1) sonra her varlığın ilişkilerini yüklemek için N ek sorgu çalıştırıldığında ortaya çıkar.
```java
// ArticleRepository.java
public interface ArticleRepository extends JpaRepository {
// PROBLEM: this query generates N+1 queries
List findAll();
// SOLUTION 1: JOIN FETCH in JPQL
@Query("SELECT a FROM Article a JOIN FETCH a.author JOIN FETCH a.comments")
List findAllWithDetails();
// SOLUTION 2: Entity Graph
@EntityGraph(attributePaths = {"author", "comments"})
@Query("SELECT a FROM Article a")
List findAllWithGraph();
}
```
```java
// Article.java
@Entity
@NamedEntityGraph(
name = "Article.withDetails",
attributeNodes = {
@NamedAttributeNode("author"),
@NamedAttributeNode("comments")
}
)
public class Article {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Author author;
@OneToMany(mappedBy = "article", fetch = FetchType.LAZY)
private List comments;
}
```
`@EntityGraph` veya `JOIN FETCH` kullanmak, N+1 sorgu yerine join'li tek bir sorguya iner ve performansı belirgin biçimde artırır.
## REST API ve web
### 11. Doğrulamalı bir REST controller nasıl oluşturulur?
Spring Boot; sağlam, otomatik girdi doğrulamalı API'ler üretmek için `@RestController` ile Bean Validation'ı birleştirir.
```java
// UserController.java
@RestController
@RequestMapping("/api/users")
@Validated // Activates validation on method parameters
public class UserController {
private final UserService userService;
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserResponse createUser(@Valid @RequestBody CreateUserRequest request) {
// Validation is performed automatically before execution
return userService.createUser(request);
}
@GetMapping("/{id}")
public UserResponse getUser(
@PathVariable @Min(1) Long id // Validation on PathVariable
) {
return userService.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
@GetMapping
public Page listUsers(
@RequestParam(defaultValue = "0") @Min(0) int page,
@RequestParam(defaultValue = "20") @Max(100) int size
) {
return userService.findAll(PageRequest.of(page, size));
}
}
```
```java
// CreateUserRequest.java
public record CreateUserRequest(
@NotBlank(message = "Name is required")
@Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
String name,
@NotBlank(message = "Email is required")
@Email(message = "Invalid email format")
String email,
@NotBlank
@Pattern(regexp = "^(?=.*[A-Z])(?=.*[0-9]).{8,}$",
message = "Password must contain at least 8 characters, one uppercase and one digit")
String password
) {}
```
Doğrulama mesajları, uluslararasılaştırma için mesaj dosyalarına dışarıdan alınabilir.
### 12. @ControllerAdvice ile istisnalar nasıl global olarak ele alınır?
Merkezi bir istisna işleyici, hata yanıtlarını standartlaştırır ve kod tekrarını önler.
```java
// GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// Handle validation errors
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorResponse handleValidationErrors(MethodArgumentNotValidException ex) {
Map errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.collect(Collectors.toMap(
FieldError::getField,
FieldError::getDefaultMessage,
(existing, replacement) -> existing
));
return new ErrorResponse("VALIDATION_ERROR", "Invalid data", errors);
}
// Handle resource not found
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
return new ErrorResponse("NOT_FOUND", ex.getMessage(), null);
}
// Handle unexpected errors
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse handleUnexpectedError(Exception ex) {
log.error("Unexpected error", ex);
return new ErrorResponse("INTERNAL_ERROR", "An error occurred", null);
}
}
```
```java
// ErrorResponse.java
public record ErrorResponse(
String code,
String message,
Map details,
Instant timestamp
) {
public ErrorResponse(String code, String message, Map details) {
this(code, message, details, Instant.now());
}
}
```
Bu yaklaşım, tüm hataların aynı formatta dönmesini sağlar ve istemci tarafındaki işlemeyi kolaylaştırır.
### 13. @RequestParam, @PathVariable ve @RequestBody arasındaki fark nedir?
Bu üç anotasyon, HTTP isteğinin farklı bölümlerinden veri çıkarır.
```java
// ProductController.java
@RestController
@RequestMapping("/api/products")
public class ProductController {
// @PathVariable: extracts values from the URL
// GET /api/products/123
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}
// @RequestParam: extracts query parameters
// GET /api/products?category=electronics&minPrice=100&page=0
@GetMapping
public Page searchProducts(
@RequestParam(required = false) String category,
@RequestParam(defaultValue = "0") BigDecimal minPrice,
@RequestParam(defaultValue = "0") int page
) {
return productService.search(category, minPrice, PageRequest.of(page, 20));
}
// @RequestBody: deserializes the JSON request body
// POST /api/products with {"name": "...", "price": ...}
@PostMapping
public Product createProduct(@Valid @RequestBody CreateProductRequest request) {
return productService.create(request);
}
// Combination of all three in the same method
// PUT /api/products/123?notify=true with JSON body
@PutMapping("/{id}")
public Product updateProduct(
@PathVariable Long id,
@RequestParam(defaultValue = "false") boolean notify,
@Valid @RequestBody UpdateProductRequest request
) {
Product updated = productService.update(id, request);
if (notify) {
notificationService.sendUpdateNotification(updated);
}
return updated;
}
}
```
`@PathVariable` belirli bir kaynağı tanımlar, `@RequestParam` sonuçları filtreler veya sayfalandırır, `@RequestBody` ise karmaşık verileri taşır.
### 14. API sürümleme nasıl uygulanır?
REST API'sini sürümlemek için her biri kendi avantajına sahip birden fazla strateji vardır.
```java
// Version via URL (recommended for clarity)
@RestController
@RequestMapping("/api/v1/users")
public class UserControllerV1 {
@GetMapping("/{id}")
public UserV1Response getUser(@PathVariable Long id) {
return userService.findByIdV1(id);
}
}
@RestController
@RequestMapping("/api/v2/users")
public class UserControllerV2 {
@GetMapping("/{id}")
public UserV2Response getUser(@PathVariable Long id) {
// V2 includes additional fields
return userService.findByIdV2(id);
}
}
```
```java
// Version via custom header
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping(value = "/{id}", headers = "X-API-Version=1")
public UserV1Response getUserV1(@PathVariable Long id) {
return userService.findByIdV1(id);
}
@GetMapping(value = "/{id}", headers = "X-API-Version=2")
public UserV2Response getUserV2(@PathVariable Long id) {
return userService.findByIdV2(id);
}
}
```
```java
// Version via media type (content negotiation)
@RestController
@RequestMapping("/api/users")
public class UserMediaTypeController {
@GetMapping(value = "/{id}", produces = "application/vnd.myapi.v1+json")
public UserV1Response getUserV1(@PathVariable Long id) {
return userService.findByIdV1(id);
}
@GetMapping(value = "/{id}", produces = "application/vnd.myapi.v2+json")
public UserV2Response getUserV2(@PathVariable Long id) {
return userService.findByIdV2(id);
}
}
```
URL üzerinden sürümleme; basitliği ve loglar/dokümantasyon üzerindeki görünürlüğü nedeniyle en yaygın kullanılan yaklaşım olmayı sürdürür.
### 15. Spring Boot'ta CORS nasıl yapılandırılır?
CORS (Cross-Origin Resource Sharing), farklı kaynaklardan gelen istekleri kontrol eder. Birkaç yapılandırma seviyesi mevcuttur.
```java
// CorsConfig.java - Global configuration
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://frontend.example.com", "https://admin.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("X-Total-Count", "X-Page-Count")
.allowCredentials(true)
.maxAge(3600); // Cache preflight for 1 hour
}
}
```
```java
// Per-controller or method configuration
@RestController
@RequestMapping("/api/public")
@CrossOrigin(origins = "*", maxAge = 3600)
public class PublicApiController {
@GetMapping("/data")
public DataResponse getPublicData() {
return dataService.getPublicData();
}
// Override at method level
@CrossOrigin(origins = "https://specific-client.com")
@PostMapping("/submit")
public SubmitResponse submitData(@RequestBody SubmitRequest request) {
return dataService.submit(request);
}
}
```
Prodüksiyonda izin verilen kaynaklar sınırlandırılmalı ve HTTPS kullanılmalıdır. `*` ile `allowCredentials(true)` asla birlikte kullanılmamalıdır.
## Spring Security
### 16. JWT ile Spring Security nasıl yapılandırılır?
Spring Security 6+, güvenlik yapılandırmasında işlevsel bir yaklaşım benimser.
```java
// SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable()) // Disabled for stateless API
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/**").authenticated()
)
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
```
```java
// JwtAuthenticationFilter.java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain
) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
String jwt = authHeader.substring(7);
String username = jwtService.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
chain.doFilter(request, response);
}
}
```
Bu yapılandırma, her isteğin Authorization başlığında geçerli bir JWT taşıması gereken stateless bir API oluşturur.
### 17. @PreAuthorize ve @PostAuthorize ile metotlar nasıl güvenli kılınır?
Metot seviyesinde güvenlik; rollere, izinlere veya verilere dayalı ince ayarlı kontrol sağlar.
```java
// UserService.java
@Service
@PreAuthorize("isAuthenticated()") // All methods require authentication
public class UserService {
// Only ADMINs can list all users
@PreAuthorize("hasRole('ADMIN')")
public List findAll() {
return userRepository.findAll();
}
// User can view their profile OR admins can view any profile
@PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
}
// Post-execution check: user can only see their own data
@PostAuthorize("returnObject.owner.id == authentication.principal.id or hasRole('ADMIN')")
public Document getDocument(Long documentId) {
return documentRepository.findById(documentId)
.orElseThrow(() -> new DocumentNotFoundException(documentId));
}
// Collection filtering: returns only authorized elements
@PostFilter("filterObject.owner.id == authentication.principal.id")
public List getUserProjects() {
return projectRepository.findAll();
}
}
```
```java
// SecurityConfig.java - Activation
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// Additional configuration if needed
}
```
`@PreAuthorize` çalıştırmadan önce, `@PostAuthorize` ise sonra denetim yapar. SpEL ifadeleri karmaşık kurallar yazmaya izin verir.
### 18. Endpoint'ler CSRF saldırılarına karşı nasıl korunur?
CSRF (Cross-Site Request Forgery), kimlik doğrulanmış kullanıcının oturumunu kötüye kullanır. Oturum tabanlı uygulamalar için koruma varsayılan olarak etkindir.
```java
// SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
// CSRF configuration for session-based applications
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
// Exclude certain endpoints from CSRF protection
.ignoringRequestMatchers("/api/webhooks/**")
)
.build();
}
}
```
```java
// CsrfController.java - Endpoint to retrieve token (SPA)
@RestController
public class CsrfController {
@GetMapping("/api/csrf")
public CsrfToken csrf(CsrfToken token) {
// Returns CSRF token to frontend
return token;
}
}
```
JWT kullanan stateless REST API'lerinde CSRF devre dışı bırakılabilir; çünkü kötüye kullanılacak bir oturum çerezi yoktur. Klasik oturum tabanlı uygulamalarda koruma şarttır.
> **Stateless API ve oturum tabanlı uygulamalar**
>
> JWT API'leri CSRF'ye karşı doğal olarak korumalıdır çünkü token her istekte açıkça gönderilmek zorundadır. Oturum çerezi kullanan uygulamalar aktif CSRF korumasına ihtiyaç duyar.
## Spring Boot test
### 19. @SpringBootTest ile @WebMvcTest arasındaki fark nedir?
Bu anotasyonlar test bağlamını ihtiyaca göre farklı şekilde yapılandırır.
```java
// UserControllerIntegrationTest.java
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class UserControllerIntegrationTest {
// Loads full context: all beans, database, etc.
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldCreateUser() {
CreateUserRequest request = new CreateUserRequest("John", "john@example.com");
ResponseEntity response = restTemplate.postForEntity(
"/api/users", request, User.class
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
assertThat(response.getBody().getName()).isEqualTo("John");
}
}
```
```java
// UserControllerUnitTest.java
@WebMvcTest(UserController.class) // Loads only the web layer
class UserControllerUnitTest {
@Autowired
private MockMvc mockMvc;
@MockitoBean // Replaces @MockBean (deprecated)
private UserService userService;
@Test
void shouldReturnUser() throws Exception {
// Mock configuration
when(userService.findById(1L))
.thenReturn(Optional.of(new User(1L, "John", "john@example.com")));
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John"))
.andExpect(jsonPath("$.email").value("john@example.com"));
}
@Test
void shouldReturn404WhenUserNotFound() throws Exception {
when(userService.findById(999L)).thenReturn(Optional.empty());
mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound());
}
}
```
`@WebMvcTest`, yalnızca belirtilen controller ve doğrudan bağımlılıklarını yükler ve daha hızlıdır. Tam entegrasyon testleri için `@SpringBootTest` gereklidir.
### 20. @DataJpaTest ile depolar nasıl test edilir?
Bu anotasyon, gömülü bir veritabanıyla kalıcılık katmanını test etmek için minimum bir bağlam kurar.
```java
// UserRepositoryTest.java
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE) // Use Testcontainers
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Autowired
private TestEntityManager entityManager;
@Test
void shouldFindByEmail() {
// Arrange: create and persist an entity
User user = new User("John", "john@example.com");
entityManager.persistAndFlush(user);
entityManager.clear(); // Clear L1 cache
// Act
Optional found = userRepository.findByEmail("john@example.com");
// Assert
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("John");
}
@Test
void shouldFindActiveUsersWithOrders() {
// Testing a complex query with joins
User activeUser = entityManager.persistAndFlush(new User("Active", "active@test.com", true));
User inactiveUser = entityManager.persistAndFlush(new User("Inactive", "inactive@test.com", false));
entityManager.persistAndFlush(new Order(activeUser, BigDecimal.valueOf(100)));
List result = userRepository.findActiveUsersWithOrders();
assertThat(result).hasSize(1);
assertThat(result.get(0).getEmail()).isEqualTo("active@test.com");
}
}
```
`TestEntityManager`, JPA testleri için yardımcı metotlar sunar; özellikle `persistAndFlush()` veritabanına anında yazmayı garanti eder.
### 21. Entegrasyon testlerinde Testcontainers nasıl kullanılır?
Testcontainers, gerçek veritabanı veya servislerle test sırasında Docker konteynerleri başlatır.
```java
// IntegrationTestBase.java
@SpringBootTest
@Testcontainers
abstract class IntegrationTestBase {
@Container
@ServiceConnection
static PostgreSQLContainer> postgres = new PostgreSQLContainer<>("postgres:16")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
@Container
@ServiceConnection
static RedisContainer redis = new RedisContainer("redis:7");
}
```
```java
// OrderServiceIntegrationTest.java
class OrderServiceIntegrationTest extends IntegrationTestBase {
@Autowired
private OrderService orderService;
@Autowired
private OrderRepository orderRepository;
@Test
void shouldProcessOrderWithRealDatabase() {
// Create an order
Order order = orderService.createOrder(
new CreateOrderRequest(1L, List.of(
new OrderItem(101L, 2),
new OrderItem(102L, 1)
))
);
// Verify in database
Order saved = orderRepository.findById(order.getId()).orElseThrow();
assertThat(saved.getItems()).hasSize(2);
assertThat(saved.getStatus()).isEqualTo(OrderStatus.PENDING);
}
@Test
void shouldCacheOrderInRedis() {
Order order = orderService.createOrder(createSampleOrderRequest());
// First call: database
Order fetched1 = orderService.findById(order.getId());
// Second call: Redis cache
Order fetched2 = orderService.findById(order.getId());
assertThat(fetched1).isEqualTo(fetched2);
// Verify cache metrics if needed
}
}
```
`@ServiceConnection` anotasyonu bağlantı özelliklerini otomatik olarak yapılandırarak `@DynamicPropertySource` ihtiyacını ortadan kaldırır.
## Yapılandırma ve izleme
### 22. @ConfigurationProperties ile yapılandırma nasıl dışsallaştırılır?
Bu anotasyon, property'leri doğrulama destekli tipli Java nesnelerine eşler.
```java
// MailProperties.java
@ConfigurationProperties(prefix = "app.mail")
@Validated
public record MailProperties(
@NotBlank String host,
@Min(1) @Max(65535) int port,
@NotBlank String username,
@NotBlank String password,
@Valid SenderConfig sender,
@Valid RetryConfig retry
) {
public record SenderConfig(
@NotBlank @Email String address,
@NotBlank String name
) {}
public record RetryConfig(
@Min(1) int maxAttempts,
@Min(100) long delayMs
) {
public RetryConfig {
// Default values
if (maxAttempts == 0) maxAttempts = 3;
if (delayMs == 0) delayMs = 1000;
}
}
}
```
```yaml
# application.yml
app:
mail:
host: smtp.example.com
port: 587
username: ${MAIL_USERNAME}
password: ${MAIL_PASSWORD}
sender:
address: noreply@example.com
name: MyApp Notifications
retry:
max-attempts: 3
delay-ms: 1000
```
```java
// MailConfig.java
@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class MailConfig {
@Bean
public JavaMailSender mailSender(MailProperties props) {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost(props.host());
sender.setPort(props.port());
sender.setUsername(props.username());
sender.setPassword(props.password());
return sender;
}
}
```
Java record'ları (Java 16'dan itibaren) değişmez ve özlü olduklarından `@ConfigurationProperties` için özellikle uygundur.
### 23. Farklı ortamlar için Spring profilleri nasıl kullanılır?
Profiller, yapılandırmayı çalıştırma ortamına göre uyarlamayı sağlar.
```yaml
# application.yml - Default configuration
spring:
profiles:
active: ${SPRING_PROFILES_ACTIVE:dev}
datasource:
url: ${DATABASE_URL:jdbc:h2:mem:testdb}
---
# application-dev.yml
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:postgresql://localhost:5432/myapp_dev
jpa:
show-sql: true
hibernate:
ddl-auto: update
logging:
level:
com.example: DEBUG
---
# application-prod.yml
spring:
config:
activate:
on-profile: prod
datasource:
url: ${DATABASE_URL}
hikari:
maximum-pool-size: 20
jpa:
show-sql: false
hibernate:
ddl-auto: validate
logging:
level:
com.example: INFO
```
```java
// DevDataInitializer.java
@Component
@Profile("dev") // Only active in development
public class DevDataInitializer implements CommandLineRunner {
private final UserRepository userRepository;
@Override
public void run(String... args) {
// Create test data
userRepository.save(new User("dev@example.com", "Dev User", "password"));
userRepository.save(new User("test@example.com", "Test User", "password"));
}
}
```
```java
// Service with conditional behavior
@Service
public class NotificationService {
@Value("${app.notifications.enabled:true}")
private boolean notificationsEnabled;
@Autowired(required = false) // Optional based on profile
private EmailService emailService;
public void sendNotification(String message) {
if (notificationsEnabled && emailService != null) {
emailService.send(message);
} else {
log.info("Notification (disabled): {}", message);
}
}
}
```
Profiller birleştirilebilir: `spring.profiles.active=prod,metrics` ikisini aynı anda etkinleştirir.
### 24. Actuator ve Micrometer ile özel metrikler nasıl yayınlanır?
Micrometer, izleme sistemleri için bir cephedir. Özel metrikler gözlemlenebilirliği zenginleştirir.
```java
// OrderMetrics.java
@Component
public class OrderMetrics {
private final Counter ordersCreated;
private final Counter ordersFailed;
private final Timer orderProcessingTime;
private final AtomicInteger activeOrders;
public OrderMetrics(MeterRegistry registry) {
// Counter for orders created by status
this.ordersCreated = Counter.builder("orders.created")
.description("Number of orders created")
.tag("application", "order-service")
.register(registry);
this.ordersFailed = Counter.builder("orders.failed")
.description("Number of failed orders")
.register(registry);
// Timer to measure processing time
this.orderProcessingTime = Timer.builder("orders.processing.time")
.description("Order processing time")
.publishPercentiles(0.5, 0.95, 0.99)
.register(registry);
// Gauge for active orders count
this.activeOrders = new AtomicInteger(0);
Gauge.builder("orders.active", activeOrders, AtomicInteger::get)
.description("Number of orders being processed")
.register(registry);
}
public void recordOrderCreated() {
ordersCreated.increment();
}
public void recordOrderFailed() {
ordersFailed.increment();
}
public Timer.Sample startProcessing() {
activeOrders.incrementAndGet();
return Timer.start();
}
public void stopProcessing(Timer.Sample sample) {
sample.stop(orderProcessingTime);
activeOrders.decrementAndGet();
}
}
```
```java
// OrderService.java
@Service
public class OrderService {
private final OrderMetrics metrics;
public Order processOrder(CreateOrderRequest request) {
Timer.Sample sample = metrics.startProcessing();
try {
Order order = createOrder(request);
metrics.recordOrderCreated();
return order;
} catch (Exception e) {
metrics.recordOrderFailed();
throw e;
} finally {
metrics.stopProcessing(sample);
}
}
}
```
Bu metrikler, Prometheus için `/actuator/prometheus` veya JSON API için `/actuator/metrics` üzerinden açılır.
### 25. Özel HealthIndicator nasıl oluşturulur?
Sağlık göstergeleri kritik bileşenlerin durumunu izler.
```java
// ExternalApiHealthIndicator.java
@Component
public class ExternalApiHealthIndicator implements HealthIndicator {
private final RestClient restClient;
private final ExternalApiProperties properties;
@Override
public Health health() {
try {
long startTime = System.currentTimeMillis();
ResponseEntity response = restClient.get()
.uri(properties.getHealthEndpoint())
.retrieve()
.toBodilessEntity();
long responseTime = System.currentTimeMillis() - startTime;
if (response.getStatusCode().is2xxSuccessful()) {
return Health.up()
.withDetail("responseTime", responseTime + "ms")
.withDetail("endpoint", properties.getHealthEndpoint())
.build();
} else {
return Health.down()
.withDetail("status", response.getStatusCode().value())
.build();
}
} catch (Exception e) {
return Health.down()
.withException(e)
.withDetail("endpoint", properties.getHealthEndpoint())
.build();
}
}
}
```
```java
// DatabaseHealthContributor.java - Composite health
@Component
public class DatabaseHealthContributor implements CompositeHealthContributor {
private final Map indicators;
public DatabaseHealthContributor(
DataSource primaryDataSource,
@Qualifier("replicaDataSource") DataSource replicaDataSource
) {
this.indicators = Map.of(
"primary", new DataSourceHealthIndicator(primaryDataSource),
"replica", new DataSourceHealthIndicator(replicaDataSource)
);
}
@Override
public HealthContributor getContributor(String name) {
return indicators.get(name);
}
@Override
public Iterator> iterator() {
return indicators.entrySet().stream()
.map(e -> NamedContributor.of(e.getKey(), e.getValue()))
.iterator();
}
}
```
`/actuator/health` uç noktası tüm göstergeleri toplar. `management.endpoint.health.show-details=always` ayarı detayları görünür kılar.
## İleri konular
### 26. Spring Cache ve Redis ile önbellek nasıl uygulanır?
Spring Cache soyutlaması, Redis gibi dağıtık önbelleklerin entegrasyonunu kolaylaştırır.
```java
// CacheConfig.java
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10))
.serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// Specific configurations per cache
Map cacheConfigs = Map.of(
"users", config.entryTtl(Duration.ofHours(1)),
"products", config.entryTtl(Duration.ofMinutes(30)),
"sessions", config.entryTtl(Duration.ofMinutes(5))
);
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.withInitialCacheConfigurations(cacheConfigs)
.build();
}
}
```
```java
// ProductService.java
@Service
public class ProductService {
// Automatic result caching
@Cacheable(value = "products", key = "#id")
public Product findById(Long id) {
log.info("Fetching product {} from database", id);
return productRepository.findById(id)
.orElseThrow(() -> new ProductNotFoundException(id));
}
// Cache update on modification
@CachePut(value = "products", key = "#product.id")
public Product update(Product product) {
return productRepository.save(product);
}
// Cache invalidation
@CacheEvict(value = "products", key = "#id")
public void delete(Long id) {
productRepository.deleteById(id);
}
// Clear entire cache
@CacheEvict(value = "products", allEntries = true)
public void clearProductCache() {
log.info("Product cache cleared");
}
// Composite key for searches
@Cacheable(value = "productSearch", key = "#category + '-' + #minPrice + '-' + #maxPrice")
public List search(String category, BigDecimal minPrice, BigDecimal maxPrice) {
return productRepository.findByCategoryAndPriceBetween(category, minPrice, maxPrice);
}
}
```
Önbellek anotasyonları şeffaftır: iş kodu değişmeden kalır, önbellek bildirimsel olarak yönetilir.
### 27. @Scheduled ile zamanlanmış görev nasıl uygulanır?
Spring, tekrarlayan görevleri planlamak için birkaç yol sunar.
```java
// SchedulerConfig.java
@Configuration
@EnableScheduling
public class SchedulerConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("scheduled-");
scheduler.setErrorHandler(t -> log.error("Scheduled task error", t));
return scheduler;
}
}
```
```java
// ScheduledTasks.java
@Component
public class ScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);
// Execute every 5 minutes
@Scheduled(fixedRate = 5 * 60 * 1000)
public void syncExternalData() {
log.info("Starting external data sync");
// Sync with external system
}
// Execute 10 seconds after the previous one ends
@Scheduled(fixedDelay = 10000, initialDelay = 5000)
public void processQueue() {
// Queue processing with delay between executions
}
// Cron expression: every day at 2 AM
@Scheduled(cron = "0 0 2 * * *", zone = "Europe/Paris")
public void dailyCleanup() {
log.info("Running daily cleanup");
cleanupService.removeExpiredSessions();
cleanupService.archiveOldData();
}
// Configurable cron via properties
@Scheduled(cron = "${app.reports.cron:0 0 6 * * MON}")
public void generateWeeklyReport() {
reportService.generateAndSend();
}
}
```
```java
// ConditionalScheduling.java - Conditional activation
@Component
@ConditionalOnProperty(name = "app.scheduling.enabled", havingValue = "true")
public class ConditionalScheduledTasks {
@Scheduled(fixedRate = 60000)
public void monitorSystem() {
// Monitoring active only if configured
}
}
```
Zamanlanmış görevler isteklerin işlenmesini engellememek için ayrı bir thread havuzunda çalışır.
### 28. ApplicationEvent ile olaylar nasıl yönetilir?
Olay sistemi uygulama bileşenlerinin gevşek bağlanmasına olanak tanır.
```java
// UserRegisteredEvent.java
public class UserRegisteredEvent extends ApplicationEvent {
private final User user;
private final Instant registeredAt;
public UserRegisteredEvent(Object source, User user) {
super(source);
this.user = user;
this.registeredAt = Instant.now();
}
public User getUser() { return user; }
public Instant getRegisteredAt() { return registeredAt; }
}
```
```java
// UserService.java - Event publishing
@Service
public class UserService {
private final ApplicationEventPublisher eventPublisher;
@Transactional
public User registerUser(CreateUserRequest request) {
User user = new User(request.email(), request.name());
user = userRepository.save(user);
// Publish event after transaction
eventPublisher.publishEvent(new UserRegisteredEvent(this, user));
return user;
}
}
```
```java
// UserEventListeners.java - Event listeners
@Component
public class UserEventListeners {
private static final Logger log = LoggerFactory.getLogger(UserEventListeners.class);
// Synchronous listener
@EventListener
public void handleUserRegistered(UserRegisteredEvent event) {
log.info("User registered: {}", event.getUser().getEmail());
}
// Asynchronous listener to avoid blocking the main thread
@Async
@EventListener
public void sendWelcomeEmail(UserRegisteredEvent event) {
emailService.sendWelcomeEmail(event.getUser());
}
// Transactional listener: executes after commit
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void notifyExternalSystem(UserRegisteredEvent event) {
// External notification only if transaction succeeds
externalApiClient.notifyNewUser(event.getUser().getId());
}
// Conditional listener with SpEL
@EventListener(condition = "#event.user.role == 'PREMIUM'")
public void handlePremiumUserRegistered(UserRegisteredEvent event) {
premiumService.initializePremiumFeatures(event.getUser());
}
}
```
İşlemsel olaylar (`@TransactionalEventListener`), veritabanı ile yan etkiler arasındaki tutarlılığı garanti eder.
### 29. RestClient (Spring Boot 3+) ile HTTP istemcisi nasıl uygulanır?
`RestClient`, senkron HTTP çağrıları için modern ve akıcı API'dir; `RestTemplate`'in yerini alır.
```java
// ExternalApiClient.java
@Component
public class ExternalApiClient {
private final RestClient restClient;
public ExternalApiClient(RestClient.Builder builder, ExternalApiProperties props) {
this.restClient = builder
.baseUrl(props.getBaseUrl())
.defaultHeader("Authorization", "Bearer " + props.getApiKey())
.defaultHeader("Accept", "application/json")
.requestInterceptor((request, body, execution) -> {
log.debug("Request: {} {}", request.getMethod(), request.getURI());
return execution.execute(request, body);
})
.build();
}
public User fetchUser(Long id) {
return restClient.get()
.uri("/users/{id}", id)
.retrieve()
.body(User.class);
}
public List searchProducts(String query, int page) {
return restClient.get()
.uri(uriBuilder -> uriBuilder
.path("/products/search")
.queryParam("q", query)
.queryParam("page", page)
.build())
.retrieve()
.body(new ParameterizedTypeReference>() {});
}
public Order createOrder(CreateOrderRequest request) {
return restClient.post()
.uri("/orders")
.contentType(MediaType.APPLICATION_JSON)
.body(request)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (req, res) -> {
throw new OrderValidationException("Invalid order: " + res.getStatusCode());
})
.body(Order.class);
}
// Error handling with ResponseEntity
public Optional findUser(Long id) {
ResponseEntity response = restClient.get()
.uri("/users/{id}", id)
.retrieve()
.toEntity(User.class);
return response.getStatusCode().is2xxSuccessful()
? Optional.ofNullable(response.getBody())
: Optional.empty();
}
}
```
`RestClient`, `WebClient`'a benzer bir API sunar fakat bloklayıcı çağrılar için tasarlanmıştır; reaktif programlama kullanmayan uygulamalar için idealdir.
### 30. Flyway ile veritabanı migrasyonları nasıl yönetilir?
Flyway, şema migrasyonlarını sürümlü ve tekrarlanabilir biçimde otomatikleştirir.
```properties
# application.properties
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
spring.flyway.baseline-on-migrate=true
spring.flyway.validate-on-migrate=true
```
```sql
-- db/migration/V1__create_users_table.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_users_email ON users(email);
```
```sql
-- db/migration/V2__add_user_status.sql
ALTER TABLE users ADD COLUMN status VARCHAR(20) DEFAULT 'ACTIVE';
CREATE INDEX idx_users_status ON users(status);
```
```sql
-- db/migration/V3__create_orders_table.sql
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
total_amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
```
```java
// FlywayConfig.java - Advanced configuration
@Configuration
public class FlywayConfig {
@Bean
public FlywayMigrationStrategy migrationStrategy() {
return flyway -> {
// Validation before migration
flyway.validate();
// Execute migrations
flyway.migrate();
};
}
// Callback for post-migration actions
@Bean
public Callback flywayCallback() {
return new BaseCallback() {
@Override
public void handle(Event event, Context context) {
if (event == Event.AFTER_MIGRATE) {
log.info("Migrations completed successfully");
}
}
};
}
}
```
Her migrasyon dosyası yalnızca bir kez yürütülür ve sağlama toplamı, kaza eseri yapılan değişiklikleri tespit etmek için doğrulanır.
## Sonuç
Bu 30 soru, teknik mülakatlarda değerlendirilen temel Spring Boot konularını kapsar. Bu kavramlara hâkim olmak; çatının ve Java geliştirme iyi uygulamalarının derinlemesine anlaşıldığını gösterir.
**Hazırlık kontrol listesi:**
- ✅ Auto-configuration mekanizmasını ve koşullarını anlamak
- ✅ Spring Data JPA'da uzmanlaşmak: sorgular, işlemler, N+1 problemi
- ✅ Spring Security'i JWT ve metot güvenliği ile yapılandırabilmek
- ✅ Farklı test anotasyonlarını ve kullanım senaryolarını bilmek
- ✅ Yapılandırma için profiller ve @ConfigurationProperties kullanmak
- ✅ Önbellek, scheduling ve olayları uygulamak
- ✅ Özel metrikler ve sağlık göstergeleri yayınlamak
- ✅ Modern HTTP çağrıları için RestClient'a hâkim olmak
Gerçek projelerde düzenli pratik yapmak; bu bilgiyi pekiştirmenin ve teknik sorulara güvenle cevap vermenin en iyi yolu olmayı sürdürür.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/tr/blog/spring-boot/30-spring-boot-interview-questions