Framework/Spring

[Spring] Classpath와 Component가 뭘까?

KOOCCI 2022. 8. 2. 00:05

목표 : 스프링의 Classpath와 Component를 설명할 수 있다.


현재 진행하고 있는 문서의 위치는 다음과 같다.

조금 생소하다면 생소하고, 많이 봤으면서도 설명하기 어려웠던 개념이 나오기에, 잘 정리하도록 해보자.

 

Classpath

먼저, Classpath가 무엇인지 부터 알도록 하자.

java는 빌드 후에 실행파일이 나오게 된다. (javac 로 컴파일 이후에 Java Byte Code인(JVM이 이해할 수 있는 언어) .class 파일이 나오는 것 정도는 알아야 한다)

즉, Classpath는 이 컴파일된 파일의 경로를 가리키게 된다.

classes 폴더

우리가 흔히, Spring을 빌드하고 나면, target 폴더에 결과물이 남는데, 이 때 classes 폴더가 바로 해당 파일의 경로가 될 것이다.

 

Classpath Scanning

이전 포스팅에서는 Annotation기반으로 소스코드 상에서 Metadata를 제공하는 방법을 알아보았다.

그럼에도 사실 이전까지 bean에 대한 기본적인 정의는 XML을 통해 제공해 주입시켜 주어야만 했다. (Autowired를 한다고 해도, Bean이 XML에 정의가 되어야 가능했다)

그럼 이제 자연스럽게 어떻게 Bean들을 소스코드 상에서 넣어줄 수 있을지가 궁금해진다.

그 방법이 바로 Classpath Scanning이다.

 

Java에서는 Classpath 경로 이후의 클래스 파일, 리소스 파일들을 읽을 수 있다.

즉, Classpath 경로 이후의 관리할 Bean을 @Component Annotation을 통해 Scanning할 수 있다.

앞서, 명시적으로 Bean을 XML에 등록해야했고, @Bean Annotation을 method에 주입해서 사용했는데, 그게 아니라, Class Level에 @Component라는 Annotation을 넣어주면서, Bean으로 넣어주라는 명령을 할 수 있다.

 

 

@Component

@Component를 주의깊게 들여다 보자.

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

먼저 Runtime에 동작한다는 점이 눈에 띄며, Indexed라는 Annotation도 있다.

그리고 그 설명이 다음과 같다.

Indicates that an annotated class is a "component". Such classes are considered as candidates for auto-detection when using annotation-based configuration and classpath scanning.

주석이 달린 class가 Component임을 나타낸다. 또한 annotation으로 configuration 되고 자동 detecting되어 classpath scanning이 된다는 내용이 나온다.

 

그리고 Marker InterfaceRepository, Service, Controller, ClassPathBeanDefinitionScanner 들이 있음을 볼 수 있다.

 

더보기

@Retention : 어노테이션의 지속시간을 의미한다. RUNTIME은 RUNTIME 시 계속 지속된다는 것(.class에 존재)을 말하며, SOURCE 는 Compile 시에만 있으며, Byte Code에는 버려지고, CLASS는 class load때 버려지며 bytecode-level post-processing 때 유용하게 사용되는 기본값이다.

@Indexed : 스프링의 스테레오타입임을 나타낸다.

stereotype

 Marker Interface : 일반적인 인터페이스와 동일하지만 사실상 아무 메소드도 선언하지 않고 의미만 전달하는 인터페이스

 

@Repository

자동으로 Bean으로 등록될 Component이며, DAO 처럼 DataBase에 Access 시 사용할 Bean일 때 설정한다.

 

@Service

자동으로 Bean으로 등록될 Component이며, Besiness 로직을 담당하는 Bean일 때 설정한다.

더보기

@Bean VS @Component

@Bean Annotation은 method에만 사용할 수 있다.

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean { ... }

 그에 비해, @Component는 TYPE (Class, interface (including annotation type), or enum declaration)이 Target으로 등록되어 있다.

 

즉, 둘 사이에는 사용할 수 있는 Target이 달라, 용도가 다르다.

@Component는 개발자가 직접 개발한 클래스에 쓰이게 되며, @Bean은 외부로부터 주입받아야 하지만, 직접 수정이 어려운 사항 (Configuration에서의 DataSource 등)을 주입할 때 사용하게 된다.

명시적으로 패키지명과 클래스명으로 하는 것이 아닌, 특정 Classpath 이하에 있는 @Component로 등록된 클래스들은 bean으로 등록될 것이라고 자동 Scanning 해주는 것이다.

결국 XML 설정 등이 줄어드는 효과를 가질 수 있다.

 

Automatically Detecting Classes and Registering Bean Definitions

그럼 어떻게 자동으로 찾는 것일까? 그 절차에 대해 알아보자.

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    // implementation elided for clarity
}

Spring은 자동으로 위 2개와 같은 스테레오타입의 클래스를 감지하고 해당 BeanDefinition 인스턴스를 ApplicationContext에 등록할 수 있다.

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
    // ...
}

그리고, 이런 클래스를 자동으로 감지하고 Bean을 등록하려면 @Configuration 에 @ComponentScan이 추가되어야 한다.

이 때, basePackages 는 두 클래스의 공통된 상위 패키지여야 한다. (혹은 각 클래스의 상위 패키지를 쉼표, 세미콜론, 공백 등으로 구분하여 등록해야 한다)

 

이를 XML로 하고 싶다면, 다음과 같이 적용할 수 있다.

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>

위 예시에서 annotation-config 설정이 빠진 것을 볼 수 있다.

<context:annotation-config/>

이는 @componentScan을 설정했을 때 생략이 가능하다.

 

AnnotationConfigApplicationContext

기존에 예시를 들 때마다, ApplicationContext에 대해 다음과 같이 선언했다.

ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");

그러나, AnnotationConfigApplicationContext를 설정하면 XML을 전혀 쓰지 않을 수 있다.

위 문서의 Constructor를 보면, componentClass 혹은 basePackage를 설정할 수 있음을 알 수 있다.

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.test.classpath");

Filter

Component Scan을 할 때, Component 등록에 대한 Filter를 적용할 수 있다.

크게 2 가지 종류로 나뉘어 진다. (includeFilters, excludeFilters)

Filter의 타입으로는 annotation, assignable, aspectj, regex, custom 이 있다.

@Slf4j
@Configuration
@ComponentScan(basePackageClasses = Main.class,
excludeFilters = @Filter(type=FilterType.ASSIGNABLE_TYPE, classes = Test.class))
public class AppConfig {
    ConfigurableApplicationContext context = new AnnotationCOnfigApplicationContext(Main.class);
    Test test = context.getBean(Test.class);
    log.info("" + test);
    context.close();
}

위와 같이 설정한다면, 에러메세지가 나올 것이다.  Test라는  Bean이 exclude 되었기 때문이다.

@Slf4j
@Configuration
@ComponentScan(basePackageClasses = Main.class,
excludeFilters = @Filter(type=FilterType.REGEX, pattern = "com.test.Test"))
public class AppConfig {
    ConfigurableApplicationContext context = new AnnotationCOnfigApplicationContext(Main.class);
    Test test = context.getBean(Test.class);
    log.info("" + test);
    context.close();
}

이번에는 REGEX를 사용했고, 역시 동일하게 에러가 나온다.

includeFilters도 사용법은 동일하니 알아두도록 하자.

 

Wrap Up

꽤 중요한 내용들을 다룬 것으로 보인다.

Annotation의 활용을 조금씩 깨우치면서 왜 이런 내용을 정리하고 있는지 생각해보자.

 

난 우선, Spring이 무엇인지, IoC Container가 무엇인지, Bean이 무엇인지, DI가 무엇인지, 그리고 나아가서 Bean의 ScopeLifeCycle을 정리하고 Annotation Configuration까지 정리하였다.

 

점진적으로 제일 처음 알아보고자 했던 2가지에 가까워지고 있다.

  1. 스프링은 무엇을 도와주는가?
  2. 스프링의 사용법은 어떻게 되는가?

Annotation을 정리하는 것은 스프링의 사용법이며, 확실히 이해하고 쓰기 위함의 준비 과정이다.

XML로 정리하던 구축방법에서 Annotation으로 설정하는 방법까지 넘어왔으며, Spring이 Annotation으로 선언된 Component들을(Bean들을) Scanning 한다는 것을 알 수 있었다.

 

아직 갈길이 멀고, AOP 와 같은 관점의 정리도 필요할 것이다.

차근히 하나씩 진행해보도록 하자.