0. 들어가기에 앞서


 이번 장의 주제는 스프링의 주요 기능이다.

 IoC(Inversion of Control. 제어의 역전)은 스프링 프레임워크의 심장부라고 할 수 있다. IoC 컨테이너는 POJO(Plain Old Java Object. 오래된 방식의 단순 자바 객체)를 구성하고 관리하는데, 스프링 프레임워크의 가장 중요한 의의가 이 POJO로 자바 애플리케이션을 개발하는 것이므로 스프링의 주요 기능은 대부분 IoC 컨테이너 안에서 POJO를 구상하고 관리하는 일과 연관되어있다.


* '빈(bean)' = 'POJO 인스턴스'

* '컴포넌트(component)' = 'POJO 클래스'



1. 자바로 POJO 구성하기


목적

스프링 IoC 컨테이너에서 어노테이션을 붙여 POJO 관리하기.


해결책

 @Configuration, @Bean을 붙인 자바 구성 클래스를 만들거나, @Component, @Repository, @Service, @Controller 등을 붙인 자바 컴포넌트를 구성하여 POJO 클래스를 설계한다.

 IoC 컨테이너는 이렇게 어노테이션(annotation)을 붙인 자바 클래스를 스캐닝하여 어플리케이션의 일부인 것 처럼 POJO 인스턴스/빈을 구성한다.


예제

 순차번호 생성기(다목적 시퀀스 생성기) 어플리케이션 개발을 위해 SequenceGenerator를 다음과 같이 구현한다.

 Sequence는 prefix, suffix, initial 세가지 프로터피를 가지고, getSequence() 메소드를 호출하면 이 세가지 값이 조합된 시퀀스가 리턴된다.


SequenceGenerator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.apress.springrecipes.sequence;
 
import java.util.concurrent.atomic.AtomicInteger;
 
public class SequenceGenerator {
 
    private String prefix;
    private String suffix;
    private int initial;
    private final AtomicInteger counter = new AtomicInteger();
 
    public SequenceGenerator() {
    }
 
    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
 
    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
 
    public void setInitial(int initial) {
        this.initial = initial;
    }
 
    public String getSequence() {
        String builder = prefix +
                initial +
                counter.getAndIncrement() +
                suffix;
        return builder;
    }
}
cs


 이제 @Configuration, @Bean 어노테이션을 이용하여 자바 POJO를 생성하는 클래스 SequenceGeneratorConfiguration을 다음과 같이 구현한다.


SequenceGeneratorConfiguration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.appress.springrecipes.sequence.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import com.apress.springrecipes.sequence.SequenceGenerator;
 
//@Configuration : 이 클래스가 구성클래스임을 알림
@Configuration
public class SequenceGeneratorConfiguration {
    //@Bean : @Configuration 어노테이션이 달린 클래스의 빈(Bean) 인스턴스 정의부
    @Bean
    public SequenceGenerator sequenceGenerator() {
        SequenceGenerator seqgen = new SequenceGenerator();
        seqgen.setPrefix("30");;
        seqgen.setSuffix("A");
        seqgen.setInitial(100000);
        
        return seqgen;
    }
}
 
cs



 스프링은 @Configuration이 달린 구성 클래스를 보면 일단 그 안의 빈(Bean)인스턴스 정의부, 즉 @Bean을 붙인, 빈 인스턴스를 생성해 반환하는 메서드를 찾는다.


 구성 클래스의 메소드에 @Bean을 붙이면 빈이 생성되는데 @Bean의 name 속성값을 지정해 빈 이름을 지정해 줄 수 있다.(ex : @Bean(name="mys1") -> 생성되는 빈 이름은 mys1으로 생성됨.). name 속성값을 지정해주지 않으면 원본 메소드 이름과 같은 이름의 빈이 생성된다.


 

 다음 단계로 어노테이션을 붙인 자바 클래스를 스캐닝하기 위해 IoC 컨테이너를 인스턴스화해야한다.


 스프링에서 제공하는 IoC 컨테이너는 기본 구현체인 Bean Factory, 그리고 이와 호환되는 고급 구현체인 Application Context 두가지가 있다.


 둘은 각각 빈 팩토리, 어플리케이션 컨텍스트에 접근하기위한 인터페이스다. 그리고 Application Context는 BeanFactory의 하위 인터페이스라서 호환성이 보장된다.


 * Application Context는 기본 기능에 충실하면서도 Bean Factory보다 발전된 기능을 가지고 있으니, 리소스 제약을 받는 상황이 아니라면 Application Context를 사용하는게 좋다.


 ApplicationCotext는 인터페이스이므로, 사용하려면 구상 클래스(concrete class)가 필요하다. 스프링에서 지원하는 ApplicationContext의 몇가지 구상체 중에서, AnnotationConfigApplicationContext가 가장 최근 작품이고, 유연하기 때문에 사용이 권장된다.


 이제 Main클래스를 통해 시퀀스 생성기를 실행하는 것을 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.apress.springrecipes.sequence;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
import com.appress.springrecipes.sequence.config.SequenceGeneratorConfiguration;
 
public class Main {
    public static void main(String[] args){
        //ApplicationContext를 구현한 구상 클래스들 중 AnnotationConfigApplicationContext 사용
        ApplicationContext context = new AnnotationConfigApplicationContext(SequenceGeneratorConfiguration.class);
        
        //getBean() 메소드로 구성 클래스에 선언된 빈을 가져옴
        SequenceGenerator generator = context.getBean(SequenceGenerator.class);
        
        System.out.println(generator.getSequence());
        System.out.println(generator.getSequence());
    }
}
cs


2. POJO 클래스에 @Component를 붙여 DAO 빈 생성하기


 지금까지는 구성클래스 값을 하드코딩해서 스프링 빈을 인스턴스화했다.

 실제로 POJO는 대부분 DB나 유저 입력을 이용해 인스턴스화한다. 이번에는 더 현실적인 시나리오로, Domain 클래스와 DAO(Data Access Object)를 이용해 POJO을 생성한다.

 DB를 실제로 구현하진 않고, DAO 클래스에 하드코딩할 것이다. (이런 구조가 추후에 많이 사용된다.)


 앞에 나온 시퀀스 생성기에 도메인 클래스, DAO 패턴을 적용하기 위해 클래스 구조를 바꿔야한다. 

 우선 Sequence 도메인 클래스를 만든다.


Sequence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.apress.springrecipes.sequence;
 
public class Sequence {
    private String id;
    private final String prefix;
    private final String suffix;
    
    public Sequence(String id, String prefix, String suffix) {
        this.id = id;
        this.prefix = prefix;
        this.suffix = suffix;
    }
    
    public String getId() {
        return id;
    }
    
    public String getPrefix() {
        return prefix;
    }
    
    public String getSuffix() {
        return suffix;
    }
}
 
cs



 다음은 DB 데이터 액세스를 처리하는 DAO 인터페이스와 이를 구현한 구상 클래스를 보자.

 

 SequenceDao

1
2
3
4
5
6
7
8
9
10
package com.apress.springrecipes.sequence;
 
public interface SequenceDao {
    //DB 테이블에서 sequenceId로 POJO나 Sequence 객체를 로드함
    Sequence getSequence(String sequenceId);
    
    //다음 시퀀스 값을 얻어옴
    int getNextValue(String sequenceId);
}
 
cs


SequenceDaoImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.apress.springrecipes.sequence;
 
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
 
import org.springframework.stereotype.Component;
 
@Component("sequenceDao")
public class SequenceDaoImpl implements SequenceDao {
    private final Map<String, Sequence> sequences = new HashMap<>();
    private final Map<String, AtomicInteger> values = new HashMap<>();
    
    //생성자에서 데이터베이스를 하드코딩으로 구현함
    public SequenceDaoImpl() {
        sequences.put("IT"new Sequence("IT""30""A"));
        values.put("IT",  new AtomicInteger(10000));
    }
    
    public Sequence getSequence(String sequenceId) {
        return sequences.get(sequenceId);
    }
    
    public int getNextValue(String sequenceId) {
        AtomicInteger value = values.get(sequenceId);
        return value.getAndIncrement();
    }
}
 
cs



 SequenceDaoImpl 클래스에 @Component("sequenceDao")를 붙이면 스프링은 이 클래스를 이용해 POJO를 생성한다. @Component에 넣은 값("sequenceDao")으로 빈 이름으로 할당된다.

 @Component는 스프링이 발견할 수 있게 POJO에 붙이는 범용 어노테이션이다. 스프링에는 Persistent(영속화), Service(서비스), Presentation(표현)의 세 레이어가 있는데, @Repository, @Service, @Controller가 각각 이 세 레이어를 가리키는 어노테이션이다.


 POJO의 쓰임새가 명확하지 않을 땐 그냥 @Com[onent를 붙여도 되고, 특정 용도에 맞게 쓰려면 구체적으로 명시하는 편이 좋다.(ex: @Repository는 발생한 예외를 DataAccessException으로 감싸 던지기 때문에 디버깅시 유리함)


스캐닝 커스터마이징

 기본적으로 스프링은 @Configuration, @Bean, @Component, @Repository, @Service, @Controller가 달린 클래스를 모두 감지하는데, 필터를 적용하여 스캐닝 과정을 커스터마이징하면 원하는 어노테이션만 골라서 컨텍스트에 포함시키거나 제외할 수 있다.

 모든 패키지를 스캐닝하면 시동 과정이 쓸데없이 느려 질 수 있어서 유용하다.


 다음 예시와 같이 필터를 적용 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.apress.springrecipes.sequence.config;
 
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
 
@Configuration
@ComponentScan(
        includeFilters = { 
                @ComponentScan.Filter(
                        type = FilterType.REGEX, 
                        pattern = {"com.apress.springrecipes.sequence.*Dao""com.apress.springrecipes.sequence.*Service" }
                }, 
        excludeFilters = {
                @ComponentScan.Filter(
                        type = FilterType.ANNOTATION, 
                        classes = {org.springframework.stereotype.Controller.class }
                }
        )
 
public class SequenceGeneratorConfiguration {
 
}
 
cs



 스프링에서 지원하는 필터 표현식은 네종류다. 

- annotation : 필터 대상 어노테이션 타입 지정

- assignable : 필터 대상 클래스/인터페이스 지정

- regex : 정규 표현식을 이용한 클래스 매칭으로 대상 클래스 지정

- aspectj : AspectJ 포인트컷 표현식으로 클래스를 매칭하여 대상 클래스 지정


 위 예시에서는 regex, annotation 표현식을 이용해 필터링을 적용하였다.

 includeFilters에서 com.appress.springrecipes.sequence.*Dao/*Service 형태의 이름을 가진 클래스를 포함하도록 했기 때문에, 어노테이션이 달려있지 않아도 스프링이 자동 감지한다.

 excludeFilters에서는 Controller 클래스를 지정해 제외시켰다.


 테스트는 다음의 코드로 진행한다.


 Main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.apress.springrecipes.sequence;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext("com.apress.springrecipes.sequence");
        
        SequenceDao sequenceDao = context.getBean(SequenceDao.class);
        
        System.out.println(sequenceDao.getNextValue("IT"));
        System.out.println(sequenceDao.getNextValue("IT"));
    }
}
 
cs



3. 레퍼런스

스프링 5 레시피(4판) - 한빛 미디어




블로그 이미지

서기리보이

,