Spring Boot는 Spring Framework 위에서 동작합니다. 객체를 만들고 연결하는 일은 Spring의 IoC 컨테이너가 맡고, Spring Boot는 그 위에 스타터와 자동 구성을 더해 설정 작업을 크게 줄여 줍니다. 이 장에서는 빈과 의존성 주입, 자동 구성, 스타터, 웹 계층, 타입이 있는 설정을 다룹니다.
빈과 의존성 주입
컨테이너가 관리하는 객체를 빈(bean) 이라고 합니다. 클래스에 스테레오타입 어노테이션을 붙이면 컴포넌트 스캔이 찾아 빈으로 등록합니다.
빈이 다른 빈을 필요로 하면 생성자 주입으로 받습니다. 생성자가 하나뿐이면 @Autowired를 붙이지 않아도 됩니다. 필드를 final로 두면 테스트에서 직접 생성하기도 쉽습니다.
@Service
public class GreetingService {
private final Clock clock;
public GreetingService(Clock clock) {
this.clock = clock;
}
public String greet(String name) {
int hour = LocalTime.now(clock).getHour();
return (hour < 12 ? "Good morning, " : "Hello, ") + name;
}
}
라이브러리 클래스처럼 어노테이션을 붙일 수 없는 객체는 @Configuration 클래스의 @Bean 메서드로 등록합니다.
@Configuration
public class AppConfig {
@Bean
public Clock clock() {
return Clock.systemDefaultZone();
}
}
자동 구성
@SpringBootApplication은 세 가지 어노테이션을 합친 것입니다. @SpringBootConfiguration(설정 클래스), @ComponentScan(컴포넌트 스캔), 그리고 @EnableAutoConfiguration(자동 구성)입니다.
자동 구성은 클래스 경로와 이미 등록된 빈을 보고 필요한 빈을 대신 만듭니다. 예를 들어 클래스 경로에 Spring MVC와 Tomcat이 있으면 내장 웹 서버와 DispatcherServlet을, JDBC 드라이버와 spring.datasource.url이 있으면 DataSource를 구성합니다. 각 자동 구성은 @ConditionalOnClass, @ConditionalOnMissingBean 같은 조건으로 보호되므로, 같은 타입의 빈을 직접 정의하면 자동 구성은 물러납니다. 직접 정의한 빈이 항상 우선합니다.
어떤 자동 구성이 왜 적용되었는지 궁금하면 --debug로 실행해 조건 평가 보고서를 봅니다. 특정 자동 구성을 끄려면 exclude를 씁니다.
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class DemoApplication { /* ... */ }
스타터
스타터는 한 기능에 필요한 의존성을 묶은 패키지입니다. 의존성 하나로 라이브러리와 그에 맞는 자동 구성이 함께 들어옵니다.
spring-boot-starter-webmvc: Spring MVC, 내장 Tomcat, JSON(Jackson)spring-boot-starter-data-jpa: Spring Data JPA, Hibernatespring-boot-starter-validation: Bean Validationspring-boot-starter-security: Spring Securityspring-boot-starter-actuator: 상태 확인, 지표 같은 운영 기능
REST 컨트롤러
@RestController의 메서드는 반환 값을 JSON으로 바꿔 응답합니다. 경로 변수는 @PathVariable, 요청 본문은 @RequestBody로 받습니다. Java 레코드는 요청·응답 객체로 쓰기 좋습니다.
@RestController
@RequestMapping("/api/todos")
public class TodoController {
private final TodoService service;
public TodoController(TodoService service) {
this.service = service;
}
@GetMapping("/{id}")
public TodoResponse get(@PathVariable Long id) {
return service.find(id);
}
@PostMapping
public ResponseEntity<TodoResponse> create(@RequestBody CreateTodo request) {
TodoResponse created = service.create(request.title());
return ResponseEntity.created(URI.create("/api/todos/" + created.id())).body(created);
}
public record CreateTodo(String title) {}
public record TodoResponse(Long id, String title, boolean done) {}
}
타입이 있는 설정: @ConfigurationProperties
@Value("${...}")로 값을 하나씩 받을 수도 있지만, 관련 설정이 여럿이면 @ConfigurationProperties로 묶는 편이 안전합니다. 접두어 아래의 키가 레코드 필드에 바인딩됩니다.
@ConfigurationProperties("app.mail")
public record MailProperties(String from, Duration timeout, boolean enabled) {}
app:
mail:
from: no-reply@example.com
timeout: 5s
enabled: true
진입점 클래스에 @ConfigurationPropertiesScan을 붙이면 이런 클래스를 찾아 빈으로 등록하므로, 다른 빈에서 생성자로 주입받을 수 있습니다. 5s처럼 쓴 값은 Duration으로 자동 변환됩니다.
Kotlin으로 쓸 때
Kotlin 클래스는 기본적으로 final이라 Spring의 프록시를 만들 수 없습니다. Initializr로 Kotlin 프로젝트를 만들면 kotlin("plugin.spring") 플러그인이 들어가, Spring 어노테이션이 붙은 클래스를 자동으로 열어 줍니다. 생성자 주입은 Kotlin의 주 생성자와 잘 맞습니다.
@Service
class GreetingService(private val clock: Clock) {
fun greet(name: String) = "Hello, $name"
}
정리
- 객체는 컨테이너가 빈으로 만들고, 생성자 주입으로 연결합니다.
- 자동 구성은 클래스 경로와 조건을 보고 빈을 만들며, 직접 정의한 빈이 항상 우선합니다.
- 스타터 하나로 기능에 필요한 의존성과 자동 구성이 함께 들어옵니다.
- 관련 설정은
@ConfigurationProperties 레코드로 묶어 타입 안전하게 씁니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.