Component Scan

์Šคํ”„๋ง ๋นˆ์„ ๋“ฑ๋กํ•  ๋•Œ๋Š” ์ž๋ฐ” ์ฝ”๋“œ์˜ @Bean์ด๋‚˜ XML์˜ <bean> ๋“ฑ์„ ํ†ตํ•ด์„œ ์„ค์ • ์ •๋ณด์— ์ง์ ‘ ๋“ฑ๋กํ•  ์Šคํ”„๋ง ๋นˆ์„ ๋‚˜์—ดํ•˜์—ฌ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ๋“ฑ๋กํ•ด์•ผ ํ•  ์Šคํ”„๋ง ๋นˆ์ด ์ˆ˜์‹ญ, ์ˆ˜๋ฐฑ๊ฐœ๊ฐ€ ๋˜๋ฉด ์ผ์ผ์ด ๋“ฑ๋กํ•˜๊ธฐ๋„ ํž˜๋“ค๋ฉฐ, ์„ค์ • ์ •๋ณด๋„ ์ปค์ง€๊ณ , ๋ˆ„๋ฝํ•˜๋Š” ๋ฌธ์ œ๋„ ๋ฐœ์ƒํ•œ๋‹ค.

@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

์Šคํ”„๋ง์€ ์„ค์ • ์ •๋ณด๊ฐ€ ์—†์–ด๋„ ์ž๋™์œผ๋กœ ์Šคํ”„๋ง ๋นˆ์„ ๋“ฑ๋กํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ์Šค์บ”์ด๋ผ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

@ComponentScan : @Component ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ๋นˆ์„ ๋‹ค ๋“ฑ๋กํ•ด์ค€๋‹ค.

  • excludeFilters : ์ œ์™ธํ•  Component ์„ค์ •

  • includeFilters : ํฌํ•จํ•  Component ์„ค์ •

  • basePackages : ํƒ์ƒ‰ํ•  ๊ธฐ๋ณธ ํŒจํ‚ค์ง€ ๊ฒฝ๋กœ(์„ค์ • ์•ˆํ•œ ๊ฒฝ์šฐ ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜ ํŒจํ‚ค์ง€ ํ•˜์œ„๋กœ ์„ค์ •)

    • basePackages = {"dh0023.springcore.order", "dh0023.springcore.member"} : ์—ฌ๋Ÿฌ ์‹œ์ž‘ ์œ„์น˜ ์ง€์ •๊ฐ€๋Šฅ

  • basePackageClassses : ์ง€์ •ํ•œ ํด๋ž˜์Šค์˜ ํŒจํ‚ค์ง€๋ฅผ ํƒ์ƒ‰ ์‹œ์ž‘ ์œ„์น˜๋กœ ์ง€์ •(default : @ComponentScan ์ด ๋ถ™์€ ์„ค์ • ์ •๋ณด ํด๋ž˜์Šค ํŒจํ‚ค์ง€ ํ•˜์œ„๋กœ ์„ค์ •)

package dh0023.springcore.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

/**
 * @ComponentScan์€ @Component ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ํด๋ž˜์Šค๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•ด์ค€๋‹ค.
 * ๊ธฐ๋ณธํŒจํ‚ค์ง€๋ฅผ ์„ค์ •ํ•ด์ฃผ์ง€ ์•Š์œผ๋ฉด, ํ˜„์žฌ ํŒจํ‚ค์ง€ ํ•˜์œ„๋กœ ์„ค์ •๋œ๋‹ค.
 * ์˜ˆ์™ธํ•˜๊ณ  ์‹ถ์€ ํด๋ž˜์Šค๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ excludeFilters๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
 */
@Configuration
@ComponentScan(
        basePackages = "dh0023.springcore",
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {

}

๊ทธ๋Ÿฌ๋ฉด ์˜์กด๊ด€๊ณ„๋Š” ์–ด๋–ป๊ฒŒ ์ฃผ์ž…ํ•˜๋Š” ๊ฑธ๊นŒ? @Autowired ๋กœ ์˜์กด๊ด€๊ณ„๋ฅผ ์ž๋™์œผ๋กœ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค.

@Bean ์œผ๋กœ ์ƒ์„ฑํ•ด ์ง์ ‘ ์˜์กด๊ด€๊ณ„๋ฅผ ์„ค์ •ํ•˜๋˜ ์ฝ”๋“œ์—์„œ, @Component ์™€ @Autowired ๋งŒ์œผ๋กœ ์˜์กด๊ด€๊ณ„์™€ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋‹ค.

package dh0023.springcore.member.service;

import dh0023.springcore.member.domain.Member;
import dh0023.springcore.member.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MemberServiceImpl implements MemberService{

    /**
     * ์ƒ์„ฑ์ž DI๋ฅผ ํ†ตํ•ด ๊ตฌํ˜„ํด๋ž˜์Šค ์˜์กด์„ฑ ์ œ๊ฑฐ => ์‹คํ–‰์—๋งŒ ์ง‘์ค‘ ๊ฐ€๋Šฅ
     */
    private final MemberRepository memberRepository;

      /**
       * Autowired๋กœ ์ž๋™ ์˜์กด๊ด€๊ณ„ ์ฃผ์ž… ๊ฐ€๋Šฅ
     */
    @Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}
  • ๋ณ„๋„๋กœ ๋นˆ ์ด๋ฆ„์„ ์„ค์ •ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์—๋Š” @Component("์„ค์ •ํ•  ๋นˆ ์ด๋ฆ„") ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • @Autowired ๋ฅผ ์ง€์ •ํ•˜๋ฉด ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ํ•ด๋‹น ์Šคํ”„๋ง ๋นˆ์„ ์ฐพ์•„์„œ ์ฃผ์ž…ํ•˜๋Š”๋ฐ ์ด๋•Œ, ํƒ€์ž…์ด ๊ฐ™์€ ๋นˆ์„ ์ฐพ์•„์„œ ์ฃผ์ž…์„ ํ•œ๋‹ค.

FilterType ์˜ต์…˜

type

์„ค๋ช…

์˜ˆ

ANNOTATION

default ์–ด๋…ธํ…Œ์ด์…˜์„ ์ธ์‹ํ•ด์„œ ๋™์ž‘

type = FilterType.ANNOTATION, classes = Configuration.class

ASSIGNABLE_TYPE

์ง€์ •ํ•œ ํƒ€์ž…๊ณผ ์ž์‹ ํƒ€์ž…์„ ์ธ์‹ํ•ด์„œ ๋™์ž‘ ํด๋ž˜์Šค ์ง์ ‘ ์ง€์ •

org.example.ExampleService

ASPECTJ

AspectJ ํŒจํ„ด ์‚ฌ์šฉ

org.example..*Service+

REGEX

์ •๊ทœ ํ‘œํ˜„์‹

org.example.Default.*

CUSTOM

TypeFilter ์ด๋ผ๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์„œ ์ฒ˜๋ฆฌ

org.example.MyTypeFilter

@ComponentScan ๊ถŒ์žฅ ์œ„์น˜

@ComponentScan ๊ถŒ์žฅ ์œ„์น˜

ํŒจํ‚ค์ง€ ์œ„์น˜๋ฅผ ๋ณ„๋„๋กœ ์ง€์ •ํ•˜์ง€ ์•Š๊ณ , ์„ค์ • ์ •๋ณด ํด๋ž˜์Šค ์œ„์น˜๋ฅผ ํ”„๋กœ์ ํŠธ ์ตœ์ƒ๋‹จ์— ๋‘๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.(์Šคํ”„๋ง ๋ถ€ํŠธ๋„ ์ด ๋ฐฉ๋ฒ•์œผ๋กœ ์‹œ์ž‘)

ํ”„๋กœ์ ํŠธ ๋ฉ”์ธ ์„ค์ • ์ •๋ณด๋Š” ํ”„๋กœ์ ํŠธ๋ฅผ ๋Œ€ํ‘œํ•˜๋Š” ์ •๋ณด์ด๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ ๋ฃจํŠธ ์œ„์น˜์— ๋‘๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

    /**
     * Exclude specific auto-configuration classes such that they will never be applied.
     * @return the classes to exclude
     */
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};

    /**
     * Exclude specific auto-configuration class names such that they will never be
     * applied.
     * @return the class names to exclude
     * @since 1.3.0
     */
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
  ...
}

์Šคํ”„๋ง ๋ถ€ํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์Šคํ”„๋ง ๋ถ€ํŠธ์˜ ๋Œ€ํ‘œ ์‹œ์ž‘ ์ •๋ณด์ธ @SpringBootApplication ์•ˆ์— @ComponentScan ์ด ํฌํ•จ๋˜์–ด์žˆ์œผ๋ฉฐ, ๋ณดํ†ต ์ตœ์ƒ๋‹จ์— ํ•ด๋‹น ํด๋ž˜์Šค๊ฐ€ ์œ„์น˜ํ•ด์žˆ๋‹ค.

@ComponentScan ๋Œ€์ƒ

@Component ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค๋ฅธ ์–ด๋…ธํ…Œ์ด์…˜๋“ค๋„ ์ถ”๊ฐ€๋กœ ๋Œ€์ƒ์— ํฌํ•จ๋œ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด @Configuration ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ดํŽด๋ณด์ž.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
    //...
}

ํ•ด๋‹น ์–ด๋…ธํ…Œ์ด์…˜ ๋‚ด๋ถ€์— @Component ์–ด๋…ธํ…Œ์ด์…˜์„ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. @ComponentScan ์€ @Component ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์–ด์žˆ๋Š” ํด๋ž˜์Šค๋Š” ๋ชจ๋‘ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•˜๋ฏ€๋กœ, @Controller, @Service, @Repository ๋“ฑ๋“ฑ ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ํด๋ž˜์Šค๋„ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

  • ์–ด๋…ธํ…Œ์ด์…˜์—๋Š” ์ƒ์†๊ด€๊ณ„๋ผ๋Š” ๊ฒƒ์ด ์—†์œผ๋ฉฐ, ํŠน์ • ์• ๋…ธํ…Œ์ด์…˜์„ ๋“ค๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ ์ž๋ฐ” ์–ธ์–ด๊ฐ€ ์ง€์›ํ•˜๋Š” ๊ธฐ๋Šฅ์€ ์•„๋‹ˆ๊ณ , ์Šคํ”„๋ง์ด ์ง€์›ํ•˜๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

์ค‘๋ณต ๋“ฑ๋ก๊ณผ ์ถฉ๋Œ

์ž๋™๋นˆ๋“ฑ๋ก vs ์ž๋™ ๋นˆ๋“ฑ๋ก

์ปดํฌ๋„ŒํŠธ ์Šค์บ”์— ์˜ํ•ด ์ž๋™์œผ๋กœ ์Šคํ”„๋ง ๋นˆ์ด ๋“ฑ๋ก๋˜๋Š”๋ฐ, ๊ทธ ์ด๋ฆ„์ด ๊ฐ™์€ ๊ฒฝ์šฐ ์Šคํ”„๋ง์€ ConflictingBeanDefinitionException ์˜ˆ์™ธ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.

์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ๋Š” ๊ฑฐ์˜ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ˆ˜๋™ ๋นˆ ๋“ฑ๋ก vs ์ž๋™ ๋นˆ ๋“ฑ๋ก

@Component
public class MemoryMemberRepository implements MemberRepository {}
@Configuration
@ComponentScan(
      excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {

      @Bean(name = "memoryMemberRepository")
    public MemberRepository memberRepository() {
            return new MemoryMemberRepository();
    }
}

๊ฐ™์€ ์ด๋ฆ„์œผ๋กœ ์ˆ˜๋™๋นˆ๊ณผ ์ž๋™๋นˆ์ด ๋“ฑ๋ก๋œ ๊ฒฝ์šฐ์—๋Š”, ์ˆ˜๋™ ๋นˆ๋“ฑ๋ก์ด ์šฐ์„ ๊ถŒ์„ ๊ฐ€์ง„๋‹ค.

Overriding bean definition for bean 'memoryMemberRepository' with a different
definition: replacing

์ˆ˜๋™๋นˆ์ด ์ž๋™ ๋นˆ์„ ์˜ค๋ฒ„๋ผ์ด๋”ฉ ํ•œ๋‹ค.

์ตœ๊ทผ ์Šคํ”„๋ง ๋ถ€ํŠธ์—์„œ๋Š” ์ˆ˜๋™ ๋นˆ ๋“ฑ๋ก๊ณผ ์ž๋™ ๋นˆ ๋“œ์˜ญ์ด ์ถฉ๋Œ๋‚˜๋ฉด ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋„๋ก ๊ธฐ๋ณธ ๊ฐ’์„ ๋ฐ”๊พธ์—ˆ์œผ๋ฉฐ, ๋งŒ์•ฝ ์˜ค๋ฒ„๋ผ์ด๋”ฉ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ณ  ์‹ถ์œผ๋ฉด spring.main.allow-bean-definition-overriding=true ๋กœ ์˜ต์…˜์„ ์„ค์ •ํ•˜๋ผ๊ณ  ๊ฐ€์ด๋“œ๋ฅผ ์ฃผ๊ณ  ์žˆ๋‹ค.

Consider renaming one of the beans or enabling overriding by setting
spring.main.allow-bean-definition-overriding=true

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋กœ ํ™•์ธ

๊ธฐ๋ณธ @ComponentScan ๋นˆ๋“ฑ๋ก ํ™•์ธ

package dh0023.springcore.scan;

import dh0023.springcore.config.AutoAppConfig;
import dh0023.springcore.member.service.MemberService;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class AutoAppConfigTest {

    @Test
    void basicScan() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);

        MemberService memberService = ac.getBean(MemberService.class);

        Assertions.assertThat(memberService).isInstanceOf(MemberService.class);
    }
}
23:44:34.697 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
23:44:34.701 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
23:44:34.713 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
23:44:34.716 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
23:44:34.739 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'autoAppConfig'
23:44:34.748 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'rateDiscountPolicy'
23:44:34.749 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memoryMemberRepository'
23:44:34.750 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'memberServiceImpl'
23:44:34.858 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'memberServiceImpl' via constructor to bean named 'memoryMemberRepository'
23:44:34.860 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'orderServiceImpl'
23:44:34.865 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'memoryMemberRepository'
23:44:34.866 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'rateDiscountPolicy'

๋กœ๊ทธ๋ฅผ ๋ณด๋ฉด singleton bean์ด ๋“ฑ๋ก๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, Autowired๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ์™ธ/ํฌํ•จ ํ™•์ธ

  • MyExcludeComponent

package dh0023.springcore.scan.filter;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
  • MyIncludeComponent

package dh0023.springcore.scan.filter;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
  • BeanA

package dh0023.springcore.scan.filter;

@MyIncludeComponent
public class BeanA {
}
  • BeanB

package dh0023.springcore.scan.filter;

@MyExcludeComponent
public class BeanB {
}
  • Test

package dh0023.springcore.scan.filter;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

public class ComponentFilterAppConfigTest {

    @Test
    void filterScan() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);

        BeanA beanA = ac.getBean("beanA", BeanA.class);
        assertThat(beanA).isNotNull();

        assertThrows(
                NoSuchBeanDefinitionException.class, () -> ac.getBean("beanB", BeanB.class)
        );


    }

    @Configuration
    @ComponentScan(
            includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
            excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
    )
    static class ComponentFilterAppConfig {
    }
}

์ฐธ๊ณ 

Last updated