2024. 4. 22. 02:10ㆍ서버
나는 Node.js로 백엔드 개발을 시작했다. 처음에는 Express를 사용하여 웹 서버를 만들었지만, 더 체계적인 서비스 로직 관리가 필요해졌고, 그 과정에서 Nest.js를 만나게 되었다. Nest.js의 컨셉이 매우 마음에 들어 이제는 내 주요 기술 스택으로 자리 잡았다.
백엔드 개발자에게 중요한 것은 사용하는 프로그래밍 언어 자체가 아니라, 사용자의 데이터를 어떻게 처리하고 부하를 어떻게 분산시키는지 등의 문제를 해결하는 "문제 해결 능력"이라고 생각해왔다. 하지만 프로덕션 레벨에서 자신 있게 로직을 구현할 때, 내가 활용할 수 있는 언어가 JavaScript와 TypeScript로 한정되어 있다는 점을 느끼게 되었다.
이런 제한을 극복하고자 나는 내 기술 스택을 확장하자는 생각이 들었고, 그 첫 단계로, Spring Boot를 공부하려고 한다.
우선 출퇴근길 지하철에서 스프링 강의들을 보고 주말에 카페에 앉아 Java 를 설치하고 InteliJ 를 설치했다.
나는 이전에 자바를 아주쬐끔 공부한적이 있어 언어 자체를 먼저 공부하진 않았다.
언어공부는 나중에 멀티쓰레드같은거 할때 해야겠다..
우선 스프링부트 문서에 들어가서 첫번째 가이드문서를 따라했고 이 과정에서 어떤점이 눈에 띄었고, 궁금하여 찾아본것 위주로 포스팅 해 봐야 겠다.
https://spring.io/guides/gs/spring-boot
Getting Started | Building an Application with Spring Boot
Spring Boot offers a fast way to build applications. It looks at your classpath and at the beans you have configured, makes reasonable assumptions about what you are missing, and adds those items. With Spring Boot, you can focus more on business features a
spring.io
Gradle vs Maven
이 패키지 매니저들을 보자마자 npm, yarn, pnpm 같은 도구들이 떠올랐다. 아주 예전에 Java를 공부할 때는, 패키지 파일을 직접 다운로드 받아 Java 버전에 맞게 import 했었다. 이런 도구들이 있었다면 훨씬 쉽게 패키지 관리를 할 수 있었겠다는 생각이 들었다.
어느 것을 사용할지 고민하다가, Gradle과 Maven을 비교해 보았다. 결과적으로 Gradle이 Maven보다 최대 100배까지 빠르고 문법도 더 간단해 보여서 Gradle을 선택했다. Maven은 이미 많은 회사에서 사용하고 있고, Gradle보다 먼저 나와서 기존 시스템과의 호환성을 중시하는 곳에서는 여전히 Maven을 선호할 수 있다는 글들을 읽었다. 하지만 나는 아직 공부를 시작하는 단계이고, XML을 별로 좋아하지 않기 때문에 Gradle을 선택하기로 했다.
첫인상
스프링부트 프로젝트 이니셜라이저를 통해 소스코드를 다운받고 압축을 풀어 코드들을 확인해보았다.
일단 눈길이 가는것은 어노테이션들 이었다.
- @SpringBootApplication
- @Bean
- @RestController
- @GetMapping
- @SpringBootTest
- @AutoConfigureMockMvc
- @Autowired
- @Test
Node.js에서는 주로 명시적으로 모듈을 임포트하고, 애플리케이션을 초기화하는 반면, Spring에서는 단순히 클래스에 어노테이션을 붙이는 것만으로도 컨트롤러가 요청을 처리할 수 있다는것이 신기했다.
@SpringBootApplication
public class HelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(HelloworldApplication.class, args);
}
}

그래서 뜯어보았다.
@ComponentScan 이라는 어노테이션이 힌트인것 같아 관련하여 검색을 해보았다.
- @CompoenetScan 어노테이션이 자동으로 해당 패키지와 하위 패키지에서 Spring 스테레오타입 어노테이션이 붙은 클래스들을 찾아 컨텍스트의 빈으로 등록
- 컨텍스트에 등록된 빈(객체) 끼리의 의존성주입
- 이때 싱글톤패턴으로 컴포넌트 1개당 객체는 1개씩 만들어진다고 한다.
- 애플리케이션 구동
으로 이루어진다고 한다.
@ComponentScan 의 Filter 를 조작하면 피처플래그처럼 개발환경에서만 특정 서비스/컨트롤러를 활성화 한다거나 하는 로직을 구현할 수 있을까 잠깐 호기심어 찾아보았는데 @Profile 어노테이션을 사용하면 특정 프로파일이 활성화될 때만 빈이 등록되도록 할 수 있다고 하여 이건 신경쓸 필요가 없을 것 같다.
@Controller vs @RestController
이전에 nestjs 로 개발을 하고있어 Controller 어노테이션의 역할은 알 것 같았다.
그런데 @RestController 와는 뭐가다른지 궁금하여 찾아보았다.

일단 @Documented 라는 어노테이션이 하나 더 붙어있는걸 확인하고 구글링을 하였다.
- Controller 는 전통적인 Spring MVC 의 View 를 반환하는데에 사용되고, 뷰 이름을 반환하면 view resolver 가 해당 뷰를 찾아 처리한다고한다.
- RestController 는 @RestController = @Controller + @ResponseBody 라고 생각하면 될 것 같다.
- @RestController 의 메서드가 반환하는 데이터가 자동으로 HTTP 응답 본문으로 바인딩 된다고 한다.
// Controller 예시
@Controller
public class MyViewController {
@GetMapping("/greeting")
public String greeting(Model model) {
model.addAttribute("message", "Hello, World");
return "greeting"; // 뷰 이름을 반환
}
}
@RestController
public class MyRestController {
@GetMapping("/data")
public Map<String, Object> getData() {
Map<String, Object> data = new HashMap<>();
data.put("message", "Hello, World");
return data; // 데이터가 JSON 형태로 클라이언트에 직접 반환됨
}
}
Autowired
Spring 에서 의존성을 주입할때 사용되는 어노테이션이다.
@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void hello_ReturnsHelloWorld() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("Hello, World!"));
}
}
위 코드가 처음 주어진 테스트코드였다.
느낌상으론 MockMvc 라는 타입의 mockMvc 라는 매개변수로 MockMvc 라는 의존성을 주입해주는 것 같았다.
한번 찾아보았다.
@Autowired는 Spring 프레임워크에서 제공하는 주요 애너테이션 중 하나로, 의존성 주입(Dependency Injection, DI)을 자동으로 처리하는 데 사용됩니다. 이 애너테이션을 통해 Spring 컨테이너는 자동으로 선언된 타입에 맞는 빈(bean)을 해당 클래스에 주입합니다.
음 그렇구나..
근데 MockMvc 를 어떤 경로를 통해 주입한다는것일지 궁금했다.
찾아보니 의존성주입엔 보통 2가지의 방식을 사용하는 것 같았다.
필드주입 vs 생성자주입 vs 세터주입 (setter)
결론부터 말하면 Spring 커뮤니티에선 일반적으로 생성자주입을 권장하고 있다고 한다.
생성자 주입이나 세터는 nestjs 에서도 쉽게 되는거니 넘어가고.. 필드주입에 대해 더 자세히 알아보았다.
필드주입 방식은 Spring 에서 의존성을 객체의 필드에 직접 주입하는 방식이라고 한다.
AutowiredAnnotationBeanPostProcessor 클래스를 통해 내부적으로 처리되고, Reflection API 를 사용해 구현되어있다.
Field 주입 (필드주입)
Method 주입 (setter 주입)
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value;
if (this.cached) {
value = resolveCachedArgument(beanName, this.cachedFieldValue);
} else {
value = resolveFieldValue(field, bean, beanName);
}
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}
위 코드는 내가 궁금해하는 필드주입에 대한 구현이다.
- this.member 를 통해 필드를 얻고 (이 객체를 통해 클래스의 멤버변수에 대한 동적 접근을 할 수 있음)
- resolveFieldValue 메소드를 통해 주입할 객체를 찾아
- ReflectionUtils.makeAccessible 을 통해 해당 필드의 접근성을 보장하여 ( private method 라도 설정할 수 있게 )
- field.set 을 통해 의존성을 주입
후기
스프링이 생각보다 더 많이 nest.js 랑 비슷해서 놀랐다.
후.. 어서 프레임워크에 대한 공부를 마치고 언어적인 특성이나 관련 툴에 대해 더 공부해야겠다고 느꼈다. 이전에 자바를 조금 공부했다고 생각했는데 많이 까먹었고 빌드매니저 등에서 애먹는 부분이 조금 있었다.
이밖에도 nestjs 에서 사용하는 @Module 에 대한 개념이나 Java 에서의 테스트 환경이 궁금했지만 이부분은 다음 포스팅에서 다루기로 하고 이만 글을 마쳐야겠다.
'서버' 카테고리의 다른 글
[문제해결] lock wait timeout exceeded try restarting transaction (0) | 2024.04.14 |
---|---|
서울 시위/행사 알리미 앱 고치기 - 타임아웃 (0) | 2023.10.19 |
Samba & Mega sync (1) | 2020.06.19 |