Spring + @Lazy

Posted at 2019. 8. 5. 00:46 | Posted in Framework/Spring

https://github.com/antop-dev/spring-lazy

요즘 MSA를 공부하면서 아래와 같은 문구를 보았다.

마이크로서비스에서는 완전 자동화를 달성하기 위해 초소한의 시동/종료 시간을 갖도록 애플리케이션의 크기를 가능한 한 작게 유지하는 것이 극단적으로 아주 중요하다.
이를 위해 마이크로서비스에서는 객체와 데이터의 지연 로딩lazy loading에 대해서도 고려해봐야 한다.

이 때 떠오른 단어는 Spring@Lazy이다.

스프링 빈 설정시 @Lazy 애노테이션만 달아주면 이 빈을 가져오는 시점에 생성하기 때문에 모든 빈을 처음 초기화시에 만들지 않는다.

그런데 약간의 의문사항이 있어서 테스트 해봤다... 구글링을 대충 해도 나오는 자료이지만 직접 해봤다.

등장 클래스

아래와 같이 3개의 클래스를 빈으로 등록했다. 패턴은 다 같고 First, Second, Third 만 바뀐다.

@Component
public class First {
    @Autowired
    private Second second;

    public First() {
        System.out.println("first class constructor");
    }

    public void go() {
        System.out.println("Hello first!");
        second.go();
    }
}

테스트 코드는 아래와 같다.

public class LazyApplicationTests {
    @Test
    public void lazy() {
        ApplicationContext context = new AnnotationConfigApplicationContext(LazyApplication.class);
        System.out.println("call first.go()");
        First first = context.getBean(First.class);
        first.go();
    }
}

일반적인 빈 설정

먼저 위와 같이 @Lazy를 사용하지 않는 일반 적인 설정이다. 후에 스프링 컨텍스트에서 First 인스턴스를 꺼내서 go() 메서드를 수행할 것이다.

예상한 대로 빈이 전부 생성 된 후(①) FIrst.go() 이후의 작업이 수행된다(②).

first class constructor
second class constructor
third class constructor
Spring context loaded call
first.go()
Hello first!
Hello second
Hello third!

하지만 좀 이상한 점이 있는데 예상은 ThridSecondFirst 순으로 생성될 줄 알았지만 반대로 생성된다. 생성자를 사용하지 않고 @Autowired나 세터setter를 사용하면 스프링이 이 클래스를 까서(?) 적용을 하게 된다.

@Autowired를 사용하지 않고 생성자constructor를 사용하면 예상한 순서대로 빈이 만들어진다.

@Component
public class First {
    private final Second second;

    public First(Second second) {
        this.second = second;
        System.out.println("first class constructor");
    }

    public void go() {
        System.out.println("Hello first!");
        second.go();
    }
}
third class constructor second class constructor first class constructor Spring context loaded call first.go() Hello first! Hello second Hello third!

@Lazy 적용

</p||_##]

First, Second, Thrid 클래스에 @Lazy를 적용해보자.

예상한대로 스프링 초기화시에는 빈을 생성하지 않고 있다가 First 인스턴스를 처음 가져올 때 생성하게 된다.

Spring context loaded
call first.go()
third class constructor
second class constructor
first class constructor
Hello first!
Hello second
Hello third!

@Lazy 사용시 주의사항

위와 같이 First 빈을 @Lazy로 하지 않을 경우 뒤에 참조하게 되는 모든 클래스의 @Lazy이 무용지물된다.

가장 처음 했던 @Lazy를 적용 하지 않은 설정과 같은 결과다.

third class constructor
second class constructor
first class constructor
Spring context loaded
call first.go()
Hello first!
Hello second
Hello third!

아래와 같다면?

Thrid, Second, Fourth 는 스프링 초기화시 생성되고 First는 지연 로딩이 적용된다.

third class constructor
second class constructor
fourth class constructor
Spring context loaded
call first.go()
first class constructor
Hello first!
Hello second
Hello third!

동시성 concurrency

First 빈이 생성 되는데 3초가 걸린다고 가정해보자.

@Component
@Lazy
public class First {
    private final Second second;

    public First(Second second) {
        this.second = second;
        // 인스턴스를 생성하는데 3초가 걸린다.
        System.out.println("It takes 3 seconds to create an instance");
        try {
            for (int i = 0; i < 3; i++) {
                System.out.println((3 - i) + "..");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
        System.out.println("first class constructor");
    }

    public void go() {
        System.out.println("Hello first!");
        second.go();
    }
}

First 인스턴스를 사용하는 다른 인스턴스에서 동시에(거의 동시에?) 요청이 들어온다면?

테스트 코드는 아래와 같다.

@Test
    public void concurrency() throws Exception {
        ApplicationContext context = new AnnotationConfigApplicationContext(LazyApplication.class);
        System.out.println("Spring context loaded");

        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(() -> {
                System.out.println("call first.go()");
                First first = context.getBean(First.class);
                first.go();
            });

            thread.start();
            System.out.println(thread.getName() + " started.");
            Thread.sleep(100);
        }

        // 결과를 보기 위해 5초 대기
        Thread.sleep(5000);
    }

걱정과는 다르게 매우 잘 작동한다! 스프링 구욷!

third class constructor
second class constructor
fourth class constructor
Spring context loaded
Thread-2 started.
Thread-2 call first.go()
It takes 3 seconds to create an instance
3..
Thread-3 started.
Thread-3 call first.go()
Thread-4 started.
Thread-4 call first.go()
2..
1..
first class constructor
Thread-2 Hello first!
Thread-4 Hello first!
Thread-2 Hello second
Thread-4 Hello second
Thread-4 Hello third!
Thread-3 Hello first!
Thread-3 Hello second
Thread-3 Hello third!
Thread-2 Hello third!

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

Spring + @Lazy  (0) 2019.08.05
Twelve Best Practices For Spring XML Configurations  (0) 2010.06.23

댓글 (Comment)

Name*

Password*

Link (Your Website)

Comment

SECRET | 비밀글로 남기기