이번 포스팅에서는 Spring Initializer를 통해 새 프로젝트를 생성하면서 프로젝트를 세팅하는 과정을 공유해보고자 합니다.
Spring Initializer
를 통해 새 프로젝트를 생성합니다.
이 때 우리는 또 한번의 선택을 거쳐야 하는데요, 바로 프로젝트 기술 환경 설정입니다.
프로젝트 이름부터 사용할 언어, 빌드 툴, JDK 버전, 패키징 타입까지 선택해줍니다.
💡 Check List
Project Name
,Group
,Artifact
- Language:
Java
orKotlin
or ex- JDK, Java Version:
8
or11
or17
- Build Tool:
Gradle
orMaven
- Packing:
jar
orwar
Spring Boot
version
블로그 작성용 프로젝트이지만 나름 기술 선택에 있어 의의가 있습니다.
project 이름
: Test용이라 spring이 정해준 대로 (demo...ㅋ)group id
: github에 작성용으로 올릴거라 내 아이디 경로로language
: 아직 코푸링을 할 자신이 없어 Java
선택Build Tool
: Gradle
, Maven
둘다 써봤지만 느낀점: Gradle
이 레퍼런스가 더 많고, Maven은 익숙. 보통 Maven
선택, 이번엔 Gradle
선택해봄Java Version
: 주로 Java 8 버전을 선택했지만, 신기술 도입(공부)를 위해 17
로 선택Packing
: Spring Boot는 내장 톰캣이기 때문에 주로 jar
로 패키징 하는 편Spring Boot
버전을 선택하고, 사용할 라이브러리들(dependencies)을 선택합니다.
💡 Spring Boot Version
Spring Boot 버전을 선택할 때는 신중히 선택해야합니다.
- Java 버전에 따라 가용한 Boot 버전
- Boot 버전에 따라 관련 라이브러리들 자동 다운로드, 기존에 돌아가던 코드 안돌아갈 수 있음
가장 큰 예시로는 Spring Boot 2 => 3 Migration시 발생하는 문제들입니다.
javax 패키지
⇒jarkata 패키지
Spring Security 5
⇒Spring Security 6
직접 기술을 선택해보지 않았다면 기술을 선택하면서 기존에 쓰던데로 쓰면 되지, 이걸 왜 고민해? 하실 수는 있겠지만, 이는 새로운 기술을 도입(도전)하느냐 마느냐의 차이입니다.
Java-Spring
스택을 Kotlin-Spring
으로 변경해볼까?Java 8
만 썼는데, 17
로 해볼까?Maven
만 고집했는데, 요즘에 Gradle
을 많이 쓴다는데 써볼까?많은 회사 혹은 개발자들은 이런 고민을 하죠
백엔드 개발자들은 프로젝트를 처음 구성할 때 사용할 기술에 관해 많은 고민을 합니다.
- 우리 데이터베이스(DB)로 CockroachDB가 요즘 흥한다던데 이걸 사용해 보는 건 어떨까?
- HTTP 클라이언트로 Retrofit이 좋을까 Feign을 사용하는 것이 좋을까?
- MySQL은 좀 진부하지 않아? 새로운 걸 좀 해볼까?
이 과정은 짧게는 개발자들 사이의 갑론을박에서 길게는 윗선의 결정까지 적지 않은 시간적 비용이 소모됩니다.
문제는 이렇게 어려운 선택들이 프로젝트를 성공적으로 이끌게 되었는가? 물론 그럴 때도 있겠지만 프로젝트 중후반에서야 "아.. 이거 OO를 사용하면 안 되는 거였는데.."라는 후회(?)를 하게 되는 경우가 적지 않게 있고, 이미 선정된 기술들이 이것 저곳 비즈니스 코드들 깊이 뿌리내려 있는 상황이라 이러지도 저러지도 못하는 상황에서 힘들게 프로젝트를 마무리하게 되는 것을 심심치 않게 보았습니다.
우선, 프로젝트의 큰 뼈대를 잡기 위해 Package
들을 생성합니다.
💡 왜 패키지를 미리 정의할까?
사실 이 Package 구조는 언제든 변할 수 있습니다. 저는 규칙을 정하는 것을 좋아하기에
무엇을 어디에 저장할 지 미리 구조를 정의해두면, 클래스가 이리저리 튀는 것을 방지할 수 있습니다.
이 패키지들은 제가 주로 사용하는 구조입니다.
annotation
: 커스텀 어노테이션 클래스 모음aop
: AOP와 관련된 클래스(Aspect, ExceptionHandler, 등) 모음api
: API 작업과 관련된 클래스(RestController, Service, VO, 등) 모음config
: 전역 빈들을 관리하기 위한 클래스 모음dao
: DB 작업과 관련된 클래스(Entity, Repository, 등) 모음utils
: 기타 static 작업 유틸 클래스 모음Spring
에서는 Spring의 IoC Container
에 의해 관리되는 POJO(Plain Old Java Object)
를 Bean
이라고 부르며, 이러한 Bean들은 Spring을 구성하는 핵심 요소입니다.
Spring Bean
- POJO(Plain Old Java Object)로써 Spring 애플리케이션을 구성하는 핵심 객체
- Spring IoC 컨테이너(또는 DI 컨테이너)에 의해 생성 및 관리
- class, id, scope, constructor-arg 등을 주요 속성
스프링은 컴포넌트 스캔을 통해 스프링 IoC 컨테이너에서 사용할 빈들을 등록합니다.
스프링에서 사용하는 컴포넌트(빈)에는 @Component
, @Controller
, @Service
, @Repository
, @Configuration
들이 있습니다.
@Component
: 컴포넌트 스캔에서 사용@Controlller
: 스프링 MVC 컨트롤러에서 사용. 스프링 MVC 컨트롤러로 인식@Service
: 스프링 비즈니스 로직에서 사용. 사실 @Service 는 특별한 처리를 하지 않는다. 대신 개발자들이 '핵심 비즈니스 로직이 여기에 있겠구나.' 라고 비즈니스 계층을 인식하는데 도움이 된다.@Repository
: 스프링 데이터 접근 계층에서 사용. 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.@Configuration
: 스프링 설정 정보에서 사용. 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.
이러한 어노테이션을 POJO에 등록하면, Spring
은 시작과 동시에 아래와 같이 지정된 basePackages
의 하위 패키지들을 탐색하여 Spring Container
에 등록합니다.
@SpringBootApplication
@ComponentScan(basePackages = "io.github.twinklekhj.demo")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
최상위 경로를 입력하면 아래와 같은 경고 메시지가 뜨는데요, 이는 이미 내장하고 있기 때문에 쓸 필요없다는 것입니다.
Redundant declaration: @SpringBootApplication already applies given @ComponentScan
즉, 프로젝트가 인지하는 최상위 경로라면 사용할 필요없고, 그 외 다른 경로를 지정하고자 하면 basePackages
를 따로 지정해야된다는 의미입니다.
패키지를 생성한 뒤에는 config
패키지에서 @Configuration
, @Bean
어노테이션을 이용하여 전역적으로 사용할 Bean
들을 생성합니다.
@Configuration, @Bean 어노테이션은 언제 사용할까?
이러한 어노테이션은 수동으로 빈을 직접 등록해줘야만 하는 상황일 때 사용합니다.
- 개발자가 직접 제어가 불가능한 라이브러리를 활용할 때
- 애플리케이션 전범위적으로 사용되는 클래스를 등록할 때
- 다형성을 활용하여 여러 구현체를 등록해주어야 할 때
예를 들면 아래와 같은 설정들이 있습니다.
Spring Boot 3
를 이용하면 자동으로 Spring Security
가 설정됩니다. 특히 가장 골치 썩는것은 CSRF 설정이죠
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
@Configuration
@EnableWebSecurity
@ComponentScan(basePackages = "io.github.twinklekhj")
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(registry -> {
registry
.requestMatchers("/**").permitAll()
.requestMatchers("/api/**").permitAll()
.requestMatchers("/static/**").permitAll();
})
.csrf(configurer -> {
configurer.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
})
.exceptionHandling(configurer -> {
configurer.accessDeniedPage("/error");
});
return http.build();
}
}
화면을 만들기 위해서 Template Engine인 Thymeleaf
를 추가해주었습니다.
Spring Boot에서도 JSP를 사용할 수 있지만, 굳이 Spring에서도 빼고싶어하는 거를 넣고 싶지 않았기에 사뿐히 Thymeleaf로 갈아탔습니다.
Thymealeaf 설정은 간단합니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
Thymeleaf
는 기본적으로 application.properties
혹은 application.yml
에서 설정 가능합니다. 아래는 기본적으로 많이 사용하는 설정 내용입니다.
# Spring Configuration
# - thymeleaf
spring:
thymeleaf:
suffix: .html
prefix: classpath:/templates/
enabled: true
cache: false # 개발중 false, 배포시 true
사실 이 설정은 Spring Auto Configure
기능에 의해 자동으로 설정됩니다.
화면을 그릴 Controller
를 생성합니다.
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HomeController {
@GetMapping("/")
public String home(){
return "index";
}
}
Thymeleaf
의 Fragment
기능을 이용하여 html을 분해하여 조각 조각 붙일 수 있습니다.
기존에 Tiles를 이용하여 Template을 만들던 저에겐 코드 복붙이란 있을 수 없는 일이었죠. 아쉽게도 Thymeleaf에는 Tiles는 없지만 Fragment 기능을 활용하여 코드를 분리할 수 있습니다.
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<header th:fragment="header">
header
</header>
나누고자 하는 th:fragment
속성을 사용하여 fragment
를 선언합니다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:replace="~{components/_header::header}"></th:block>
<th:block th:replace="~{components/_side::sidebar}"></th:block>
<main>
Body
</main>
<th:block th:replace="~{components/_footer::footer}"></th:block>
</body>
</html>
<th:block>
태그를 이용하여 Fragment를 불러옵니다.
완성-⭐