기본부터 다지는 Spring(udemy 참고) Ch03.정리

khyojun·2023년 12월 18일
post-thumbnail

이번 챕터에서는 여러가지 스프링 annotation 및 활용법을 확인하게 된다.

  • Lazy
  • BeanScope
  • PostConstruct, PreDestroy
  • CDI
  • XML 빈 등록(고대의 방식)

@Lazy

기본적으로 Spring에서 Bean은 Eager하게 초기화 된다. 그런데 혹시나 만약 요구사항에 따라서 필요할때만 사용해야하는 빈이면?
어떻게 해야할까?

바로 @Lazy를 사용하면 된다.

Lazy를 사용하기 이전과 이후의 코드를 한 번 살펴보자.

BEFORE


@Component
class ClassB{

    private ClassA classA;
    public ClassB(ClassA classA) {
        System.out.println("Some Initialization");
        this.classA = classA;
    }

    @Override
    public String toString() {
        return "ClassB{" +
            "classA=" + classA +
            '}';
    }
}

@ComponentScan
public class LazyInitializationLauncherApplication {

    public static void main(String[] args) {
        try(var context = new AnnotationConfigApplicationContext(LazyInitializationLauncherApplication.class)){

            System.out.println("Initialization of context is completed");
            System.out.println(context.getBean(ClassB.class)); // lazy 쓰면 사용하는 시점에서 초기화 진행! 이렇게 하면 스프링 초기 구성시 오류 발견 못함! 조심해서 사용! 거의 사용도 안함!

        }
    }

}

📋 결과물

  • Some Initialization
  • Initialization of context is completed
  • ClassB{classA=com.example.learnspringframework.examples.d1.ClassA@69e1dd28}

AFTER(@Lazy 추가 이후)

import org.springframework.context.annotation.Lazy;

@Component
@Lazy
class ClassB {

    private ClassA classA;

    public ClassB(ClassA classA) {
        System.out.println("Some Initialization");
        this.classA = classA;
    }

    @Override
    public String toString() {
        return "ClassB{" +
            "classA=" + classA +
            '}';
    }
}

📋 결과물

  • Initialization of context is completed
  • Some Initialization
  • ClassB{classA=com.example.learnspringframework.examples.d1.ClassA@696da30b}

다음과 같이 @Lazy를 추가한 것으로 ClassB를 사용할 때 초기화가 진행되어진다! lazy하게 진행이 되어졌다.

그러나! 중요한 점!

@Lazy를 자주 사용하지 않는데는 이유가 또 있다! 초기에 오류를 잡을 수가 없기 때문이다.

스프링을 구성하면서 오류를 잡을 수 있지만 @Lazy가 되어져버리면 이후에 잡아버리게 되니 런타임중에 Error가 터질 수 있다!

그래서 진짜 필요한 상황이 아니라면 잘 사용하지 않는다고 한다!

BeanScope

흔히들 Spring Bean의 Scope라고 하면 대부분 Singleton이라고 얘기를 많이 하는 편이다.

그렇지만 Singleton도 종류가 있다.

  • Java Singleton : JVM 당 한 개의 인스턴스
  • Spring Singleton : IoC 컨테이너 당 한 개의 인스턴스

이 점을 유의해야한다.

그래서 어찌 되었든 우리는 Spring의 Singleton이다. 그 점을 알고 가보자.

흔히 Spring Bean Scope라고 할 때 Singleton 도 있지만 사실 Prototype 이 있다.

  • Prototype: 요청할 때마다 새로운 빈 객체를 생성해서 던져주는 친구!

예제를 통해서 어떤 차이가 있나 알아보자.

@Component // 기본 싱글톤
class NormalClass{
}

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 빈 요청시 새로운 인스턴스
@Component
class PrototypeClass{
}

@Configuration
@ComponentScan // 자동으로 현재 패키지 기준 아래쪽으로 스캔
public class  BeanScopeLauncherApplication {
    public static void main(String[] args) {
        try(var context = new AnnotationConfigApplicationContext(
            BeanScopeLauncherApplication.class)
        ){
            System.out.println(context.getBean(NormalClass.class));
            System.out.println(context.getBean(NormalClass.class));

            System.out.println(context.getBean(PrototypeClass.class));
            System.out.println(context.getBean(PrototypeClass.class));
        }
    }
}

해당하는 방식으로 진행을 하게 되었을 때 결과가 다음과 같이 나오게 된다!

com.example.learnspringframework.examples.e1.NormalClass@503d687a
com.example.learnspringframework.examples.e1.NormalClass@503d687a
com.example.learnspringframework.examples.e1.PrototypeClass@6a370f4
com.example.learnspringframework.examples.e1.PrototypeClass@2abf4075

보면 기존의 싱글톤은 같은 객체를 던지고 나머지 Prototype을 선언하니까 부를때마다 다른 객체를 던진다.

사실 이 Prototype 말고도 다른 타입들이 존재한다. 참고하고 이후에 사용하자!

  • Request : Request 요청당 객체 1개씩
  • Session : Session 당 1개씩
  • Application : web app 1개당 인스턴스 1개씩
  • Websocket : web socket 1개당 인스턴스 1개씩

PostConstruct, PreDestroy

그런 순간이 있다. bean의 생성되고 의존성이 주입된 이후 바로 실행하고 싶은 로직이 있을 수 있다.

그럴 때 @PostContruct를 사용한다.

순서는 다음과 같다.

생성자 호출 -> 의존성 주입 끝 -> @PostConstruct

그리고 bean lifeCycle이 끝나고 객체가 없어지기 직전에 자원을 정리하는등의 로직을 사용하고 싶을 수 있다.

close() 하기 직전에 실행 -> ((AbstractApplicationContext) context).close()

그럴 때 @PreDestroy를 사용한다.

예제를 통해 알아보자.


@Component
class SomeClass{

    private SomeDependency someDependency;

    public SomeClass(SomeDependency someDependency) {
        this.someDependency = someDependency;
        System.out.println("All dependencies are ready");
    }

    @PostConstruct // 의존성 연결하고 연결한 순간 바로 주입 완료된 후 메서드 바로 spring 이 실행함!
    public void initialize(){
        someDependency.getReady();
    }

    @PreDestroy // 빈이 삭제되기 직전 수행할 로직! db 연결 끊을 때 관련 혹은 데이터 초기ㅗ하 시킬 때 관련 applicationContext에서 삭제되기 직전 사용함!
    public void cleanup(){
        someDependency.clean();
    }
}

@Component
class SomeDependency{
    public void getReady() {
        System.out.println("Some logic using SomeDependency");
    }
    public void clean() {
        System.out.println("Clean this Database");
    }
}

@ComponentScan // 자동으로 현재 패키지 기준 아래쪽으로 스캔
public class PrePostAnnotationsContextLauncherApplication {

    public static void main(String[] args) {
        try(var context = new AnnotationConfigApplicationContext(
            PrePostAnnotationsContextLauncherApplication.class)
        ){
            //Arrays.stream(context.getBeanDefinitionNames()).forEach(System.out::println);
        }
    }
}

이렇게 실행을 하게 되면

All dependencies are ready
Some logic using SomeDependency
Clean this Database

다음과 같이 진행됨을 통해서 알 수 있다.

CDI

CDI라고 하는 친구가 있다. Contexts and Dependency Injection이라고 부르는 친구인데 의존성 주입의 규약과 같은 친구이다.

강의에서 말하는 바는 다음과 같다.

  • CDI 는 규약(인터페이스)이다.
  • 중요 Inject API Annotation이 존재한다.
    • @Inject (Spring에서는 @Autowired)
    • @Named (Spring에서는 @Component)
    • @Qualifier
    • @Scope
    • @Singleton

그래서 신기한 것이 이 친구들을 import 하면

import jakarta.inject.Inject;
import jakarta.inject.Named;

이렇게 import가 되어지는데 이를 통해 spring 말고도 jakarta 자바진영에서도 의존성 주입에 대한 어노테이션을 제공한다는 것을 알 수 있다.
이를 통해 외부에 의존하지 않는 때 묻지 않는 객체를 만들 수 있을 거 같다는 생각이 든다.

XML 빈 등록

사실 고대의 유산이다. 왜냐하면 이전 글에서도 봤듯이 bean을 등록하는 쉬운 방법들이 많이 존재한다. 그렇지만 이 항목을 알아야 하는 이유가 있긴 있다.

사실 우리가 건들게 될 코드가 과거의 코드를 개선하는 작업을 할 수도 있기 때문이다. 그렇기 때문에 알아야 한다!

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- bean definitions here -->

  <bean id="name" class="java.lang.String">
    <constructor-arg value="khyojun"/>
  </bean>

 <context:component-scan base-package="com.example.learnspringframework.game"></context:component-scan>
  
  <bean id="game" class="com.example.learnspringframework.game.PacmanGame">
  </bean>
  
  <bean id="gameRunner" class="com.example.learnspringframework.game.GameRunner">
    <constructor-arg ref="game"></constructor-arg>
  </bean>

</beans>

빈을 등록하는 친구인데 요약하면 다음과 같다.

  • name이라는 빈이고 String의 인스턴스이다. 생성자 인자로 khyojun라는 값을 제공한다.
  • component-scan을 작성한 패키지부터 진행한다.
  • game이라는 이름의 빈이고 PacmanGame의 인스턴스이다.
  • gameRunner라는 이름의 빈이고 GameRunner의 인스턴스이다. 그리고 다른 빈 ID가 game이라는 것을 참조해 현재 GameRunner 인스턴스의 생성자에 주입을 한다.

이와 같이 어렵지만 그래도 어떻게 보는지 알아야 한다는 것을 기억해야 한다!

출처

profile
코드를 씹고 뜯고 맛보고 즐기는 것을 지향하는 개발자가 되고 싶습니다

0개의 댓글