Backend/Spring

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

멍분이 2021. 10. 22. 22:35

안녕하세요. 이번 포스팅에서는 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의 자동 설정 및 의존성 관리에 대해 알아보았습니다.

 

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