Study/Mastering-Spring-5-Study

ch04. 스프링부트_시작하기

hongeeii 2022. 12. 31.
728x90
반응형

스프링 부트 시작하기

  • 왜 스프링 부트인가?
  • 스프링 부트가 제공하는 기능은 무엇인가?
  • 자동 설정인가?
  • 스프링 부트란?
  • 스프링 부트를 사용할 때 백그라운드에서는 어떤 일이 발생할까?
  • 스프링 이니셜라이저를 사용해 새로운 스프링 부트 프로젝트를 어떻게 만들까?
  • 스프링 부트로 어떻게 애플이케이션을 모니터링할까?
  • 스프링 부트 액추에이터는 무엇일까?
  • 스프링 부트로 어떻게 생산성을 높일 수 있을까?



스프링 부트를 통해 개발자는 마이크로서비스의 비즈니스 논리에 집중할 수 있다.

스프링 부트의 목표는 마이크로서비스 개발에 관련된 모든 기술적 세부사항을 관리하는 것이기 때문이다.

스프링 부트를 사용하면 스프링 기반 프로젝트를 빠르게 구축할 수 있다.
코드 생성과 많은 XML 설정의 사용을 피해라. 다양한 비기능적 특징이 있다.

  • 비기능이란
    • 서버와 스펙의 버전 관리 및 설정에 관한 기본 처리
    • 애플리케이션 시큐리티를 위한 기본 옵션
    • 애플리케이션 모니터링
    • 외부 설정의 여러 옵션



  • 스타터 프로젝트
    • 스타터는 여러 목적으로 사용자 정의가 단순화된 의존 관계 설명자다.
    • spring-boot-starter-web은 스프링 및 스프링 MVC 프레임워크를 사용해 Restful API를 포함한 웹 애플리케이션을 구축하는데 사용된다.
    • spring-boot-starter-test 의존관계는 단위 테스트에 필요한 테스트 프레임워크를 제공한다.
    • spring-boot-starter-tomcat은 톰캣을 임베디드 서블릿 컨테이너로 사용하기 위한 스타터다. 웹 애플리케이션을 실행하기 위해 기본적으로 포함돼 있다.



스프링 부트 애플리케이션을 시작하는 과정

  1. pom.xml 파일에 spring-boot-starter-parnet 구성
  2. 요구되는 스타터 프로젝트로 pom.xml 파일 구성
  3. 애플리케이션을 실행할 수 있도록 spring-boot-maven-plugin 구성
  4. 첫번째 스프링 부트 launch 클래스 생성
  • Spring Boot Starter Parent
    • 프로젝트 시작 시기에 다양한 라이브러리들을 사용하게되면 라이브러리 버전간의 충돌문제가 발생할 수 있다.
    • Spring Boot의 starter가 의존성 조합을 제공해준다면 starter-parent는 해당 의존성 조합간의 충돌 문제가 없는 검증 된 버전정보 조합을 제공한다.
    • spring-boot-starter-parent 버전만 설정해도 수많은 라이브러리들의 버전충돌 문제를 피할 수 있다.
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.7.RELEASE</version>
</parent>

<dependencies>
    ...(생략)

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
</dependencies>

특정 의존성이 버전을 변경하고자 한다면 아래와 같이 properties를 이용해서 해당 의존성 버전을 override하면 된다.

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

<properties>
    <aspectj.version>1.9.4</aspectj.version>
</properties>

<dependencies>
    ...(생략)

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
</dependencies>

 

자동 설정

ApplicationContext ctx = SpringApplication.run(Application.class, args);
String[] beanNames =  ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);

for(String beanName : beanNames) {
  System.out.println(beanName);
}


출력결과

application
basicErrorController
characterEncodingFilter
conventionErrorViewResolver
mvcViewResolver 
...

spring-boot-starter-web에 의존관계를 추가하면 빈이 자동 설정된다.

스프링 이니셜라이저 링크

spring boot autoconfiguration 정리

  • ConditionalOnClass : ({Example.class, ...}) : 언급된 클래스 중 하나라도 클래스 패스에 있으면 자동 설정이 사용된다. 웹 스타터 프로젝트를 추가할 때 언급된 모든 클래스의 의존 관계를 가져온다.
    • 스프링4에서 도입된 어노테이션으로 조건부로 Bean을 스프링컨테이너에 등록하는 역할을 합니다.
    • @ConditionalOnClass : 특정 Class 파일이 존재하면 Bean 을 등록.
    • @ConditionalOnBean : 특정 Bean 이 존재하면 Bean 을 등록.
    • @ConditionalOnClass 는 해당 Class 가 있는지를 검사하는 조건 어노테이션 입니다.
      • ConditionalOnClass를 사용하는 이유
      • 이 어노테이션이 작성되는 클래스는 외부 라이브러리로 제공되는 프로젝트 이다.
      • 이 어노테이션이 작성되는 클래스는 의존성에 대한 제어권을 갖지 않습니다.
  • ConditionalOnMissingBean(WebMvcConfigurationSupport.class) : Bean이 존재 하지 않을 때 실행되는 어노테이션이다. bean Name이나 class, annotaion으로 설정할 수 있다.
@ConditionalOnMissingBean(name = "helloConfigSample") //helloConfigSample 존재 하지 않을 때 실행해라.
  • ConditionalOnBean(ViewResolver.class) : ViewResolver.class가 클래스 패스에 있으면 빈이 생성한다.
@Configuration 
public class SampleAAutoConfiguration { 
    @Bean 
    public SomeAClass SomeAClass() {
         return new SomeAClass(); 
         } 
     } 
     @Configuration 
     public class SampleBAutoConfiguration { 
          @Bean 
          @ConditionalOnBean(SomeAClass.class) 
          public SomeBClass someBClass() { 
               return new SomeBClass();
               } 
          }

위 어플리케이션을 실행해보면 생성이 안되는 경우가 발생한다.

SpringBootAutoConfiguration bean이 생성될 때 의존성을 확인하고 bean 생성 순서를 재정렬하지만
@ConditionalOnBean annotation 조건에 대해서는 의존성을 확인하지 않아 재정렬이 되지 않어서이다.
따라서 위의 설정은 각 AutoConfiguration 간 순서를 지정해주어야 올바르게 동작한다.

@Configuration 
public class SampleAAutoConfiguration { 
     @Bean 
     public SomeAClass SomeAClass() { 
         return new SomeAClass(); 
         }
     } 
     @Configuration 
     @AutoConfigureAfter(SampleAAutoConfiguration.class) 
     public class SampleBAutoConfiguration { 
         @Bean 
         @ConditionalOnBean(SomeAClass.class)
         public SomeBClass someBClass() { 
             return new SomeBClass(); 
             }
         }
  • @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10) : 특정 자동 설정의 우선순위를 지정한다.
  • ConditionalOnProperty : prefix 속성의 해당하는 프로퍼티 값이 있다면 실행된다.
@ConditionalOnProperty(prefix = "autoconfig.sample", name = "id" )
  • EnableConfigurationProperties : 위 ConditionalOnProperty 같이 사용하는 어노테이션이다. 프로퍼티가 있다면 id값을 저장 해놓기 위한 class가 필요한데 그 용도로 사용한다. EnableConfigurationProperties 만 있어도 사용은 가능하지만 프로퍼티의 autoconfig.sample.id 설정하지 않았다면 null 로 나온다. 만약 사용한다면 위의 어노테이션이랑 사용하길 권장된다.
  • ConditionalOnWebApplication : web인지 아닌지 판단하는 어노테이션이다. web일 경우에 실행된다.
  • ConditionalOnNotWebApplication : web이 아닐경우 실행된다.
  • ConditionalOnJava : 자바 버전을 설정하는 어노테이션이다. 현재 버전이 설정한 버전보다 낮으면 실행된다. ex) 현재 1.8버전이고 설정한 버전이 1.8보다 작거나 같다면 실행되고 높으면 실행되지 않는다.
@ConditionalOnJava(value = ConditionalOnJava.JavaVersion.SIX)
  • ConditionalOnResource : 리소스 경로에 파일이 있으면 실행되는 어노테이션이다. 특정 경러롤 지정해 주면 된다.
@ConditionalOnResource(resources = "classpath:/META-INF/resourcesfile")
  • ConditionalOnExpression : 표현식으로 나타낼 수 있는 어노테이션이다.
@ConditionalOnExpression(value = "'${spring.application.name}' == 'spring-autoconfig'")

모든 자동 설정 로직은 스프링부트 애플리케이션이 시작할 때 실행된다.
특정 의존 관계나 스타터 프로젝트의 특정 클래스를 class path에서 사용할 수 있는 경우, 자동 설정 클래스가 실행된다.
자동 설정 클래스는 이미 빈이 구성돼 있는지 확인한다.

애플리케이션 구성 외부화

애플리케이션은 일반적으로 JAR나 WAR로 한 번 빌드된 후 여러 환경에 배포된다.
서로 다른 환경 간에 변경되는 설정들은 구성 파일 또는 데이터베이스로 외부화하는 것이 좋다.

  • application.properties 스프링 부트에서 구성 값이 선택되는 기본 파일이다. 스프링 부트는 클래스 패스의 어디서나 application.properties 파일을 선택할 수 있다.
    • appliaction.properties에서 정의할 수 있는 설정
    • 로깅 구성
    • 임베디드 서버 구성 사용자 정의
    • 스프링 mvc 설정
    • 스프링 스타터 시큐리티 구성
    • 데이터 소스, JDBC와 JPA 사용자 정의
    • 프로파일
    • HTTP 메시지 변환기
    • 트랜잭션 관리
    • 국제화 등

데이터 서비스 예제

somdataservice.url = http://abc.service.com/something

@Component
public class SomeDataService {
    @Value("${somedataservice.url}")
    private String url;
    
    public String retrieveSomeData() {
    //URL을 사용하고 데이터를 얻는 로직
        return "data from service";
    }
}

@Value("${somedataservice.url}") : somedataservice.url의 값이 url변수에 자동으로 연결된다. url 값은 빈 메소드에서 사용할 수 있다.

@Value 어노테이션은 동적 구성을 제공하지만 몇 가지 단점이 있다.

  • 한 서비스에서 3개의 속성 값을 사용하려면 @Value를 사용해 세 가지 속성 값을 오토와이어링 해야 한다.
  • @Value 어노테이션과 메시지 키는 애플리케이션에 분산된다. 애플리케이션에서 구성 가능한 값 리스트를 찾으려면 애플리케이션에서 @Value 어노테이션을 검색해야 한다.

ConfigurationProperties

  • 사전 정의된 빈 구조에 모든 특성을 보유한다.
  • 빈은 모든 애플리케이션 속성의 중앙 저장소 역할을 한다.
  • 구성 빈은 애플리케이션 구성이 필요한 모든 곳에 자동 연결될 수 있다.
my.name = yuni
my.age = 25
my.fullname = ${my.name} Yang


먼저 @ConfigurationProperties 어노테이션을 붙여 클래스를 만들어 준다. key값이 my로 시작했기때문에 값은 my로 준다.

@ConfigurationProperties("my")
public class MyProperties {

}

@ConfigurationProperties 어노테이션을 붙여주면, 메타데이터를 생성해 자동완성 기능을 가능하게 해주는 의존성을 추가하라고 뜬다.
org.springframework.boot spring-boot-configuration-processor true

@Component
@ConfigurationProperties
public class MyProperties {
  private String name;
  private int age;
  private String fullName;
  
  public String getName() { 
  	return name;
  }
  public void setName() {
  	this.name = name;
  }
  public int getAge() {
  	return age;
  }
  public String getFullName() {
  	return fullName;
  }
  public void setFullName(String fullName) {
  	this.fullName = fullName;
  }
  public void setName(String name) {
  	this.name = name;
  }
  public void setAge(int age) {
  	this.age = age;
  }
  
}

바인딩은 자바 빈 속성 설명자를 통해 발생하므로 getter와 setter가 필요하다. class를 활성화 하려면 main에 @EnableConfigurationProperties 어노테이션을 사용하여 사용할 프로퍼티 클래스를 값으로 줘서 사용해야한다.
그렇게 하면, 만들어준 프로퍼티 클래스도 Bean으로 등록해주고 @ConfigurationProperties 어노테이션도 처리해준다.

@SpringBootApplication
@EnableConfigurationProperties(MyProperties.class)
public class YuniApplication {

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

}
@ConfigurationProperties
@Validated
public class MyProperties {
	@NotEmpty
	private String name;
	private int age;
	private String fullName;
}

@Value 어노테이션으로 프로퍼티 값을 쓰는 것보다, 이런식으로 prefix값으로 프로퍼티를 구분하여, 클래스를 만들어 @ConfigurationProperties 어노테이션을 사용하여 프로퍼티 값을 사용하는 것이 매핑도 유연하게 할 수 있다는 장점이 있다.

기본적으로 외부에서 구성된 값을 구성 등록 정보 빈에 바인딩할 때 에러가 발생하면 서버가 시작되지 않는다.
따라서 프로덕션 환경에서 실행중인 애플리케이션이 잘못 구성 돼 발생하는 문제를 방지할 수 있다.

다른 환경에 profile 설정하기

@Profile("!prd") 
	@Bean(name = "sqlSessionFactory") 
	public SqlSessionFactory devSqlSessionFactory() throws Exception { 
		SqlSessionFactoryBean factoryBean = new RefreshableSqlSessionFactoryBean(); 
		
  factoryBean.setDataSource(oracleDataSource()); 
		factoryBean.setVfs(SpringBootVFS.class); 
		factoryBean.setConfigLocation(applicationContext.getResource("classpath:mybatis/mybatis-config.xml")); 
		
  ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 
		Resource[] resource = resolver.getResources("classpath*:mybatis/oracle/**/*.xml"); 
		
  factoryBean.setMapperLocations(resource); 
		((RefreshableSqlSessionFactoryBean) factoryBean).setInterval(1000); 
		
  return factoryBean.getObject(); 
	} 
	@Profile("prd") 
	@Bean(name = "sqlSessionFactory") 
	public SqlSessionFactory sqlSessionFactory() throws Exception { 
		SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); 
		
  factoryBean.setDataSource(oracleDataSource()); 
		factoryBean.setVfs(SpringBootVFS.class); 
		factoryBean.setConfigLocation(applicationContext.getResource("classpath:mybatis/mybatis-config.xml")); 
		
  ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); 
		Resource[] resource = resolver.getResources("classpath*:mybatis/oracle/**/*.xml"); 
		
  factoryBean.setMapperLocations(resource); 
		
  return factoryBean.getObject(); 
	}

method와 class에도 Profile을 정의할 수 있다.

YAML 구성

YAML은 YAML Aint Markup Language의 줄임말이다.

spring:
  profiles:
    active: prod
security:
  basic:
    enabled: false
  user:
    name=user-name
    password=user-password
oauth2:
  client:
    clientId: clientId
    clientSecret : clientSecret
    authorized-grant-types: authorization_code,refresh_token,password
    scope:openid
application:
  enableSwitchForService1: true
  service1Url: http://abc-dev.service.com/somethingelse
  service1Timeout: 250

YAML은 단일 구성 파일에서 여러 프로필의 구성을 저장할 수 있다는 장점이 있다.
YAML 파일은 @PropertySource 어노테이션을 사용해서 로드될 수 없습니다. 때문에 이 방법으로 값을 로드해야 하는 경우 프로퍼티 파일을 사용해야 합니다.

임베디드 서버

전통적인 자바 애플리케이션은 자바 웹 애플리케이션을 사용해 WAR나 EAR를 빌드해 서버에 배포한다.
서버에 WAR를 배포하려면 서버에 웹 서버나 애플리케이션 서버가 설치돼 있어야 한다.
애플리케이션 서버는 서버에 설치된 자바 인스턴스의 맨 위에 있다.
애플리케이션을 배포하려면 자바 및 애플리케이션이 시스템에 설치돼 있어야 한다.



스프링 부트는 웹 서버가 애플리케이션 배포 가능한 JAR의 일부인 임베디드 서버 개념을 도입한다.

  • 임베디드 서버를 사용해 애플리케이션을 배포하려면 자바가 서버에 설치돼 있어야 한다.
  • 스프링 부트로 모든 애플리케이션을 빌드할 때의 기본값은 JAR를 빌드하는 것이다.
  • spring-boot-starter-web에서 기본 임베디드 서버는 톰캣이다.
  • mvn clean을 사용해 JAR를 빌드

    JAR를 사용하는 대신 기존 WAR 파일 빌드 pom.xml의 패키지를 war로 변경하면 된다.
    war로 웹 스피어(webSphere)나 웹 로직(webLogic)과 같은 애플리케이션 서버나 톰캣과 같은 웹 서버에 배포할 수 있다.



스프링 부트 != 서버

Spring 프레임워크를 쉽게 사용할 수 있게 해주는 Tool일뿐, 스프링 부트 자체는 웹 서버가 아니다.

@SpringBootApplication
public class Application {
    //자동 설정이 없다고 가정하고 Servlet을 만들어서 톰캣에 등록
    public static void main(String[] args) {
        Tomcat tomcat = new Tomcat(); // 톰캣 객체 생성
        tomcat.setPort(8080); // 포트 설정
        Context context = tomcat.addContext("/", "/"); // 톰캣에 컨텍스트 추가

        // 서블릿 객체 생성
        HttpServlet servlet = new HttpServlet() {
            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                PrintWriter writer = resp.getWriter();
                writer.println("<h1>Hello world!</h1>");
            }
        };

        final String servletName = "myServlet";
        // 톰캣에 서블릿 추가
        tomcat.addServlet("/", servletName, servlet);
        // 컨텍스트에 서블릿 매핑
        context.addServletMappingDecoded("/home", servletName);

        try {
            // 톰캣 실행 및 대기
            tomcat.start();
            tomcat.getServer().await();
        } catch (LifecycleException e) {
            e.printStackTrace();
        }
    }
}

스프링에서는 Tomcat에 Servlet을 등록하고 실행하는 일련의 작업이 자동 설정으로 정의되어 있다. 자동 설정 덕분에 위 작업을 수행하지 않아도 된다.
ServletWebServerFactoryAutoConfiguration : 서블릿 웹 서버 생성하는 설정 파일

TomcatServletWebServerFactory를 들어가보면 톰캣을 생성하고 서블릿, 디스패처 설정을 하는 코드가 있다.

public WebServer getWebServer(ServletContextInitializer... initializers) {
          Tomcat tomcat = new Tomcat();
          File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
          tomcat.setBaseDir(baseDir.getAbsolutePath());
          Connector connector = new Connector(this.protocol);
          tomcat.getService().addConnector(connector);
          this.customizeConnector(connector);
          tomcat.setConnector(connector);
          tomcat.getHost().setAutoDeploy(false);
          this.configureEngine(tomcat.getEngine());
          // ...
  }
  

내장 웹 서버 변경

spring-boot-starter-web는 기본적으로 spring-boot-starter-tomcat 을 포함하고 있다. Tomcat 대신 Jetty를 내장 서버로 사용하도록 설정을 변경해보자.
pom.xml에서 dependencies를 아래와 같이 변경한다. tomcat의 include를 제거하기 위해 태그를 추가했다.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <!-- Exclude the Tomcat dependency -->
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- Use Jetty instead -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>	
	

개발자 도구를 사용해 생산성 향상

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
	<optional>true</optional>
    </dependency>
</dependencies>	
  • 클래스 패스의 파일이 변경될 때 자동으로 다시 시작하는 중요한 기능이 있다. 다음 시나리오에서 애플리케이션이 자동으로 다시 시작된다.
    • 컨트롤러나 서비스 클래스를 변경할 때
    • 속성 파일을 변경할 때
  • 스프링 부트 개발자 도구의 장점
    • 개발자는 매번 애플리케이션을 중지하고 시작할 필요가 없다. 애플리케이션은 변경되는 즉시 자동으로 다시 시작된다.
    • 스프링부트 개발자 도구의 재시작 기능은 지능적이다. 새롭게 개발된 클래스만 리로드한다. third-party jar은 리로드 하지 않는다.

애플리케이션 모니터링에 스프링 부트 액추어터 사용하기

  • 서비스가 느려지거나 다운되는 경우, 즉시 알고 싶다.
  • 서버에 충분한 여유 공간이나 메모리가 있는지 확인하고 싶다.
728x90
반응형

추천 글