스프링 레시피 CH2.1 자바로 POJO 구성하기

2023. 12. 10. 21:42spring

 

제어의 역전(IoC)은 스프링 프레임워크의 심장이다.

IoC 컨테이너는 POJO를 구성하고 관리한다.

스프링 프레임워크의 가장 중요한 의의가 이 POJO로 자바 애플리케이션을 개발하는 것이다.

그러므로 스프링의 주요 기능은 대부분 IoC 컨테이너 안에서 POJO를 구성 및 관리하는 일과 연관돼 있다.

 

*이 책에서 POJO랑 빈을 같은 의미로 혼용해서 쓴다.

 

2-1 자바로 POJO 구성하기(빈 등록)

 

1. POJO 설계

2. @Configuration, @Bean을 붙여 자바 구성 클래스로 만들거나

    @Component, @Repository, @Service, @Controller 등을 붙인 자바 컴포넌트를 구성한다.

 

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

 

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() {
        StringBuilder builder = new StringBuilder();
        builder.append(prefix)
                .append(initial)
                .append(counter.getAndIncrement())
                .append(suffix);

        return builder.toString();
    }
}

 

 

- 2.1.1구성 클래스에서 @Configuration과 @Bean을 붙여 자바 POJO 생성하기

 

@Bean이 붙은 메소드에서 객체를 초기화 하고 리턴하면 IoC 컨테이너에서  POJO 인스턴스로 정의할 수 있다.

@Configuration
public class SequenceGeneratorConfiguration {

    @Bean
    SequenceGenerator sequenceGenerator() {
        SequenceGenerator seqgen = new SequenceGenerator();
        seqgen.setPrefix("30");
        seqgen.setSuffix("A");
        seqgen.setInitial(100000);

        return seqgen;
    }
}

 

- 2.1.2IoC 컨테이너를 초기화하여 애너테이션 스캐닝하기

 

애너테이션을 붙인 자바 클래스를 스캐닝하려면 IoC 컨테이너를 인스턴스화 해야한다.

스프링이 @Configuration, @Bean을 발견하고 IoC 컨테이너에 빈을 등록한다.

 

스프링은 기본 구현체인

1. 빈 팩토리와 이와 호환되는

2. 고급 구현체인 애플리케이션 컨텍스트,

 

두 가지 IoC 컨테이너를 제공한다. 구성 파일은 두 컨테이너 모두 동일하다.

 

애플리케이션 컨텍스트는 기본 기능에 충실하고 빈 팩토리보다 발전된 기능을 지니고 있으므로 리소스에 제약을 받는 상황이 아니라면 가급적 애플리케이션 컨텍스트를 사용하자!

 

ApplicationContext는 인터페이스라 사용하려면 구현체가 필요하다.

public class Main {

    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(SequenceGeneratorConfiguration.class);
    }
}

 

애플리케이션 컨텍스트를 인스턴스화한 이후에 context는 POJO 인스턴스 또는 빈에 액세스하는 창구 노릇을 한다.

 

- 2.1.3 IoC 컨테이너에서 POJO 인스턴스/빈 가져오기

 

구성 클래스에 선언된 빈을 애플리케이션 컨텍스에서 가져오려면 유일한 빈 이름을 getBean() 메서드의 인수로 호출한다.

SequenceGenerator generator2 = (SequenceGenerator) context.getBean("sequenceGenerator");

 

캐스팅을 안하고 가져올 수도 있다.

SequenceGenerator generator = context.getBean("sequenceGenerator", SequenceGenerator.class);

 

이런 식으로 POJO 인스턴스/빈을 스프링 외부에서 생성자를 이용해 여느 객체처럼 사용할 수 있다.

 

 

public class Main {

    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext(SequenceGeneratorConfiguration.class);

        SequenceGenerator generator = context.getBean("sequenceGenerator", SequenceGenerator.class);
        SequenceGenerator generator2 = (SequenceGenerator) context.getBean("sequenceGenerator");

        System.out.println(generator.getSequence());
        System.out.println(generator2.getSequence());
    }
}

=>
301000000A
301000001A

 

 

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

Domain 클래스 및 DAO를 이용해 POJO를 생성해보자

 

Sequence 도메인 클래스 만들기

public class Sequence {
    private final 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;
    }
}

 

DAO 인터페이스 만들기

(편의상 시퀀스 인스턴스 및 값은 하드코딩)

 

public interface SequenceDao {
    public Sequence getSequence(String sequenceId);
    public int getNextValue(String sequenceId);
}

========================================================================

@Component("sequenceDao")
public class SequenceDaoImpl implements SequenceDao{
    
    private final Map<String, Sequence> sequence = new HashMap<>();
    private final Map<String, AtomicInteger> values = new HashMap<>();

    public SequenceDaoImpl() {
        sequence.put("IT", new Sequence("IT", "30", "A"));
        values.put("IT", new AtomicInteger(100000));
    }

    @Override
    public Sequence getSequence(String sequenceId) {
        return sequence.get(sequenceId);
    }

    @Override
    public int getNextValue(String sequenceId) {
        AtomicInteger value = values.get(sequenceId);
        return value.getAndIncrement();
    }
}

 

SequenceDaoImpl 클래스에 @Component("sequenceDao")를 붙이면 스프링은 이 클래스를 이용해 POJO 생성.

 

@Component는 스프링이 발견할 수 있게 POJO에 붙이는 범용 애너테이션이다.

 

persistence(영속화) => @Repository

service(서비스)        => @Service

presentatioon(표현) => @Controller

 

POJO의 쓰임새가 명확하지 않을 땐 @Component를 붙여도 되지만 특정 용도에 맞는 부가 혜택을 누리려면 구체적으로 명시하는 편이 좋다.

 

- 2.1.5 애너테이션을 스캐닝하는 필터로 IoC 컨테이너 초기화하기

기본적으로 스프링은 @Configuration, @Bean, @Component, @Repository, @Service, @Controller가 달린 클래스를 모두 감지한다. 이때 스캐닝 과정을 커스터마이징해 특정 애너테이션을 붙인 POJO를 스프링 애플리케이션에 넣거나 뺄 수 있다.

 

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

annotation, assignable은 각각 필터 대상 애너테이션 타입 및 클래스/인터페이스를 지정하며

regex, aspectj는 각각 정규표현식과 AspectJ 포인트컷 포현식 클래스를 매칭하는 용도로 쓰인다.

 

예를 들어보자

 

//@Component("sequenceDao")
public class SequenceDaoImpl implements SequenceDao{

    private final Map<String, Sequence> sequence = new HashMap<>();
    private final Map<String, AtomicInteger> values = new HashMap<>();

    public SequenceDaoImpl() {
        sequence.put("IT", new Sequence("IT", "30", "A"));
        values.put("IT", new AtomicInteger(100000));
    }

    @Override
    public Sequence getSequence(String sequenceId) {
        return sequence.get(sequenceId);
    }

    @Override
    public int getNextValue(String sequenceId) {
        AtomicInteger value = values.get(sequenceId);
        return value.getAndIncrement();
    }
}
@ComponentScan(
        basePackages = "com.spring.study.sequence",
        includeFilters = {
                @ComponentScan.Filter(
                        type = FilterType.REGEX,
                        pattern = {"com.spring.study.sequence.dao.*DaoImpl",
                                   "com.spring.study.sequence.service.*Service"}
                )
        },
        excludeFilters = {
                @ComponentScan.Filter(
                        type = FilterType.ANNOTATION,
                        classes = {org.springframework.stereotype.Controller.class}
                )
        }
)
@Configuration
public class SequenceConfig {

    private final SequenceDao sequenceDao;
    public SequenceConfig(SequenceDao sequenceDao) {
        this.sequenceDao = sequenceDao;
        System.out.println(sequenceDao.getNextValue("IT"));
    }
}

 

이름에 DaoImpl이나 Service가 포함된 클래스를 감지하려면 includeFilters를 적용하면된다. 그러면 어노테이션에 주석처리가 되어있어도 스프링이 자동 감지한다.

 
public class SequenceExample {
    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext("com.spring.study.sequence.config");
    }
}
=>
100000

 

- 2.1.6 IoC 컨테이너에서 POJO 인스턴스/빈 가져오기

public class SequenceExample {
    public static void main(String[] args) {
        ApplicationContext context =
                new AnnotationConfigApplicationContext("com.spring.study.sequence");

        SequenceDao sequenceDao = context.getBean(SequenceDao.class);

        System.out.println(sequenceDao.getNextValue("IT"));
        System.out.println(sequenceDao.getNextValue("IT"));
    }
}