@SpringBootApplication

SpringBootApplication 어노테이션은 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성 자동 설정 등을 해주는 어노테이션입니다.

해당 어노테이션이 있는 위치부터 설정을 읽어나가기 때문에 이 어노테이션을 포함하고 있는 클래스는 항상 프로젝트의 최상단에 위치해야만 합니다.

해당 어노테이션의 내부구조는 다음과 같습니다.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...

중요하게 봐야 할 3가지 어노테이션을 포함하고 있습니다.

  • EnableAutoConfiguration
  • CompnentScan
  • SpringBootConfiguration

각각의 어노테이션의 역할을 알아보겠습니다.

@EnableAutoConfiguration - 설정 자동 등록

Spring boot의 핵심으로 미리 정의되어 있는 Bean들을 가져와서 등록해줍니다. 미리 정의되어 있는 Bean들은 spring-boot-autoconfigure > META-INF > spring.factories에 위치해 있습니다.

@ComponentScan - 빈 등록하기

@Component, @Configuration, @Repository, @Service, @Controller, @RestController 와 같은 어노테이션이 붙어 있는 클래스들을 찾아서 Bean으로 등록해줍니다.

@SpringBootConfiguration - @Configuration

@Configuration은 스프링에 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스를 나타낼 때 사용하는 어노테이션입니다.

@SpringBootConfiguration은 이 대안으로 볼 수 있습니다. 가장 큰 차이점은 구성을 자동으로 찾을 수 있다는 것입니다.

@SpringBootApplication 어노테이션은 위와 같은 3가지 역할을 수행하며 간단히 얘기하면 스프링 부트의 자동 설정, 빈 읽기와 등록 자동 설정의 역할을 합니다.

자바 함수형 인터페이스

함수형 인터페이스(Functional interface)는 1개의 추상 메소드를 갖고 있는 인터페이스, Single Abstraction Method(SAM)를 의미합니다.

예를 들어 아래와 같은 인터페이스를 함수형 인터페이스라고 합니다.

public interface FunctionalInterface {
    public abstract void doSomething(String text);
}

자바의 람다식은 함수형 인터페이스로만 접근이 가능하기 때문에 함수형 인터페이스를 사용합니다.

예를 들면 아래와 같은 식으로 말이죠.

public interface FunctionalInterface {
     public abstract void doSomething(String text);
}

FunctionalInterface func = text -> System.out.println(text);
func.doSomething("do something"); // do something

정리하자면 함수형 인터페이스를 사용하는 것은 람다식으로 만든 객체에 접근하기 위해서 입니다. 람다식을 사용할 때 마다 함수형 인터페이스를 매번 정의하기는 불편하기에 java.util.function패키지에서 다양한 인터페이스를 제공합니다.

대표적으로 제공하는 기본 함수형 인터페이스는 다음과 같습니다.

  • Runnable
  • Supplier
  • Consumer
  • Function<T, R>
  • Predicate

람다란?

람다 함수는 프로그래밍에서 익명 함수를 지칭하는 용어입니다. 간단히 말하면 함수를 단순하게 표현하는 방법이죠. 람다는 2가지 특징을 갖고 있습니다.

  • 익명 함수로 이름을 가질 필요가 없다.
  • 두 개 이상의 입력이 있는 함수는 최종적으로 1개의 입력만 받는 람다 대수로 단순화 될 수 있다. - 커링(curring)

기본적인 람다 함수의 표현법을 확인해보겠습니다.

(int x) -> x+1
(int x) -> { return x+1; }
(String inputStr) -> inputStr.length()

application.yml

기본적으로 환경별로 설정을 구분하기 위해 Property를 활용하며 Property를 적용하기 위해 application.properties나 application.yml과 같은 설정 파일 기반으로 적용하거나 @Configuration 어노테이션을 사용하여 각 환경별 구분된 환경 정보를 가져갈 수 있습니다.

그러나 최근 어플리케이션은 Cloud Native하게 개발방식이 변화되고 있습니다. Spring Boot기반의 Runtime Framework의 활용도가 높아지고 있으며 Cloud Platform에 빠르게 이식되어 민첩하게 배포 될 수 있도록 개발해야 하죠. 이런 것을 CNA라고 부릅니다.

이때 환경 설정을 어플리케이션 레벨에서 관리하게 될 경우 소스코드와의 Dependency가 많아지고, 환경 설정의 변경이 발생할때 마다 매번 소스코드 체크아웃부터 시작한다면, CNA의 신속한 반영을 저해하는 요소가 될 수 있습니다. 따라서 Kubernetes와 같은 클라우드 환경에 적용하기 위해서는 어플리케이션 레벨이 아닌 오브젝트 레벨에서 환경 설정을 관리할 수 있도록 구성해야 합니다.

이를 위해 Kubernetes는 configMap, secret이라는 오브젝트를 사용하여 Pod에서 참조할 수 있도록 제공하고 있습니다.

manifest.yml

manifest.yml 파일은 응용 프로그램 배포를 위한 설명 파일입니다. 응용 프로그램 이름, .war/.jar 파일의 경로 등과 같이 클라우드에 응용 프로그램을 배포하는데 필요한 정보를 포함하고 있습니다.

cf push 명령을 통해 push 명령이 실행된 디렉토리에서 manifest.yml 파일을 찾고 이 파일의 배포 값을 사용합니다.

manifest.yml은 소스 제어에 체크인 할 수 있고, env 변수와 같은 것을 설정하고 서비스를 대량으로 바인드 할 수 있다는 장점들이 있습니다.

cli명령을 이용해 파일 없이도 정보를 지정할 수 있지만, manifest.yml 파일을 사용하는 장점이 큽니다.

useeffect uselayouteffect


useEffect와 useLayoutEffect는 동일한 형태로 사용되는 훅 입니다. 이 둘은 이팩트를 발생시키고 clean-up함수를 리턴해주는 패턴으로 사용되죠. 이 둘의 차이점을 살펴보겠습니다.

useEffect로 전달된 함수는 지연 이벤트 동안 레이아웃 배치와 그리기를 완료한 후 발생합니다. 대부분의 작업이 브라우저에서 화면을 업데이트 하는 것을 차단해서는 안되기 때문에 이는 구독이나 이벤트 핸들러 설정 등 대부분의 작업에 적합하죠.

문제는 브라우저의 상태값이 이펙트에 의존할경우 입니다. 이런 경우 useEffect훅을 사용하면 DOM의 레이아웃 배치와 페인트가 끝난 후 이펙트 함수를 호출하여 사용자 경험이 안좋아 질 수 있습니다.

function Example() {
  const [name, setName] = useState("")
  
  useEffect(() => {
    setName("바보");
  }, []);
  
  return (
  	<>
      <div className="App">{`내 이름은 ${name} 입니다!`}</div>
    </>
  );
}

useEffect를 사용한 위 코드는 다음 순서대로 동작합니다.

  1. <div>내 이름은 입니다!</div>
  2. 이펙트 내부의 setName 호출
  3. 리렌더링 수행 (<div>내 이름은 바보 입니다!</div>)

화면이 복잡해질수록 이러한 구조는 렌더링 시간이 증가하게 됩니다. 그럼 사용자는 이펙트가 수행되기 전 화면을 보는 시간이 길어지게 되고 문제가 발생하겠죠.

이러한 문제를 해결하기 위한 훅이 useLayoutEffect훅 입니다.

function Example() {
  const [name, setName] = useState("")
  
  useLayoutEffect(() => {
    setName("바보");
  }, []);
  
  return (
  	<>
      <div className="App">{`내 이름은 ${name} 입니다!`}</div>
    </>
  );
}

레이아웃 이펙트는 위 그림에서 보듯 화면에 DOM을 그리기 전에 이펙트를 수행합니다. 따라서 기존과 달리 1번이 페인팅 되지 않죠.

  1. 레이아웃 이펙트 내부의 setName 호출
  2. 페인트 (<div>내 이름은 바보 입니다!</div>)

더 이상 사용자는 잘못 표시되는 화면을 볼 일이 없습니다.

이 훅은 DOM에서 레이아웃을 읽고 동기적으로 리렌더링 하는 경우 사용하면 좋습니다. useLayoutEffect의 내부에 예정된 갱신은 브라우저가 화면을 그리기 이전 시점에 동기적으로 수행됩니다.

다만 화면 갱신 차단의 방지가 가능한 경우 표준인 useEffect를 먼저 사용해 보는 것이 좋습니다.

정리

useEffect는 render, paint이후 비동기적으로 실행됩니다. 비동기 적으로 실행되어 초기 렌더링을 막지 않지만 내부에 dom에 영향을 주는 코드가 있을 경우 사용자 입장에서는 화면의 깜빡임을 보게 됩니다.

useLayoutEffect는 컴포넌트들이 render 된 후 실행되며, 실행이 완료된 이후 paint가 됩니다. 그리고 이 작업은 동기적으로 실행됩니다. paint가 되기 전에 실행되므로 dom을 조작하는 코드가 존재하더라도 사용자는 깜빡임을 경험하지 않습니다.

useLayoutEffect는 동기적으로 실행되고 내부의 코드가 모두 실행된 후 painting이 되기 때문에 로직이 복잡할 경우 사용자가 레이아웃을 보는데까지 걸리는 시간이 길어집니다. 따라서 기본적으로는 useEffect를 사용하는 것이 좋습니다.

  • data fetch
  • event handler
  • state reset
    ... 등

useLayoutEffect는 기본적으로 useEffect를 사용하다가 화면 깜빡거리는 등의 상황이 발생했을때 dom과 관련된 state으로 인해 초기 깜빡임을 해결해야 하고, 내부 로직이 많이 복잡하지 않는 등의 상황에 한하여 사용하는 것이 좋습니다.

<출처>

profile
웹 개발을 공부하고 있는 윤석주입니다.

0개의 댓글