Portfolio/Spring Boot

스프링 부트 7일차 - 스프링 부트 Jar, SpringApplication, 로거

Foo 2019. 4. 7. 00:25
728x90

1. mvn package를 하면 하나의 JAR 파일이 생김

   그 파일을 실행하려면 java -jar xxx.jar 로 실행하면됌


2. 1은 인텔리제이에서는 Maven의 Lifecycle에서 조작할 수 있음.

   package를 만들면 그 파일은 프로젝트의 target에 생성됨.


3. Java 스펙에는 Jar에 내장된 또다른 Jar를 로딩하는 표준적인 방법은 없음.

   스프링 부트에서는 내장 Jar를 구분해서 로딩시켜줌.

 * uber Jar 라는 것도 있었으나 해당 Jar은 내장된 Jar에 대해서 구분해서 로딩시켜주는게 아니라 그냥 하나로 압축해버리는 것이었기 때문에 구분이 잘 되지 않았음.


참고 : https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html


4. 스프링 애플리케이션이 제공해주는 여러가지 옵션을 사용하기 위해서는 두 코드 중 아래 코드가 좀 더 낫다.

SpringApplication.run(Application.class, args);


SpringApplication app = new SpringApplication(Application.class);
app.run(args);


5. 그냥 실행시키면 스프링 애플리케이션의 로그는 INFO 레벨의 로그만 출력된다.

  이를 DEBUG 레벨까지 보고싶다면 다음과 같이 VM options에 -dDebug를 줘야한다.


6. 실행시 콘솔에 출력되는 배너를 바꿔주고 싶다면 resources 폴더에 banner.txt를 만들어주고 내용을 넣어주면 해당 내용이 배너로 출력된다.


  .   ____          _            __ _ _

 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \

( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \

 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )

  '  |____| .__|_| |_|_| |_\__, | / / / /

 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::        (v2.1.4.RELEASE)



https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-spring-application.html#boot-features-banner 를 들어가서 배너에 정해진 변수를 넣어주는 것도 가능하다.


7. SpringApplicationBuilder를 이용해 빌더 패턴으로도 사용 가능

package me.junhyung.webservershowcase;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder()
.sources(Application.class)
.run(args);
}
}


8. 애플리케이션 컨텍스트가 만들어진 이후에 발생한 이벤트에 대한 리스너는 Bean으로 등록되어 관리 가능

   애플리케이션 컨텍스트가 만들어지기 전에 발생한 이벤트에 대한 리스너는 관리가 안됌.

 => 애플리케이션 컨텍스트가 만들어지기 전에 발생한 이벤트에 대한 리스너는 다음과 같은 방법으로 직접 추가해줘야함.

Application.java

package me.junhyung.webservershowcase;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {


public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.addListeners(new SampleListener());
app.run(args);
}
}

SampleListener.java

package me.junhyung.webservershowcase;

import org.springframework.boot.context.event.ApplicationStartingEvent;
import org.springframework.context.ApplicationListener;

public class SampleListener implements ApplicationListener<ApplicationStartingEvent> {
@Override
public void onApplicationEvent(ApplicationStartingEvent applicationStartingEvent) {
System.out.println("=======================");
System.out.println("Application is starting");
System.out.println("=======================");
}
}


  * SampleListener은 어차피 애플리케이션 컨텍스트가 생기기 전에 등록되어야 하기 때문에 @Component 어노테이션을 줘서 Bean으로 등록시킬 필요가 없음. 그냥 app.addListeners로 등록하니까.


9. 어떤 Bean에 생성자가 1개이고, 그 생성자의 파라미터가 Bean일 경우 그 Bean을 스프링이 알아서 주입해줌.

package me.junhyung.webservershowcase;

import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;

@Component
public class SampleListener {
public SampleListener(ApplicationArguments arguments) {
System.out.println("foo: " + arguments.containsOption("foo"));
System.out.println("bar: " + arguments.containsOption("bar"));
}
}

위 코드에서 SampleListener의 생성자 ApplicationArguments arguments 부분은 스프링에서 관리 중인 Bean을 주입해준다.


10. ApplicationAruguments는 VM option은 포함하지 않고 Program arguments만 포함함.


11. 애플리케이션을 실행한 뒤에 뭔가 실행하고 싶을 때

   ApplicationRunner를 이용해 실행, CommandLineRunner를 통해서 좀 더 로우레벨로 실행 가능


12. @Order(x)를 통해 Runner의 실행 순서 결정 가능

    숫자가 낮을 수록 먼저 실행 됨. @Order(1)이 @Order(3)보다 먼저 실행 됨.


13. 외부 환경 변수는 다음과 같은 우선순위를 가지고 높은 우선순위에 있는게 낮은 우선순위에 있는 것을 오버라이딩함.

  1. Devtools global settings properties on your home directory (~/.spring-boot-devtools.properties when devtools is active).
  2. @TestPropertySource annotations on your tests.
  3. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  4. Command line arguments.
  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  6. ServletConfig init parameters.
  7. ServletContext init parameters.
  8. JNDI attributes from java:comp/env.
  9. Java System properties (System.getProperties()).
  10. OS environment variables.
  11. RandomValuePropertySource that has properties only in random.*.
  12. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
  13. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
  14. Application properties outside of your packaged jar (application.properties and YAML variants).
  15. Application properties packaged inside your jar (application.properties and YAML variants).
  16. @PropertySource annotations on your @Configuration classes.
  17. Default properties (specified by setting SpringApplication.setDefaultProperties).


참고 : https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config


14. 테스트를 할때는 테스트용 application.properties가 소스의 application.properties를 덮어 쓴다. 그래서 필요한 properties는 테스트용에도 들어가야한다.

  => 근데 이러면 매번 테스트에도 필요한 프로퍼티를 추가해줘야함.

       때문에 테스트 쪽에는 프로퍼티를 지우고 @TestPropertySource를 이용해서 프로퍼티를 테스트용에다 추가해주는 방식이 좋음.

package me.junhyung.webservershowcase;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@TestPropertySource(properties = "junhyung.name=foo")
@SpringBootTest
public class SpringApplicationTests {

@Autowired
Environment environment;

@Test
public void contextLoads() {
assertThat(environment.getProperty("junhyung.name"))
.isEqualTo("foo");
}
}


근데 또 이게 넘 많으면 관리하기가 어려움. 테스트 쪽에서는 application.properties 라는 이름 말고 다른 이름(ex. test.properties)으로 테스트용 프로퍼티들을 만들어두고, @TestPropertySource에서 locations로 지정해주면 됨.

package me.junhyung.webservershowcase;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@TestPropertySource(locations = "classpath:/test.properties")
@SpringBootTest
public class SpringApplicationTests {

@Autowired
Environment environment;

@Test
public void contextLoads() {
assertThat(environment.getProperty("junhyung.name"))
.isEqualTo("foo");
}
}


15. application.properties도 위치에 따라 우선순위가 있음

  1. file:./config/
  2. file:./
  3. classpath:/config/
  4. classpath:/


16. application.properties에 다음과 같이 properties가 등록되어 있는 상태에서 이 properties를 Bean으로 만들 수 있음.

junhyung.name=lee
junhyung.age=${random.int(100)}


이 프로퍼티들은 junhyung 이라는 prefix가 있는데, 이것을 Bean형태로 만들어주기 위해서는 다음과 같은 코드가 작성되면 됨.

JunhyungProperties.java

package me.junhyung.webservershowcase;

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

@Component
@ConfigurationProperties("junhyung")
public class JunhyungProperties {

private String name;

private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}


Bean으로서이 조건에 맞아야하기 때문에 getter, setter 메소드를 구현해주어야함.

이렇게 한 후 이 Bean을 주입받는 쪽에서는 @Autowired로 주입받으면 됨.


SampleListener.java

package me.junhyung.webservershowcase;

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

@Component
public class SampleListener implements ApplicationRunner {

@Autowired
JunhyungProperties junhyungProperties
;

@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("==========");
System.out.println(junhyungProperties.getName());
System.out.println(junhyungProperties.getAge());
System.out.println("==========");
}
}


17. @Validated 를 이용하면 프로퍼티에 대한 검증을 할 수 있음. (JSR-303)

     * hibernate-validator에 포함되어 있음.

package me.junhyung.webservershowcase;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotEmpty;

@Component
@ConfigurationProperties("junhyung")
@Validated
public class JunhyungProperties {

@NotEmpty
private String name;

private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

  이것은 name이 @NotEmpty하지 않도록 만들어준다. Empty라면 에러가 난다.


18. 어떤 프로파일을 사용할지 정하려면 application.properties에 옵션을 다음과 같이 주면 됨.

     spring.profiles.active=production

  이렇게 주면 @Profile("production")으로 되어있는 프로파일만 활성화 됨.


  * 당연히 앞에 배운 내용을 응용하여 커맨드라인 args로 줘도 됨.


19. 스프링에서는 Commons Logging을(로깅 퍼사드) 사용하지만,

    스프링부트에서는 SLF4j를 로킹 퍼사드로 사용, 최종적으로는 Logback으로 로깅이 되는것