
Spring Boot에서는 시작 클래스가 들어 있는 패키지를 기준으로controller,service,domain같은 클래스를 찾아서 등록한다.
그래서 처음에는 그냥 파일만 만들면 다 읽히는 것처럼 보이지만, 실제로는 어느 패키지 아래에 두었는지가 아주 중요하다.
이번 상황에서는src/main/java아래에com.example.springedu만 있었던 것이 아니라,thymeleaf.exam이라는 별도 패키지가 하나 더 생겼다.
이때 초보자가 가장 많이 헷갈리는 부분은, 같은java폴더 아래에 있으니 당연히 같이 읽힐 것이라고 생각하는 점이다.
하지만Spring은 폴더 이름만 보고 찾는 것이 아니라, 시작 클래스의 패키지를 기준으로 스캔 범위를 정해서 찾는다.
즉, 이번 수정은 단순히 코드 한 줄을 바꾼 것이 아니라,Spring이 어디까지 찾아가야 하는지를 직접 알려 준 작업이라고 보면 된다.
처음 프로젝트를 만들면 시작 클래스인
SpringeduApplication이com.example.springedu패키지 안에 들어 있다.
이 상태에서는com.example.springedu와 그 아래 하위 패키지들이 기본 스캔 대상이 된다.
그래서com.example.springedu.controller,com.example.springedu.domain,com.example.springedu.service처럼 아래쪽에 만든 클래스들은 별도 설정 없이도 잘 읽힌다.
이런 이유로 처음에는controller를 하나 만들고 실행했을 때 특별한 설정 없이 바로 동작하는 경우가 많다.
여기서 말하는 “읽는다”는 것은 파일을 눈으로 본다는 뜻이 아니다.
Spring이 실행되면서@Controller,@Service,@Repository,@Component같은 클래스를 찾아서 자기 관리 대상, 즉bean으로 등록한다는 뜻이다.
이 과정을 이해해야 왜 어떤 클래스는 인식되고 어떤 클래스는 인식되지 않는지 바로 연결해서 볼 수 있다.
이번에는
src/main/java아래에 기존의com.example.springedu만 있는 것이 아니라,thymeleaf.exam패키지가 새로 생겼다.
겉으로 보면 둘 다 같은java아래에 있으니 같이 읽혀야 할 것처럼 보인다.
하지만 중요한 기준은 폴더의 물리적인 위치보다 패키지 이름의 시작점이다.
com.example.springedu와thymeleaf.exam은 서로 같은 트리 아래에 있는 하위 패키지 관계가 아니다.
즉,thymeleaf.exam은com.example.springedu밑에 있는 것이 아니라, 아예 다른 시작점에서 출발하는 별도 패키지이다.
그래서 시작 클래스가com.example.springedu안에 있는 상태에서는thymeleaf.exam쪽 클래스들을 기본 설정만으로는 찾지 못한다.
파일은 분명 존재하는데Spring이 관리 대상으로 등록하지 않으니, 결과적으로controller를 못 찾거나 주입이 안 되거나 화면 연결이 안 되는 문제가 생길 수 있다.
즉, 같은src/main/java아래에 있다는 것과Spring이 자동으로 읽는다는 것은 같은 말이 아니다.
초보자는 이 둘을 같은 의미로 받아들이기 쉬운데, 실제로는 전혀 다르다.
이 문제를 해결하려면
Spring에게 직접 스캔 범위를 넓혀서 알려 줘야 한다.
그때 사용하는 것이@ComponentScan이다.
@ComponentScan은 말 그대로 어떤 패키지들을 스캔할지 직접 지정하는 설정이다.
기본 동작에만 맡기지 않고, “이 패키지도 같이 찾아라”라고 명시하는 역할을 한다.
이번 경우에는 기존 패키지인com.example.springedu와 새로 추가한thymeleaf.exam을 함께 적어 주면 된다.
그러면Spring은 두 패키지를 모두 뒤져서controller나domain같은 관리 대상 클래스를 등록하게 된다.
즉, 이번 수정의 핵심은 이것이다.
기본 스캔 범위에 없는 패키지가 생겼기 때문에, 스캔할 패키지를 직접 추가했다.
아래처럼 시작 클래스에
@ComponentScan을 추가해서 두 패키지를 함께 지정하면 된다.// SpringeduApplication.java package com.example.springedu; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication // `Spring Boot` 시작 설정 @ComponentScan(basePackages = {"com.example.springedu", "thymeleaf.exam"}) // 두 패키지를 함께 스캔 public class SpringeduApplication { public static void main(String[] args) { SpringApplication.run(SpringeduApplication.class, args); // 애플리케이션 실행 } }여기서 가장 중요한 줄은
@ComponentScan(basePackages = {"com.example.springedu", "thymeleaf.exam"})이다.
이 한 줄이 “기존 패키지만 보지 말고thymeleaf.exam도 같이 찾아라”라는 뜻을 가진다.
만약 이 설정이 없으면thymeleaf.exam안에controller를 잘 만들어 두어도,Spring은 그 존재를 모른 채 실행할 수 있다.
즉, 클래스가 없는 것이 아니라 스캔 범위 밖에 있어서 등록되지 않는 것이다.
아래 흐름으로 이해하면 가장 쉽다.
@SpringBootApplication이 붙은 시작 클래스가 실행의 기준점이 된다.- 기본적으로는 시작 클래스가 있는 패키지와 그 하위 패키지만 자동 스캔한다.
thymeleaf.exam은 그 하위 패키지가 아니므로 자동 스캔 대상이 아니다.- 그래서
@ComponentScan으로 직접 패키지를 추가해야 한다.이 흐름을 잡으면 “왜 갑자기 설정을 추가해야 하지?”라는 의문이 거의 사라진다.
문법을 외우는 것이 아니라,Spring이 클래스를 찾는 범위를 넓혀 주는 작업이라고 이해하면 된다.
그리고 여기서controller와domain이 읽힌다는 말도 정확히 보면, 단순히 해당 파일을 열 수 있다는 뜻이 아니다.
Spring이 실행 시점에 찾아서 관리하고, 필요하면 연결하고, 요청이 왔을 때 사용할 수 있는 상태로 만든다는 뜻이다.
이 의미를 알고 보면 “읽는다”는 표현이 훨씬 선명해진다.
이 부분이 가장 많이 헷갈린다.
결론부터 말하면, 아니다.
src/main/java아래에 있다는 사실만으로 자동 스캔이 보장되지는 않는다.
src/main/java는 자바 소스 파일을 두는 기본 위치일 뿐이다.
그 아래에서 실제로 어떤 패키지 구조를 가지는지는 별개 문제다.
Spring은 실행할 때 시작 클래스의 패키지를 기준으로 스캔하기 때문에, 같은java아래에 있더라도 패키지 시작점이 다르면 자동으로 포함되지 않을 수 있다.
예를 들어com.example.springedu.controller는com.example.springedu의 아래쪽이라서 기본 스캔 대상이 된다.
하지만thymeleaf.exam은 아예 다른 이름으로 시작하므로 기본 대상이 아니다.
이 차이를 분명히 알아야 다음에 패키지 구조를 바꿀 때도 헤매지 않는다.
즉, 중요한 기준은src/main/java아래에 있느냐가 아니라, 시작 클래스 기준 스캔 범위 안에 있느냐이다.
이번 문제는 단순히
Thymeleaf를 쓰기 위해 설정 하나 추가한 정도로 끝나지 않는다.
프로젝트 구조를 바꾸거나 패키지를 나눌 때마다 다시 만날 수 있는 기본 원리이다.
처음에는 “왜 분명 클래스가 있는데 인식이 안 되지?”라고 느껴질 수 있다.
그럴 때는 코드 자체를 의심하기 전에, 먼저 시작 클래스 위치와 스캔 범위를 확인하는 습관을 들이면 좋다.
특히 패키지를 새로 만들었는데controller가 동작하지 않거나bean을 찾지 못한다는 오류가 나오면, 가장 먼저 스캔 범위를 의심해 보면 된다.
이 흐름을 알고 있으면 디렉터리 구조를 바꾸는 작업이 훨씬 덜 막막해진다.
이번 수정은
thymeleaf.exam패키지가com.example.springedu의 하위 패키지가 아니기 때문에 필요했다.
기본 설정만으로는Spring이 그 패키지까지 자동으로 스캔하지 못하므로,@ComponentScan으로 직접 범위를 추가해 준 것이다.
결국 핵심은 하나다.
Spring은 시작 클래스 기준으로 패키지를 스캔하므로, 구조가 바뀌면 스캔 범위도 함께 확인해야 한다.
이 점만 확실히 잡고 있으면, 앞으로 패키지를 나눌 때 왜 어떤 클래스는 읽히고 어떤 클래스는 안 읽히는지 스스로 판단할 수 있다.