스프링 부트 구조
스프링 부트는 여러 개의 계층으로 이루어져 있습니다. 여기서 계층이란, 각자의 역할과 책임이 있는 어떤 소프트웨어의 구성 요소라고 할 수 있습니다. 스프링 부트는 이러한 각 계층들이 양 옆의 계층과 통신하는 구조를 따릅니다.
그럼 먼저 어떤 계층이 있는지 알아보겠습니다.
Spring Boot의 계층
1. 프레젠테이션 계층
Http 요청을 받은 후, 그 요청을 비즈니스 계층으로 전송하는 역할을 합니다. Controller가 이 계층의 역할을 담당합니다.
2. 비즈니스 계층
모든 비즈니스 로직(서비스를 만들기 위한 로직)을 처리하는 계층입니다. Service가 비즈니스 계층의 역할을 담당합니다.
3. 퍼시스턴스 계층
DAO 객체를 사용하여 데이터베이스에 접근하는 것과 같이, 모든 데이터베이스 관련 로직을 처리합니다.. 여기서 DAO 객체란, 데이터베이스 계층과 상호작용하기 위한 객체입니다. Repository가 퍼시스턴스 계층의 역할을 합니다.
위 계층의 통신 구조를 시각화 해보면 아래와 같습니다.
위 구성 요소들은 스프링 컨테이너를 형성합니다. 그럼 어떻게 HTTP 요청이 스프링 프레임워크 내부로 들어와 요청을 처리하는지 조금 더 자세히알아보겠습니다.
스프링 부트로 만든 Application에 HTTP 요청이 들어오면 우선 1. tomcat과 같은 WAS로 HTTP 요청이 가게 됩니다. 그 후 Spring Container 내부에 있는 2. Dispatcher Servlet이라는 서블렛이 요청된 HTTP url을 분석하여 이 요청을 처리할 수 있는 Controller를 찾아 요청을 넘겨주면서 프레젠테이션 계층의 역할이 시작됩니다. 요청을 처리할 수 있는 Controller가 요청을 받으면 3. 해당 요청을 처리할 수 있는 메서드를 실행해 비즈니스 계층과 퍼시스턴스 계층을 통하면서 필요한 데이터를 가져옵니다. 그 데이터를 다시 Dispatcher Servlet으로 반환하면, 4. Dispatcher Servlet이 View Resolver라는 Template 엔진을 사용해 HTML 문서를 만들거나 JSON, XML 등의 데이터를 생성하고 적절한 5. View로 전달하여 적절하게 시각화 해 주게 됩니다.
위 동작 원리는 조금 복잡해 보이지만 잘 알아두면 Spring 기반 Application 실행 시 오류가 발생할 때 어느 부분을 잘 살펴봐야 하는지 빠르게 파악할 수 있어 중요하다고 할 수 있습니다.
위에서 배운 Spring Boot의 동작 원리를 예제를 통해 살펴보겠습니다.
프로젝트 구조는 아래와 같습니다.
이 예제에서는 GET을 이용하여 HTTP 요청(/test)을 보내면, data.sql로 생성한 데이터베이스에 저장된 member 테이블의 모든 멤버들의 정보를 반환하도록 하는 예제입니다. 각 클래스의 코드는 아래와 같습니다.
1. TestController
@RestController
public class TestController {
@Autowired
TestService testService;
@GetMapping("/test")
public List<Member> getAllMembers(){
List<Member> members = testService.getAllMembers();
return members;
}
}
2. TestService
@Service
public class TestService {
@Autowired
MemberRepository memberRepository;
public List<Member> getAllMembers(){
return memberRepository.findAll();
}
}
3. Member
@Getter
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id", updatable=false)
private Long id;
@Column(name="name", nullable = false)
private String name;
}
4. MemberRepository
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}
5. data.sql
INSERT INTO member (id, name) VALUES (1, 'name 1')
INSERT INTO member (id, name) VALUES (2, 'name 2')
INSERT INTO member (id, name) VALUES (3, 'name 3')
위 코드를 이용하여 Postman으로 http:localhost:8080/test 요청을 보내면 아래와 같이 data.sql에서 저장한 데이터를 얻을 수 있습니다.
위 예제가 어떻게 돌아가는지 Spring Boot 동작 원리로 살펴보겠습니다.
우선 Postman에서 해당 url을 1. Tomcat(WAS)에 요청하면, 2. Dispatcher Servlet이 해당 요청 URL을 분석하여 GET /test 요청을 처리할 수 있는 Controller인 TestController을 찾아 요청을 보냅니다. 그러면 3. TestController는 해당 URL에 맞는 메서드인 getAllMembers()를 실행시키고 그로 인해 TestService에서 비즈니스 로직을 수행하면서 원하는 값을 돌려 받을 수 있게 됩니다. 이 값을 다시 Dispatcher Servlet에 보내주고, 4. Dispatcher Servlet은 View Resolver를 통해 위와 같이 JSON 형태의 문자열로 출력해 주면서 Spring Boot가 동작하게 되는 것입니다.
참고
[신선영] 스프링 부트 3 백엔드 개발자 되기 (자바편, 2023)