반응형

된다.

 

Bean 디버깅에 대해 구글링해보면 아래와 같이 Bean 을 직접 디버깅하기 보다는 Bean 에 대한 log 설정을 추가하여 살펴보는 것이 다였다. 그래서 내가 개발한 Bean 은 디버깅을 하지 못 하는 것일까? 계속 고민했었는데 아니다. 잘 된다.

 

java - Debugging Spring configuration - Stack Overflow

 

Debugging Spring configuration

I am working on a Java application that uses Spring and Hibernate and runs on a Websphere. I have run into a problem, where I expect Spring to load a Dao into my object, but for some reason that d...

stackoverflow.com

 

위와 같은 내용은 내가 개발한 Bean 이 아닌 Spring 내부 Bean 이거나 써드 파티 모듈의 Bean 의 동작을 확인하고 싶을 때 설정하는 것이다. 내가 개발한 Bean 은 BreakPoint 도 잘 설정이 되고 잘 멈춘다.

 

만약 Bean 내부에 브레이크포인트가 설정을 하였는데도 멈추지 않는다면 아래와 같은 경우를 의심해보아야 한다.

 

1. Bean 으로 등록만 하고 코드에서 사용하지 않는 경우

2. Bean 내부 브레이크포인트들이 써드파티 모듈에서만 처리되는 경우

3. Bean 이 정상적으로 로드되지 않는 경우

 

3 번은 아래와 같이 테스트하여 Bean 이 로드되는지 확인하면 된다.

@SpringBootTest
public class Test {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void contextLoads() throws Exception {
        if (applicationContext != null) {
            String[] beans = applicationContext.getBeanDefinitionNames();

            for (String bean : beans) {
                System.out.println("bean : " + bean);
            }
        }
    }
}

 

필자는 아래 네이버에서 작성한 Jackson Module 글을 읽고 똑같이 Module 을 설정하여 조금 더 깔끔한 코드를 만들려고 노력했는데 아무리 해봐도 되지 않았다. Jackson의 확장 구조를 파헤쳐 보자 (naver.com)

 

Bean 은 정상적으로 로드가 되는데 브레이크포인트도 걸리지 않고 정상적으로 파싱이 되지 않아 3일을 버린 것 같다. ㅠ

결론은 Bean 으로 만든 Module 을 ObjectMapper 에 연동하지 않아 발생한 문제였다. 아래와 같이 코드를 수정하니 정상적으로 json 이 파싱되었다.

 

objectMapper.registerModule(내가 만든 모듈);

 

이 문장 하나 때문에 Jackson 라이브러리 분석을 얼마나 했는지 모른다. 결국 Bean 으로 설정한 Module 과 registerModule 로 연동이 되지 않아서였다.

 

정상적으로 Bean BreakPoint 에 도착한 모습

반응형
반응형

1. 개요

의존 관계를 외부에서 주입(Dependecy Injection(DI))받는 것이 아닌, 직접 필요한 의존 관계를 찾는 것을 의존관계 조회(탐색) (Dependency Lookup(DL)) 이라고 한다. Spring 의 Application Context 전체를 주입받게 된다면, Spring Container 에 종속적인 코드가 되고, 그로 인해 단위 테스트도 어려워지게 된다. 이 때 필요한 것이 의존 관계 조회이다.

 

2. 본론

Spring 에서는 ObjectProvider 인터페이스로 DI Container 에서 해당 객체를 찾아 반환해준다.

DI Container 로 관리되는 Bean 객체들이 서로 의존성으로 엮여 있다면, 단위 테스트하기 많이 불편해지는데 해당 객체에서 필요한 객체들만 가지고 와 테스트하거나 의존성을 맺을 수 있므로 엄청난 이점을 가져다 준다. 사용방법은 단순하다.

 

ObjectProvider<찾고자 하는 클래스> testProvider = new ObjectProvider<>();

찾고자 하는 클래스 test = testProvider.getObject()

 

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

public class SingletonWithPrototypeTest1 {

    @Test
    void prototypeFind() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(PrototypeBean.class);
        PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
        prototypeBean1.getCount();
        assertThat(prototypeBean1.getCount()).isEqualTo(1);

        PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
        prototypeBean2.getCount();
        assertThat(prototypeBean2.getCount()).isEqualTo(1);
    }

    @Test
    void singletonClientUsePrototype() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
        ClientBean clientBean1 = ac.getBean(ClientBean.class);
        int count1 = clientBean1.logic();
        assertThat(count1).isEqualTo(1);

        ClientBean clientBean2 = ac.getBean(ClientBean.class);
        int count2 = clientBean2.logic();
        assertThat(count2).isEqualTo(1);
    }

    @Scope("singleton")
    static class ClientBean {

        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider;

        public int logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }
    }

    @Scope("prototype")
    static class PrototypeBean {
        private int count = 0;

        public void addCount() {
            count++;
        }

        public int getCount() {
            return count;
        }

        @PostConstruct
                public void init() {
            System.out.println("PrototypeBean.init " + this);
        }

        @PreDestroy
        public void destory() {
            System.out.println("PrototypeBean.destory");
        }
    }
}

 

스프링의 ObjectProvider 말고도 JSR330 Provider 자바 표준 프로바이더도 있다.

implementation 'javax.inject:javax.inject:1' 만 의존성 추가해주면 사용이 가능하고 앞에 구문에서 ObjectProvider 를 Provider 로 수정하면 된다.

반응형
반응형

Spring 에서 Bean 으로 등록할 수 있는 @Bean, @Component, @Controller, @Service, @Repository 등 어노테이션들은 그 구현 클래스 내부에 변수를 생성해서는 안 된다. Bean 객체들은 기본 정책 상 싱글톤 객체이기 때문에 그렇다.

 

서로 다른 요청에 따라 쓰레드 별로 스택 메모리 영역을 차지한다고 하더라도, 이미 힙 영역에 Bean 객체가 로딩되어 그 로딩된 객체를 공유하기 때문에 상태 변수가 내부에 있다면 그 상태 변수가 의도하지 않은 상태로 실행이 될 수 있다.

 

따라서, Bean 객체는 메소드만 공유해야 하며, Bean 객체의 상태값을 유지하게 만드는 필드 값이 없는 무상태(Stateless)로 설계해야한다.

 

그런데 직접 설계해보니 결국 스프링은 대량의 요청을 받는 프레임워크이기 때문에 공유하는 오브젝트가 있을 수도 있다. 예를 들어 해당 요청의 서비스를 해야할지 말아야 할지 결정하는 전역변수 같은 경우이다. 되도록이면 사용하지 말아야 겠지만...

 

그 대신 레이스 컨디션이 되지 않도록 multi-thread 환경에 안전한 자료구조와 알고리즘을 사용하자.

 

참고

[스프링] 싱글톤 방식의 주의점 - 스프링 빈은 항상 무상태(stateless)로 설계하자 (tistory.com)

반응형

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

[Spring Boot] Auto-Configuration  (0) 2022.09.23

+ Recent posts