본문 바로가기
Spring

[Spring] 의존성 주입(Dependency Injection) - (2) 어노테이션 기반 설정

by Amy IT 2022. 8. 21.

 

목차

     

    어노테이션을 사용한 빈 설정 방법

    스프링 프레임워크에서 빈을 설정하는 방법으로 XML 기반 설정, 자바 기반 설정 외에 자바 어노테이션(Annotaion, @) 기반 설정 방법이 있습니다. 자바에서 어노테이션은 코드 사이에 주석처럼 쓰이며 특별한 의미, 기능을 수행하도록 하는 기술입니다. 즉, 프로그램에 추가적인 정보를 제공해주는 메타 데이터라고 볼 수 있습니다. 어노테이션 기반 설정 방법은 @Component 같은 마커 어노테이션(Marker Annotation)이 부여된 클래스를 탐색해서(Component Scan) DI 컨테이너에 빈을 자동으로 등록하는 방법입니다.

     

    Component Scan 설정

    지정한 패키지의 하위 클래스를 탐색한 후 DI 컨테이너에 객체를 생성하여 등록합니다.

     

    방식 예시
    XML 방식  <context:component-scan base-package="패키지명"></context:component-scan>
    Annotation 방식 @ComponentScan(basePackages = {"패키지명"})

     

    XML 방식을 사용하기 위해서는 XML 파일의 namespace context를 체크한 후, 다음의 코드를 추가해 줍니다.

    <context:component-scan base-package="패키지명"></context:component-scan>

    <context:component-scan> 태그는 지정된 패키지내 클래스를 검색하여 자동으로 빈을 등록합니다. 여기에는 <context:annotation-config> 태그의 기능이 포함되는데, 이는 XML 파일에 이미 등록되어 있는 빈을 어노테이션 기반으로 설정하기 위해 어노테이션을 스캔하고 활성화하는 기능을 수행합니다. 

     

    Component Scan 대상 어노테이션

    이미지 출처&nbsp;https://velog.io/@_koiil/Spring-Component%EC%99%80-Repository-Service-Controller

    지정된 패키지내 모든 클래스들 중 다음의 특정 어노테이션이 선언된 클래스의 빈을 자동으로 생성 및 등록하게 됩니다. @Service, @Repository, @Controller 어노테이션은 모두 @Component의 특수한 형태로서 보다 구체적인 용도를 위해 사용합니다. 이 때문에 @Component 어노테이션으로 모든 컴포넌트 클래스를 지정할 수 있지만, 용도에 맞게 각 역할을 명시적으로 구분하여 사용하는 것이 좋습니다.

     

    어노테이션 설명
    @Component 스프링에서 관리되는 모든 컴포넌트에 대한 일반 스테레오 타입(generic stereotype)이다.
    @Service 비즈니스 로직과 관련된 코드가 있는 Service 클래스에 사용한다.
    @Repository DB에 접근하는 코드가 있는 DAO 또는 Repository 클래스에 사용한다. @Repository를 사용하면 persistence layer의 예외를 자동으로 스프링의 예외로 translate해 주는 장점이 있다.
    @Controller Spring MVC 패턴에서 Controller 역할을 한다고 명시하기 위해 사용한다.
    @Configuration 설정 정보를 작성하는 Configuration 클래스에 사용하며 @Bean 어노테이션을 사용하여 빈을 정의하는 메소드가 포함될 수 있다.

     

    @Component 사용 예시

    applicationContext.xml

    자동으로 스캔 대상이 되도록 할 패키지 경로를 지정해 줍니다. 해당 패키지 안에 있는 모든 클래스들 중 @Component 어노테이션이 지정된 클래스가 스캔 대상이 됩니다.

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
    <context:component-scan base-package="com.dto"></context:component-scan>
    
    </beans>

     

    Product.java

    package com.dto;
    
    public interface Product {
    
    }

     

    Store.java

    클래스 선언 부분에 @Component를 설정해줌으로써 스프링 컨테이너는 해당 클래스를 bean으로 생성하고 관리하게 됩니다. @Component(value="이름")으로 Bean의 이름을 지정할 수 있고, 지정하지 않으면 컨테이너가 자동으로 클래스 이름의 첫글자를 소문자로 변경하여 이름을 설정해 줍니다.

    package com.dto;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class Store {
    	private Product product;
    
    	//Getters and Setters
    }

     

    TestMain.java

    public class TestMain {
    	public static void main(String[] args) {
    		ApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");
    		
    		Store store = ctx.getBean("store", Store.class);
    		System.out.println(store);
    		//com.dto.Store@45b9a632
    	}
    }

    자동으로 생성 및 등록되어 있는 빈을 가져온 것을 확인할 수 있습니다.

     

     

    어노테이션을 사용한 의존성 주입 방법

    XML 설정 파일이 아닌 어노테이션을 이용해 의존성 주입을 설정할 수 있습니다.

     

    @Required

    • setter 메소드에 붙여 반드시 주입해야 하는 필수 프로퍼티로 설정합니다.
    • Spring 5.1 버전 부터 Deprecated 되었기 때문에, 반드시 주입해야 할 프로퍼티는 생성자 주입을 이용합니다.
    • 스프링 5.1이상을 사용하거나 자바 파일로 bean을 등록했을 경우 무시됩니다.

    Store.java

    @Component
    public class Store {
    	private Product product;
        
    	@Required
    	public void setProduct(Product product) {
    		this.product = product;
    	}
    }

    setter 메소드에 @Required를 사용하였으나 필요한 프로퍼티를 주입하지 않은 경우, BeanInitializationException: Property 'product' is required for bean 'store' 라는 에러 메시지가 출력됩니다.

     

     

    @Autowired

    • 객체 타입을 통해 빈 객체를 자동으로 주입합니다. 
    • 필드, 생성자, setter 메소드에 붙일 수 있습니다.
    • 필드, setter 메소드에 붙여서 사용할 경우 반드시 기본 생성자가 정의되어 있어야 합니다.
    • 필드에 붙이면 setter 메소드를 통해 주입되며 setter 메소드가 없을 경우 컴파일 과정에서 자동으로 추가됩니다.
    • 기본적으로 필수 프로퍼티로 설정되지만, @Autowired(required = false)로 필수 프로퍼티 해제가 가능합니다.
    • 동일한 타입 빈이 여러 개인 경우 이름을 기준으로 빈을 주입합니다.

    Pencil.java

    @Component
    public class Pencil implements Product{
    	
    }

     

    Store.java

    @Component
    public class Store {
    	@Autowired
    	private Product product;
    
    	@Override
    	public String toString() {
    		return "Store [product=" + product + "]";
    	}
    }

     

    TestMain.java

    public class TestMain {
    	public static void main(String[] args) {
    		ApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");
    		
    		Store store = ctx.getBean("store", Store.class);
    		System.out.println(store);
    		//Store [product=com.dto.Pencil@309e345f]
    	}
    }

    위의 예제에서 Pencil.java 클래스를 추가하여 Store 클래스에 자동 주입되도록 설정했습니다. 다른 설정을 하지 않았는데도 Product 인터페이스를 구현하고 있는 Pencil 타입 빈이 Store 빈에 자동으로 주입된 것을 확인할 수 있습니다.

     

     

    @Qualifier

    • @Autowired와 함께 사용합니다.
    • @Autowired를 통한 자동 주입 시 같은 타입의 빈이 여러 개 등록되어 있으면 예외가 발생합니다. 이때 @Qualifier를 사용하면 특정 빈을 주입하도록 설정할 수 있습니다.

    Desk.java

    @Component
    public class Desk implements Product {
    
    }

    위의 예제에서 Product 인터페이스를 구현하는 Desk 클래스를 선언하고 다시 메인을 실행해 보면, NoUniqueBeanDefinitionException: No qualifying bean of type 'com.dto.Product' available: expected single matching bean but found 2: desk,pencil 라는 에러 메시지가 출력되는 것을 확인할 수 있습니다. 이는 @Autowired 어노테이션을 사용해 Product 타입으로 자동 주입이 되도록 설정하였는데, 동일한 Product 타입의 빈이 여러 개 있기 때문에 발생하는 문제입니다.

     

    이때 다음과 같이 @Autowired와 함께 @Qualifier 어노테이션을 사용하여 정확히 어떤 bean을 사용할지 빈의 id를 지정하여 특정 의존 객체를 주입할 수 있도록 할 수 있습니다.

     

    Store.java

    @Component
    public class Store {
    	@Autowired
    	@Qualifier(value = "desk")
    	private Product product;
    
    	@Override
    	public String toString() {
    		return "Store [product=" + product + "]";
    	}
    }

     

    TestMain.java

    public class TestMain {
    	public static void main(String[] args) {
    		ApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");
    		
    		Store store = ctx.getBean("store", Store.class);
    		System.out.println(store);
    		//Store [product=com.dto.Desk@25d250c6]
    	}
    }

    "desk"라는 id의 빈을 주입하도록 설정했기 때문에, Pencil 빈이 아닌 Desk 빈이 자동 주입된 것을 확인할 수 있습니다.

     

     

    @Resource

    • 필드명과 동일한 이름의 id를 가진 빈을 주입합니다.
    • 필드명과 빈의 id가 다르면 @Resource(name="id")으로 주입할 빈을 지정할 수 있습니다.
    • @Autowired + @Qualifier와 유사한 기능을 수행합니다. 
    • 이름으로 빈을 찾지 못한 경우 타입을 기준으로 빈을 주입합니다.

    Store.java

    @Component
    public class Store {
    	//@Resource
    	@Resource(name = "desk")
    	private Product product;
    
    	@Override
    	public String toString() {
    		return "Store [product=" + product + "]";
    	}
    }

    @Resource 어노테이션을 사용하면 필드명과 동일한 id를 가진 빈을 주입합니다. 그런데 여기에서는 product라는 이름을 가진 빈이 등록되어 있지 않은데 Product 타입을 가진 빈도 하나가 아닌 여러 개이기 때문에,  NoUniqueBeanDefinitionException: No qualifying bean of type 'com.dto.Product' available: expected single matching bean but found 2: desk,pencil 라는 에러가 발생합니다. 따라서 @Resource 어노테이션 사용시 name 속성을 이용해 빈의 id를 지정하여 해당 id를 가진 빈을 자동 주입하게 설정합니다. 

     

    TestMain.java

    public class TestMain {
    	public static void main(String[] args) {
    		ApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");
    		
    		Store store = ctx.getBean("store", Store.class);
    		System.out.println(store);
    		//Store [product=com.dto.Desk@1fe20588]
    	}
    }

    "desk"라는 id의 빈을 주입하도록 설정했기 때문에, desk 빈이 주입된 것을 확인할 수 있습니다.

     

     

    @Value

    • 특정 값을 주입할 때 사용합니다.
    • 대표적인 용도는 자바코드 외부의 리소스나 환경정보 설정값을 사용하는 경우입니다.

    Store.java

    @Component
    public class Store {
    	@Value(value = "Emart")
    	private String storeName;
    
    	@Override
    	public String toString() {
    		return "Store [storeName=" + storeName + "]";
    	}
    }

     

    TestMain.java

    public class TestMain {
    	public static void main(String[] args) {
    		ApplicationContext ctx = new GenericXmlApplicationContext("classpath:applicationContext.xml");
    		
    		Store store = ctx.getBean("store", Store.class);
    		System.out.println(store);
    		//Store [storeName=Emart]
    	}
    }

     

     

     

    참고

     

    댓글