study-toby-spring / dongheon.shin

MIT License
0 stars 0 forks source link

@Autowired vs @Resource vs @Inject #1

Open XDXO opened 7 years ago

XDXO commented 7 years ago

개요

대략적으론 알지만 자세히는 몰랐던 내용 중 하나로 항상 검색하면 나오는 블로그 게시글을 거의 그대로 퍼 온 내용을 기반으로 한다. 업무하면서 대부분 객체 주입을 위해 @Autowired를 무감각하게 사용하다보니 그렇겠거니 했었는데 이번 기회에 개념과 동작 방식, 예제로 정리를 해보려고 한다.

구분

Class Package Wiring Strategy
@Autowired org.springframework.beans.factory.annotation 타입 우선 - Spring 전용
@Inject javax.inject 타입 우선 - 자바 범용
@Resource javax.annotation 이름 우선

@Autowired, @Inject

타입 기반으로 객체를 주입한다. 주어진 타입이 빈으로 등록되어 있다면 자동으로 연결지어 준다. 하지만 다음 상황에 대해 이해하고 주입을 사용해야 올바르게 기능을 활용할 수 있을 것이다.

public class Shape { ... }

public class Triangle extends Shape { ... }
public class Rectangle extends Shape { ... }
public class Square extends Rectangle { ... }

다음의 상속 계층을 가지는 도메인이 있다고 가정하고 아래처럼 빈으로 등록하자.

<beans ... >

    <bean class="Shape"/>
    <bean class="Triangle"/>
    <bean class="Rectangle"/>
    <bean class="Square"/>

</beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/context/application-context.xml")
public class ShapeTest {

    @Autowired
    private Shape shape;

    @Test
    public void shouldReturnShapeType() {
        assertThat(shape.getClass().getSimpleName(), is("Shape"));
    }
}

@Autowired를 사용하면 우선 타입을 검사해서 주입할 수 있는 대상을 확인한다. 그런데 @AutowiredShape뿐만 아니라 Shape의 구현 타입을 모두 찾는다. 만약 주입할 수 있는 대상이 하나라면 다음과 같은 예외를 발생시킨다.

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'Shape' available: expected single matching bean but found 4: Shape#0,Triangle#0,Rectangle#0,Square#0

한편 Shape형 변수에 주입할 수 있는 빈이 하나라면 그것이 ShapeShape의 구현체이든 주입이 가능하다. 여러 구현체가 이미 빈으로 등록된 경우, @Qualifier(name ="...")로 한정할 수 있다.

@Resource

@Bean(name = "...") 혹은 <bean name="..."/>로 빈을 등록하면 @Qualifier(name ="...") 혹은 식별자가 없는 경우에는 변수의 이름과 일치하는 빈을 찾아 주입한다. 만약에 이름으로 빈을 검색하는데 실패했다면 타입으로 검사한다. 함수에 @Bean(name = "...")으로 객체를 제공하는 경우, name이 생략되었다면 함수의 이름을 사용하며 만약 이름을 부여했을 때 함수의 이름과 동일하다면 예외가 발생한다. @Resource@Autowired와 마찬가지로 이름이 맞지 않는다면 타입으로 검사하기 때문에, 익명으로 인해 주입해야 할 타입 식별이 어려운 상황이라면 동일하게 에러를 발생시킨다.

<beans ... >

    <bean class="Shape"/>
    <bean name="shape" class="java.lang.Object"/>

</beans>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/context/application-context.xml")
public class ShapeTest {

    @Resource
    private Object shape;

    @Test
    public void shouldReturnShapeType() {
        assertThat(shape.getClass().getSimpleName(), is("Object"));
    }
}

위의 예제에서 볼 수 있듯이, @Resource가 이름을 보고 먼저 빈을 주입하기 때문에 Shape 타입이 아닌 Object 타입의 빈 객체를 가져오므로 아래 테스트가 통과한다.

id vs name

id는 이 빈 객체를 참조하기 위한 유일한 식별자이며, name은 콤마나 세미콜론, 공백으로 구분하여 여러 별칭을 부여할 수 있다는 것이 큰 차이점이다.

Bean Name Generator

@Qualifier(name ="...") 등으로 이름을 제한하는 경우에는 제한할 이름이 필요한데 @Component를 사용한 경우는 AnnotationBeanNameGenerator, 그 외의 경우는 DefaultBeanNameGenerator를 사용하여 충돌이 나지 않도록 이름을 부여한다.

public class AnnotationBeanNameGenerator implements BeanNameGenerator {

    @Override
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        if (definition instanceof AnnotatedBeanDefinition) {
            String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
            if (StringUtils.hasText(beanName)) {
                // Explicit bean name found.
                return beanName;
            }
        }
        // Fallback: generate a unique default bean name.
        return buildDefaultBeanName(definition, registry);
    }

}
public class DefaultBeanNameGenerator {
    public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
        return BeanDefinitionReaderUtils.generateBeanName(definition, registry);
    }
}

public class BeanDefinitionReaderUtils {

    public static String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
        return generateBeanName(beanDefinition, registry, false);
    }

    public static String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry, boolean isInnerBean) throws BeanDefinitionStoreException {
        String generatedBeanName = definition.getBeanClassName();
        if(generatedBeanName == null) {
            if(definition.getParentName() != null) {
                generatedBeanName = definition.getParentName() + "$child";
            } else if(definition.getFactoryBeanName() != null) {
                generatedBeanName = definition.getFactoryBeanName() + "$created";
            }
        }

        if(!StringUtils.hasText(generatedBeanName)) {
            throw new BeanDefinitionStoreException("Unnamed bean definition specifies neither 'class' nor 'parent' nor 'factory-bean' - can't generate bean name");
        } else {
            String id = generatedBeanName;
            if(isInnerBean) {
                id = generatedBeanName + "#" + ObjectUtils.getIdentityHexString(definition);
            } else {
                for(int counter = -1; counter == -1 || registry.containsBeanDefinition(id); id = generatedBeanName + "#" + counter) {
                    ++counter;
                }
            }

            return id;
        }
    }
}

예제

객체 주입 예제 링크

XDXO commented 7 years ago

@Autowired가 실제로 프록시 객체를 반환하는지 확인할 것