티스토리 뷰

이전 Xml Config 방식에서 ComponentScan을 사용하는 방법은 다음과 같았다. 

<context:component-scan base-package="com.example.a">

applicationContext를 구성할때 이렇게 명시적으로 내가 읽어들여야하는 component들이 있는 package를 넣어줬다. 

 

하지만 Springboot에서는 Xml Config보다는 Java Config를 사용하고 @기반의 설정을 많이 한다. 아니 이 Component Scan을 하지도 않는데 알아서 잘 된다. 어떻게 된 일일까? 바로 Springboot의 핵심! @SpringBootApplication 에 답이 있다. Springboot Main Class에 있는 @SpringBootApplication를 ctrl을 누르고 눌러서 들어가보자. 

 

@SpringBootApplication

@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 {
... 생략 ...

    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
    
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

... 생략 ...

들어가보면 이런식으로 구성이 되어있다. 복잡해 보이지만 쉽게 설명을 하자면 내가 아무런 ComponentScan 관련 설정을 하지 않았다면 바로 이 @SpringBootApplication 가 정의된 곳이 base package가 되는 것이다. 그래서 처음 프로젝트 구조를 만들때 이 Springboot Main Class의 package가 매우 중요하다. 

그리고 아래에 나와있는 @AliasFor 부분에 나온 basePackages와 basePackagesClasses도 중요하다. Springboot Main Class의 위치에 구애받지 않고 내가 마음대로 ComponentScan을 할 곳을 정의할때 사용된다. 뒤의 예제를 통해 알아보도록 하겠다. 

 

ComponentScan 예제

package 구조

일단 ComponentScan 이해를 돕기 위해 샘플 프로젝트를 만들었다. 패키지의 구조를 유심히 보자. 

com.example.a 라는 패키지 밑에 Springboot의 Main Class인 AprojectApplication.java 가 있다. 그 하위 package로 test가 있고 그 안에 @Component로 정의된 ATest라는 파일이 있다. 

com.example.b 라는 패키지 밑에는 @Component로 정의된 BTest라는 파일이 있다. 

 

com.example.b.test.BTest.java

@Component
public class BTest {

    @Bean
    public String bComponentScan () {
         return "B package Component Scan Success";
    }
}

com.example.a.test.ATest.java

@Component
public class ATest {

    @Autowired
    private BTest bTest;
	
    @Bean
    public String aComponentScan () {
        String result = bTest.bComponentScan();
        return result + "\nA package Component Scan Success";
    }
}

이 파일들의 내용은 ATest 라는 곳에서 BTest에서 만든 bean을 주입받는것이 전부인 로직이다. 이 상태에서 기동을 하게 되면 아래와 같은 오류를 만날것이다. 

 

***************************
APPLICATION FAILED TO START
***************************

Description:

Field bTest in com.example.a.test.ATest required a bean of type 'com.example.b.test.BTest' that could not be found.

The injection point has the following annotations:
	- @org.springframework.beans.factory.annotation.Autowired(required=true)


Action:

Consider defining a bean of type 'com.example.b.test.BTest' in your configuration.

ATest에서 필요한 bean BTest를 찾을수 없다는 오류이다. BTest에 대해 ComponentScan이 안된것이다. 

 

이것을 되게 하려면 여러가지 방법이 있다. 하나씩 살펴보자.


1. Springboot Main Class의 package 이동

첫번째 방법은 ComponentScan의 기준점이 되는 Springboot Main Class 를 이동시키는 방법이다. 필자가 위에 예제로 만들어놓은 프로젝트는 '지양'해야 할 구조이다. '지향' 해야 하는 구조는 모든 package를 포함할 수 있는 곳에 Springboot Main Class가 위치해야 하는 것이다. 다음과 같이 말이다. 

 

package 구조

a package와 b package 모두 Component Scan이 필요한 상황이면 위처럼 com.example 바로 하위에 AprojectApplication.java 가 위치를 해야 하는것이다. 

 

 

2. @ComponentScan(basePackages ..) 사용

Springboot Main Class의 위치를 변동해서 해결을 할 수 없는 경우가 있다. 예를들면 다음과 같은 경우이다. 

package 구조

AProject는 BProject를 포함하고 있는 구조이다. 즉 BProject가 라이브러리화 되어서 A에 껴지는 상황인 것이다. 이럴때 BProject의 Bean을 가져와서 사용하고 싶은데 AProjectApplication.java는 com.example.a 라는 package 하위에 있고 주입받고 싶은 Bean은 coa.example.b 라는 package 하위에 있다. AProjectApplication.java를 어디로 옮겨도 BProject의 Component를 Scan 할수가 없다. 이럴 경우는 @ComponentScan(basePackages) 를 활용하도록 하자. 

 

Springboot Main Class

@SpringBootApplication
@ComponentScan(basePackages = {"com.example.a","coa.example.b"})
public class AprojectApplication {

기존에 Xml Config 방식에서 썼던 것과 제일 유사한 방식이다. Springboot Main Class에서 위와 같이 basePackages 안에 Component Scan 할 대상(package) 에 대해 기술해주면 된다. 

 

 

3. ComponentScan(basePackageClasses ..) 사용

2번과 거의 유사한데 조금 더 안전한 방식이다. 안전하다는것은 사람이 위처럼 package를 직접 기술하면 실수가 있을수도 있다. 하지만 클래스명으로 이 부분에 대해 기술을 하게 되면 명확히 에러를 볼 수 있으므로 실수를 줄일 수 있다. 여기서 클래스명이란 내가 아무런 클래스(인터페이스)를 만들어서 Component Scan을 할 적절한 위치에 위치시키고 '여기서부터 Scan 해' 하는 기준점이 되는 클래스를 말한다. 말로는 조금 이해가 어려울수 있으니 변한 구조를 보자. 

 

package 구조

Scan을 하고 싶은 기준점에 이 인터페이스를 위치시키면 된다. package 구조가 아닌 이곳을 기준으로 Component Scan을 수행한다. 그럼 이 인터페이스의 내용은 뭐가 들었는지 보자. 

 

AprojectBasePackage.java

public interface AprojectBasePackage {

}

아무것도 없다. 이렇게 적당한 이름으로 만들어서 Component Scan 기준점에 위치시켜 놓으면 된다. 

그리고 Springboot Main Class에서는 다음과 같이 작성해주면 된다. 

 

Springboot Main Class

@SpringBootApplication
@ComponentScan(basePackageClasses = {AprojectBasePackage.class, BprojectBasePackage.class})
public class AprojectApplication {

이런 모습으로 되기 때문에 2번보다 안전하다고 한 것이다. 이제 글 시작부분에 나온 AliasFor에 나온 basePackages와 basePackageClasses 에 대한 이해가 조금 될 것이라고 본다. 

 

 

이정도만 알고 있어도 Springboot 프로젝트에서 Component Scan 을 사용하는데 불편함은 없을것 같다. 

 

끝!

댓글
최근에 올라온 글
최근에 달린 댓글
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30