티스토리 뷰

4) Java Config를 이용한 설정

Java config를 이용한 설정을 위한 어노테이션

@Configuration 스프링 설정 클래스를 선언하는 어노테이션
@Bean bean을 정의하는 어노테이션
@ComponentScan @Controller, @Service, @Repository, @Component 어노테이션이 붙은 클래스를 찾아 컨테이너에 등록
@Component 컴포넌트 스캔의 대상이 되는 애노테이션 중 하나로써 주로 유틸, 기타 지원 클래스에 붙이는 어노테이션
@Autowired 주입 대상이되는 bean을 컨테이너에 찾아 주입하는 어노테이션

 

Java Config를 이용해 설정하기

  • xml파일에서 만들었던 것을 어노테이션과 자바 config를 이용하여 만들어 보겠습니다.
  • ApplicationConfig.java라는 자바 config파일을 만듭니다. 자바 파일에서 가장 먼저 하셔야 될 일은 클래스 위에 @Configuration이라는 어노테이션을 붙여 config파일이라는 것을 알려야 합니다.
  • 그 다음 bean을 등록합니다. bean @Bean이라고 하는 어노테이션을 사용합니다.
  • public Car car(Engine e)라는 해당 메서드를 호출하게 되면 Car 객체를 만들고 (Car c = new Car();)
    Car
    객체의 setEngine()이라는 메서드에 주입받은 엔진을 넣어서 (c.setEngine(e))
    Car
    객체를 리턴해주는 (return c;)   형태로 되어 있습니다.
  • 엔진을 생성하기 위해 public 메서드를 만드는데 return 타입이 Engine이고 이름이 engine() 인 메서드를 생성합니다. (public Engine engine(){})
    그런 다음에 new Engine() 해서 생성한 엔진 객체를 리턴하면 됩니다. (return new Engine();)
  • @Configuration 은 스프링 설정 클래스라는 의미를 가집니다. 자바Config로 설정을 할 클래스 위에는 @Configuration가 붙어 있어야 합니다.
  • 다시 한번 정리하면 지금 @로 시작하는 것들은 어노테이션입니다. 이런 어노테이션은 jdk 5부터 지원되기 시작해서 그것보다 낮은 버전에서는 사용할 수 없습니다. 사전적인 의미는 주석이라는 뜻이지만 자바 어노테이션은 특수한 의미를 부여하는 역할을 수행합니다. 이런 특수한 의미는 컴파일 시에, 혹은 런타임 시에 해석이 될 수 있습니다.
  • Spring은 설정을 위해서 다양한 어노테이션을 제공합니다. 그 중 방금 사용한 @configuration Spring 설정 클래스라는 의미를 가지는 클래스이고, ApplicationContext를 구현한 객체인 AnnotationConfigApplicationContext는 나중에 자바 config 클래스를 읽어서 IoC DI를 적용하게 됩니다. 이때 AnnotationConfigApplicationContext 설정 파일 중에 @bean이라는 어노테이션이 붙어 있는 메서드들을 자동으로 실행하여 해당 메서드의 리턴 객체들을 기본적으로 싱글턴으로 관리합니다.

 

ApplicationConfig.java

package kr.or.connect.diexam01;

import org.springframework.context.annotation.*;

 

@Configuration

public class ApplicationConfig {

           @Bean

           public Car car(Engine e) {

                      Car c = new Car();

                      c.setEngine(e);

                      return c;

           }

          

           @Bean

           public Engine engine() {

                      return new Engine();

           }

}

 

  • 이번에는 이 설정 파일을 읽어서 실제 실행을 시켜주는 자바 파일을 하나 만들어 보겠습니다.
  • ApplicationContextExam03.java를 생성해 주시고 main메서드를 넣습니다. 하는 방법은 xml로 설정하던 때와 비슷합니다. ApplicationContext에 레퍼런스를 변수와 함께 지정하고 Car를 가져와서 run()을 시킵니다. 다만 앞의 내용과 다른 것은 ClassPathXmlApplicationContext가 아닌 AnnotationConfigApplicationContext를 사용한다는 것입니다. 그리고 이것이 설정을 가지고 있는 클래스를 읽어들여야 하므로 인자에는 classpath가 아닌 자바 config파일을 .class형태로, 클래스로 넣습니다.
  • bean을 만들어 내는 공장인 AnnotationConfigApplicationContext가 생성될 때 파라미터인 config한테 등록한 설정을 읽어들여서 공장을 만들었을 것이고 bean들을 이미 다 생성하여 갖고 있을 겁니다. 그리고 사용자가 요청했을 때 그에 알맞은 결과를 반환할 것입니다.
  • getBean()의 파라미터에 메서드 이름을 설정했을 경우에는 오타가 나기도 쉽고, 형변환하는 것도 매번 귀찮습니다이럴 경우에는 파라미터로 문자열("car") 대신 요청하는 클래스 타입(Car.class)을 넣어주셔도 좋습니다파라미터로 요청하는 class 타입으로 지정 가능합니다.
    Car car = (Car) ac.getBean(Car.class);
  • getBean()의 파라미터에 클래스를 넣으면 ApplicationContext가 관리하는 객체 중에서 Car라는 클래스가 있으면 무조건 알아서 반환을 해주기 때문에 에러가 나지 않습니다.
  • 그리고 ApplicationContext는 ApplicationConfig에서 파라미터가 필요없는 bean 생성 메서드를 먼저 실행하고 반환받은 객체를 관리합니다그런 다음 파라미터에 생성된 객체들과 같은 타입이 있는 객체가 있을 경우에 해당 객체를 파라미터로 전달해서 아까 생성하지 못한 객체를 생성합니다. getBean()으로 객체를 호출하지 않아도 무조건 생성합니다. 만약 파라미터로 지정된 객체가 없다면 에러가 발생합니다.

ApplicationContext 객체 생성 시 등록된 객체 자동 생성
engine 객체 제거 시 에러 발생 화면

 

ApplicationContextExam03.java

package kr.or.connect.diexam01;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

 

public class ApplicationContextExam03 {

 

public static void main(String[] args) {

  ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);

 

  Car car = (Car)ac.getBean("car");

  car.run();

                     

           }

}

 

  • 다른 어노테이션을 사용해보겠습니다. 이번에는 ApplicationConfig2라는 클래스를 만들고 이전과 마찬가지로 @configuration 어노테이션을 설정합니다.
  • 그 다음에 @ComponentScan이라고 하는 것을 추가합니다. 이게 뭐냐면 인자로 지정된 패키지안의 클래스들을 등록하라는 의미입니다. 사용자가 일일이 알려주지 않아도 config가 어노테이션 붙어있는 것들을 찾아내서 등록을 하도록 하는 것이 @ComponentScan입니다@ComponentScan을 수행하게 할 때는 파라미터에다가 반드시 패키지 명을 알려주셔야 합니다. @ComponentScan("kr.or.connect.diexam01") 이렇게 알려줘야 합니다. 패키지를 지정하지 않으면 전체를 스캔하므로 굉장히 광범위하기 때문에 @ComponentScan에 정확한 패키지를 알려주시는 게 중요해요. 물론 하위 패키지도 검색하니 정확한 패키지명을 알려줘야 합니다.
  • 이때 @ComponentScan에서 읽어들이는 어노테이션은 컨트롤러, 서비스, 리포지토리, 컴포넌트입니다. 이런 어노테이션이 붙어있는 클래스들을 찾아가지고 모두 bean으로 등록을 시켜줍니다.
    다시 말해, @ComponentScan 어노테이션은 파라미터로 들어온 패키지 이하에서 @Controller, @Service, @Repository, @Component 어노테이션이 붙어 있는 클래스를 찾아 메모리에 몽땅 올려줍니다.

 

ApplicationConfig2.java

package kr.or.connect.diexam01;

import org.springframework.context.annotation.*;

 

@Configuration

@ComponentScan("kr.or.connect.diexam01")

public class ApplicationConfig2 {

}

 

  • 이제 Componentscan이 어떻게 동작하는지 알아보기 위해서 Car 클래스와 Engine 클래스 위에다가 @Component라는 것을 한번 붙여보겠습니다. Car와 Engine 클래스 선언 코드 위에 @Componenet를 적어줍니다.
  • 그리고 이때 Car라는 클래스에서 setEngine()이라는 메서드도 지웁니다. 대신에 Engine v8이라는 필드, 여기에다가 @Autowired라고 하는 어노테이션을 붙입니다. 이 어노테이션은 Engine 타입의 객체가 생성된 게 있으면 자동으로 이 필드에 주입하라고 명령하는 의미입니다. @Autowired가 알아서 해주기 때문에 굳이 setter 메서드는 없어도 괜찮습니다.

 

Engine.java

package kr.or.connect.diexam01;

 

import org.springframework.stereotype.Component;

 

@Component

public class Engine {

           public Engine() {

                      System.out.println("Engine 생성자");

           }

          

           public void exec() {

                      System.out.println("엔진이 동작합니다.");

           }

}

 

Car.java

package kr.or.connect.diexam01;

 

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

 

@Component

public class Car {

           @Autowired

           private Engine v8;

          

           public Car() {

                      System.out.println("Car 생성자");

           }

          

           public void run() {

                      System.out.println("엔진을 이용하여 달립니다.");

                      v8.exec();

           }

}

 

  • 이번에는 수정된 어노테이션을 읽어들이는 ApplicationContext 객체를 만들어 보겠습니다. ApplicationExam04 클래스를 생성하겠습니다. ApplicationContext를 생성하고 이번에는 ApplicationConfig2.class를 사용합니다.
  • 실행시키기 전에 잠깐 보면 ApplicationConfig.java에 오류가 하나 나있습니다. 현재 ApplicationConfig.java도 있고 ApplicationConfig2.java도 있는데 둘 다 어노테이션이 붙어있기 때문에 둘 다 모두 설정으로 봅니다. ApplicationConfig.java의 setEngine() 하는 부분에 에러가 발생하였습니다. 아까 setEngine() 메서드 제거해서 이 부분에 에러가 발생한겁니다. 실행을 위해 삭제합니다.
  • @ComponentScan을 config에 설정하면 지정 범위 내의 다른 config의 @Bean까지 bean으로 생성합니다.
  • @Component는 id처럼 이름을 정해줄 수도 있습니다. 기본적으로 id는 클래스 이름의 첫 글자를 소문자로 바꾼 이름을 기본으로 합니다.
  • ...더보기
    문제는 getBean()에서 호출할 때 파라미터로 클래스를 적는 방법과 id를 적는 방법이 있는데 만약 동일한 bean에 id를 다르게 준다면 (그러니까 Car 클래스에 @Component("car2")를 설정하고 ApplicationConfig에 @Bean public Car car(...... 이런 식으로 id를 다르게 준다면) getBean()의 파라미터로 클래스를 넣으면 에러가 발생합니다. 
    Exception in thread "main" org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'kr.or.connect.diexam01.Car' available: expected single matching bean but found 2: car2,car

    다르게 주면 오직 문자열로만 bean을 호출해야 합니다.

    반면 같게 주면 문자열이나 클래스 둘 다 사용할 수 있습니다.


    확실하지 않지만 @ComponenetScan은 @Component, @Controller, @Service, @Repository 밖에 못 찾는다고 하는데 @Bean 도 검색을 하는 것 같습니다. ApplicaionConfig2에 @ComponentScan을 설정하고 ApplicaionConfig의 메서드를 getBean()을 이용하여 호출할 수 있으며, 메서드의 @Bean을 지웠을 때는 호출을 하지 못합니다.

    아래 화면의 car는 car 클래스에 @Component를 설정한 것이고 car2는 이전에 설정한 @Bean을 지우고 실행했을 때의 화면입니다. 

 

 ApplicationContextExam04.java

package kr.or.connect.diexam01;

 

import org.springframework.context.ApplicationContext;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

 

public class ApplicationContextExam04 {

 

public static void main(String[] args) {

ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig2.class);

                        

Car car = ac.getBean(Car.class);

car.run();

                     

}

}

Spring에서 사용하기에 알맞게 @Controller, @Service, @Repository, @Component 어노테이션이 붙어 있는 객체들은 ComponentScan을 이용해서 읽어들여 메모리에 올리고 DI를 주입하도록 하고, 이러한 어노테이션이 붙어 있지 않은 객체는 @Bean어노테이션을 이용하여 직접 생성해주는 방식으로 클래스들을 관리하면 편리합니다.

 

  • ComponentScan을 이용하면 @Controller, @Service, @Repository, @Component 어노테이션이 붙어 있는 객체들을 스캔합니다. 각각의 bean에다가 붙이는 어노테이션인데 bean이 실제 이 객체가 하는 일에 따라서 어노테이션을 다르게 붙여주는 겁니다.
  • 그래서 그런 네 가지 어노테이션이 붙어있는 객체들을 @ComponentScan이라는 것을 이용하면 해당 객체들을 읽어내서 메모리에 올리고 DI를 주입하도록 합니다. 그리고 이런 어노테이션이 붙어있지 않은 객체들은 @Bean이라는 어노테이션을 이용해서 직접 생성해주는 방식으로 클래스를 관리할 수 있습니다.
  • 이렇게 CompnonetScan만 다 하면 굉장히 편한데 일부러 뭐 하러 bean을 이렇게 등록하는 방법들을 배웠을까요? 아까 설명했듯이 ComponentScan은 약속된 어노테이션(controller, service, repository, component)이 붙어있는 것들만 읽어옵니다.
  • 그런데 여러분이 나중에 Spring JDBC를 쓴다든가 다른 라이브러리가 갖고있는 객체들을 사용한다든가 이랬을 때는 우리가 그 라이브러리를 열어서 어노테이션을 붙일 수는 없으니 그랬을 때는 이렇게 bean을 등록하는 방법을 이용하서 해당 bean을 등록하면 편리하게 쓸 수 있을 겁니다. 이렇게 자바 config를 이용해서 IOC DI를 설명해보았습니다.

생각해보기

  • 다루는 빈(Bean)이 많아질수록 xml로 설정하는 것과 @ComponentScan, @Component, @Autowired를 이용하는 것 중 어떤 것이 유지보수에 좋을 것 같습니까?
  • @AutoWired Field, Constructor, Setter Method 에 사용할 수 있습니다. 각각의 방식에 장단점에 대해서 더 생각해보세요.

참고 자료

[참고링크] Spring JavaConfig Reference Guide
https://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/

[참고링크] Field Dependency Injection Considered Harmful
http://vojtechruzicka.com/field-dependency-injection-considered-harmful/

 

'부스트코스 웹 프로그래밍 > 3. 웹 앱 개발: 예약서비스 1' 카테고리의 다른 글

8. Spring JDBC - BE (2)  (0) 2019.08.04
8. Spring JDBC - BE (1)  (0) 2019.08.04
7. Spring Core - BE (3)  (0) 2019.08.03
7. Spring Core - BE (2)  (0) 2019.08.03
7. Spring Core - BE (1)  (0) 2019.08.03
Comments