본문 바로가기

Backend/Spring

Spring Boot의 의존성 관리 및 Bean 생성 과정

안녕하세요. 이번 포스팅에서는 SpringBoot가 어떻게 자동적으로 필요한 Dependencies를 받아오는지, 필요한 Bean들을 생성하는지에 대한 포스팅을 진행해보려 합니다.

 

 

Dependencies 관리

만약에 저희가 maven project로 다음의 dependency만 추가한 뒤, package를 실행해 보면 어떠한 일이 벌어질까요?

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>

 

보시는 바와 같이 정말 많은 의존성 패키지들이 추가됩니다. 

 

심지어 저희는 spring-boot-starter-web이나 spring-boot-starter-test의 버전조차 명시하지 않았는데 이렇게 자동으로 필요한 의존성들이 로딩됩니다.

 

pom.xml을 보시면

 

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.5</version>
    </parent>

 

이러한 spring-boot-starter-parent가 설정되어있는 것을 볼 수 있습니다.

 

이 spring-boot-starter-parent를 살펴보시면

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    
    ***
    <artifactId>spring-boot-dependencies</artifactId>
    ***
    
    <version>2.5.5</version>
  </parent>
  ... 이하 생략
</project>

 

다시 spring-boot-dependencies 라는 xml 파일을 부모 의존성으로 가지고 있는 것을 볼 수 있습니다.

 

이 spring-boot-dependencies라는 xml파일에 저희가 필요한 모든 의존성에 대한 버전 정보가 기록되어있습니다.

 

이런 식으로 정의가 되어있습니다. 따라서 저희는 이 spring-boot-dependencies에 있는 의존성을 명시하게 되면, 별도의 설정이 없게 되면 적힌 버전에 따라서 로딩이 되는 것입니다.

 

(따라서 이 파일에 존재하지 않는 라이브러리에 대해서는 버전을 명시해주어야 합니다. 또한 설정된 버전 말고 다른 버전을 사용하시고 싶으면, 직접 버전 추가를 해 주셔야 합니다.)

 

이렇게 되면 어떠한 장점이 있을까요?

 

개발자가 해야 할 일이 줄어듭니다.

 

가령, 만약에 저희가 사용 중인 써드파티 라이브러리의 버전이 더 이상 지원이 안된다고 생각해보면, 저희는 라이브러리의 버전을 변경해주어야 합니다. 하지만 기존의 스프링 라이브러리와 호환이 잘 되는지 안되는지 직접 버전마다 변경을 해보며 확인해야 합니다...

 

이 과정을 묶어서 하나의 파일로 제공해주기 때문에 개발자는 이러한 과정에 필요한 노력이 줄어들게 됩니다. 

 

 

그러면 내가 사용하는 이 라이브러리가 버전 관리를 해주는 라이브러리인지 확인하려면 직접 확인해보아야 할까요?

 

사실 intelliJ를 사용하시는 분이라면, 왼쪽 화살표를 클릭하시면 spring boot dependencies가 관리해주는 버전 정보를 알 수 있습니다. 따라서 저 문양이 확인되시면 버전 관리가 이루어지고 있는 라이브러리라고 생각하시면 됩니다.

 

아니라면, 직접 루트 파일에 가셔서 확인해보실 수 있습니다.

 

Auto Configuration

저희가 SpringBoot Project를 처음 생성하면 다음과 같은 코드가 생성됩니다.

 

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

 

이 상태에서 저희는 Application의 main 메서드만 실행하여도 웹 애플리케이션을 생성할 수 있습니다.

 

그 이면에는 다양한 일들을 Spring framework에서 제공해주기 때문입니다.

 

위에 적었던 Dependencies 관리는 물론이고, Tomcat이라는 Web Application Server도 띄워줍니다. Tomcat의 IOC container에서는 Spring Application에 필요한 Bean들을 자동으로 생성해줍니다.

 

@SpringBootApplication은 그러면 어떠한 동작을 하길래, Bean들을 생성해줄까요?

 

@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 = EnableAutoConfiguration.class
    )
    Class<?>[] exclude() default {};

    @AliasFor(
        annotation = EnableAutoConfiguration.class
    )
    String[] excludeName() default {};

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "basePackages"
    )
    String[] scanBasePackages() default {};

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

    @AliasFor(
        annotation = ComponentScan.class,
        attribute = "nameGenerator"
    )
    Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

 

 

보면 굉장히 복잡해 보이는데, 가장 중요한 Annotation은 다음과 같습니다.

@SpringBootConfiguration

@ComponentScan

@EnableAutoConfiguration

 

위에서부터 @SpringBootConfiguration은 흔히 사용하는 @Configuration과 비슷한 기능을 합니다.

 

다만 @SpringBootConfiguration은 스프링 부트 애플리케이션의 Configuration을 제공한다는 것을 명시하기 위해 사용한다고 합니다. 실제로도 SpringBootConfiguration은 Configuration을 포함하고 있기 때문에 기능상으로는 차이가 없고 다만, SpringBootConfiguration은 자동적으로 어디에 위치하는지를 파악하기 쉽게 만들어준다 합니다.

 

https://www.baeldung.com/springbootconfiguration-annotation

 

위 링크를 보시면 둘의 차이점에 대해서 서술하고 있습니다.

 

이제 실제로 Bean 등록에 필요한 어노테이션을 살펴보겠습니다.

 

실제로 SpringBoot는 두 단계에 나누어 Bean을 생성합니다.

@ComponentScan

@EnableAutoConfiguration

 

이렇게 두 단계로 나누는데요. Component Scan은 해당 class가 존재하는 루트 package로부터 하위 package에 있는 @Component 어노테이션을 찾아서 모두 생성합니다.

예를 들어 @Component, @Configuration, @Service, @Controller와 같은 어노테이션들이 빈으로 생성됩니다.

 

하지만 저희가 WebApplication을 생성하는데 필요한 Bean들을 모두 명시해서 생성하지는 않습니다. 따라서 웹 애플리케이션이 실행되는데 필요한 상당한 양의 Bean들이 자동적으로 생성되어 IOC container안에 생성되어야 합니다.

 

이 과정이 AutoConfiguration입니다.

 

 

SpringBoot autoconfigure라는 프로젝트에서 Spring Web Application에 필요한 Meta정보들을 가지고 있습니다. 파일 중에 spring.factories라는 파일이 존재하는데, 여기에 Auto Configure에 필요한 파일들이 명시되어있습니다.

 

 

 

이 EnableAutoConfiguration이라는 Key 값을 가지고 해당 위치로 온 다음에 하위에 필요한 Bean들을 모두 생성해줍니다. 정확히 말하면 "모두"는 아닙니다. 해당 파일을 각각 살펴보시면, Conditional이 붙어있는데 즉 생성하는 조건이 맞으면 생성하고 아니면 생성하지 않습니다.

 

오늘은 이렇게 Spring의 자동 설정 및 의존성 관리에 대해 알아보았습니다.

 

*저의 글에 대한 피드백이나 지적은 언제나 환영합니다.