Framework

DAY1.05 어노테이션 기반 설정

유호야 2020. 12. 14. 23:08
반응형

5.1 어노테이션 설정 기초

대부분의 프레임워크가 그렇듯 스프링 프레임워크 역시 XML 설정이 매우 중요하다.

5.1.1 Context 네임스페이스 추가

어노테이션 설정을 추가하려면 스프링 설정 파일의 루트 엘리먼트인 <beans>에 Context 관련 네임스페이스와 스키마 문서의 위치를 등록해야 한다.  [Namespaces] 탭을 선택하고 'context' 항목 체크

applicationContext.xml 에 springframework.org/schema/context 등 주소값이 추가된다.

5.12 컴포넌트 스캔(component-scan) 설정

스프링 설정 파일에 애플리케이션에서 사용할 객체들을 <bean> 등록하지 않고 자동으로 생성하려면
<context:component-scan /> 이라는 엘리먼트를 정의해야 한다.
해당 설정을 제외한 나머지 <bean>설정은 모두 삭제하거나 주석 처리 해야 한다.

<context:component-scan base-package="polymorphism"></context:component-scan>

여기서 중요한 것은 <context:component-scan /> 엘리먼트의 base-package 속성이다.
만약 속성값을 "com.springbook.biz" 형태로 지정하면
com.springbook.biz 패키지로 시작하는 모든 패키지를 스캔 대상에 포함한다. 

5.1.3 @Component

<context:component-scan>를 설정했으면 이제 스플이 설정 파일에 클래스들을 일일이 <bean> 엘리먼트로 등록할 필요가 없다. @Component만 클래스 선언부 위에 설정하면 끝난다. 예를들어 LgTV 클래스에 대한 <bean> 등록을 XML 설정과 어노테이션 설정으로 처리하면 다음과 같다.

XML 설정

<bean class = "polymorphism.LgTV"></bean>

Annotation 설정

@Component
public class LgTV implements TV {
	public LgTV() {
    	system.out.println("===> LgTV 객체 생성");
    }
}

두 설정 모두 해당 클래스에 기본 생성자가 있어야만 컨테이너가 객체를 생성할 수 있다.
그러나 클라이언트 프로그램에서 LgTV 객체를 요청할 수는 없다. 클라이언트가 스프링 컨테이너가 생성한 객체를 요청하려면, 요청할 때 사용할 아이디나 이름이 반드시 설정되어 있어야 한다.

// 1. Spring 컨테이너를 구동한다.
AbstractApplicationContext factory =
	new GenericXmlApplicationContext("applicationContext.xml");
// 2. Spring 컨테이너로부터 필요한 객체를 요청한다.
TV tv = (TV)factory.getBean("tv");

 

XML 설정

<bean id = "tv" class = "polymorphism.LgTV"></bean>

Annotation 설정

@Component("tv")
public class LgTV implements TV {
	public LgTV() {
    	system.out.println("===> LgTV 객체 생성");
    }
}

 

5.2 의존성 주입 설정

5.2.1 의존성 주입 어노테이션

스프링에서 의존성 주입을 지원하는 어노테이션으로는 
@Autowired, @Inject, @Qualifier, @Resource가 있다.

어노테이션 설명
@Autowired 주로 변수 위에 설정하여 해당 타입의 객체를 찾아서 자동으로 할당한다.
@Inject  특정 객체의 이름을 이용하여 의존성 주입할 때 사용한다.
@Qualifier @Autowired 와 동일한 기능을 제공
@Resource @Autowired 와 @Qualifier 의 기능을 결합한 어노테이션이다.

이 중에서 @Autowired와 @Qualifier는 스프링에서 제공하지만, 나머지 어노테이션은 스프링에서 제공하지 않는다.

5.2.2 @Autowired

@Autowired 은 생성자나 메소드, 멤버변수 위에 모두 사용할 수 있다.
어디에 사용하든 결과가 같아서 상관없지만, 대부분은 멤버변수 위에 선언하여 사용한다.
스프링 컨테이너는 멤버 변수 위에 붙은 @Autowired를 확인하는 순간 해당 변수의 타입을 체크한다.
그리고 그 타입의 객체가 메모리에 존재하는지를 확인한 후에, 그 객체를 변수에 주입한다. 

그런데 만약 @Autowired가 붙은 객체가 메모리에 없다면 컨테이너가 NoSuchBeanDefinitionException을 발생시킨다.
이 메세지는 @Autowired 대상 객체가 메모리에 존재하지 않는다는 의미이다.

package polymorphism;

import ... Autowired;
import ... Component;

@Component("tv")
public class LgTV implements TV {
	@Autowired
    private Speaker speaker;
    
    public LgTV() {
    System.out.println("===> LgTV 객체 생성");
    }
    public void powerOn(){
    	System.out.println("LgTV---전원 켠다.");
    }
    public void volumeUp(){
    	speaker.volumeUp();
    }
   public void volumeDown(){
    	speaker.volumeDown();
    }

위처럼 설정하면 LgTV 클래스에는 의존성 주입에 사용했던 Setter 메소드나 생성자는 필요없다.
그리고 스프링 설정 파일 역시 <context:component-scan /> 외에는 아무런 설정도 하지 않는다.
그러나 SonySpeaker 객체가 메모리에 없으면 에러가 발생하므로 반드시 SonySpeaekr 객체가 메모리에 생성되어 있어야 한다. SonySpeaekr 객체를 생성하려면 다음과 같이 두가지 방법 중 하나를 처리해야 한다.

xml 설정

<bean id = "sony" class = "polymorphism.SonySpeaker"></bean>

Annotation 설정

@Component("sony")
public class SonySpeaker implements Speaker {
	public SonySpeaker() {
    	System.out.println("===> SonySpeaker 객체 생성")
    }
}

어떤 방법을 사용하든 상관없다. 의존성 주입 대상이 되는 SonySpeaker 객체가 메몰에 생성만 되면
@Autowired에 의해서 컨테이너가 SonySpeaker 객체를 speaker 변수에 자동으로 할당하기 때문이다.\

 

5.2.3 @Qualifier

... 

5.2.4 @Resource 

...

5.2.5 어노테이션과 XML 설정 병행하여 사용하기

스프링으로 의존성 주입을 처리할 때, XML 설정과 어노테이션 설정은 장단점이 서로 상충한다. 앞에서 살펴본 대로 XML 방식자바 소스를 수정하지 않고 XML 파일의 설정만 변경하면 실행되는 Speaker를 교체할 수 있어서 유지보수가 편하다. 하지만 XML 파일의 설정만 변경하면 실행되는 Speaker를 교체할 수 있어서 유지보수가 편하다. 하지만 XML 설정에 대한 부담 역시 존재한다. 그리고 자바 소스에 의존관계와 관련된 어떤 메타데이터도 없으므로 XML 설정을 해석해야만 무슨 객체가 의존성 주입되는지를 확인할 수 있다. 

반면에 어노테이션 기반 설정은 XML 설정에 대한 부담도 없고, 의존관계에 대한 정보가 자바 소스에 들어있어서 사용하기는 편하다. 하지만 의존성 주입할 객체의 이름이 자바 소스에 명시되어야 하므로 자바 소스를 수정하지 않고 Speaker를 교체할 수 없다는 문제가 생긴다. 이런 문제를 서로의 장점을 조합하는 것으로 해결할 수 있는데, 다음 실습으로 확인해보자.

package polymorphism;

import ... Autowired;
import ... Component;

@Component("tv")
public class LgTV implements TV {
	@Autowired
    private Speaker speaker;
    
    public LgTV() {
    	System.out.println("===> LgTV 객체 생성됨");
    }
}

에러 발생 : Speaker 타입의 객체가 메모리에 두 개(Sony / Apple) 있어서이다. 하지만 기존의 SonySpeaker, AppleSpeaker 클래스에 설정했던 @Component를 제거하여 객체가 자동으로 생성되는 것을 차단한다. 

public class SonySpeaker implements Speaker {
	public SonySpeaker() {
    	System.out.println("===>SonySpeaker 객체 생성됨");
	}
}

public class AppleSpeaker implements Speaker {
	public AppleSpeaker() {
    	System.out.println("===>AppleSpeaker 객체 생성됨");
	}
}

 

그러고  나서 둘 중에 하나만 스프링 설정 파일에 <bean> 등록하여 처리하면 된다.

<beans>
	<context:component-scan base-package = "polymorphism" />
   	<bean class = "polymorphism.SonySpeaker"></bean>
</beans>

위 설정대로라면 LgTV에 설정한 @Autowired에 의해서 SonySpeaker 객체가 의존성 주입된다.
이후에 AppleSpeaker로 교체할 때는 SonySpeaker 를 AppleSpeaker로만 수정하면 된다.

5.3 추가 어노테이션

프레젠테이션 레이어 : 사용자와의 커뮤티케이션을 담당
비즈니스 레이어 : 사용자의 요청에 대한 비즈니스 로직 처리를 담당

우리는 앞에서 @Component를 이용하여 스프링 컨테이너가 해당 클래스 객체를 생성하도록 설정할 수 있었다. 그런데 시스템을 구성하는 모든 클래스에 @Component를 할당하면 어떤 클래스가 어떤 역할을 수행하는지 파악하기 어렵다. 스프링 프레임워크에서는 클래스들을 분류하기 위해서 @Component를 상속하여 다음과 같은 세 개의 어노테이션을 추가로 제공한다.

어노테이션 위치 의미
@Service XXXServiceImpl 비즈니스 로직을 처리하는 Service 클래스
@Repository XXXDAO 데이터베이스 연동을 처리하는 DAO 클래스
@Controller XXXController 사용자 요청을 제어하는 Controller 클래스

이처럼 어노테이션을 나눈 이유는 단순히 해당 클래스를 분류하기 위해서만은 아니다. @Controller는 해당 객체를 MVC(Model-View-Controller) 아키텍쳐에서 컨트롤러 객체로 인식하도록 해주며, @Repository는 DB 연동 과정에서 발생하는 예외를 변환해주는 특별한 기능이 추가되어 있다. 이와 관련한 자세한 내용은 해당 과정에서 다루도록 할 것이다. 따라서 여기서는 단순히 클래스들을 분류하는 의미로만 이해하고 넘어가도록 하자.

 

반응형