자바 #3 (예외 · 동시성 · Gradle) 를 매듭짓고 오늘부터 Spring Boot. 첫 회차는 프로젝트 스캐폴딩 · 첫 실행 · 컨트롤러 · 의존성 주입 감 잡기.
Table of contents
Open Table of contents
Spring Initializr — 프로젝트 스캐폴딩
스프링 프로젝트는 Spring Initializr 라는 도구로 만든다. 웹에서 필요한 옵션 (Gradle · Java · Spring Boot 버전 · 의존성) 을 고르고 zip 을 받는 방식. hello-spring 프로젝트를 하나 뽑음.
./gradlew bootRun — 서버 실행
./gradlew bootRun
gradlew = Gradle Wrapper. 자바 #3 편에서 잠깐 언급 했던 그 wrapper 가 여기서 실제로 쓰인다 — 팀원마다 Gradle 버전 안 맞는 문제를 프로젝트 자체가 해결.
bootRun 은 Spring Boot 애플리케이션을 실행하는 표준 Gradle 태스크.
함정 — 터미널이 “80% EXECUTING” 에서 멈춰있다
![Spring Boot 실행 로그 — Tomcat initialized with port 8080, Starting service [Tomcat], Started HelloSpringApplication in 1.246 seconds, 하단에 "80% EXECUTING [14m 55s]" 진행바가 계속 유지](/assets/posts/spring-boot-log-01-first-run-controller-and-di/01-spring-boot-startup-80-executing.png)
명령을 넣고 봤더니 진행바가 80% EXECUTING [14m 55s] 에서 안 움직인다. 처음엔 뭔가 걸린 줄 알고 계속 기다렸다.
실제로는 이미 서버가 8080 에 떠 있는 상태. 로그를 보면 Started HelloSpringApplication in 1.246 seconds 로 이미 완료 표시. Gradle daemon 이 서버 프로세스를 계속 붙잡고 있어야 하니 태스크 자체는 종료되지 않아서 진행률이 저 상태로 유지되는 것.
왜 그냥 8080 이 뜨는가 — Tomcat 내장
간단한 명령어만 넣었는데 WAS 가 알아서 8080 포트에 뜬다. Spring Boot 는 Tomcat 을 내장 (embedded) 하고 있어서 가능한 것. Tomcat 을 별도로 설치·설정하지 않아도 애플리케이션이 자기 WAS 를 하나 들고 다니는 셈.
이게 예전 스프링 (spring MVC 등) 에서는 톰캣을 따로 설치하고 .war 를 그 위에 올려야 했던 흐름을 대체하는 부분.
엔드포인트가 없으면 에러 페이지
서버가 뜨긴 했지만 아무 경로도 만들지 않은 상태 → 브라우저로 http://localhost:8080 에 접속하면 에러 페이지가 나온다.
응답을 붙이려면 컨트롤러 를 만들고 경로를 지정해줘야 함:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Boot";
}
}
@RestController— 이 클래스가 REST 응답을 뱉는 컨트롤러라고 스프링에 알림@GetMapping("/hello")— HTTP GET/hello로 오는 요청을 이 메서드가 받음
이제 http://localhost:8080/hello 로 가면 “Hello, Spring Boot” 가 뜬다.
의존성 주입 (DI) — 서비스/컨트롤러 분리
방금 예제에서는 컨트롤러가 직접 문자열을 return 했다. 실제 서비스에서는 컨트롤러는 요청만 받고 로직은 서비스에서 처리. 이게 계층 분리의 기본형.
서비스와 컨트롤러를 나눠서 만들어봄:
@Service
public class GreetingService {
public String greet(String name) {
return "Hello, " + name;
}
}
@RestController
public class HelloController {
private final GreetingService service;
public HelloController(GreetingService service) { // 생성자로 서비스 요구
this.service = service;
}
@GetMapping("/hello")
public String hello() {
return service.greet("Spring");
}
}
여기서 이상한 점 — HelloController 를 만든 적이 없는데 어떻게 서비스가 들어와있는가? new GreetingService() 를 어디에서도 한 적이 없는데?
답: 스프링이 넣어준 것. 이걸 의존성 주입 (Dependency Injection) 이라고 부른다.
빈 (Bean) — 스프링이 관리하는 인스턴스
동작 흐름:
- 시작 시: 스프링이
@Service가 붙은 클래스를 찾아서 인스턴스를 하나 만들어 보관. 이 인스턴스를 빈 (Bean) 이라고 부름 - 컨트롤러 요청 시: 컨트롤러 생성자가
GreetingService를 요구하면 스프링이 빈을 알아서 꽂아줌
즉 개발자는 “이 클래스가 필요하다” 만 선언하고, 누가 만드는지 · 언제 만드는지 · 어떻게 재사용되는지 는 스프링이 관리.
인터페이스 감각의 자동화
이 그림에서 익숙한 감각이 떠올랐다 — 자바 #1 에서 배운 인터페이스. 인터페이스 = 구현체를 모르고 넘겨받아서 쓰는 계약성.
DI 는 이 계약성을 스프링이 빈을 통해 자동으로 처리 하는 것.
// 인터페이스로 받으면
private final GreetingService service; // 실제 구현체가 뭔지 컨트롤러는 모름
// 스프링이 빈을 꽂아줄 때 어떤 구현체든 교체 가능
이 구조가 갖는 실질적 이득 2가지:
- 결합도가 낮아진다 — 컨트롤러는 서비스 인터페이스만 알면 됨. 구현체가 바뀌어도 컨트롤러는 그대로
- 테스트가 쉬워진다 — 테스트 시 진짜 서비스 대신 mock 서비스 를 빈으로 꽂아 넣을 수 있음. DB 없이도 컨트롤러 테스트 가능
회고
첫 회차에 잡은 감각 3가지:
- 자바 편에서 예감했던 것들이 실제로 조립되기 시작 — 스레드풀 (Tomcat 이 내부적으로 굴린다) · 예외 처리 (컨트롤러에서 실제 예외 흐름) 등이 조각으로만 있던 게 스프링에서 하나로 얹어지기 시작.
gradlew bootRun= “Gradle 배웠던 게 여기서 쓰이는구나” — 자바 #3 편에서 wrapper 개념을 손에 익혔는데 첫 실전 활용 지점이 됨. 학습이 순서에 맞게 쌓인 것.- DI 는 인터페이스 감각의 자동화 — 자바 #1 부터 이어진 “구현체 몰라도 규격만 지키면 됨” 이라는 자바의 계약성이 스프링에선 프레임워크가 자동으로 이어주는 형태. 자바 문법 훑기가 헛되지 않았다는 감각.
더 공부해볼 것
1. MVC 패턴 vs 서비스 계층 (헷갈렸음)
- MVC 는 Model-View-Controller — 표현 계층 (요청 처리) 의 패턴
- 서비스 계층 은 비즈니스 로직 계층 — MVC 의 C 뒤에 붙는 별도 축
- 이 둘의 관계 · 스프링에서 어떻게 조합되는지 정확히 정리 필요
- 참고: Spring 공식 문서 — Web MVC
2. @Component vs @Service vs @Repository vs @Controller
- 모두 스프링이 관리하는 빈으로 등록하는 어노테이션
- 역할별로 나뉜 이유 (레이어 표시 · AOP 대상 특화 · 예외 변환 등)
- 그냥
@Component로 다 해도 동작은 하지만 왜 나눠 쓰는가
3. REST API 설계 — CRUD
@GetMapping·@PostMapping·@PutMapping·@DeleteMapping- 요청 바디 · 경로 변수 · 쿼리 파라미터 각각 어떻게 받는가
- 응답 상태 코드 표준 (200 · 201 · 400 · 404 · 500)
- 실제 CRUD 하나 만들어보면서 감 잡기
4. 의존성 주입의 3가지 방식
- 생성자 주입 (오늘 예제) — 왜 이게 권장인가
- Setter 주입 — 언제 쓰는가
- 필드 주입 (
@Autowired) — 왜 안티패턴으로 취급되는가
5. Spring Boot 의 auto-configuration
- 왜
application.properties몇 줄만 넣어도 Tomcat/DB 설정이 알아서 되는가 @EnableAutoConfiguration·spring.factories흐름- 자동 설정을 override 하고 싶을 때 접근법