Refactoring/Moonkey

[졸업작품] spring boot로 작업한 뭉키를 다시 돌아보며 (1)

수짱수짱 2023. 2. 2. 04:32

선배가 작성한 pull request 받은 부분을 다시 확인하며 내가 프로젝트 과정에서 놓친 부분들을 다시 돌아보기로 하였다.


1. Iterator를 사용하여 비효율적으로 반복하는 부분을 stream으로 개선

 

일단 stream이란 java 8부터 추가된 컬렉션, 배열의 저장 요소를 하나씩 참조하여 람다식(함수 스타일)으로 처리할 수 있도록 도와주는 반복자이다. java 7까지는 Iterator 반복자를 통해 컬렉션 요소를 순차적으로 처리하였다.

 

stream을 통해 컬렉션 요소를 순차적으로 처리함으로써 람다식으로 요소 처리 코드를 제공, 내부 반복자를 사용함에따라 병렬 처리가 쉬움, 중간처리와 최종처리의 파이프라인 작업을 수행한다는 장점을 얻을 수 있다.

Stream API의 특징
- 원본 데이터를 변경X
- 일회용이다. 즉, 재사용은 X
- 내부 반복으로 작업을 처리

(1) 람다식으로 요소 처리 코드 제공

 stream이 제공하는 대부분의 요소 처리 메소드는 함수적 인터페이스 매개 타입을 가진다 

따라서 람다식 or 메소드 참조를 이용해 요소 처리 내용을 매개 값으로 전달할 수 있다.

이때, 함수적 인터페이스란 추상 메소드가 단 1개만 정의된 인터페이스다.
람다식을 실행하면 인터페이스를 구현하는 객체가 생성된다.
람다식은 이름을 따로 지정하지 않으므로 익명 구현 객체가 생성된다.

추상 메소드가 여러개라면 람다식으로 표현했을 때 어떤 메서드를 실행해야 하는지 컴파일러가 알 수 없으므로 @FunctionalInterface 어노테이션을 붙여 추상메소드가 1개만 선언되도록 한다.
단, 스트림 소스의 종류도 고려해야 한다.
ArrayList는 인덱스로 요소를 관리하여 데이터를 쪼개기 쉽지만 HashSet, TreeSet은 요소 분리가 쉽지 않고 LinkedList도 링크를 따라가야 하기에 분리가 쉽지 않다.
=> 따라서 요소 분리가 쉽지 않은 자료구조는 상대적으로 병렬 처리가 늦다.

 

(2) 내부 반복자

외부 반복자, 내부 반복자 비교

 외부 반복자는 개발자가 코드로 직접 컬렉션의 요소를 반복해서 가져오는 코드 패턴이다.

예시로는 index를 이용한 for문Iterator를 사용하는 While문이 대표적인 외부 반복자이다.

 

내부 반복자는 컬렉션 내부에서 요소를 반복시키고 개발자는 요소당 처리해야 할 코드만 제공하는 코드 패턴이다.

장점으로는 요소를 반복시키는 방식에 대한 것은 컬렉션에 맡기고 개발자는 요소 처리 코드에만 집중할 수 있다.

내부반복자는 요소들의 반복 순서를 변경하거나 멀티코어 CPU를 활용하기 위해 요소들을 분배시켜 병렬 작업을 할 수 있게 도와주기에 하나씩 처리하는 순차적 외부 반복자보다는 효율적으로 요소를 반복할 수 있다.

싱글 코어 CPU인 경우에는 순차 처리가 빠르다. 병렬 스트림을 사용할 경우 스레드 수만 증가하고 동시성 작업으로 처리되지 않기에 나쁜 결과를 낼 수 있다. 하지만 코어가 많을수록 병렬 작업 처리 속도는 빨라진다.

 

 

(3) 병렬 처리

 병렬 처리는 한 가지 작업을 서브 작업으로 나누고 서브 작업들을 분리된 스레드에서 병렬적으로 처리하는 것이다.

병렬 처리 스트림을 이용하면 런타임 시 하나의 작업을 서브 작업으로 자동으로 나누고 서브 작업의 결과를 자동으로 결합해서 최종 결과물을 생성해낸다.

단, 요소의 수가 적고 요소당 처리 시간이 짧으면 순차 처리가 병렬 처리보다 더 빠를 수 있다.
병렬 처리에는 스레드풀 생성과 스레드 생성이라는 오버헤드가 발생하기 때문이다.

 

(4) 중간처리와 최종처리

 중간처리: 매핑(map), 필터링(filter, distinct), 정렬(sorted), 반복(peek) 등
 최종처리: 반복(forEach), 카운팅(count), 평균(average), 리듀스(reduce) 등

Stream은 데이터를 가공하는 중간 처리와 결과물을 내는 최종 처리의 파이프라인으로 이루어져 있다.

또한, 해당 데이터로 최종 처리를 해야만 중간 처리 과정을 수행한다.

즉, Stream은 최종 처리 전까지는 연산을 미루는 지연(Lazy)되는 성격을 가진다.

 

< 참조 >

- https://minkwon4.tistory.com/306

 

[Java] Stream과 사용시 주의할 점

Stream이란 Stream(스트림)은 자바 8버전부터 추가된 컬렉션(배열 포함)의 저장 요소를 하나씩 참조해서 람다식(함수적 스타일)으로 처리할 수 있도록 도와주는 반복자이다. 자바 7버전까지는 컬렉

minkwon4.tistory.com

- https://makecodework.tistory.com/entry/Java-%EB%9E%8C%EB%8B%A4%EC%8B%9DLambda-%EC%9D%B5%ED%9E%88%EA%B8%B0

 

[Java] 람다식(Lambda) 익히기

수정이력 2022.04.16. 글 표현 다듬기. 불필요한 내용 삭제 람다식이란 람다식은 익명함수(anonymous function)로 구동된다. 자바 8버전부터 적용 가능하다. 람다식은 마치 함수처럼 작성하지만, 실행시

makecodework.tistory.com

- https://my-t-space.tistory.com/48

 

stream 인터페이스(java 8 람다식)

Stream 컬렉션, 배열 등의 저장 요소를 하나씩 참조하여 함수형 인터페이스(람다식)를 적용하며 반복적으로 처리할 수 있도록 해주는 기능. java8에서 부터 람다식으로 stream 인터페이스를 사용할

my-t-space.tistory.com

 


2. Account를 가져오는 부분이 많이 중복되어 AccountService에서 getAccount 메소드를 통해 가져올 수 있도록 하였고 AccountService에서 StoreService를 참조하여 생기는 순환참조 문제를 해결

(1)  AccountService에서 Account 객체를 가져오는 부분이 중복되어 getAccount 메서드를 이용하여 해결

AccountSerivce의 getAccount 메서드

 

중복된 부분을 getAccount 메서드를 통해 해결 1

 

중복된 부분을 getAccount 메서드를 통해 해결 2

 

중복된 부분을 getAccount 메서드를 통해 해결 3, DeliveryService의 코드

Account 객체를 가져오는 코드가 계속해서 중복되어 비효율적으로 똑같은 코드를 수행하는 부분이 많다.

이를 getAccount 메서드로 선언하여 해결하였다.


(2) AccountService에서 StroeService를 참조하여 생기는 순환 참조 문제 해결

AccountService에서 StoreService를 참조하여 생기는 순환 참조 문제

AccountSerivce에서 repository를 전부 import하여 AccountService에서 storeService의 unregsiter 함수를 사용하는 것이 아니라 각 Store 객체에 접근하여 repository 인터페이스의 메소드를 이용한다.

활성화중인 파티라면 예외를 발생시키고 그렇지 않다면 해당 가게의 메뉴 리스트를 find해 찾은 메뉴 리스트와 가게를 가게와 메뉴에서 삭제한다.

순환참조문제란?
A 클래스가 B 클래스의 Bean을 주입받고 B 클래스가 A 클래스의 Bean을 주입받는 상황처럼 서로 순환되어 참조할 경우 발생하는 문제를 의미한다.
참조: https://ch4njun.tistory.com/269

 


3. 등록된 Service및 Repository Bean을 IoC Container를 통해 의존성 주입하지 않고 생성자로 주입하여 이를 수정

@RequiredArgsConstructor 어노테이션을 이용한 생성자 주입 방법 1
@RequiredArgsConstructor 어노테이션을 이용한 생성자 주입 방법 2
@RequiredArgsConstructor 어노테이션을 이용한 생성자 주입 방법 3

 

@RequiredArgsConstructor 어노테이션이란?
final이 붙거나 @NotNull 이 붙은 필드의 생성자를 자동 생성해주는 롬복(lombok) 어노테이션이다.

 

Spring에서 의존성 주입의 방법 3가지

  • Filed(필드) 주입 방법
    • 멤버 객체에 @Autowired 어노테이션을 붙여 주입
    • 코드가 간결해지는 장점이 있다.
    • 하지만 필드 주입은 외부에서 접근이 불가능하다는 단점이 존재한다. 
      • 테스트 코드의 중요성이 부각됨에 따라 필드의 객체를 수정할 수 없는 필드 주입은 사용되지 않게 되었다.
    • 또한, 필드 주입은 반드시 DI 프레임워크가 존재해야 하므로 반드시 사용을 지양한다.
  • Setter 주입 방법
    • setter 메소드 위에 @Autowired를 선언
    • setter 메소드의 파라미터에 해당하는 객체를 BeanFactory에서 가져온다.
    • 생성자 주입과 다르게 주입받는 객체가 변경될 가능성이 있는 경우 사용하지만 실제로 이런 경우는 드물다.
  • Constructor(생성자) 주입 방법
    • 위의 두 주입 방식은 Service Bean이 만들어지고 그 뒤 BeanFactory에서 의존 객체를 가져와 주입하지만 이 방법은 ServiceBean이 만들어지는 그 시점에서 모든 의존관계를 BeanFacotory를 통해 가져와 주입한다.
      • 따라서 NullPointerException을 방지할 수 있다.
        • 생성자 주입 방식은 객체 생성 시점에 바로 주입해야 하므로 방지할 수 있다.
          • 따라서 테스트코드에서 Service를 instance할 때 등의 테스트 코드를 작성하기 편하다.
        • 필드와 setter 방식은 객체가 모두 생성된 후 주입되므로 실제 코드가 호출되기 전 까지 NPE를 알 수 없다. NPE가 뜨긴 하나 실제 코드가 수행되는 순간에만 알아챌 수 있다.
      • 컴파일 시점에 의존성을 확인하므로 final 키워드를 사용할 수 있다. 반면에 다른 주입 방법은 생성자 호출(객체 생성) 이후에 호출되므로 final 키워드를 사용할 수 없다.
        • 생성자 주입을 이용하면 애플리케이션 구동 시점(객체 생성 시점)에 에러가 발생하기 때문에 순환 참조 문제를 방지할 수 있다.
        • 필드 주입 방식으로는 구동 시점에 에러가 발생하지 않는데 이는 빈의 생성과 조립(@Autowired) 시점이 분리되어 있기 때문이다.
        • 하지만 생성자 주입은 객체의 생성과 조립(의존관게 주입)이 동시에 실행되므로 순환 참조 문제를 방지할 수 있는 것이다.
      • final 키워드를 붙이면 Lombok과 결합되어 코드를 간결하게 작성할 수 있다.
        • @RequiredArgsConstructor 
    • 장점으로는 불변객체를 만들 수 있다.즉, 한번 할당된 서비스를 null로 바꾸거나 새로운 instance를 할당할 수 없다.
    • 생성자의 호출 시점에 1회 호출되는 것이 보장된다. 그렇기에 주입받은 객체가 변하지 않거나 반드시 객체의 주입이 필요한 경우에 강제하기 위해 사용한다.
    • Spring 프레임워크에서는 생성자 주입을 적극 지원하기에 생성자가 1개만 있을 경우 @Autowired를 생략해도 주입이 가능하다.

 

의존성 주입 방법 3가지 예시

1. Constructor(생성자)

public class ExampleClass {
	
    private final AccountService accountService;
    private final SignService signService;
    
    public ExampleClass(AccountService accountService, SignService signService){
    	this.accountService = accountService;
        this.signService = signService;
    }

}

=> 생성자가 1개이므로 @Autowired를 생략해도 아래와 같은 코드이다.

public class ExampleClass {
	
    private final AccountService accountService;
    private final SignService signService;
    
    @Autowired
    public ExampleClass(AccountService accountService, SignService signService){
    	this.accountService = accountService;
        this.signService = signService;
    }

}

 

* lombok을 사용하는 경우

@RequiredArgsConstructor
public class ExampleClass {
	
    private final AccountService accountService;
    private final SignService signService;

}

=> 위의 코드와 똑같은 역할을 수행한다. 단, @RequiredArgsConstructor 어노테이션을 통해 간결하게 작성할 수 있다.

 

2. Setter

public class ExampleClass {
	
    private AccountService accountService; // fianl 삭제
    private SignService signService; // final 삭제
    
   	@Autowired
    public void setAccountService(AccountService accountService){ // set메서드
    	this.accountService = accountService;
    }
   
	@Autowired
    public void setSignService(SignService signService){ // set메서드
    	this.signService = signService;
    }

}

@Autowired로 주입할 대상이 없는 경우에는 오류가 발생한다.

주입할 대상이 없어도 동작하도록 하려면 @Autowired(required=false)를 통해 설정할 수 있다.

 

3. Field(필드)

public class ExampleClass {
	
    @Autowired
    private AccountService accountService; // fianl 삭제
    
    @Autowired
    private SignService signService; // fianl 삭제
   
}

 

 

< 참조 >

-https://mangkyu.tistory.com/125

 

[Spring] 다양한 의존성 주입 방법과 생성자 주입을 사용해야 하는 이유 - (2/2)

Spring 프레임워크의 핵심 기술 중 하나가 바로 DI(Dependency Injection, 의존성 주입)이다. Spring 프레임워크와 같은 DI 프레임워크를 이용하면 다양한 의존성 주입을 이용하는 방법이 있는데, 각각의 방

mangkyu.tistory.com

- https://velog.io/@developerjun0615/Spring-RequiredArgsConstructor-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%A3%BC%EC%9E%85

 

[Spring] @RequiredArgsConstructor 어노테이션을 사용한 "생성자 주입"

의존성주입의 종류로는 Constructor(생성자),Setter,Field 타입이 있다.Constructor(생성자)Setter3.Field생성자주입의 단점은 위의 Constructor(생성자) 코드처럼 생성자를 만들기 번거롭다는 것이다. 하지만 이

velog.io

- https://sunghs.tistory.com/144

 

[Spring] 스프링 의존성 주입 방법

스프링 의존성 주입 3가지 방법 소스는 여기서 볼 수 있습니다. 의존성 주입(Dependency Injection)은 많이 아실거라 생각합니다. Spring 에서 지원하는 의존성 주입은 3가지 방법으로 사용가능합니다. Fi

sunghs.tistory.com