Spring 04 : 컴포넌트 스캔, 라이프사이클

LeeWonjin·2022년 8월 2일

2022 백엔드스터디

목록 보기
14/20

환경
윈도우즈 10 / 이클립스 2022-06 (4.24.0) / Java SE 18 / 메이븐 3.8.6 / spring-context 5.3.22

교재
책 : 초보 웹 개발자를 위한 스프링5 프로그래밍 입문 챕터 5~6
영상 : 예제로 배우는 스프링 입문(개정판)

컴포넌트 스캔

스프링 설정 코드에 @Bean 메소드를 직접 기술하지 않고 어떤 클래스를 Bean객체로 등록하는 방법

Bean객체를 수동 등록하는 경우

컴포넌트 스캔 기능 없이 설정파일에 수동으로 Bean객체를 등록하는 경우 아래와 같이 구성 가능

  • 구현 코드 : Gorani.java, Person.java, GoraniDao.java
  • 설정 파일 : pom.xml, AppCtx.java
  • 진입점 : Main.java
[코드 - 컴포넌트 스캔 - 1]

// pom.xml
// ** dependency 중 aspectjrt, aspectjweaver는 이후 등록 제외할 컴포넌트를 지정할 때 사용하기 위해 미리 넣어놓았음
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>in.wonj</groupId>
  <artifactId>chap05-practice</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <dependencies>
  	<dependency>
  		<groupId>org.springframework</groupId>
  		<artifactId>spring-context</artifactId>
  		<version>5.3.22</version>
  	</dependency>
  	<dependency>
  		<groupId>org.aspectj</groupId>
  		<artifactId>aspectjrt</artifactId>
  		<version>1.9.7</version>
  	</dependency>
  	<dependency>
  		<groupId>org.aspectj</groupId>
  		<artifactId>aspectjweaver</artifactId>
  		<version>1.9.7</version>
  	</dependency>
    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
      <version>1.3.2</version>
    </dependency>
  </dependencies>
  
  <build>
  	<plugins>
  		<plugin>
  			<groupId>org.apache.maven.plugins</groupId>
  			<artifactId>maven-compiler-plugin</artifactId>
  			<version>3.10.1</version>
  			<configuration>
  				<release>18</release>
  			</configuration>
  		</plugin>
  	</plugins>
  </build>
</project>

// AppCtx.java
package chap05practice;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
@Configuration
public class AppCtx {
	@Bean
	public GoraniDao goraniDao() { return new GoraniDao(); }
	@Bean
	public Person person() { return new Person(); }
}

// Main.java
package chap05practice;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
	public static ApplicationContext ctx;
	public static void main(String[] args) {
		ctx = new AnnotationConfigApplicationContext(AppCtx.class);
		
		Person person = ctx.getBean("person", Person.class);
		person.buy(0, "young");
		person.buy(11, "ilil");
		person.buy(53, "osam");
		person.sell(53);
		person.say();
		// [Gorani] #0 : young
		// [Gorani] #11 : ilil
	}
}

// GoraniDao.java
package chap05practice;
import java.util.HashMap;
public class GoraniDao {
	private HashMap<Integer, Gorani> list = new HashMap<Integer, Gorani>(); // Database
	
	public void add(Gorani gorani) { 
		list.put(gorani.getId(), gorani);
	}
	
	public void remove(int id) {
		list.remove(id);
	}
	
	public void printAll() {
		(list.values()).forEach(item -> System.out.println(item));
	}
}

// Gorani.java
package chap05practice;

public class Gorani {
	private int id;
	private String name;
	
	public Gorani(int id, String name) {
		this.id = id;
		this.name = name;
	}
	
	public int getId() { return id; }
	public String getName() { return name; }
	public void setName(String name) { this.name = name; }
	
	@Override
	public String toString() {
		return "[Gorani] #" + id + " : " + name;
	}
}

// Person.java
package chap05practice;
import org.springframework.beans.factory.annotation.Autowired;
public class Person {
	@Autowired
	GoraniDao goraniDao;
	
	public void buy(int id, String name) {
		goraniDao.add(new Gorani(id, name));
	}
	public void sell(int id) {
		goraniDao.remove(id);
	}
	public void say() {
		goraniDao.printAll();
	}
}

Bean객체 자동등록

아래와 같이 두 어노테이션을 붙여 자동 등록

  • 자동등록 대상 클래스에 @Component
  • 설정파일의 클래스에 @ComponentScan(basePackages={"chap05practice"}) (컴포넌트를 검색할 패키지 지정)

@ComponentScan어노테이션 인수 중 이름이 복수형(-s, -es) 인 것은 값으로 한 개 또는 배열을 취함
셋 모두 컴파일 가능한 문장

  • @ComponentScan(basePackages = "something")
  • @ComponentScan(basePackages = {"something"})
  • @ComponentScan(basePackages = {"something", "another"})

아래 코드의 경우 Person클래스를 자동 등록
등록할 Bean객체의 이름을 별도로 지정하지 않았음
→ 이 경우 Bean 이름 = 클래스 식별자에서 첫글자를 소문자로 바꾼 것

e.g.

클래스 이름Bean 이름
Personperson
JavaSpringStudyjavaSpringStudy
lowercaseStartlowercaseStart
[코드 - 컴포넌트 스캔 - 2]
** [코드 - 컴포넌트 스캔 - 1] 에서 일부 변경

// AppCtx.java
package chap05practice;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan; // import 추가
@Configuration
@ComponentScan(basePackages = {"chap05practice"}) // 어노테이션 추가
public class AppCtx {
	@Bean
	public GoraniDao goraniDao() { return new GoraniDao(); }
	// Person 등록 부분 삭제
}

// Person.java
package chap05practice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; // import 추가
@Component // 어노테이션 추가
public class Person { // 자동 등록된 Bean객체의 이름은 "person"
	@Autowired
	GoraniDao goraniDao;
	
	public void buy(int id, String name) {
		goraniDao.add(new Gorani(id, name));
	}
	public void sell(int id) {
		goraniDao.remove(id);
	}
	public void say() {
		goraniDao.printAll();
	}
}

Bean객체 자동 등록시 이름 지정

@Component("새로운 이름")과 같이 Bean객체의 이름 지정 가능

[코드 - 컴포넌트 스캔 - 3]
** [코드 - 컴포넌트 스캔 - 1] 에서 일부 변경
// AppCtx.java 
package chap05practice;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
@Configuration
@ComponentScan(basePackages = {"chap05practice"})
public class AppCtx {
	@Bean
	public GoraniDao goraniDao() { return new GoraniDao(); }
}

// Person.java
package chap05practice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("componentPerson") // Bean객체 이름 지정
public class Person {
	@Autowired
	GoraniDao goraniDao;
	
	public void buy(int id, String name) {
		goraniDao.add(new Gorani(id, name));
	}
	public void sell(int id) {
		goraniDao.remove(id);
	}
	public void say() {
		goraniDao.printAll();
	}
}

// Main.java
package chap05practice;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
	public static ApplicationContext ctx;
	public static void main(String[] args) {
		ctx = new AnnotationConfigApplicationContext(AppCtx.class);
		
		// bean이름 person --> componentPerson으로 변경
		Person person = ctx.getBean("componentPerson", Person.class);
		person.buy(0, "young");
		person.buy(11, "ilil");
		person.buy(53, "osam");
		person.sell(53);
		person.say();
		// [Gorani] #0 : young
		// [Gorani] #11 : ilil
	}
}

Bean객체 자동등록 제외

@ComponentScan에 excludeFilters = { @Filter } 인수를 넘겨 자동 등록하지 않을 클래스 지정 가능

@Filter는 아래 형태로 사용

  • 정규표현식 : @Filter(type=FilterType.REGEX, pattern="정규표현식")
  • aspectJ패턴 : @Filter(type=FilterType.ASPECTJ, pattern="aspectJ패턴")
  • 어노테이션 : @Filter(type=FilterType.ANNOTATION, classes={어노테이션 클래스명.class})
  • 특정 클래스 및 자손 : Filter(type=FilterType.ASSIGNABLE_TYPE, classes={클래스명.class})
[코드 - 컴포넌트 스캔 - 4] 정규식으로 클래스 제외
// AppCtx.java 
@ComponentScan(basePackages = {"chap05practice"},
  excludeFilters = {
    @Filter(type=FilterType.REGEX, pattern="chap05practice\..*son") 
    // chap05practice 패키지 내 "son"으로 끝나는 클래스 제외 (e.g. Person클래스)
})
public class AppCtx { ... }

[코드 - 컴포넌트 스캔 - 5] aspectJ패턴으로 클래스 제외
// AppCtx.java 
@ComponentScan(basePackages = {"chap05practice"},
  excludeFilters = {
    @Filter(type=FilterType.ASPECTJ, pattern="chap05practice.*son")
    // chap05practice 패키지 내 "son"으로 끝나는 클래스 제외 (e.g. Person클래스)
})
public class AppCtx { ... }

[코드 - 컴포넌트 스캔 - 6] 특정 어노테이션 붙은 클래스 제외
// AppCtx.java 
@ComponentScan(basePackages = {"chap05practice"},
  excludeFilters = {
    @Filter(type=FilterType.ANNOTATION, classes={MyAnnotation.class})
})
public class AppCtx { ... }

// Person.java
@MyAnnotation
@Component
public class Person { ... }

[코드 - 컴포넌트 스캔 - 7] 특정 클래스 및 자손 제외
// AppCtx.java 
@ComponentScan(basePackages = {"chap05practice"},
  excludeFilters = {
    @Filter(type=FilterType.ASSIGNABLE_TYPE, classes={Person.class})
})
public class AppCtx { ... }

// Person.java
@Component
public class Person { ... }

Bean객체 이름 충돌

  • @Component로 자동 등록한 Bean 2개의 이름이 중복되는 경우
    • 예외 발생
  • @Component로 자동 등록한 Bean과 수동등록한 Bean의 이름이 중복되는 경우
    • 수동등록한 Bean만 등록
  • 같은 클래스에 대해서 다른 이름으로 각각 자동/수동 등록한 Bean
    • 2개의 Bean 등록

라이프사이클

스프링 컨테이너와 Bean객체는 아래의 라이프 사이클을 가짐

  1. 스프링 컨테이너 초기화
  2. Bean객체 생성, 의존 설정
  3. Bean객체 초기화 콜백 호출
    --- 프로그램 실행 ---
  4. Bean객체 소멸 콜백 호출
  5. Bean객체 소멸
  6. 스프링 컨테이너 종료

3번, 4번 시점에 호출되는 메소드를 정의하기 위해 세 가지 방법 사용 가능
각 방법의 코드에서 아래의 Main.java를 공통적으로 사용

package chap05practice;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

public class Main {
	public static AbstractApplicationContext ctx; 
	public static void main(String[] args) {
		ctx = new AnnotationConfigApplicationContext(AppCtx.class);
		ctx.close();
	}
}
  • InitializingBean, DisposableBean인터페이스의 afterPropertiesSet(), destroy()메소드 구현
// AppCtx.java
package chap05practice;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;

@Configuration
@ComponentScan(basePackages = {"chap05practice"})
public class AppCtx { }

// Wonjin.java
package chap05practice;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

@Component
public class Wonjin implements InitializingBean, DisposableBean{
	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("<afterPropertiesSet()> is called");
	}
	@Override
	public void destroy() throws Exception {
		System.out.println("<destroy()> is called");
	}
}
  • @Bean어노테이션의 인수 initMethod, destoryMethod의 값으로 메소드 이름 입력
// AppCtx.java
package chap05practice;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;

@Configuration
@ComponentScan(basePackages = {"chap05practice"})
public class AppCtx {
	@Bean(initMethod = "init", destroyMethod = "close")
	public Wonjin wonjin() { return new Wonjin(); }
}

// Wonjin.java
package chap05practice;
import org.springframework.stereotype.Component;

@Component
public class Wonjin{
	public void init() {
		System.out.println("init() is called");
	}
	public void close() {
		System.out.println("close() is called");
	}
}
  • @PostConstruct, @PreDestroy 어노테이션 사용
    ( java9부터 Deprecated. 사용하려면 javax.annotation-api 디펜던시 필요 )
// AppCtx.java
package chap05practice;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;

@Configuration
@ComponentScan(basePackages = {"chap05practice"})
public class AppCtx { }

// Wonjin.java
package chap05practice;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;

@Component
public class Wonjin{
	@PostConstruct
	public void init() {
		System.out.println("init() is called");
	}
	@PreDestroy
	public void close() {
		System.out.println("close() is called");
	}
}

참고

profile
노는게 제일 좋습니다.

0개의 댓글