해당 내용은 김영한씨의 강의 및 스프링 프레임워크 공식 문서, 공식문서 번역을 참고하여 작성한 글입니다.

올바르지 않은 내용이 있다면 의견 남겨주세요. 언제든지 환영입니다.

 

1. Lifecycle Callbacks

데이터베이스 커넥션 풀이나, 네트워크 소켓처럼 애플리케이션 시작 시점에 필요한 연결을 미리 해두고, 애플리케이션 종료 시점에 연결을 모두 종료하는 작업을 진행하려면, 객체의 초기화와 종료 작업이 필요하다.

아래는 김영한씨의 강의의 예제 코드를 참고하여 우리가 의도하는 결과가 나오는지 테스트해보았다.

public class NetworkClientTest {

    private String url;
    
    public NetworkClientTest() {
        System.out.println("현재 url은 다음과 같습니다 : " + url);
        connect();
        call("초기화 연결되었습니다");
    }
    
    // 연결함수 
    public void connect() {
        System.out.println("connect, 연결된 url : " + url);
    }
    
    public void disconnect() {
        System.out.println("disconnect, 연결 끊어버린 url : " + url);
    }
    
    // URL 세팅 함수
    public void setUrl(String url) {
        this.url = url;
    }
    
    // 호출함수
    // @param msg : 호출하려는 메세지
    public void call(String msg) {
        System.out.println("call message : " + msg + " url : " + url);
    }
}

lifecycle 테스트 결과

나는 객체가 생성되면서, 원하는 url이 세팅되도록 의도했으나 그게 되지 않았다. 당연하다. 스프링 빈은 객체를 생성할 때 생성자 > 세터기반으로 생성하고 그 뒤에 의존관계를 주입한다. 이미 저 코드는 주입까지 모두 마친 후에 내가 초기값을 할당한게 된다. 그럼 connect() 함수가 실행 되기 전, 초기값을 세팅하고 싶으면 어떻게 해야할까? 방법은 아래 3가지로 나타낼 수 있다.

 

2. InitializingBean, DisposableBean

InitializingBean, DisposableBean 인터페이스들로 초기화 작업 및 연결을 끊은 이후의 작업을 진행할 수 있다.

public class NetworkClientTest implements InitializingBean, DisposableBean {

    private String url;

    public NetworkClientTest() {
        System.out.println("현재 url은 다음과 같습니다 : " + url);
    }

    // 연결함수
    public void connect() {
        System.out.println("connect, 연결된 url : " + url);
    }

    public void disconnect() {
        System.out.println("disconnect, 연결 끊어버린 url : " + url);
    }

    // URL 세팅 함수
    public void setUrl(String url) {
        this.url = url;
    }

    // 호출함수
    // @param msg : 호출하려는 메세지
    public void call(String msg) {
        System.out.println("call message : " + msg + " url : " + url);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("afterPropertiesSet, 초기화 작업 함수");
        connect();
        call("초기화 연결되었습니다");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("destroy, 연결 파괴될 때 callback");
    }
}

InitializingBean, DisposableBean lifecycle 테스트 결과

다만, 위의 인터페이스들은 스프링 전용 인터페이스이며 스프링 공식문서에서는 JSR-250의  @PostConstruct @PreDestroy을 사용하길 권장한다. 해당 @은 스프링 전용 인터페이스가 아니고 자바 표준이며 스프링이 아닌 다른 컨테이너에서도 동작하기 때문이다.

 

3. @PostConstruct @PreDestroy

public class NetworkClientTest{

    private String url;

    public NetworkClientTest() {
        System.out.println("현재 url은 다음과 같습니다 : " + url);
    }

    // 연결함수
    public void connect() {
        System.out.println("connect, 연결된 url : " + url);
    }

    public void disconnect() {
        System.out.println("disconnect, 연결 끊어버린 url : " + url);
    }

    // URL 세팅 함수
    public void setUrl(String url) {
        this.url = url;
    }

    // 호출함수
    // @param msg : 호출하려는 메세지
    public void call(String msg) {
        System.out.println("call message : " + msg + " url : " + url);
    }

    @PostConstruct
    public void init() throws Exception {
        System.out.println("PostConstruct 초기화 작업 함수");
        connect();
        call("초기화 연결되었습니다");
    }

    @PreDestroy
    public void close() throws Exception {
        System.out.println("PreDestroy, 연결 파괴될 때 callback");
    }
}

@PostConstruct @PreDestroy lifecycle 테스트 결과

@PostConstruct, @PreDestroy가 붙은 메서드는 명시적으로 선언된 callback 메서드와 생명주기의 같은 지점에서 호출된다. @만 붙이면 되므로 매우 편리한 장점을 가지고 있다. 다만 외부 라이브러리에는 적용하지 못하므로, 외부 라이브러리를 초기화, 종료해야한다면 @Bean의 기능을 사용한다.

 

4. Receiving Lifecycle Callbacks

public class NetworkClientTest{

    private String url;

    public NetworkClientTest() {
        System.out.println("현재 url은 다음과 같습니다 : " + url);
    }

    // 연결함수
    public void connect() {
        System.out.println("connect, 연결된 url : " + url);
    }

    public void disconnect() {
        System.out.println("disconnect, 연결 끊어버린 url : " + url);
    }

    // URL 세팅 함수
    public void setUrl(String url) {
        this.url = url;
    }

    // 호출함수
    // @param msg : 호출하려는 메세지
    public void call(String msg) {
        System.out.println("call message : " + msg + " url : " + url);
    }

    public void init() throws Exception {
        System.out.println("init 초기화 작업 함수");
        connect();
        call("초기화 연결되었습니다");
    }

    public void close() throws Exception {
        System.out.println("PreDestroy, 연결 파괴될 때 callback");
    }
}

Receiving Lifecycle Callbacks lifecycle 테스트 결과

@Bean을 통해 정의된 모든 클래스는 일반적인 생명주기 콜백을 지원한다. @Bean은 Spring XML의 bean 엘리먼트 속성인 init-method, destroy-method에 초기화, 파괴용도의 콜백 메서드를 지정할 수 있다. 만약, 기본적으로 'close', 'shoutdown'이라는 이름을 가진 public 메서드를 갖고 있다면 자동적으로 콜백에 참여하게 된다. 호출하기를 원하지 않는다면 @Bean(destroyMethod="") 와 같이 추가한다. @Bean은 기본적으로 (inferred, 추론) 하기 때문이다.

 

5. Combining Lifecycle Mechanisms

빈(Bean) 생명주기 동작 방법을 3가지를 알아보았다. 만약 하나의 빈에 여러개의 생명주기 동작이 설정되었고, 각기 다른 메서드 이름으로 구성되었을 때 각 메서드는 다음과 같은 순서로 실행된다.
만일 중복된 메서드 이름이 존재한다면 한번만 실행된다.

Initialization methods Destroy methods
1. @PostConstruct 
2. InitializingBean 인터페이스에서 정의된 afterPropertiesSet()
3. 커스텀한 init()
1. @PreDestroy
2. DisposableBean 인터페이스에서 정의된 destroy()
3. 커스텀한 destroy()

 


참고 1) 김영한(스프링 핵심원리 - 기본편)

참고 2) https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factory-lifecycle-combined-effects

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

참고 3) https://johngrib.github.io/wiki/spring/document/core/01-12-java-based-container-config/#receiving-lifecycle-callbacks

 

Spring Core Technologies - 1.12. Java-based Container Configuration

 

johngrib.github.io

 

'Java > Spring' 카테고리의 다른 글

[Spring] MVC 발전과정(2)  (0) 2024.10.31
[Spring] MVC 발전과정(1)  (0) 2024.10.30
[Spring] Singleton  (0) 2023.02.03
[Spring] IOC  (0) 2023.01.26
[Spring] DI  (0) 2023.01.23

해당 내용은 김영한씨의 강의 및 스프링 프레임워크 공식 문서를 번역하여 작성한 글입니다.

특히 공식 문서에 있어 올바르지 않은 번역 의견은 언제든지 환영합니다. 댓글 달아주세요

 

1. Singleton

프로젝트 하다보면 하나의 객체만 생성하고 그 객체를 공유할 경우가 많다. 그럴 때, 이 싱글톤 패턴을 활용해서 적용할 수 있다. 해당 싱글톤 패턴을 사용하면 객체 공유가 가능하며 여러 고객이 동시에 요청할 때 요청시마다 객체가 생성되는 것이 아니므로 메모리 낭비를 줄일 수 있다. 즉 싱글톤은 JVM당 하나의 인스턴스만 갖도록 하는 디자인 패턴인 것이다.

public class SingleObject {

   //create an object of SingleObject
   private static SingleObject instance = new SingleObject();

   //make the constructor private so that this class cannot be
   //instantiated
   private SingleObject(){}

   //Get the only object available
   public static SingleObject getInstance(){
      return instance;
   }

   public void showMessage(){
      System.out.println("Hello World!");
   }
}

singleton 참조값 일치

다만, 이런 싱글톤 패턴을 사용하기 위해서는 내부에서 private static 변수를 선언하고, 객체를 new() 로 생성할 수 없도록 생성자를 작성해주어야 한다. 이것은 작성 코드가 길어진다는 단점이 된다. 또한 값을 변경할 수 있는 필드가 있을 경우 문제가 발생한다는 큰 단점이 존재한다. 아래 코드는 김영한씨의 강의 중 유지(stateful)하게 설계되었을 때의 문제코드이다.

public class StatefulService {
    //상태를 유지하는 필드
    private int price; 
    
    public void order(String name, int price) {
       System.out.println("name = " + name + " price = " + price);
       
       //여기가 문제!
       this.price = price; 
    }
    
    public int getPrice() {
       return price;
    }
}

하나의 객체를 공유하다보니 내가 원하지 않는 결과가 나왔다. 그렇기에 싱글톤 패턴을 사용하기 위해서는 

  • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
  • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
  • 가급적 읽기만 가능해야한다.
  • 필드 대신에 자바에서 공유되지 않는, 지역변수, 파라미터, ThreadLocal등을 사용한다.

2. Singleton Container

스프링 컨테이너는 우리가 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다. 싱글톤 scope로 정의한 bean에 대해 딱 하나의 인스턴스를 만드는 것이다. 그리고 이러한 싱글톤 인스턴스는 싱글톤 bean을 모아두는 캐시에 저장이 되며 요청이 오면 그 캐시에서 인스턴스를 반환해준다. 아래 그림은 스프링 공식 문서의 싱글톤 scope 동작 그림이다.

스프링 공식 문서 - singleton scope 사진

그럼, 우리는 이제 궁금해지기 시작한다. 공식 문서에서 accountDao를 수없이 작성했지만 하나만 생성될 수 있던 이유가 무엇일까

컨테이너 생성 흐름

위의 사진과 같은 흐름으로 컨테이너가 생성이 된다. 또한 scope란 속성이 싱글톤이므로 doRegisterBeanDefinitions에서 싱글톤으로 생성이 되겠다.

<!-- the following is equivalent, though redundant (singleton scope is the default); using spring-beans-2.0.dtd -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>

 

3. Java Based Singleton Container

그럼 java를 기반으로 한 싱글톤은 어떻게 생성이 되는 것일까, 

@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(getMemberRepository());
    }

    @Bean
    public MemberRepository getMemberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(getMemberRepository(), getDiscountPolicy());
    }
}

다음과 같은 AppConfig.java 에서 new로 객체를 생성해주고 있기 때문이다. 특히 getMemberRepository()는 memberService(), getMemberRepository(), orderService()를 통해서 최소 3번 new로 객체생성하고 있어 궁금증이 생길 수 있다. 정답은 @Configuration으로 가능하다. (사실은 @Configuration 클래스 안에 있는 @Bean 메서드가 처리되는 것이다)


The @Bean methods in a regular Spring component are processed differently than their counterparts inside a Spring @Configuration class. The difference is that @Component classes are not enhanced with CGLIB to intercept the invocation of methods and fields. CGLIB proxying is the means by which invoking methods or fields within @Bean methods in @Configuration classes creates bean metadata references to collaborating objects. Such methods are not invoked with normal Java semantics but rather go through the container in order to provide the usual lifecycle management and proxying of Spring beans, even when referring to other beans through programmatic calls to @Bean methods. In contrast, invoking a method or field in a @Bean method within a plain @Component class has standard Java semantics, with no special CGLIB processing or other constraints applying.


공식문서에 보면, spring 컴포넌트의 @Bean 메서드는 spring이 @configuration 클래스에 있는 @Bean 메서드와는 다르게 처리된다. 차이점은 메서드나 필드 호출을 가로채기 위한 @Component 클래스에 CGLIB가 사용되지 않는다는 점이다.

 

CGLIB는 바이트 코드 생성 라이브러리로 Java 바이트 코드를 생성하고 변환하기 위한 고급 API이다. 동적 프록시 객체를 생성하고 필드 액세스를 가로채기 위해 AOP, 테스트, 데이터 액세스 프레임워크에서 사용된다고 한다.

public class ConfigurationSingleton {

    ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    
    @Test
    void configurationTest() {
        MemberServiceImpl memberServiceImpl = ac.getBean(MemberServiceImpl.class);
        OrderServiceImpl orderServiceImpl = ac.getBean(OrderServiceImpl.class);
        MemberRepository memberRepository = ac.getBean(MemberRepository.class);

        System.out.println("memberServiceImpl = " + memberServiceImpl.getMemberRepository());
        System.out.println("orderServiceImpl = " + orderServiceImpl.getMemberRepository());
        System.out.println("memberRepository = " + memberRepository);

        Assertions.assertThat(memberServiceImpl.getMemberRepository()).isSameAs(memberRepository);
    }

    @Test
    void configurationDeep() {
        AppConfig appConfig = ac.getBean(AppConfig.class);
        System.out.println(appConfig.getClass());
    }
}

그래서 위와 같은 코드 작성했을 때, @Configuration 작성 유무에 따라 memberRepository 객체가 싱글톤으로 유지되는지를 확인해볼 수 있다. 결론은 싱글톤을 사용하기 위해 우리는 @Configuration-@Bean을 한 세트로 기억하도록 한다.


참고 1) 김영한(스프링 핵심원리 - 기본편)

참고 2) https://www.tutorialspoint.com/design_pattern/singleton_pattern.htm

 

Design Pattern - Singleton Pattern

Singleton pattern is one of the simplest design patterns in Java. This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object.

www.tutorialspoint.com

참고 3) https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factorybeans-annotations

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

참고 4) 스프링 공식문서 번역한 블로그
https://johngrib.github.io/wiki/spring/document/core/01-10-classpath-scanning-and-managed-components/#1105-defining-bean-metadata-within-components

 

Spring Core Technologies - 1.10. Classpath Scanning and Managed Components

 

johngrib.github.io

 

'Java > Spring' 카테고리의 다른 글

[Spring] MVC 발전과정(1)  (0) 2024.10.30
[Spring] 빈(Bean) 생명주기  (0) 2023.02.23
[Spring] IOC  (0) 2023.01.26
[Spring] DI  (0) 2023.01.23
[Spring] Thymeleaf Layout Dialect 설정  (0) 2022.09.27

해당 내용은 김영한씨의 강의 및 스프링 프레임워크 공식 문서를 번역하여 작성한 글입니다.

특히 공식 문서에 있어 올바르지 않은 번역 의견은 언제든지 환영합니다. 댓글 달아주세요

 

1. IOC(Inversion of Control)

제어의 역전 IoC(Inversion of Control) 기존 프로그램은 클라이언트 구현 객체가 스스로 필요한 서버 구현 객체를 생성하고, 연결하고, 실행했다. 한마디로 구현 객체가 프로그램의 제어 흐름을 스스로 조종했다. 개발자 입장에서는 자연스러운 흐름이다. 하지만 프로그램의 제어 흐름을 구현 객체가 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)이라 한다.

 

예를 들어, 

public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository = new MemoryMemberRepository();
    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();

위의 코드를 보면, OrderServicempl가 MemoryMemberRepository, FixDiscountPolicy란 객체를 생성하고 연결했다.

역할과 구현으로 나누었다고 하지만 OrderServiceImpl은 MemberRepository와 MemoryMemberRepository를 바라보고 있으며 또 DiscountPolicy, FixDiscountPolicy를 동시에 바라보고 있다.

public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    // 생성자
    public OrderServiceImpl (MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    };
public class AppConfig {

    public MemberService memberService() {
        return new MemberServiceImpl(getMemberRepository());
    }

    private MemberRepository getMemberRepository() {
        return new MemoryMemberRepository();
    }

    public OrderService orderService() {
        return new OrderServiceImpl(getMemberRepository(), getDiscountPolicy());
    }

    private DiscountPolicy getDiscountPolicy() {
        return new RateDiscountPolicy();
    }
}

그렇기에 AppConfig 파일을 만듦으로써, OrderServiceImpl은 자신의 로직을 실행하는 역할만 담당하고 어떤 구현 객체들이 실행될지 알 수 없어지게 수정했다. 즉 이전의 OrderServiceImpl가 가지고 있던 제어 흐름을 이제 AppConfig가 가져간 것이다. 이렇게 프로그램의 제어 흐름을 직접 제어하는 것이 아니라 외부에서 관리하는 것을 제어의 역전(IoC)인 것이다.

 

2. Introduction to the Spring IoC Container

IoC의 원리는 의존성 주입(DI)*이다. 스프링 프레임워크는 빈(🫘) 생성 시에 컨테이너에 의존성을 주입한다. 이러한 과정은 Service Locator pattern** 과 같은 메커니즘을 사용하여 종속성의 인스턴스화 또는 위치를 제어하는 빈 자체의 역(즉, 제어의 반전)인 것이다.

 

org.springframework.beans와 org.springframework.context 패키지는 스프링 IOC 컨테이너의 기본 패키지들이다. BeanFactory 인터페이스는 객체 생성과 검색에 대한 기능을 정의하고 있다. 예를 들어 생성된 객체를 검색하는데 필요한 getBean() 메서드가 존재하며, 객체 검색 이외에도 싱글톤/프로토타입 빈인지 여부 확인하는 기능이 존재한다. ApplicationContext는 메시지 소스를 활용한 국제화 기능(한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로), 환경 변수(로컬, 개발, 운영 구분 처리), 애플리케이션 이벤트(이벤트 발행하고 구독하는 모델), 리소스조회(파일, 클래스패스, 외부)를 가지고 있는 BeanFactory의 서브인터페이스다.  

 

BeanFactory, ApplicationContext

ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면 중복 오류가 발생한다")
void findBeanByParentTypeDuplicate(){
    DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
    Assertions.assertThrows(NoUniqueBeanDefinitionException.class, ()-> ac.getBean(DiscountPolicy.class));
}
@Configuration
static class TestConfig {

    @Bean
    public DiscountPolicy rateDiscountPolicy() {
        return new RateDiscountPolicy();
    }

    @Bean
    public DiscountPolicy fixDiscountPolicy() {
        return new FixDiscountPolicy();
    }
}

 

3. XML-based Container  Configuration and  Java-based Container Configuration

xml과 @을 기반으로 한 IOC 설정방법은 다음과 같다.

같은 코드의 java, xml based container configuration

요즘 많은 개발자들은 Java를 기반으로 한 스프링 설정을 많이 사용한다. XML 기반은 <bean>을 이용해 구성한다면 Java 구성에서는 일반적으로 @Configuration 클래스 내에서 @Bean을 사용한다.


* DI는 [Spring] DI로 따로 정리되어있다.

** Service Locator pattern은 디자인 패턴 중 하나로, 중개자를 통해 접근하도록 하는 패턴이다.

 

2023/01/26 : 왜 BeanFactory보다 ApplicationContext를 써야하는지 정리할 것

참고 1) 김영한(스프링 핵심원리 - 기본편)

참고 2) 최범균(초보 웹 개발자를 위한 스프링 4 프로그래밍 입문)

참고 3) https://docs.spring.io/spring-framework/docs/current/reference/html/core.html

 

Core Technologies

In the preceding scenario, using @Autowired works well and provides the desired modularity, but determining exactly where the autowired bean definitions are declared is still somewhat ambiguous. For example, as a developer looking at ServiceConfig, how do

docs.spring.io

 

'Java > Spring' 카테고리의 다른 글

[Spring] 빈(Bean) 생명주기  (0) 2023.02.23
[Spring] Singleton  (0) 2023.02.03
[Spring] DI  (0) 2023.01.23
[Spring] Thymeleaf Layout Dialect 설정  (0) 2022.09.27
[Spring] 정적 웹 페이지 vs 동적 웹 페이지  (0) 2022.09.20

+ Recent posts