[Java 풀스택 개발자] 백엔드 프로그래밍 편:스프링 부트

부트캠프 일지/멀티캠퍼스 TIL
2026.04.14
반응형

 

0. 들어가기 전에

지난 글에서는 Spring MVC의 요청 흐름을 중심으로, DispatcherServlet이 어떤 기준으로 핸들러를 찾고 뷰를 렌더링하는지 따라가 보았다. 지난 번에는 이걸 기반으로 MyBatis를 한다고 예고했었는데, 수업의 내용이 스프링 부트Spring Boot로 바뀌었다. 벌써 스프링부트를 배울 차례라니, 감회가 새로운 것과 별개로 또 다시 새로운 것을 익혀야 되었다. Spring Boot부터는 전에 했던 Spring과는 설정 파일도 달라지고 실행 방식도 달라진다. "그냥 편하게 해주는 도구"라고만 이해하면 이후에 디버깅 포인트가 생겼을 때 답답해지기 쉽다. 그러하여 실전과 가장 가까운 스프링 부트를 먼저 다루는 쪽이 나에게 더 도움이 될 거 같아, 주제를 바꾸게 되었다.

 

그래서 이번 글은 “Spring Boot가 무엇을 자동으로 해 주는지”를 외우는 것이 아니라, 왜 Spring Boot가 필요해졌는지, 어떤 철학으로 스프링 프로젝트의 기본값을 정리했는지, 그리고 장점과 단점이 정확히 어디서 갈리는지를 이론 위주로 정리해보려 한다.

 

 

 

1. Spring Boot는 무엇이고, 무엇이 아닌가

Spring Boot를 “스프링의 최신 버전”이라고 받아들이면 첫 단추부터 어긋난다. Boot는 스프링 프레임워크를 대체하는 게 아니라, 스프링 생태계를 프로젝트로 굴리기 쉬운 형태로 표준화한 도구에 가깝다. 스프링이 제공하는 IoC/DI, AOP, MVC 같은 엔진은 그대로이고, Boot는 그 엔진을 바로 시동 걸 수 있도록 “기본 옵션 세팅”을 제공한다.

 

즉, Spring Boot의 핵심은 “새로운 기능”보다 관례(Convention)다. 어떤 의존성을 추가하면 어떤 설정이 자동으로 따라오고, 어떤 설정을 프로퍼티에 적으면 어떤 빈이 생기며, 어떤 기능은 기본적으로 켜지고 어떤 기능은 명시해야만 켜진다. Boot를 배운다는 건 이 관례를 배운다는 뜻이다.

 

또 하나 중요한 점은 Boot가 “마법처럼 모든 걸 해결한다”는 오해다. Boot가 대신해 주는 것은 반복 설정이고, 해결해 주지 않는 것은 설계 책임이다. 예를 들어 컨트롤러가 비대해지는 문제, 트랜잭션 경계가 흐려지는 문제, 예외 응답이 일관되지 않는 문제는 Boot를 써도 그대로 남는다. Boot는 좋은 설계를 자동으로 만들어주지 않는다. 다만 좋은 설계를 적용하기 좋은 “기본 환경”을 제공해 주는 쪽에 가깝다.

 

 

“Boot를 쓴다”는 말이 의미하는 것

부트로 프로젝트를 시작하면, 개발자는 스프링의 각 모듈을 “선택하고 조립하는 과정”을 상당 부분 건너뛰게 된다. 대신 Starter 선택, 프로퍼티 설정, 그리고 필요한 곳만 커스터마이징이라는 패턴으로 이동한다. 그래서 부트를 처음 배우는 시점에는 설정이 줄어든 것처럼 보이지만, 시간이 지나면 “설정을 어디에 적는가”가 바뀐 것임을 체감하게 된다.

 

이 변화는 학습 방식도 바꾼다. 전통 스프링에서는 “설정 파일을 어떻게 작성하는가”가 큰 주제였다면, 부트에서는 “기본값은 무엇이고, 어떤 조건에서 자동으로 켜지며, 어디에서 오버라이드할 것인가”가 큰 주제가 된다. 같은 스프링을 쓰더라도, 관찰해야 할 지점이 달라지는 것이다.

 

💡 토막 개념: Convention over Configuration

자주 쓰는 설정을 프레임워크가 기본값으로 제공하고, 개발자는 필요한 부분만 덮어쓰는 방식이다. 설정을 “없애는” 것이 아니라, “기본값을 정해두고 선택지를 줄이는” 철학에 가깝다.

 

실무 TIP

Spring Boot가 도입된 프로젝트는 온보딩이 빨라지지만, 처음에는 “왜 되는지”가 잘 안 보이기도 한다. 이럴 때는 Boot를 의심하기보다, 기본값이 무엇인지부터 확인하는 습관이 도움이 된다. 설정이 줄어든 만큼, 문제의 원인은 “설정 누락”이 아니라 “기본값과 요구사항의 불일치”에서 많이 발생한다.

 

 

 

2. 왜 Spring Boot가 등장했을까

전통적인 스프링 프로젝트는 유연했다. 유연하다는 말은 바꿀 수 있는 게 많다는 뜻이고, 동시에 결정해야 할 것도 많다는 뜻이다. 과거에는 웹 프로젝트를 시작할 때마다 이런 질문을 다시 했다.

 

라이브러리 버전 조합, 실행 방식, 설정 관리, 환경 분리는 어떻게 할 거야?

 

이와 같은 질문과 그에 대한 결정이 프로젝트마다 반복됐고, 이 반복이 곧 비용이 됐다. 그리고 이 비용은 팀 규모가 커질수록 커질 수밖에 없다. 특히 수업/토이 단계에서는 체감이 약하지만, 실무에서는 “프로젝트를 새로 만들 때마다 똑같은 초기 작업”이 누적된다. Spring Boot는 이 반복을 줄이기 위해 등장했다. 핵심은 표준 실행 형태검증된 의존성 조합, 그리고 외부화된 설정이다.

 

Boot는 스프링을 대체하지 않고, 스프링을 프로젝트로 굴리기 쉽게 만든다.

 

 

“설정이 많다”는 문제는 왜 반복됐을까

스프링 프로젝트에서 설정이 많아지는 가장 큰 이유는 “어디까지가 프레임워크가 책임지고, 어디부터가 애플리케이션이 책임지는가”가 프로젝트마다 달라지기 때문이다. 예를 들어 MVC만 쓰는 프로젝트에서도 뷰 기술이 JSP인지, 템플릿 엔진인지, REST API 중심인지에 따라 필요한 빈과 설정이 달라진다. JDBC를 붙이면 커넥션 풀과 트랜잭션 설정이 따라오고, 보안을 붙이면 필터 체인과 인증 방식 설정이 따라온다.

 

여기서 스프링이 제공하는 유연함은 장점이지만, 팀 입장에서는 “기본값이 없어서 매번 새로 정해야 한다”는 단점으로 체감될 수 있다. Boot는 바로 이 지점을 겨냥한다. “대부분의 경우”를 기준으로 기본값을 만들어 두고, 프로젝트가 정말 다른 선택을 해야 할 때만 설정을 강제한다.

 

💡 토막 개념: 기본값(Default)과 오버라이드(Override)

Boot의 설계는 “기본값 제공 → 필요할 때만 덮어쓰기”로 요약된다. 그래서 Boot 프로젝트에서 커스터마이징은 전부 새로 만드는 것이 아니라, 기존 기본값을 어디서 덮어쓸지를 정하는 문제로 바뀐다.

 

실무 TIP

설정을 줄이겠다고 모든 걸 자동으로 맡기면, 나중에 팀 내에서 “이 프로젝트는 왜 이렇게 돌아가지?” 같은 질문이 쌓이기 쉽다. 반대로 자동 설정을 전부 꺼 버리면 부트를 쓰는 의미가 줄어든다. 보통은 기본값을 문서화하고, 바꾸는 지점만 팀 합의로 고정하는 방식이 가장 안전하다.

 

💡 토막 개념: 표준 실행 형태

“어떻게 실행하는가”를 프로젝트마다 다르게 두지 않고, 공통된 실행 방식과 설정 위치를 제공하는 것이다. 예를 들어 내장 서버로 실행되는 단일 실행 파일 형태, 그리고 application.properties/yml 중심의 설정 관리가 대표적이다.

 

 

 

3. Starter: 의존성은 왜 묶어서 주는가

Spring Boot의 첫 체감 포인트는 보통 Starter다. Starter는 단순히 “묶음”이 아니라, 호환되는 조합을 제공한다. 웹 개발을 하려면 Spring MVC뿐 아니라 JSON 직렬화, 검증, 로깅, 내장 서버 같은 것들이 같이 움직여야 한다. 이 조합을 개발자가 매번 손으로 맞추면, 버전 충돌이나 누락이 쉽게 발생한다.

 

그래서 Boot는 spring-boot-starter-web 같은 이름으로 “웹을 하려면 보통 이것들이 같이 필요하다”는 합의를 제공한다. 덕분에 팀은 라이브러리 선택의 반복에서 벗어나고, 기능 개발에 집중할 수 있다.

 

하지만 여기서 오해하면 안 되는 점이 있다. Starter가 ‘가볍다’는 뜻은 아니다. Starter는 대부분의 경우 넉넉한 조합을 제공한다. 따라서 불필요한 의존성이 섞일 수도 있고, 특정 환경에서는 더 정교한 커스터마이징이 필요할 수 있다. 다만 초기에는 “안전한 기본값”이 더 큰 가치가 되는 경우가 많다.

 

 

의존성 충돌은 왜 자주 생길까

자바 생태계에서는 라이브러리 버전이 서로 얽히는 경우가 많다. A 라이브러리가 B의 특정 버전을 요구하고, 다른 라이브러리가 B의 더 높은 버전을 요구하면 런타임에서 충돌이 터지기도 한다. 그리고 충돌은 컴파일 타임이 아니라 실행 시점에 드러나는 경우가 있어 더 골치가 아프다.

 

Boot는 이 문제를 “검증된 버전 조합”으로 해결하려 한다. 흔히 말하는 BOM(의존성 관리 표준)을 통해 라이브러리 버전을 한 곳에서 관리하고, starter를 통해 자주 쓰는 조합을 하나의 선택지로 만든다. 그래서 개발자가 모든 라이브러리를 직접 조합하는 대신, 스프링부트가 제공하는 조합을 기본값으로 삼게 된다.

 

💡 토막 개념: BOM(Bill of Materials)

프로젝트에서 사용되는 라이브러리들의 “호환 버전 목록”을 의미한다. Boot는 이 목록을 통해 “이 버전 조합은 같이 써도 된다”는 기준을 제공한다.

 

 

Starter는 “무엇을 쓸지”를 묶고, BOM은 “어떤 버전 조합이 안전한지”를 정리한다.

 

 

실무 TIP

Starter를 최소화하려고 처음부터 모든 의존성을 직접 조합하면, 의도치 않은 충돌이 늘어날 수 있다. 초반에는 Starter를 그대로 쓰고, 병목이나 보안/라이선스 이슈가 생길 때 필요한 의존성만 남기는 방향으로 최적화하는 순서가 흔하다.

 

 

 

4. Auto Configuration: 자동 설정은 어디서 결정될까

자동 설정은 Spring Boot의 “편함”을 만드는 핵심이지만, 동시에 “블랙박스”로 느껴지는 지점이기도 하다. 자동 설정은 마음대로 켜지는 것이 아니라, 조건에 의해 켜진다. 대표적인 조건은 다음과 같다.

 

클래스패스에 특정 클래스가 존재하는가, 프로퍼티로 특정 기능이 활성화되어 있는가, 그리고 같은 타입의 빈이 이미 등록되어 있는가. 이 세 가지가 자동 설정의 골격이다. 그래서 어떤 기능이 “갑자기 켜졌다”면, 실제로는 프로젝트에 어떤 의존성이 추가되었거나, 어떤 프로퍼티가 기본값으로 활성화되어 있었을 확률이 높다.

 

자동 설정은 ‘무조건’이 아니라 ‘조건부’다.

 

 

💡 토막 개념: 조건부 구성(Conditional)

특정 조건이 만족될 때만 빈을 등록하는 방식이다. Boot의 자동 설정은 대부분 이 조건부 구성을 이용해 “있으면 켜고, 없으면 건드리지 않는” 형태로 작성되어 있다.

 

초보자가 가장 많이 헷갈리는 것은 “내가 설정을 안 했는데 왜 돌아가지?”다. 이 질문은 자연스럽다. 하지만 답도 단순하다. Boot가 제공하는 기본값이 있고, 그 기본값이 현재 프로젝트 조건과 맞아서 자동으로 구성된 것이다. 따라서 부트 프로젝트에서 디버깅을 할 때는 “내 설정이 없다”가 아니라, 기본값이 무엇인지를 먼저 확인해야 한다.

 

 

자동 설정이 “꺼지는” 순간

자동 설정은 항상 켜져 있는 기능이 아니다. Boot는 “이미 사용자가 정의한 빈이 있으면, 기본 빈은 등록하지 않는다” 같은 규칙을 자주 사용한다. 이 덕분에 개발자는 기본값을 손쉽게 바꿀 수 있지만, 반대로 말하면 “내가 빈 하나를 등록했는데 다른 기능이 갑자기 바뀌었다” 같은 현상이 생길 수 있다.

 

예를 들어 JSON 직렬화를 커스터마이징하려고 특정 설정 빈을 추가했다가, 메시지 컨버터 구성이 달라지는 식이다. 이런 상황에서 핵심은 “부트가 뭘 했는지”가 아니라 “내가 어떤 오버라이드를 했는지”다. 결국 자동 설정은 조건의 게임이고, 조건의 입력값은 클래스패스와 프로퍼티, 그리고 사용자가 등록한 빈이다.

 

// 예시: 스프링부트 앱의 시작점
// 이 한 줄이 '패키지 스캔 범위'와 '자동 설정 활성화'의 출발이 된다
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

💡 토막 개념: @SpringBootApplication

부트 애플리케이션의 시작점이다. 보통 컴포넌트 스캔의 기준 패키지를 잡고, 자동 설정을 활성화하는 역할을 한다. 그래서 이 클래스의 위치(패키지)가 바뀌면 빈 스캔 범위가 달라질 수 있다.

 

시작 클래스의 패키지 위치가 “어디까지 빈을 찾을지”의 기준이 된다.

 

실무 TIP

자동 설정이 문제를 만들었다고 느낄 때, 무작정 설정을 전부 수동으로 바꾸면 유지보수 비용이 커진다. 대신 “기본값을 유지하고, 필요한 부분만 오버라이드한다”가 Boot 스타일에 더 가깝다. 예를 들어 같은 타입의 빈을 직접 등록하면, Boot가 제공하던 기본 빈이 조건부로 빠지는 경우가 많다. 이 원리를 알면 수정 포인트가 줄어든다.

 

 

 

5. Externalized Configuration: 설정은 왜 코드 밖으로 나가야 할까

Spring Boot에서 설정의 중심은 application.properties 또는 application.yml이다. 여기서 중요한 건 “파일 형식”이 아니라 “설정의 위치”다. 설정이 코드 밖으로 나가면, 환경별로 바꿔야 하는 것들을 안전하게 분리할 수 있다.

 

개발/테스트/운영은 DB 주소도 다르고, 로그 레벨도 다르고, 외부 API 엔드포인트도 다르다. 이걸 코드에 박아두면 배포할 때마다 위험해진다. Boot는 이런 문제를 해결하기 위해 프로파일과 외부화된 설정 체계를 제공한다. 그래서 “동일한 코드”를 “다른 환경 설정”으로 실행하는 흐름이 자연스러워진다.

 

설정 우선순위가 중요한 이유

부트에서 설정은 한 곳에서만 읽히지 않는다. 로컬의 프로퍼티 파일, 프로파일별 파일, 환경 변수, 실행 인자 등 여러 경로로 들어올 수 있다. 그래서 같은 키를 여러 곳에 적어두면 “어떤 값이 최종 적용되는가”를 놓치기 쉽다. 이게 바로 부트 프로젝트에서 발생하는 대표적인 혼란 포인트다.

 

따라서 설정을 설계할 때는 “어디에 적을 것인가”를 팀 규칙으로 정하는 편이 좋다. 예를 들어 민감 정보는 환경 변수/시크릿 매니저로, 공통값은 프로퍼티로, 개발에서만 필요한 값은 dev 프로파일 파일로 두는 식이다. 이렇게 정리하면 설정이 늘어도 구조가 흐트러지지 않는다.

 

값이 어디서 들어오는지 알면, “왜 이 값이 적용됐지?”가 빠르게 정리된다.

 

 

 

💡 토막 개념: 프로파일(Profile)

실행 환경을 구분하기 위한 단위다. 예를 들어 dev/prod처럼 환경을 나누고, 환경마다 다른 설정 파일을 적용할 수 있다. “코드는 같고 설정만 다르다”는 운영의 기본을 지원하는 기능이다.

 

# 예시: 개발 환경 프로파일 활성화
spring.profiles.active=dev

# 예시: 서버 포트 변경
server.port=8081

 

실무 TIP

운영 사고 중에는 “환경 설정이 섞였다”가 생각보다 많다. 개발 DB로 붙거나, 운영에서 디버그 로그가 켜지거나, 외부 API가 스테이징을 가리키는 식이다. 프로파일을 제대로 분리해 두면 이런 사고를 크게 줄일 수 있고, 배포 파이프라인에서도 설정 주입이 정돈된다.

 

 

 

6. 실행과 배포: 내장 서버와 실행 단위

Spring Boot는 내장 톰캣(또는 Jetty/Undertow)을 기본으로 사용한다. 이 덕분에 “외부 WAS에 war를 올린다”는 배포 방식이 아니라, 애플리케이션이 스스로 서버를 띄우는 실행 형태가 기본값이 된다. 개발자 입장에서는 실행이 단순해지고, 환경 차이가 줄어드는 장점이 있다.

 

내장 서버가 중요한 이유는 단순히 편해서가 아니다. 실행 단위가 하나로 정리되면, 개발 환경과 운영 환경의 차이가 줄어들고 배포도 단순해진다. 물론 조직의 인프라 정책에 따라 war 배포가 필요한 곳도 있지만, Boot가 내장 서버를 기본값으로 삼은 데에는 “표준 실행 형태”를 만들겠다는 의도가 있다.

 

Boot는 애플리케이션이 서버를 포함하는 실행 형태를 기본값으로 둔다.

 

 

내장 서버가 바꾸는 것은 “개발 속도”만이 아니다

외부 WAS 배포 방식에서는 “애플리케이션”과 “서버 설정”이 분리되어 있기 쉽다. 이 구조는 인프라가 강하게 통제되는 조직에서는 장점이 될 수 있지만, 개인 개발이나 작은 팀에서는 배포가 느려지는 원인이 되기도 한다. 내장 서버 방식은 애플리케이션이 서버를 포함하므로, 실행과 배포 단위가 일치한다. 결국 “무엇을 배포했는지”를 추적하기 쉬워지고 롤백도 단순해진다.

 

다만 내장 서버라고 해서 운영이 자동으로 쉬워지는 건 아니다. 포트, 스레드, 타임아웃, TLS, 프록시 뒤에서의 헤더 처리 같은 운영 요소는 여전히 남는다. 부트는 이 요소들을 프로퍼티로 조정할 수 있게 정리해 두었고, 이것이 “운영 표준”과 연결되는 지점이다.

 

💡 토막 개념: 프로덕션 레디(Production-ready)

개발 환경에서만 돌아가는 코드가 아니라, 운영에서 관측·보안·장애 대응까지 고려해 “배포 가능한 상태”로 만든다는 뜻이다. 부트는 기본적으로 운영을 전제로 한 설정/도구(예: actuator, 외부화된 설정)를 제공하려는 성향이 강하다.

 

 

 

7. 장단점: 무엇을 얻고 무엇을 잃는가

Spring Boot의 장점을 한 문장으로 요약하면 “표준화와 생산성”이다. 프로젝트 시작 속도가 빨라지고, 설정이 줄고, 팀이 공유할 수 있는 기본 구조가 생긴다. Starter로 의존성 충돌이 줄고, 프로파일과 외부화된 설정으로 환경 분리가 쉬워진다. 그리고 운영 관측을 위한 도구를 붙이기도 쉬워진다.

 

반대로 단점은 “자동화로 인해 원인이 가려질 수 있다”는 것이다. Boot를 처음 접하면 “내가 뭘 설정했는지”보다 “부트가 뭘 해줬는지”가 더 크게 느껴지고, 그 감각이 익숙해질 때까지는 디버깅이 어렵게 느껴질 수도 있다.

 

결국 중요한 기준은 이것이다. Boot는 스프링을 쉽게 쓰기 위한 도구이고, 설계의 책임을 대신하지 않는다. 그래서 Boot를 배운다는 건 “설정을 줄이는 법”만 배우는 게 아니라, “설정과 실행을 표준화하는 이유”를 함께 배우는 것이다.

 

편해진 만큼, 기본값과 조건을 이해하는 책임이 생긴다.

 

 

“부트를 쓴다”는 선택을 한 문장으로 정리하면

부트를 쓴다는 건 “설정이 줄어든다”가 아니라, “설정의 위치가 바뀐다”에 더 가깝다. XML이나 자바 설정 코드로 흩어져 있던 값들이 프로퍼티로 모이고, 라이브러리 조합은 starter로 모인다. 그리고 자동 설정이 기본값을 채운다. 그래서 프로젝트는 빠르게 출발할 수 있지만, 동시에 개발자는 “기본값을 이해하는 책임”을 갖게 된다.

 

이 균형을 잡는 게 결국 학습의 핵심이다. 초반에는 기본값을 받아들이고 흐름을 익히고, 이후에 요구사항이 생길 때마다 “어떤 기본값을 어떤 방식으로 오버라이드할 것인지”를 한 단계씩 늘려가는 것이 가장 무난한 학습 루트다.

 

그래서 지금 시점에서의 목표는 “부트의 모든 설정 키를 외운다”가 아니다. Starter를 추가하면 무엇이 켜질 가능성이 있는지, 프로퍼티는 어디에서 덮어쓰이는지, 빈을 직접 등록하면 자동 설정이 어떤 조건으로 빠지는지 같은 큰 규칙을 잡는 것이 먼저다. 이 규칙이 잡히면, 이후에 기능을 붙일 때마다 학습이 ‘누적’된다.

 

결국 Spring Boot는 “빠르게 시작”하라고 주는 도구이면서, 동시에 “같은 방식으로 운영”하라고 주는 도구다. 이 두 가지를 같이 잡으면, 부트가 단순 편의 기능이 아니라 프로젝트의 뼈대라는 점이 더 또렷해진다.

 

실무 TIP

Spring Boot를 “편하니까 쓴다”로만 끝내면, 나중에 팀 규모가 커졌을 때 규칙이 흔들린다. 반대로 “자동 설정은 무조건 나쁘다”로 접근하면 생산성이 떨어진다. 대부분의 팀은 기본값을 존중하되, 이유가 있는 커스터마이징만 한다는 균형점을 찾는다. 그 균형점이 곧 “왜 부트인가”에 대한 실전 답에 가깝다.

 

 

 

8. 마무리하며

이번 글에서는 Spring Boot가 스프링을 대체하는 것이 아니라, 스프링 프로젝트의 실행과 설정을 표준화하기 위해 등장한 도구라는 점을 중심으로 정리해 보았다. Starter가 의존성 조합을 제공하는 이유, 자동 설정이 조건부로 동작하는 이유, 그리고 외부화된 설정과 프로파일이 운영에서 왜 중요한지를 이론 위주로 정리했다.

 

결론적으로 Spring Boot는 “스프링을 더 쉽게 쓰게 해주는 도구”이면서도, 동시에 “팀이 같은 방식으로 개발하고 운영하게 만드는 표준”이다. 그래서 부트를 배울 때는 기능을 나열하기보다, 기본값이 만들어내는 흐름을 먼저 잡는 편이 오래 간다. 이 흐름을 한 번 이해하고 나면, 이후에 MyBatis나 Security 같은 모듈을 붙일 때도 “어떤 starter를 추가했고, 어떤 자동 설정이 켜졌고, 어떤 프로퍼티가 영향을 주는지”가 훨씬 명확해진다.

 

회고

다음 주에 뭘 쓸지는 솔직히 잘 모르겠다. 이론을 마무리하고 본격적으로 프로젝트에 들어가는 단계이기 때문에, 아마 백엔드를 마무리할 좋은 글로 찾아오고 싶어서 고민이 많다. 실전과 관련된 글을 써보고 싶어, 이번주는 보류하도록 한다.

 

이미지 출처

※ 모든 이미지는 직접 제작하거나 저작권 문제가 없는 이미지로 제작되었습니다.

반응형
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31