[Level2] 프로세스

     

    프로그래머스

    SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

    programmers.co.kr

     

    문제 이해

    슈도 코드

    scoville 배열 -> 우선순위 큐
    int count = 0;
    scoville.peek() >= k -> return count;
    반복:
    	오름차순 정렬
    	count++;
    	scoville.poll() + (scoville.poll() * 2) >= k
    	-> break;
    	else scoville.add();
    
    
    // GPT
    1. 우선순위 큐(PriorityQueue)를 생성하고 scoville 배열의 모든 원소를 추가한다.
    2. count(섞은 횟수)를 0으로 초기화한다.
    3. 만약 최솟값(큐의 peek)이 K 이상이면 count를 반환하고 종료한다.
    4. 반복문 시작 (큐의 크기가 2 이상일 동안 반복):
        4.1. count를 1 증가시킨다.
        4.2. 가장 작은 값(첫 번째 poll)과 두 번째로 작은 값(두 번째 poll)을 꺼낸다.
        4.3. 두 개의 값을 섞어서 새로운 값을 만든다: newScoville = first + (second * 2)
        4.4. 만약 newScoville이 K 이상이면 count를 반환하고 종료한다.
        4.5. 그렇지 않으면 newScoville을 큐에 추가한다.
    5. 반복이 끝난 후, 남아있는 값이 K 이상이면 count를 반환하고, 그렇지 않으면 -1을 반환한다.

    참고한 부분

    // 변경 전 (몇 개의 테스트케이스 실패)
    if (sc < K) pQueue.add(sc);
    // 변경 후
    pQueue.add(sc);
    
    // -> K 미만일 경우에만 우선순위 큐에 다시 추가하는 게 아니라 무조건 추가해야 함.

    전체 코드

    import java.util.*;
    
    class Solution {
        public long solution(int[] scoville, int K) {
            long answer = 0;
            PriorityQueue<Integer> pQueue = new PriorityQueue<>();
            for (int s: scoville) {
                pQueue.add(s);
            }
            
            while (pQueue.size() > 1) {
                if (pQueue.peek() >= K) return answer;
                int sc = pQueue.poll() + (pQueue.poll() * 2);
                pQueue.add(sc);
                answer++;
            }
            
            if (!pQueue.isEmpty() && pQueue.peek() < K) {
                answer = -1;
            }
            return answer;
        }
    }

     

    [241114 TIL] AI 검증 비즈니스 프로젝트

    카테고리, 가게, 리뷰 CRUD API 구현

    • 코드리뷰 수정사항 반영
    • Soft Delete 수정
    • 리뷰 CRUD 추가 기능 구현

    코드리뷰 수정사항 반영

     

    ✔️ 카테고리 아이디 null 체크 부분 ➡️ 공백값도 같이 체크 가능하게 변경

     

    • 수정 전
    if (categoryId == null) 

     

    • 수정 후
    if (!StringUtils.hasText(String.valueOf(categoryId)))

     

    StringUtils.hasText 이용

     

    내부 로직

    public static boolean hasText(@Nullable String str) {
        return str != null && !str.isEmpty() && containsText(str);
    }
    
    private static boolean containsText(CharSequence str) {
        int strLen = str.length();
        
        for(int i = 0; i < strLen; ++i) {
          if (!Character.isWhitespace(str.charAt(i))) {
            return true;
          }
        }
        
        return false;
    }

     

    • str 이 null 인가
    • str이 빈 문자열 ("")인가
    • str이 공백으로만(whitespace) 이루어져 있는가 

     

    스코프는 번역 그대로 빈이 존재할 수 있는 범위를 뜻한다

     

    스프링은 다음과 같은 다양한 스코프를 지원

    • 싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프
    • 프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프
    • 웹 관련 스코프
      • request웹 요청이 들어오고 나갈때 까지 유지되는 스코프
      • session웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
      • application웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프

     

    싱글톤 빈 스코프

    싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 항상 같은 인스턴스의 스프링 빈을 반환

    1. 싱글톤 스코프의 빈을 스프링 컨테이너에 요청
    2. 스프링 컨테이너는 본인이 관리하는 스프링 빈을 반환
    3. 이후에 스프링 컨테이너에 같은 요청이 와도 같은 객체 인스턴스의 스프링 빈을 반환

    프로토타입 스코프

     

    프로토타입 스코프를 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환

    1. 프로토타입 스코프의 빈을 스프링 컨테이너에 요청
    2. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입
    3.  스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환
    4. 이후에 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환 

     

    프로토타입 빈의 특징 정리

    • 스프링 컨테이너에 요청할 때 마다 새로 생성
    • 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입 그리고 초기화까지만 관여
    • 종료 메서드가 호출되지 않음
    • 프로토타입 빈은 프로토타입 빈을 조회한 클라이언트가 관리
    • 종료 메서드에 대한 호출도 클라이언트가 직접 해야함

    프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점

    스프링 컨테이너에 프로토타입 스코프의 빈을 요청하면 항상 새로운 객체 인스턴스를 생성해서 반환하지만, 싱글톤 빈과 함께 사용할 때는 의도한 대로 잘 동작하지 않으므로 주의

     

    1. 클라이언트A는 스프링 컨테이너에 프로토타입 빈을 요청
    2. 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x01), 해당 빈의 count 필드 값은 0
    3. 클라이언트는 조회한 프로토타입 빈에 `addCount()` 를 호출하면서 count 필드를 +1
    4. 결과적으로 프로토타입 빈(x01)의 count는 1
    5. 클라이언트 B도 같은 과정을 반복하여 프로토타입 빈(x02)의 count도 1

     

    싱글톤 빈에서 프로토타입 빈 사용(clientBean)

    1. clientBean 은 싱글톤이므로, 보통 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 주입도 발생
      1. clientBean 은 의존관계 자동 주입을 사용한다주입 시점에 스프링 컨테이너에 프로토타입 빈을 요청
      2. 스프링 컨테이너는 프로토타입 빈을 생성해서 clientBean 에 반환, 프로토타입 빈의 count 필드 값은 0
      3. 이제 clientBean 은 프로토타입 빈을 내부 필드에 보관 (정확히는 참조값을 보관)
    2. 클라이언트 A는 clientBean 을 스프링 컨테이너에 요청해서 받고, 싱글톤이므로 항상 같은 clientBean 이 반환
      1. 클라이언트 A는 clientBean.logic() 을 호출
      2. clientBean 은 prototypeBean의 addCount() 를 호출해서 프로토타입 빈의 count를 증가 ➡︎ count값이 1
    3. 클라이언트 B는 clientBean 을 스프링 컨테이너에 요청해서 받고, 싱글톤이므로 항상 같은 clientBean 이 반환 (여기서 중요한 점, clientBean 이 내부에 가지고 있는 프로토타입 빈은 이미 과거에 주입이 끝난 빈, 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 새로 생성이 된 것이지사용 할 때마다 새로 생성되는 것이 아님!)
      1. 클라이언트 B는 clientBean.logic() 을 호출
      2. clientBean 은 prototypeBean의 addCount() 를 호출해서 프로토타입 빈의 count를 증가 ➡︎ 래 count 값이 1이었으므로 2가 됨

     

    프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결

    • 실행해보면 ac.getBean() 을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인
    • 의존관계를 외부에서 주입(DI) 받는게 아니라 이렇게 직접 필요한 의존관계를 찾는 것을 Dependency Lookup (DL) 의존관계 조회(탐색) 
    • 이렇게 스프링의 애플리케이션 컨텍스트 전체를 주입받게 되면, 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트도 어려워짐
    • 지금 필요한 기능은 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는 딱! DL 정도의 기능만 제공하는 무언가가 있으면 됨

     

    ObjectFactory, ObjectProvider

    • 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것이 바로 ObjectProvider, 참고로 과거에 ObjectFactory 가 있었는데, 여기에 편의 기능을 추가해서 ObjectProvider 가 만들어짐
    • 실행해보면 prototypeBeanProvider.getObject() 을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인
    • ObjectProvider 의 getObject() 를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환 (DL)
    • 스프링이 제공하는 기능을 사용하지만기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워짐
    • 지금 딱 필요한 DL 정도의 기능만 제공
    • ObjectFactory: 기능이 단순, 별도의 라이브러리 필요 없음, 스프링에 의존
    • ObjectProvider: ObjectFactory 상속, 옵션, 스트림 처리등 편의 기능이 많고, 별도의 라이브러리 필요 없음, 스프링에 의존

     

    JSR-330 Provider

    • JSR-330 자바 표준을 사용하는 방법 - 스프링 부트 3.0은 jakarta.inject.Provider 사용
    • 실행해보면 provider.get() 을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인 
    • provider 의 get() 을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환 (DL)
    • 자바 표준이고기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬워짐
    • Provider 는 지금 딱 필요한 DL 정도의 기능만 제공
    • get() 메서드 하나로 기능이 매우 단순
    • 별도의 라이브러리가 필요
    • 자바 표준이므로 스프링이 아닌 다른 컨테이너에서도 사용 가능

     

    웹 스코프

    • 웹 스코프는 웹 환경에서만 동작
    • 웹 스코프는 프로토타입과 다르게 스프링이 해당 스코프의 종료시점까지 관리, 따라서 종료 메서드가 호출

    웹 스코프 종류

    • request: HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고, 관리
    • session: HTTP Session과 동일한 생명주기를 가지는 스코프
    • application: 서블릿 컨텍스트( ServletContext )와 동일한 생명주기를 가지는 스코프
    • websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프

     

    빈 생명주기 콜백

    애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요

     

    • 스프링 빈은 객체를 생성하고, 의존관계 주입이 다 끝난 다음에야 필요한 데이터를 사용할 수 있는 준비가 완료됨
    • 초기화 작업은 의존관계 주입이 모두 완료되고 난 다음에 호출해야 함
    • 스프링은 의존관계 주입이 완료되면 스프링 빈에게 콜백 메서드를 통해서 초기화 시점을 알려주는 다양한 기능 제공
    • 스프링은 스프링 컨테이너가 종료되기 직전에 소멸 콜백을 줌

     

    스프링 빈의 간단한 라이프사이클

    • 객체 생성 ➡︎ 의존관계 주입

    스프링 빈의 이벤트 라이프사이클

    • 스프링 컨테이너 생성 ➡︎ 스프링 빈 생성 ➡︎ 의존관계 주입 ➡︎ 초기화 콜백 ➡︎ 사용 ➡︎ 소멸전 콜백 ➡︎ 프링 종료
      • 초기화 콜백: 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
      • 소멸전 콜백: 빈이 소멸되기 직전에 호출

     

    ✚ 객체의 생성과 초기화를 분리

    • 생성자: 필수 정보(파라미터)를 받고, 메모리를 할당해서 객체를 생성하는 책임
    • 초기화는 이렇게 생성된 값들을 활용해서 외부 커넥션을 연결하는 등 무거운 동작을 수행

     


    빈 생명주기 콜백의 3가지 방법

    1. 인터페이스(InitializingBean, DisposableBean)
    2. 설정 정보에 초기화 메서드, 종료 메서드 지정
    3. @PostConstruct, @PreDestroy 애노테이션 지원

     

    1. 인터페이스(InitializingBean, DisposableBean)

    • 초기화: InitializingBean - afterPropertiesSet() 메서드
    • 소멸: DisposableBean - destroy() 메서드
    public class NetworkClient implements InitializingBean, DisposableBean {
        
        ...
        중략
        ...
        
        @Override
        public void afterPropertiesSet() throws Exception {
        	connect();
            call("초기화 연결 메시지"); 
        }
        
        @Override
        public void destroy() throws Exception {
            disConnect();
        }
    }

     

    단점

    • 초기화, 소멸 메서드의 이름 변경 불가
    • 외부 라이브러리에 적용 불가
    • 초창기 방법이라 거의 사용하지 않음

     

     

    2. 빈 등록 초기화, 소멸 메서드 지정

    설정 정보에 @Bean(initMethod = "init", destroyMethod = "close") 처럼 초기화, 소멸 메서드를 지

     

    public class NetworkClient {
    	
        ...
        중략
        ...
        
        public void init() { 
            System.out.println("NetworkClient.init");
            connect();
            call("초기화 연결 메시지");
        }
    	
        public void close() {
        	System.out.println("NetworkClient.close");
            disConnect();
        }
    }
    @Configuration
    static class LifeCycleConfig {
    
        @Bean(initMethod = "init", destroyMethod = "close")
        public NetworkClient networkClient() {
        	...
        }
    }

     

    특징

    • 메서드 이름 자유로움
    • 스프링 빈이 스프링 코드에 의존하지 않음
    • 설정 정보를 사용하기에 외부 라이브러리에도 적용 가능

     

     

    3. 애노테이션 @PostConstruct, @PreDestroy 

    public class NetworkClient {
    	
        ...
        중략
        ...
        
        @PostConstruct
        public void init() { 
            System.out.println("NetworkClient.init");
            connect();
            call("초기화 연결 메시지");
        }
        
        @PreDestroy
    	public void close() {
     		System.out.println("NetworkClient.close");
     		disConnect();
        }
    }

     

    특징

    • 가장 편리하게 초기화와 종료를 실행
    • 최신 스프링 권장 방법
    • 컴포넌트 스캔과 잘 어울림
    • 외부 라이브러리에 적용 불가

     

     

    의존관계 자동 주입

    스프링은 의존관계도 자동으로 주입하는 @Autowired 라는 기능 제공

     

    @Autowired 의존관계 자동 주입 동작

    • 생성자에 @Autowired 를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입
    • 이때 기본 조회 전략은 타입이 같은 빈을 찾아서 주입
      • getBean(MemberRepository.class) 와 동일하다고 이해하면 됨

     

    다양한 의존관계 주입 방법

    @Component
    public class OrderServiceImpl implements OrderService {
        
        // 1. 생성자 주입
        private final MemberRepository memberRepository;
        private final DiscountPolicy discountPolicy;
        
        @Autowired
        public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        	this.memberRepository = memberRepository;
            this.discountPolicy = discountPolicy;
        }
         
         
        // 2. 수정자 주입
        private MemberRepository memberRepository;
        private DiscountPolicy discountPolicy;
    
        @Autowired
        public void setMemberRepository(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
        }
        
        @Autowired
        public void setDiscountPolicy(DiscountPolicy discountPolicy) {
            this.discountPolicy = discountPolicy;
        }
        
        
        // 3. 필드 주입
        @Autowired
        private MemberRepository memberRepository;
        @Autowired
        private DiscountPolicy discountPolicy;
        
        
        // 4. 일반 메서드 주입
        private MemberRepository memberRepository;
        private DiscountPolicy discountPolicy;
        
        @Autowired
        public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
             this.memberRepository = memberRepository;
             this.discountPolicy = discountPolicy;
         }
    }
    1. 생성자 주입
      • 생성자를 통해서 의존관계를 주입받는 방법
      • 생성자 호출 시점에 딱 1번만 호출되는 것 보장
      • 불편, 필수 의존관계에 사용
      • 생성자가 딱 1개만 있으면 @Autowired 생략 가능
    2. 수정자 주입(setter 주입)
      • setter 라 불리는 필드의 값을 변경하는 수정자 메서드를 통해서 의존관계 주입하는 방법
      • 선택, 변경 가능성이 있는 의존관계에 사용
    3. 필드 주입
      • 필드에 바로 주입하는 방법
      • 코드가 간결
      • 외부에서 변경이 불가능해서 테스트하기 힘듦
      • 애플리케이션의 실제 코드와 관계 없는 테스트 코드/스프링 설정을 목적으로 하는 @Configuration 같은 특별한 용도로만 사용
    4. 일반 메서드 주입
      • 일반 메서들르 통해 주입받는 방법
      • 한번에 여러 필드를 주입 받을 수 있음
      • 일반적으로 잘 사용하지 않음

     

    ✚ 주입할 스프링 빈이 없어도 동작해야 할 때, 자동 주입 대상을 옵션으로 처리하는 방법

    • @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨
    • org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력
    • Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력

     

    ✚ 롬복 라이브러리가 제공하는 @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동 생성

     

     

    ✚ 조회 대상 빈이 2개 이상일 때 해결 방법

     

    • @Autowired 필드 명 매칭
    • @Qualifier ➡︎ @Qualifier 끼리 매칭 ➡︎ 빈 이름 매칭 
    • @Primary 사용

     

    ‼️ 생성자 주입 권장, 옵션이 필요하면 수정자 주입, 필드 주입은 XXX ‼️

    ‼️ 최근에는 생성자를 딱 1개 두고, @Autowired 생략 &   @RequiredArgsConstructor 함께 사용 ‼️

     

    컴포넌트 스캔

    스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 @ComponentScan 이라는 기능을 제공

     

    @ComponentScan 컴포넌트 스캔 동작

    • @ComponentScan 은 @Component 가 붙은 모든 클래스를 스프링 빈으로 등록
    • 이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용
      • 빈 이름 기본 전략: MemberServiceImpl 클래스 ➡︎ memberServiceImpl
      • 빈 이름 직접 지정:  @Component("memberService2")이런식으로 이름을 부여

    탐색 위치와 기본 스캔 대상 

    모든 자바 클래스를 다 컴포넌트 스캔하면 시간이 오래 걸림 ➡︎ 꼭 필요한 위치부터 탐색하도록 시작 위치를 지정

    @ComponentScan(
             basePackages = "hello.core",
     }
    •  basePackages: 탐색할 패키지의 시작 위치를 지정 - 이 패키지를 포함해서 하위 패키지를 모두 탐색
    •  basePackageClasses지정한 클래스의 패키지를 탐색 시작 위치로 지정
    •  지정하지 않으면 @ComponentScan 이 붙은 설정 정보 클래스의 패키지가 시작 위치

    ‼️ 권장하는 방법
    설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것

     


     

    컴포넌트 스캔 기본 대상 및 부가 기능

    • @Component: 컴포넌트 스캔에서 사용
    • @Controller: 스프링 MVC 컨트롤러에서 사용 / 스프링 MVC 컨트롤러로 인식
    • @Service: 스프링 비즈니스 로직에서 사용 / 특별한 처리를 하지 않지만, 대신 개발자들이 비즈니스 계층을 인식하는데 도움
    • @Repository스프링 데이터 접근 계층에서 사용 / 스프링 데이터 접근 계층으로 인식데이터 계층의 예외를 스프링 예외로 변환
    • @Configuration스프링 설정 정보에서 사용 / 스프링 설정 정보로 인식하고스프링 빈이 싱글톤을 유지하도록 추가 처리

     

    필터

    • includeFilters: 컴포넌트 스캔 대상을 추가로 지정
    • excludeFilters컴포넌트 스캔에서 제외할 대상을 지정

     

    FilterType 옵션

    • ANNOTATION: 기본값, 애노테이션을 인식해서 동작 ex) org.example.SomeAnnotation
    • ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작 ex) org.example.SomeClass
    • ASPECTJ: AspectJ 패턴 사용 ex) org.example..*Service+
    • REGEX: 정규 표현식 ex) `org\.example\.Default.*`
    • CUSTOM: TypeFilter 이라는 인터페이스를 구현해서 처리 ex) org.example.MyTypeFilter

     

    중복 등록과 충돌

    컴포넌트 스캔에서 같은 빈 이름을 등록

     

    • 자동 빈 등록 vs 자동 빈 등록
      • 컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 그 이름이 같은 경우 스프링은 오류를 발생
        • ConflictingBeanDefinitionException 예외 발생
    • 수동 빈 등록 vs 자동 빈 등록
      • 수동 빈 등록이 우선권 - 수동 빈이 자동 빈을 오버라이딩

     

    ‼️ 같은 빈 이름 X 중요 - 스프링 부트는 수동 빈/자동 빈 충돌 시 오류 발생

    + Recent posts