본문 바로가기
  • 실행력이 모든걸 결정한다
Spring Series/Spring Framework

[Spring] Bean의 생명주기 / properties 파일

by 김코더 김주역 2020. 12. 22.
반응형

1. Bean의 생명주기

bean의 생명 주기에서 특정 시기에 추가적인 동작을 시키고 싶다면 어떻게 하면 될까?

 

1) 인터페이스 방법

InitializingBean, DisposableBean 인터페이스를 상속해서 afterPropertiesSet(), destroy() 메소드를 오버라이딩 하면 두 메소드는 각각 bean 초기화 과정, bean 소멸 과정에서 호출된다.
Context 클래스에서 close() 메소드로 컨테이너를 닫지 않는다면 destory() 메소드는 호출 되지 않는다.

package com.example.demo;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class Programmer implements InitializingBean, DisposableBean{
	private String name;
	private int age;
	private int codeLevel;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public int getCodeLevel() {
		return codeLevel;
	}
	public void setCodeLevel(int codeLevel) {
		this.codeLevel = codeLevel;
	}
	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("afterPropertiesSet()");
	}
	@Override
	public void destroy() throws Exception {
		System.out.println("destroy()");
	}
}


3개의 Programmer 오브젝트 생성 및 해제

 

 

2) @(Annotation) 방법

@PostConstruct, @PreDestroy 어노테이션을 각각 bean 초기화 과정, bean 소멸 과정에서 호출할 메소드 앞에 붙여주는 방법이다.
Context 클래스에서 close() 메소드로 컨테이너를 닫지 않는다면 destoryMethod() 메소드는 호출 되지 않는다.

package com.example.demo;

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

public class Programmer {
	private String name;
	private int age;
	private int codeLevel;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public int getCodeLevel() {
		return codeLevel;
	}
	public void setCodeLevel(int codeLevel) {
		this.codeLevel = codeLevel;
	}
	@PostConstruct
	public void initMethod() {
		System.out.println("initMethod()");
	}
	@PreDestroy
	public void destroyMethod() {
		System.out.println("destroyMethod()");
	}
}


3개의 Programmer 오브젝트 생성 및 해제

 

 

3) Bean의 scope

- Spring이 관리하는 bean의 관리 범위를 의미하며, 하나의 scope 안에서는 매번 같은 오브젝트를 가져온다.
- scope 속성을 생략한 bean은 기본적으로 싱글톤으로 관리가 되는데, 굳이 표시하고 싶다면 scope를 singleton으로 지정하면 된다. 싱글톤은 최대 하나만 존재할 수 있는 객체이다.

<bean id="..." class="..." scope="singleton">
	...
</bean>

또는

@Component
@Scope("singleton")
public class ... {}


- singleton 외의 scope로는 다음과 같은 종류들이 있다.

  • prototype : https://kimcoder.tistory.com/464 참고
  • request : 하나의 웹 요청 안에서 만들어지고 해당 요청이 끝날 때 제거된다.
  • session : HTTP session과 같은 존재 범위를 갖는다. 로그인 정보나 사용자의 선택 옵션을 저장해두기에 유용하며, HTTP session에 저장되는 정보를 모든 계층에서 안전하게 이용할 수 있다.
  • globalSession : Portlet에만 존재하는 글로벌 세션에 저장된다. Portlet은 재사용 가능한 웹 구성요소로서 날씨, 토론방, 뉴스같이 포탈 사용자에게 관련 정보나 지식을 보여주는 메뉴를 의미한다.
  • application : 웹 애플리케이션마다 만들어지는 서블릿 컨텍스트에 저장된다. 그래서 singleton scope와 존재 범위가 비슷하지만, 웹 애플리케이션과 애플리케이션 컨텍스트의 존재 범위가 다른 경우가 있기 때문에 singleton scope와 따로 구분한다. 그리고 singleton scope와 마찬가지로 상태를 갖지 않거나 읽기전용이 되어야 한다.

※ prototype, request, session, globalSession은 독립적인 상태를 저장해두고 사용하는 데 필요하다.

- 스프링 웹 플로우나 제이보스 씸과 같은 프레임워크를 이용하여 Scope 인터페이스를 구현한 다양한 커스텀 scope를 제공받을 수 있다.


 

2. Properties 파일

1) .properties 파일이란?

Spring 프로젝트를 생성했을 때 src/main/resources 경로에 생기는 .properties 파일의 용도는 무엇일까?

- Spring에서는 bean 설정 작업에 필요한 property 정보를 컨테이너가 관리하고 제공해주는데, 컨테이너는 지정된 정보 소스로부터 프로퍼티 값을 수집하여 이를 bean 설정 작업에 참고할 수 있게 해준다. 컨테이너가 프로퍼티 값을 가져오는 대상을 property source라고 한다.
- 기본적으로 ISO-8859-1 인코딩을 지원한다.

 

2) Environment 객체

- .properties 파일로부터 가져오는 프로퍼티 값은 컨테이너가 관리하는 Environment 오브젝트에 저장된다.
- @Autowired를 통해 필드로 주입받을 수 있다.
- Environment 객체의 getProperty() 메소드를 이용하여 프로퍼티 값을 불러올 수 있다.

예시

이 예제에서는 JAVA 소스코드상에서 properties 파일의 속성값들을 불러오고, 이 속성값들을 Bean에 넣어 외부에서 Bean 객체를 생성해보기로 한다.
src/main/resource 아래에 2개의 properties 파일을 만들어보았다.
작성법은 간단하다. "속성=속성값" 형태를 줄을 경계로 나열하면 된다.
관리자가 2명인 환경이라고 보면 좋을 듯하다.

<jooyeok.properties>

infoId_1=jooyeok
infoPw_1=07031


<lamb.properties>

infoId_2=lamb
infoPw_2=01051


<InfoConnection.java>
- EnvironmentAware 인터페이스를 상속해서 setEnvironment 메소드를 오버라이드 한 것이 특징이다.
- setEnvironment 메소드는 afterPropertiesSet 메소드보다도 일찍 호출되며, Environment 객체를 setter 메소드로 미리 지정해두는 역할을 한다.
- afterPropertiesSet 메소드에서는 setter 메소드들을 사용해서 properties 파일의 속성값들을 Bean에 적용해준다.

package com.example.demo;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;

public class InfoConnection implements EnvironmentAware, InitializingBean, DisposableBean {
	private Environment env;
	private String infoId_1;
	private String infoPw_1;
	private String infoId_2;
	private String infoPw_2;

	@Override
	public void setEnvironment(Environment env) {
		setEnv(env);
	}
	
	public Environment getEnv() {
		return env;
	}

	public void setEnv(Environment env) {
		this.env = env;
	}

	//setter, getter 생략
	
	@Override
	public void afterPropertiesSet() throws Exception {
		setInfoId_1(env.getProperty("infoId_1"));
		setInfoPw_1(env.getProperty("infoPw_1"));
		setInfoId_2(env.getProperty("infoId_2"));
		setInfoPw_2(env.getProperty("infoPw_2"));
	}
	
	@Override
	public void destroy() throws Exception {
		System.out.println("destroy()");
	}
}

 

<application.xml>

- id가 infoConnection인 InfoConnection 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="infoConnection" class="com.example.demo.InfoConnection"/>
    
</beans>

 

<DemoApplication.java>

package com.example.demo;

import java.io.IOException;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.support.ResourcePropertySource;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		
		// Part 1 - properties들을 env에 등록
		ConfigurableApplicationContext ctx = new GenericXmlApplicationContext();
		ConfigurableEnvironment env = ctx.getEnvironment();
		MutablePropertySources mps = env.getPropertySources();
		
		try {
			mps.addLast(new ResourcePropertySource("classpath:jooyeok.properties"));
			mps.addLast(new ResourcePropertySource("classpath:lamb.properties"));
			// env.getProperty("infoId_1") -> "jooyeok" 출력
		} catch (IOException e) {
			e.getStackTrace();
		}
		
		// Part 2 - env에 등록된 내용들이 저장되어 있는 infoConnection Bean을 호출
		GenericXmlApplicationContext gCtx = (GenericXmlApplicationContext)ctx;
		gCtx.load("application.xml");
		gCtx.refresh();
		InfoConnection infoConnection = gCtx.getBean("infoConnection",InfoConnection.class);
		// infoConnection.getInfoId_1(); -> "jooyeok 출력
		// infoConnection.getInfoPw_1(); -> "07031" 출력
		// infoConnection.getInfoId_2(); -> "lamb" 출력
		// infoConnection.getInfoPw_2(); -> "01051" 출력
		ctx.close();
		gCtx.close();
	}
}

※ @PropertySource("[.properties 파일]") 어노테이션을 클래스에 추가하여 리소스를 등록하는 방법도 있다.


Part 1
- ConfigurableApplicationContext는 최상위 Context 클래스이다. 이 컨텍스트 객체에서 getEnvironment() 메소드로 Environment 객체를 가져왔으면 properties 리소스들을 Environment 객체의 PropertySource에 추가한다. 이렇게 해서 Environment 객체에는 properties 파일들의 모든 속성값이 담기게 되었다.

- 최종적으로 Environment 객체의 getProperty("[속성명]") 으로 속성값을 반환하여 JAVA 소스코드상에서 properties 파일의 속성값을 불러올 수 있게 된 것이다.

- main문에서 실행되는 것이기 때문에 일단은 동작하지만, 실제 웹 환경에서는 ApplicationContextInitializer 인터페이스의 initialize 메소드를 오버라이딩하여 그 안에 part 1 코드를 작성하고, 이 컨텍스트 초기화 클래스를 web.xml에 다음과 같이 <context-param> 또는 <init-param>의 contextInitializerClasses 파라미터로 지정해주면 된다.

<context-param>
    <param-name>contextInitializerClasses</param-name>
    <param-value>MyContextInitializer</param-value>
</context-param>


Part 2
- GenericXmlApplicationContext 클래스의 load() 메소드를 사용하기 위해 ctx를 형변환하여 gCtx라는 이름으로 사용하겠다고 선언한뒤, load() 메소드로 application.xml 파일을 받아서 Bean 객체를 불러온다.
- load후에는 컨테이너 설정을 새로고침하는 refresh() 메소드를 필수적으로 호출해야 한다.

반응형

댓글