Dev/SpringBoot

[SpringBoot] 스프링부트 자동설정

창문닦이 2019. 12. 13. 17:27

스프링 부트 스타터

스프링 부트를 구성하는 핵심요소 3가지 : 스타터, 자동설정, 액추에이터

스타터(starter) : 스프링이 제공하는 특정 모듈을 사용할 수 있도록 관련된 라이브러리 의존성을 해결
자동설정(AutoConfiguration) : 스타터를 통해 추가한 모듈을 사용할 수 있도록 관련된 빈 설정을 자동으로 처리
액추에이터(Actuator) : 스프링 부트로 개발된 시스템을 모니터링할 수 있는 다양한 기능 제공

[스타터로 의존성 관리]

스타터는 필요한 라이브러리들을 관련된 것끼리 묶어서 마치 패키지처럼 제공한다.
프로젝트에서 사용하고 싶은 모듈이 있으면 그 모듈에 해당하는 스타터만 의존성으로 추가하면 된다.
1. 프로젝트에 의존성 추가하기
- pom.xml 파일에 <dependency> 설정 추가
2. 스타터로 의존성 추가하기
스프링부트는 다양한 스타터를 제공하며 spring-boot-start-모듈명 형태를 갖는다.
스타터를 이용하면 프로젝트의 라이브러리 의존성 문제를 간단하게 해결 가능.
3. 이클립스에서 스타터 추가하기
진행중인 프로젝트에서 새로운 스타터를 추가할 때는 직접 타이핑하거나 ctrl + space 사용 가능.

[스타터 사용하기]
기본스타터 (프로젝트 생성시 체크한 web, devTools, Lomomk에 대한 의존성, 테스트 관련 의존성은 기본적으로 추가 되어있다.)
- spring-boot-starter-web, spring-boot-devtools, lombok, spring-boot-starter-test
스프링 MVC를 이용해서 웹 애플리케이션을 개발할 때에는 아무리 간단하더라도 기본적으로 사용하는 라이브러리는 수십개가 넘는다.
이 의존성을 일일이 설정하지 않고 스타터로 처리 가능. 스타터는 관련된 라이브러리들을 묶음으로 관리하는 역할

 

[스타터 설정 이해하기]

스타터는 최소한의 설정으로 수많은 라이브러리를 자동으로 관리.
특정 모듈과 관련된 의존성을 패키지처럼 관리하기 때문에 프로젝트에 새로운 모듈을 쉽게 등록하거나 제거할 수 있다.

[POM 파일 상속 구조]

메이븐은 상속을 통해 복잡한 설정을 재사용 가능
<parent> 엘리먼트 : 다른 POM 설정을 부모로 지정하여 부모로부터 모든 설정을 상속받을때 사용
해당 스타터에 어떠한 모듈이 설정되어 있는지 궁금하다면 Ctrl + Space 로 조회해보자 !!
복잡한 의존성을 숨길 수 있어서 쉽게 프로젝트를 관리 가능하다

[의존성 재정의]

- 스타터 재정의
부모로부터 상속받은 프로퍼티를 사용하지 않고 재정의 가능
프로젝트의 pom.xml 을 열어서 <version>태그를 변경하면 버전만 변경된 것이 아니라 스타터가 관리하는 수많은 의존성 역시 호환 가능한 버전으로 자동 변경된다.
- 프로퍼티 재정의
스타터가 제공하는 라이브러리중에 특정 프레임워크의 버전만 변경하고 싶은 경우 부모로부터 상속받은 프로퍼티만 재정의 하면 된다.
<properties> 하위에 바꾸고 싶은 속성태그를 재정의


스프링 부트의 자동설정

[자동설정 이란?]

@SpringBootApplication : 복잡한 환경설정 없이도 웹 애플리케이션을 만들고 실행시켜주는 어노테이션. 자동설정 기능이 동작하여 수많은 빈들이 등록되고 동작한다.

SpringBootApplication.java

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

@EnableAutoConfiguration, @ComponentScan 스프링 컨테이너 초기화와 관련된 어노테이션

@ComponentScan
@Configuration, @Repository, @Service, @Controller, @RestController가 붙은 객체를 메모리에 올리는 역할을 한다. excludeFilters에 해당하는 클래스를 제외하고 나머지 객체들을 스캔해서 초기화하도록 설정. 내가 만든 컨트롤러 객체를 메모리에 올리는 작업

@EnableAutoConfiguration
자동설정과 관련된 어노테이션. 스프링부트는 스프링 컨테이너를 구동시 두단계로 나누어 객체를 생성함. 스프링부트의 meta 파일(spring.factories)을 읽어서 미리 정의된 자바 설정 파일(@Configuration)들을 빈으로 등록하는 역할.
- spring.factories  : 스프링부트의 메타데이터가 저장된 파일
- 자동 설정 기능 제공 모듈: 설정을 제공하는 @Configuration 적용 클래스 구현
- 자동 설정 기능 제공 모듈: spring.factories 파일 작성
- 적용: 자동 설정이 필요한 프로젝트에서 모듈에 대한 의존 추가
- Maven Dependencies  > spring-boot-autoconfigure-2.2.2.RELEASE.jar 에 @EnableAutoConfiguration이 포함되어 있다.

이 jar 파일안에 있는 spring.factories를 열면 보이는 # Auto Configure 설정이 있다.
수많은 환경설정 클래스들은 모두 스프링 빈 설정파일로서 @Configuration 을 가지고 있다.
각 클래스에는 스프링부트가 지원하는 기능들이 모듈별로 설정되어 있다.

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
… 생략

이 중에서 WebMvcAutoConfiguration 클래스를 분석해보자

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
		ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
	//생략
}


@Configuration 어노테이션은 클래스가 스프링 빈 설정 클래스임을 의미한다. 
@ComponentScan이 처리될 때 자신뿐만아니라 이 클래스에 @Bean으로 설정된 모든 빈들도 초기화 진행
 
@SpringBootConfiguration = @Configuration
환경설정 빈 클래스를 표현하기 위해 사용. 스프링부트 환경설정 클래스임을 표현하기 위해 이름만 변경.
@SpringBootConfiguration를 @Configuration로 변경하여 실행해도 결과 동일

@ConditionalOnWebApplication(type = Type.SERVLET)
웹 어플리케이션 타입이 어떻게 설정되어 있느냐를 확인하는 어노테이션. type 속성값이 SERVLET일때만 설정 적용

@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
특정 클래스가 클래스 패스에 존재할 때, 현재 설정 클래스를 적용하라는 의미.
Servlet, DispatcherServlet, WebMvcConfigurer 클래스가 있을 경우 현재 애플리케이션이 웹 기반.

@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
특정 클래스 객체가 메모리에 없을때 현재 설정 클래스를 적용하라는 의미.

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
자동 설정 클래스들으 우선순위를 지정할때 사용. 현재 가장 높은 우선순위에서 10단계 더 높여 설정

@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class })
현재의 자동설정 클래스가 다른 자동설정 클래스 다음에 적용되도록 지정.

[사용자 정의 스타터]

스프링부트의는 범용적인 프로젝트 개발을 목표로 설계되어, 특정 도메인이나 비즈니스에 특화되어 있지 않다.
스프링 부트가 사용하는 기존 스타터나 자동설정과 동일한 네이밍룰을 따르는 게 좋다.
사용자 정의 스타터를 만들기위해 Maven project 생성 > pom.xml에 자동설정을 하기위한 라이브러리 추가.

<dependency>
     <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>2.2.2.RELEASE</version>
</dependency>

 추가되는 라이브러리들의 버전을 일괄적으로 관리하고 싶으면 dependencyManagement 설정을 추가한다. 부모 pom파일에 정의된 설정정보를 상속받을 수 있다. dependency설정의 version을 제거해도 자동으로 부모 파일에서 속된 프로퍼티 정보들이 적용된다.

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>
    </dependencies>
</dependencyManagement>

자동설정을 구현해보자.
1. BoardConfiguration 객체 생성

package com.studyboot.jdbc.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.studyboot.jdbc.util.JDBCConnectionManager;

// @Configuration을 가지고 있으므로 자동으로 빈 등록. 빈 등록시 @Bean설정들도 객체로 등록
@Configuration
// 활성화할 프로퍼티 클래스를 지정할 때 사용 
@EnableConfigurationProperties(JDBCConncetionManagerProperties.class)
public class BoardAutoConfiguration {

	@Autowired // 의존성 주입하여 사용가능
	private JDBCConncetionManagerProperties properties;
	
	@Bean
	public JDBCConnectionManager getJdbcConnectionManager() {
		JDBCConnectionManager manager = new JDBCConnectionManager();
		manager.setDriverClass("oracle.jdbc.driver.OracleDriver");
		manager.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
		manager.setUsername("friday");
		manager.setPassword("friday");
		
		return manager;
	}
}


2. JDBCJDBCConnectionManager 객체 생성

package com.studyboot.jdbc.util;

import java.sql.Connection;
import java.sql.DriverManager;

// 특정 데이터베이스와 커넥션을 관리하기 위한 멤버변수 소유
public class JDBCConnectionManager {
	private String driverClass;
	private String url;
	private String username;
	private String password;
	
	public void setDriverClass(String driverClass) {
		this.driverClass = driverClass;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	// 커넥션 객체를 리턴
	public Connection getConnection() {
		try {
			Class.forName(driverClass);
			return DriverManager.getConnection(url, username, password);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer("JDBCConnectionManager [driverClass=");		

		sb.append(driverClass)
		.append(",url=").append(url)
		.append(",username=").append(username)
		.append(",password=").append(password)
		.append("]");
		return sb.toString();
	}
	
}


3. sping.factories 파일 생성

메이븐 프로젝트 빌드하기 : Run As > Maven Install
메이븐으로 프로젝트를 빌드할 때 package나 install을 실행할 수 있다.
- package : target 폴더에 jar파일 생성
- install :  다른프로젝트에서 현재 프로젝트를 사용할 수 있도록 메이븐 로컬 리포지터리에도 등록해준다.
- 생성경로 : C:\Users\사용자이름\.m2\repository\com\studyboot\board-spring-boot-starter

 


다른 프로젝트에 적용시 POM파일에 dependency를 입력하고 메이븐 업데이트를 진행해보자. 내가 정의한 스타터가 반영되어 모듈이 추가된것을 확인 가능하다.

<dependency>		
  <groupId>com.studyboot</groupId>
  <artifactId>board-spring-boot-starter</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>

프로젝트에 추가된 모듈

컨테이너가 컴포넌트 스캔할 수 있도록 @Service 어노테이션을 추가하자. ApplicationRunner를 구현했으므로 객체가 생성되자마자 run 메소드를 수행한다.

package com.studyboot.jdbc.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Service;

@Service
public class JDBCConnectionManagerRunner implements ApplicationRunner {
	
	@Autowired
	private JDBCConnectionManager connectionManager;
	
	@Override
	public void run(ApplicationArguments args) throws Exception {
		System.out.println("커넥션 매니저" + connectionManager.toString());
	}
}

자동설정이 정상적으로 동작해서 원하던대로 콘솔에 로그가 찍혔다

[자동설정 재정의하기]

- 빈 재설정 
자동설정의 내용을 변경하고 싶으면 해당 프로젝트안에서 설정 클래스를 생성한다(@Configuration). 

package com.studyboot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.studyboot.jdbc.util.JDBCConnectionManager;

@Configuration
public class BoardConfiguration {
	
	// 자동설정한 내용이 아닌 프로젝트내에서 DB설정을 특정값으로 변경하고 싶을 경우 
	@Bean
	public JDBCConnectionManager getJDBConnectionManager() {
		JDBCConnectionManager manager = new JDBCConnectionManager();
		manager.setDriverClass("org.h2.Driver");
		manager.setUrl("jdbc:h2:tcp://localhost/~/test");
		manager.setUsername("h2");
		manager.setPassword("h2");
		return manager;
	}
}

이전에 생성된 빈을 새로게 생성한 빈이 덮어 쓸수 있도록 application.properties 파일에 아래와 같이 설정을 추가하면 된다.
메모리에 동일한 타입의 빈이 등록되어 있으면 새로운 빈이 기존의 빈을 덮어쓰도록 하는 설정이다.

spring.main.allow-bean-definition-overriding=true

빈 중복 등록으로 인한 오류 발생 메세지

Field connectionManager in com.studyboot.jdbc.util.JDBCConnectionManagerRunner required a single bean, but 2 were found:
- getJDBConnectionManager: defined by method 'getJDBConnectionManager' in class path resource [com/studyboot/config/BoardConfiguration.class]
- getJdbcConnectionManager: defined by method 'getJdbcConnectionManager' in class path resource [com/studyboot/jdbc/config/BoardAutoConfiguration.class]



- @Conditional 어노테이션 활용하기
@Conditional
조건에 따라 새로운 객체를 생성할지 말지 결정할 수 있다.
@ConditionalOnMissingBean
등록하려는 빈이 메모리에 없는 경우에만 현재의 빈을 처리하도록 한다. 자동설정 클래스에 해당 어노테이션을 붙여주면 프로젝트내에 동일한 타입 빈이 없을때 자동등록시 빈을 생성.
1. 사용자가 정의한 @Configuration은 @ComponentScan에 의해 먼저 등록된다.
2. 자동설정인 @EnableAutoConfiguration이 동작하는 시점에는 이미 등록된 빈을 사용하고 새롭게 빈 생성 진행하지않음
사용자 정의한 스타터에 있는 BoardAutoConfiguration 클래스에 @ConditionalOnMissingBean 어노테이션을 작성한다.

BoardAutoConfiguration.java
@Configuration
public class BoardAutoConfiguration {

	@Autowired
	private JDBCConncetionManagerProperties properties;
	
	@Bean
	@ConditionalOnMissingBean
	public JDBCConnectionManager getJdbcConnectionManager() {
		//생략
	}
}

-프로퍼티 파일 이용하기
<context:place-holder> 태그는 외부 프로퍼티를 이용하여 객체를 생성 가능하게 한다. 외부 설정 파일을 주입해서 객체 생성하는 방법을 실행해보자. 먼저 프로젝트의 프로퍼타 파일에 데이터베이스 관련 프로퍼티를 추가한다.

# Bean overriding 설정
spring.main.allow-bean-definition-overriding=true

# 데이터 소스 : h2
board.jdbc.driverClass=org.h2.Driver
board.jdbc.url=jdbc:h2:tcp://localhost/~/test
board.jdbc.username=h2
board.jdbc.password=h2

프로젝트내에 환경설정 객체가 생성되지 않도록 어노테이션을 주석처리한다.

//@Configuration
public class BoardConfiguration {
	
	// 자동설정한 내용이 아닌 프로젝트내에서 DB설정을 특정값으로 변경하고 싶을 경우 
	//@Bean
	public JDBCConnectionManager getJDBConnectionManager() {
		//생략
	}
}


스타터 프로젝트에 프로퍼티 클래스를 추가로 작성한다. @ConfigurationProperties(prefix = "board.jdbc") 

package com.studyboot.jdbc.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "board.jdbc")
public class JDBCConncetionManagerProperties {
	private String driverClass;
	private String url;
	private String username;
	private String password;
    //getter, setter 생략
}

When using @ConfigurationProperties it is recommended to add 'spring-boot-configuration-processor' to your classpath to  generate configuration metadata [@ConfigurationProperties를 사용하려면 해당 모듈에 대한 의존성 주입 필요하다.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
  <version>2.2.2.RELEASE</version>
</dependency>

@EnableConfigurationProperties(JDBCConncetionManagerProperties.class)
스타터 프로젝트에 있는 BoardAutoConfiguration 클래스에 해당 어노테이션을 작성해준다. 이 어노테이션은 활성화할 프로퍼티 클래스를 지정할 때 사용한다.

BoardAutoConfiguration.java
@Configuration
@EnableConfigurationProperties(JDBCConncetionManagerProperties.class)
public class BoardAutoConfiguration {

	@Autowired
	private JDBCConncetionManagerProperties properties;
	
	@Bean
	@ConditionalOnMissingBean
	public JDBCConnectionManager getJdbcConnectionManager() {
		manager.setDriverClass(properties.getDriverClass());
		manager.setUrl(properties.getUrl());
		manager.setUsername(properties.getUsername());
		manager.setPassword(properties.getPassword());
		
		return manager;
	}
}

요약

자동설정기능은 기본적인 설정은 자동으로 진행하고 필요한 만큼의 기능은 재정의해서 사용가능하다
사용자 정의 스타터를 통해 특정 환경에서만 사용하는 기능들을 자동으로 처리가능하다

참고 서적 : 누구나 끝까지 따라 할 수 있는 스프링 부트 퀵스타트
https://cornswrold.tistory.com/314
https://javacan.tistory.com/entry/spring-boot-auto-configuration