이번 게시글에서는 Spring Framework에서 사용할 수 있는 여러 기능에 대해서 알아보자.
- Lazy Intialzation
- Prototype
- PostConstruct
- PreDestroy
가장 먼저 Lazy initialization에 대해서 알아볼 것이다.
스프링의 초기화 방식에는 즉시 초기화와 지연 초기화 두 가지가 있다. default 설정은 즉시 초기화이다.
오늘도 코드로 예시를 들기 위해 기반 클래스를 하나 만들어보자. 기반 클래스의 이름은 LazyInitializationLauncherApplication이라고 가정하자.
그리고 이 클래스 안에 Bean 몇가지를 만들어보자.
@Component
class ClassA {
}
@Component
class ClassB {
}
여기서 ClassB는 멤버변수로 ClassA classA를 가지고, 초기화 로직을 가진다고 가정하자.
@Component
class ClassB {
private ClassA classA;
public ClassB(ClassA classA) {
//Logic
System.out.println("Some Initialization Logic");
this.classA = classA;
}
}
이 코드를 실행하면 Some Initialzation Logic
라고 출력되는 걸 확인할 수 있다.
중요한 것 이것이다. 우리는 기반 클래스를 작성하고 아무 코드도 작성하지 않았다. 즉, 우리는 ClassB 객체를 생성하거나 사용하지 않았다. 그럼에도 초기화가 자동으로 이루어졌다.
만약 이러한 상황을 원하지 않는다면 어떻게 해야 할까?
우리는 이러한 상황에서 @Lazy 어노테이션을 이용할 수 있다.
@Component
@Lazy
class ClassB {
private ClassA classA;
public ClassB(ClassA classA) {
//Logic
System.out.println("Some Initialization Logic");
this.classA = classA;
}
}
이제는 Some Initialization Logic
이라는 출력을 볼 수 없다. 왜냐하면 @Lazy 어노테이션을 사용하는 경우, 해당 클래스의 객체를 사용해야 초기화가 이루어지기 때문이다.
아래와 같이 ClassB 객체를 사용하면 초기화가 이루어지는 것을 확인할 수 있다. 적는 김에 주석에 이것저것 적어놓았으니 확인하라.
package com.in28minutes.learn_spring_framework.example.d1;
import ....
@Component
class ClassA {
}
@Component
@Lazy // The bean is only loaded when you try to use that class.
/*
Exploring Lazy Initialization of Spring Beans
Default initialization for Spring Beans: Eager
Eager initialization is recommended:
- Errors in the configuration are discovered immediately at application startup
However, you can configure beans to be lazily initialized using Lazy annotation:
- Not recommended, Not frequently used
Lazy annotation:
- Can be used almost everywhere @Component and @Bean are used
- Lazy-resolution proxy will be injected instead of actual dependency
- Can be used on Configuration(@Configuration) class:
- All @Bean methods within the @Configuration will be lazily initialized
If you use lazy initialization,
Spring configuration errors won't be detected when the application starts;
you'll get runtime errors
*/
class ClassB {
ClassA classA;
public ClassB(ClassA classA) {
//Logic
System.out.println("Some Initialization Logic");
this.classA = classA;
}
public void doSomething() {
System.out.println("Do Something");
}
}
@Configuration
@ComponentScan//("com.in28minutes.learn_spring_framework.example.a1") // Spring needs to know where to find the Component.
public class LazyInitializationLauncherApplication {
public static void main(String[] args) {
try(var context =
new AnnotationConfigApplicationContext
(LazyInitializationLauncherApplication.class)) {
System.out.println("Initialization of context is completed");
context.getBean(ClassB.class).doSomething();
}
}
}
물론 이렇게 지연 초기화를 사용할 일은 자주 없다. 왜냐하면 즉시 초기화를 사용하는 경우에는 Spring 구성에 오류가 있을 경우 애플리케이션이 시작할 때 오류를 바로 확인할 수 있지만 지연 초기화를 사용하면 그렇지 않아 런타임 오류가 발생한다.
Heading | Lazy Initialization | Eager Initialization |
Initialization time | Bean initialized when it is first made use of in the application | Bean initialized at startup of the application |
Default | Not Default | Default |
Code Snippet | @Lazy OR @Lazy(vaule=true) | @Lazy(value=false) OR Absence of @Lazy |
What happens if there are errors in initializing? | Errors will result in runtime exceptions | Errors will prevent application from starting up |
Usage | Rarely used | Very frequently used |
Memory Consumption | Less(until bean is intialized) | All beans are initialized at startup |
Recommended Scenario | Beans very rarely used in your app | Most of your beans |
다음은 Prototype에 대해 알아 보자.
이번에 만들 기반 클래스의 이름은 BeanScopesLauncherApplication이라고 지어보자.
그리고 NormalClass과 PrototypeClass라는 이름의 빈 두 가지를 만들어보자.
@Component
class NormalClass {
}
@Component
class PrototypeClass {
}
그리고 PrototypeClass에 @Scope 어노테이션을 추가해보자. Scope 옆의 괄호에 `value=ConfigurableBeanFactory를 입력하고 점을 찍으면 두 가지 스코프가 표시된다. 싱글톤과 프로토타입을 확인할 수 있는데, 싱글톤이 기본으로 설정되어 있는 스코프이다. 여기에는 SCOPE_PROTOTYPE을 추가하자.
@Component
class NormalClass {
}
@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
class PrototypeClass {
}
이렇게 하면 프로토타입 클래스가 된다. 그런데 일반 클래스와 프로토타입 클래스는 무엇이 다른 걸까?
코드를 통해 알아보자.
@Configuration
@ComponentScan//("com.in28minutes.learn_spring_framework.example.a1") // Spring needs to know where to find the Component.
public class BeanScopesLauncherApplication {
public static void main(String[] args) {
try(var context =
new AnnotationConfigApplicationContext
(BeanScopesLauncherApplication.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));
System.out.println(context.getBean(PrototypeClass.class));
}
}
}
위의 코드를 작성한 뒤 실행해보자. NormalClass 객체를 호출하는 경우에는 특정 객체, 그러니까 동일한 Bean을 계속해서 호출하고 있지만, PrototypeClass 객체를 호출하는 경우에는 호출할 때마다 새로운 Bean이 호출된다.
이는 싱글톤은 Spring IoC 컨테이너 당 객체 인스턴스가 하나이지만, 프로토타입은 Spring IoC 컨테이너 당 객체 인스턴스가 여러 개일 수 있다는 차이점에서 비롯된다.
싱글톤, 프로토타입 외에도 Request, Session, Application, Websocket과 같은 여러 스코프가 있지만 이 강의에서는 알 필요 없다.
다만 싱글톤을 앎에 있어 주의해야 할 것은 Spring 싱글톤과 GOF에서 설명하는 Java 싱글톤이 다르다는 것이다.
자바 싱글톤(GOF) 대 스프링 싱글톤
- 스프링 싱글톤: Spring IoC 컨테이너당 하나의 객체 인스턴스
- 자바 싱글톤(GOF): JVM당 하나의 객체 인스턴스
JVM에서 하나의 Spring IoC 컨테이너만 실행하는 경우, Spring 싱글톤과 Java 싱글톤을 서로 바꿔 사용할 수 있다.
그러나 JVM에서 여러 개의 Spring IoC 컨테이너를 실행하는 경우 Spring 싱글톤은 Java 싱글톤과 다르다.
일반적으로 JVM에서 여러 개의 Spring IoC 컨테이너를 실행하지는 않는다. 따라서 99.99%의 경우 Spring 싱글톤과 Java 싱글톤은 정확히 동일하다.
Heading | Prototype | Singleton |
Instances | Possibly Many per Spring IOC Container | One per Spring IOC Container |
Beans | New bean instance created every time the bean is referred to | Same Bean instance reused |
Default | NOT Default | Default |
Code Snippet | @Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE) | @Scope(value=ConfigurableBeanFactory.SCOPE_SINGLETON) OR Defalut |
Usage | Rarely used | Very frequently used |
Recommended Scenario | Stateful beans(e.g., If you need to create a bean that holds user information, you must create a separate bean for each user.) | Stateless beans |
이제 마지막으로 PostConstruct와 PreDestroy에 대해 알아보자.
이번 기반 클래스의 이름은 PrePostAnnotationsContextLauncherApplication이라고 하자.
그리고 SomeClass, SomeDependency라는 이름의 클래스를 만들어보자. SomeClass는 SomeDependency 인스턴스를 멤버변수로 가진다.
@Component
class SomeClass {
private SomeDependency someDependency;
public SomeClass(SomeDependency someDependency) {
super();
this.someDependency = someDependency;
System.out.println("All dependencies are ready~!");
}
}
@Component
class SomeDependency {
}
우리가 지금까지 많이 봐왔던 코드라고 할 수 있다.
그런데 여기서 우리가 초기화 과정의 일부로 의존성에서(멤버변수에서)특정 메소드를 호출하고자 한다면 어떻게 해야 할까? 이때 우리는 @PostConstruct를 사용한다. 사용 방법은 다음과 같다.
@Component
class SomeClass {
private SomeDependency someDependency;
public SomeClass(SomeDependency someDependency) {
super();
this.someDependency = someDependency;
System.out.println("All dependencies are ready~!");
}
@PostConstruct
public void initialize() {
someDependency.getReady();
}
}
@Component
class SomeDependency {
public void getReady() {
System.out.println("Some Logic using SomeDependency");
}
}
위와 같이 어떤 메소드에 @PostConstruct 어노테이션을 붙이면, Spring은 의존성을 연결한 다음 @PostConstruct 어노테이션을 찾아 해당 어노테이션이 붙어있는 메소드를 호출한다.
@PostConsturct 어노테이션은 초기화를 수행하기 위해 의존성 주입이 완료된 후 실행해야 하는 메소드에서 사용하며, 이 메소드는 클래스를 사용하기 전에 호출되어야 한다. 즉, 다른 Bean이 이 Bean을 사용할 수 있게 되기 전에 이 메소드가 호출된다.
만약 우리가 어플리케이션을 종료하기 전에 어떤 작업을 수행하고 싶다면 이때는 @PreDestroy를 사용할 수 있다.
@PreDestroy
public void cleanup() {
System.out.println("Clean Up!");
/*
Before the bean is deleted from the container, before it is deleted from the application context
If you want to perform some behavior, you can use the PreDestroy annotation
*/
}
@PreDestroy어노테이션은 인스턴스를 삭제하는 과정 중에 있음을 알려주는 콜백 알림으로 트리거로 하여 메소드를 사용한다. 이를 통해 일반적으로 리소스를 해제하거나, 활성화된 연결을 닫는데 사용한다.
'강의 > Java Spring Boot' 카테고리의 다른 글
CDI, XML 겉핣기 (0) | 2024.07.29 |
---|---|
Sterotype Annotation (0) | 2024.07.29 |
Spring Framework 의존성 주입의 다양한 유형 (0) | 2024.07.25 |
Spring Framework를 사용하여 Java 객체(Bean)을 생성하기 (0) | 2024.07.24 |
Java Spring Framework 시작하기 (1) | 2024.07.15 |