Post

TIL-Spring 입문주차 과제 피드백 수정

2024-05-20

오늘의 학습 🌠


1

Spring 입문주차 과제 수정


  • 의존성 주입을 할 때 생성자 주입을 통해 해결함.

  • 생성자 주입은 생성자를 통해 의존 관계를 주입하는 방식

  • 생성자 주입을 사용해야 하는 이유

    • 객체의 불변성 확보
      • 실제로 개발을 하다 보면 의존 관계의 변경이 필요한 상황은 거의 없음. 하지만 수정자 주입이나 일반 메서드 주입을 이용하면 불필요하게 수정의 가능성을 열어두어 유지보수성을 떨어뜨림. 그러므로 생성자 주입을 통해 변경의 가능성을 배제하고 불변성을 보장하는 것이 좋음
    • 테스트 코드의 작성
      • 테스트가 특정 프레임워크에 의존하는 것은 침투적으로 좋지 못함. 그러므로 가능한 순수 자바로 테스트를 작성하는 것이 가장 좋은데 생성자 주입이 아닌 다른 주입으로 작성된 코드는 순수한 자바 코드로 단위 테스트를 작성하는 것이 어려움.
1
2
3
4
5
6
7
8
9
public class UserServiceTest {

    @Test
    public void addTest() {
        UserService userService = new UserService();
        userService.register("MangKyu");
    }

}
  • 위의 테스트 코드는 Spring 위에서 동작하지 않으므로 의존 관계 주입이 되지 않을 것이고, userRepository가 null이 되어 add 호출시 NPE가 발생할 것. 이를 해결하기 위해 Setter를 사용하면 변경 가능성을 열어두게 되는 단점을 갖게 됨.

  • 반대로 테스트 코드에서 @Autowired를 사용하기 위해 스프링을 사용하면 단위 테스트가 아닐 뿐만 아니라 컴포넌트들을 등록하고 초기화하는 시간 때문에 테스트 비용이 증가하게 됨. 그렇다고 대안으로 리플렉션을 사용하면 깨지기 쉬운 테스트가 됨.

  • 반면에 생성자 주입을 사용하면 컴파일 시점에 객체를 주입받아 테스트 코드를 작성할 수 있으며 주입하는 객체가 누락된 경우 컴파일 시점에 오류를 발견할 수 있음.

    • final 키워드 작성 및 Lombok과의 결합
      • 생성자 주입을 사용하면 필드 객체에 final 키워드를 사용할 수 있으며 컴파일 시점에 누락된 의존성을 확인할 수 있음. 반면에 다른 주입 방법들은 객체의 생성(생성자 호출) 이후에 호출되므로 final 키워드를 사용할 수 없음.
      • 또한 final 키워드를 붙이면 Lombok과 결합되어 코드를 간결하게 작성할 수 있음. Lombok에는 final 변수를 위한 생성자를 대신 생성해주는 @RequiredArgsConstructor를 사용할 수 있음.

      • @RequiredArgsConstructor는 특정 변수만을 활용하는 생성자를 자동완성 시켜주는 어노테이션. 생성자의 인자로 추가할 변수에 @NonNull 어노테이션을 붙여서 해당 변수를 생성자의 인자로 추가할 수 있음. 아니면 해당 변수를 final로 선언해도 의존성을 주입받을 수 있음.
      • Spring과 같은 DI 프레임워크는 Lombok과 궁합을 보여주는데 다음과 같이 간편하게 작성할 수 있음.
1
2
3
4
5
6
7
8
9
10
11
12
@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final MemberService memberService;

    public void register(String name) {
        userRepository.add(name);
    }

}

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.sparta.schedulemanagement.controller;  
  
import com.sparta.schedulemanagement.dto.ScheduleRequestDto;  
import com.sparta.schedulemanagement.dto.ScheduleResponseDto;  
import com.sparta.schedulemanagement.service.ScheduleService;  
import org.springframework.web.bind.annotation.*;  
  
import java.util.List;  
  
@RestController  
@RequestMapping("/api/schedule")  
public class ScheduleController {  
  
    private final ScheduleService scheduleService;  
  
    public ScheduleController(ScheduleService scheduleService) {  
        this.scheduleService = scheduleService;  
    }  
  
    // 일정 등록  
  @PostMapping  
  public ScheduleResponseDto createSchedule(@RequestBody ScheduleRequestDto requestDto) {  
        return scheduleService.createSchedule(requestDto);  
    }  
  
    // 선택 일정 조회  
  @GetMapping("/{scheduleId}")  
    public ScheduleResponseDto getSchedule(@PathVariable Long scheduleId) {  
        return scheduleService.getSchedule(scheduleId);  
    }  
  
    // 전체 일정 조회  
  @GetMapping  
  public List<ScheduleResponseDto> getAllSchedule() {  
        return scheduleService.getAllSchedule();  
    }  
  
    // 선택 일정 수정  
  @PutMapping("/{scheduleId}")  
    public ScheduleResponseDto updateSchedule(@PathVariable Long scheduleId, @RequestBody ScheduleRequestDto requestDto) {  
        return scheduleService.updateSchedule(scheduleId, requestDto, requestDto.getPassword());  
    }  
  
    // 선택 일정 삭제  
  @DeleteMapping("/{scheduleId}")  
    public Long deleteSchedule(@PathVariable Long scheduleId, @RequestBody ScheduleRequestDto requestDto) {  
        return scheduleService.deleteSchedule(scheduleId, requestDto.getPassword());  
    }  
}
  • 다음 해당 코드의 의존성 주입을 할 때 생성자 주입을 통해 해결했는데 Lombok의 @RequiredArgsConstructor를 이용하면 코드가 간결하게 변경할 수 있습니다.
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.sparta.schedulemanagement.controller;  
  
import com.sparta.schedulemanagement.dto.ScheduleRequestDto;  
import com.sparta.schedulemanagement.dto.ScheduleResponseDto;  
import com.sparta.schedulemanagement.service.ScheduleService;  
import lombok.RequiredArgsConstructor;  
import org.springframework.web.bind.annotation.*;  
  
import java.util.List;  
  
@RestController  
@RequestMapping("/api/schedule")  
@RequiredArgsConstructor  
public class ScheduleController {  
  
    private final ScheduleService scheduleService;  
  
    // 일정 등록  
  @PostMapping  
  public ScheduleResponseDto createSchedule(@RequestBody ScheduleRequestDto requestDto) {  
        return scheduleService.createSchedule(requestDto);  
    }  
  
    // 선택 일정 조회  
  @GetMapping("/{scheduleId}")  
    public ScheduleResponseDto getSchedule(@PathVariable Long scheduleId) {  
        return scheduleService.getSchedule(scheduleId);  
    }  
  
    // 전체 일정 조회  
  @GetMapping  
  public List<ScheduleResponseDto> getAllSchedule() {  
        return scheduleService.getAllSchedule();  
    }  
  
    // 선택 일정 수정  
  @PutMapping("/{scheduleId}")  
    public ScheduleResponseDto updateSchedule(@PathVariable Long scheduleId, @RequestBody ScheduleRequestDto requestDto) {  
        return scheduleService.updateSchedule(scheduleId, requestDto, requestDto.getPassword());  
    }  
  
    // 선택 일정 삭제  
  @DeleteMapping("/{scheduleId}")  
    public Long deleteSchedule(@PathVariable Long scheduleId, @RequestBody ScheduleRequestDto requestDto) {  
        return scheduleService.deleteSchedule(scheduleId, requestDto.getPassword());  
    }  
}
  • @RequiredArgsConstructor를 사용함으로써
1
2
3
 public ScheduleController(ScheduleService scheduleService) {  
        this.scheduleService = scheduleService;  
    } 
  • 해당 부분 삭제

추가 🕤


1

🐱‍🏍— —🤸🏻‍♀️ ~~~ 야~호~

This post is licensed under CC BY 4.0 by the author.