Spring Configuration

디우·2022년 6월 25일
0

우아한테크코스 레벨2 기간에 학습한 Spring Configuration 강의 내용을 정리한다.


스프링 컨테이너 설정 방법

스프링 컨테이너(ApplicationContext)는 객체 컨테이너로 설정 정보를 읽어와서 객체를 생성하고, 초기화하고 보관, 제거 등의 작업을 수행하며 스프링 컨테이너가 이렇게 관리하는 객체를 스프링 빈이라고 한다.
(스프링 컨테이너는 내부적으로 빈 객체와 빈 이름을 연결하는 정보를 갖고 있다.)

이런 스프링 컨테이너는 IoC 컨테이너 또는 DI 컨테이너라고 하는데, 그 이유는 의존관계 주입을 대신해서 객체를 생성하고 관리하면서 의존관계를 설정해주기 때문이다.

이러한 스프링 컨테이너에 빈을 설정하는 방법은 크게 3가지로 XML, Annotation, Java-based 가 있다.
최근 많은 스프링 사용자들은 어노테이션 기반으로 빈을 설정하며 그 예는 다음과 같다.

@Config
public class AppConfig {
	
    @Bean
    public MyBean myBean() {
    	return enw MyBean();
    }
}

@Bean & @Configuration

스프링의 새로운 Java-configuration은 @Bean 어노테이션 클래스와 @Configuration 어노테이션 클래스를 지원한다.

@Bean 어노테이션은 메소드가 Spring IoC 컨테이너에서 관리할 새 객체를 인스턴스화, 구성 및 초기화하는데 사용된다. 그리고 이는 <beans/> XML configuration과 동일한 역할을 수행한다.

@Bean vs @Component

스프링을 다루다보면 @Bean@Component 를 자주 마주하게 된다. 결국 이 둘 모두 빈을 등록할 때 사용하는 어노테이션인데, 이 둘이 어떨 때 사용해야 할지 어떻게 다른지 막막할 때가 있다.

@Component는 쉽게 말해 개발자가 컨트롤 가능한 클래스에 대해서 Bean을 등록할 때 사용한다. 예를 들어, 우리가 직접 만든 자바 클래스에 대해서는 @Component를 사용한다.

반면, 우리가 직접 제어할 수 없는 클래스들, 예를 들어 외부 라이브러리를 통해서 가져다가 사용하는 클래스에 대해서는 Bean을 등록하기 위해 @Bean을 사용한다. (Config 클래스의 @Config 어노테이션과 함께)

또 다른 차이점은 선언 레벨이다. @Bean의 경우에는 @Target이 ElementType.METHOD 로 제한되어 있어 메소드 레벨에서만 선언가능하다. 반면 @Component는 @Target이 ElementType.TYPE 으로 클래스 레벨에서 선언되어야 한다.

그리고 @Bean만의 장점으로는 메소드 레벨에서 선언이 간으하기 때문에 유연하게 빈을 등록할 수 있다. 분기문을 통해서 조건에 따라서 거기에 맞는 구현 클래스를 인터페이스 기반 빈에 등록해줄 수 있는 것이다.

Injecting Inter-bean Dependencies

빈이 서로에 대해 종속성을 가질 때에는 다음 예시 코드와 같이 해당 종속성을 표현하는 것이다. 종속성을 표현하는 방법은 하나의 빈 메소드가 다른 메소드를 호출하도록 하는 것이다.

@Configuration
public class AppConfig {
	
    @Bean
    public BeanOne beanOne() {
    	return new BeanOne(beanTwo());
    }
    
    @Bean
    public BeanTwo beanTwo() {
    	return new BeanTwo();
    }
}

Inter-bean 의존성을 선언하는 방법은 @Configuration 클래스 내에서 @Bean 메소드가 선언된 경우에만 동작한다. @Component를 사용한 경우에 대해서는 inter-bean dependencies를 정의해서 사용할 수 없다.

@PropertySource

@PropertySource 어노테이션은 스프링 환경에 PropertySource를 추가하기 위한 편리하고 선언적인 매커니즘을 제공한다.
testbean.name = myTestBean, 키-값 쌍을 포함하는 app.properties라는 파일이 주어지면 다음과 같은 @Configuration 클래스는 testBean.getName()에 대한 호출이 myTestBean을 반환하는 방식으로 @PropertySource를 사용한다.

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
	
    @Autowired
    Environment env;
    
    @Bean
    public TestBean testBean() {
    	TestBean testBean = new TestBean();
        testBean.setName(env.getProperty("testbean.name"));
        return testBean;
    }
}

@PropertySource 를 이용하여 JwtTokenKeyProvider를 생성하면서 application.properties의 secret-key 값을 가져다가 사용하는 예제를 보면 다음과 같다.

security-jwt-token-secret-key= ey.....
security-jwt-token-expire-length= 3600000
@Configuration
@PropertySource("classpath:application.properties")
public class PropertySourceConfig {

    private final Environment env;

    public PropertySourceConfig(Environment env) {
        this.env = env;
    }

    @Bean
    public JwtTokenKeyProvider jwtTokenKeyProvider() {
        return new JwtTokenKeyProvider(env.getProperty("security-jwt-token-secret-key"));
    }
}

@Value

@Value 어노테이션은 외부 속성을 주입하는데 사용한다.
이번에는 앞선 예시에서 token의 expire-length를 application.properties에서 읽어온다고 해보자. 다음과 같다. (PropertySource 어노테이션을 Config파일에 선언해줌으로써 읽어들일 properties를 지정해주었다.)

@Configuration
@PropertySource("classpath:application.properties")
@ComponentScan("nextstep.helloworld.config.environment")
public class ValueConfig {
}
@Component
public class JwtTokenExpireProvider {
    private long validityInMilliseconds;

    public JwtTokenExpireProvider(@Value("${security-jwt-token-expire-length") long validityInMilliseconds) {
        this.validityInMilliseconds = validityInMilliseconds;
    }

    public long getValidityInMilliseconds() {
        return validityInMilliseconds;
    }
}

SpringBoot와 Auto-configuration

스프링 부트는 '바로 실행할 수 있는 애플리케이션을 쉽게 만들 수 있게 도와준다.'
즉, 스프링을 보다 편리하게 사용할 수 있게 도와준다.

그 중 한 예시로 Auto-configuration 이라는 것이 있다. 즉, 우리가 별다른 설정을 해주지 않아도, 스프링부트가 자동으로 설정을 해주는 것이다.

Spring Boot Auto-configuration은 추가한 jar 종속성을 기반으로 Spring 애플리케이션을 자동으로 구성하려고 시도한다. 예를 들어 HSQLDB가 클래스 경로에 있고, DB 연결 Bean을 수동으로 구성하지 않은 경우 Spring Boot는 메모리 내 DB를 자동 구성한다.
우리는 단지 클래스 중 하나에 @EnableAutoConfiguration 또는 @SpringBootApplication 어노테이션을 추가하기만 하면된다.

즉, 별다른 설정없이 의존성만 추가하면 우리는 JdbcTemplate 등을 사용할 수 있는데, 그 비밀은 바로 우리의 프로젝트 제일 상단의 main 메소드를 가진 클래스에 붙는 @SpringBootApplication 어노테이션에 있다.

@SpringBootApplication 어노테이션을 들여다보면 @EnableAutoConfiguration 이라는 어노테이션이 포함되어 있다. @EnableAutoConfiguration은 스프링부트의 meta 파일을 읽어서, 미리 정의되어 있는 자바 설정 파일(@Configuration)들을 빈으로 등록하는 역할을 수행한다. (spring.factories라는 스프링부트의 meta 파일을 읽어들ㅇ니다.)

즉 @SpringBootApplication 어노테이션이 붙은 main 클래스를 실행하면 @EnableAutoConfiguration에 의해 spring.factories 안에 들어있는 수많은 자동 설정들이 조건에 따라 적용이 되어 수 많은 빈들이 자동으로 생성되는 것이다.

Spring Cods 를 보면 Auto Configuration Class들이 나열되어 있고, 여기에는 JdbcTemplateAutoConfiguration이 포함되어 있다.

@Import 어노테이션을 보면 JdbcTemplateConfiguration 클래스와 NamedParameterJdbcTemplateConfiguration 클래스와 같이 2개 이상의 설정 파일이 @Import 어노테이션을 통하여 설정되어 있다. (@Import는 여러 설정 파일을 import하는 경우에 사용한다고 한다.)

그런데 이 때 JdbcTemplateAutoConfiguration을 잘보면 @Conditional 어노테이션이 함께 있는 것을 확인할 수 있다.
이는 스프링 부트의 조건부 어노테이션으로 스프링4에서 도입된 어노테이션이다. 조건부로 Bean을 Spring Container에 등록하는 역할을 수행한다. 이런 @Conditional 어노테이션의 파생 어노테이션으로 @ConditionalOnClass (특정 Class 파일이 존재하면 Bean을 등록) 와 @ConditionalOnBean (특정 Bean이 존재하면 Bean을 등록) 가 있다.
그 중 JdbcTemplateAutoConfiguration에서 사용된 @ConditionalOnClass는 주어진 클래스가 클래스 경로에서 발견 될 수 있는 경우에만 Bean을 등록하는데 사용될 수 있다는 것을 의미한다.
즉, 해당 클래스가 있는지를 검사하는 조건 어노테이션인 것이다.

따라서 위 경로의 두 클래스가 존재하는 경우에만 의존성을 추가해준다. 그 말은 즉 spring-boot-starter-jdbc 의존성을 제거하면 auto-configuration을 해주지 않는다는 것이다.

그럼 DataSource는 어디서 빈으로 만들어줄까?
-> DataSourceAutoconfiguration 에서 빈으로 만들어주며, EmbeddedDataSourceConfiguration vs PooledDataSourceConfiguration 은 @Conditional에 따라 동작한다.

profile
꾸준함에서 의미를 찾자!

0개의 댓글