ExceptionHandler

스프링 부트에서 에러를 처리해 주는 ExceptionHandler가 있다.

 

@Controller
public class SampleController {

  @GetMapping("/exception")
  public String hello() {
    throw new SampleException();
  }

  @ExceptionHandler(SampleException.class)
  public @ResponseBody AppError sampleError(SampleException e) {
    AppError appError = new AppError();
    appError.setMessage("error msg");
    appError.setReason("reason!!");

    return appError;
  }
}
public class SampleException extends RuntimeException {

}
public class AppError {
    private String message;
    private String reason;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getReason() {
        return reason;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }
}

다음과 같이 에러 상황을 일으키고 처리하는 코드를 볼 수 있다.

@ExceptionHandler 로 exception에 대한 핸들러를 정의해주고

어떻게 에러를 처리할 것인지 정의한 sampleError()가 있다. 

 

이 ExceptionHandler는 해당 컨트롤러에서만 사용된다.

전역적으로 사용하기 위해서는 새로운 클래스를 만들어 @ControllerAdvice를 정의해주면 된다.

@ControllerAdvice
public class ExceptionController {

  @ExceptionHandler(SampleException.class)
  public @ResponseBody AppError sampleError1(SampleException e) {
   AppError appError = new AppError();
   appError.setMessage("error msg");
   appError.setReason("reason!!");
    
    return appError;
  }
 }

 

 

커스텀 에러 페이지

스프링 부트에서는 상태값에 따라 에러페이지를 보여주기 위해서

resources/static/error 에 에러코드 이름으로 html 페이지를 만들어 주면 된다.

404에러가 났을 때 resources/static/error/404.html 페이지를 보여준다.

resources/static/error/5xx.html 처럼 앞자리만 표시할 수 도 있다. 이 경우 500번대 에러일 경우 해당된다.

Thymeleaf

스프링 부트에서는 JSP를 사용하는 기존 방법이 아닌 템플릿 기반의 화면 구성을 제공한다.

FreeMarker, Mustache, Thymeleaf와 같은 템플릿 엔진을 지원한다.

Thymeleaf를 이용한 화면을 구성해보자.

 

 	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

먼저 다음과 같은 의존성을 추가해준다.

 

@Controller
public class SampleController {
    
    @GetMapping("/hello")
    public String hello(Model model){
        model.addAttribute("name","ny");
        
        return "hello";
    }
}

컨트롤러를 만들어준다.

 

 

다음과 같이 resources/templates 경로에 템플릿을 작성한다. 기본적으로 .html 확장자를 사용한다.

 

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1 th:text="${name}">Name</h1>
</body>
</html>

xmlns:th="http://www.thymeleaf.org"을 추가해 줘야 사용할 수 있다.

 

 

http://localhost:8080/hello 을 호출하면 다음과 같은 결과화면을 볼 수 있다.

 

 

 

HtmlUnit

스프링 부트에서는 html 템플릿 뷰 테스트를 위한 tool을 제공한다.

 

<dependency>
	<groupId>org.seleniumhq.selenium</groupId>
	<artifactId>htmlunit-driver</artifactId>
	<scope>test</scope>
</dependency>
<dependency>
	<groupId>net.sourceforge.htmlunit</groupId>
	<artifactId>htmlunit</artifactId>
	<scope>test</scope>
</dependency>

먼저 다음과 같은 의존성을 추가해준다.

 

@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class)
public class SampleControllerTest {
    @Autowired
    WebClient webClient;

    public void hello() throws IOException {
        HtmlPage page = webClient.getPage("/hello");
        HtmlHeading1 h1 = page.getFirstByXPath("//h1");
        assertThat(h1.getTextContent());
    }
}

다음과 같이 WebClient를 주입받아 사용하면 된다.

웹 JAR

 

클라이언트에서 사용하는 react나 vue같은 라이브러리들을  웹 Jar 형태로 스프링부트에 추가해 줄 수 있다.

스프링 부트에서는 /webjars/** 로 기본맵핑이 된다.

 

<dependency>
    <groupId>org.webjars.bower</groupId>
    <artifactId>jquery</artifactId>
    <version>3.4.1</version>
</dependency>

jquery를 이용하기 위해서는 다음과 같은 pom.xml에 추가해주면 된다.

 

<script src="/webjars/jquery/3.4.1/dist/jquery.min.js"></script>
<script type="text/javascript">
    $(function () {
        alert("jquery!!");
    });
</script>

 

다음과 같이 jquery를 이용할 수 있다.

 

<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>webjars-locator-core</artifactId>
  <version>0.43</version>
</dependency>

만약 버전을 생략하고 싶다면 webjars-locator-core 를 추가해 준다.

 

<script src="/webjars/jquery/dist/jquery.min.js"></script>
<script type="text/javascript">
    $(function () {
        alert("jquery!!");
    });
</script>

다음과 같이 버전을 생략할 수 있다.

정적리소스

클라이언트에서 요청이 왔을 때 요청에 따라 리소스를 보여준다.

 

정적리소스의 기본 위치는 다음과 같다.

  • classpath:/static

  • classpath:/public

  • classpath:/resources/

  • classpath:/META-INF/resources

resources/static/hello.html 을 만든 후 http://localhost:8080/hello.html 로 요청하면 리소스 응답을 확인할 수 있다.

 

리소스 접근은 기본적으로 root로 설정되며 이 url설정을 바꾸고 싶다면 application.properties에서 

spring.mvc.static-path-pattern 설정해 주면 된다.

spring.mvc.static-path-pattern = /static/** 으로 설정하면

http://localhost:8080/static/hello.html 로 요청을 하면 된다.

 

 

WebMvcConfigurer

스프링에서 제공하는 기본적인 리소스핸들러 외에 추가적으로 리소스핸들러를 설정해줄 수 있다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/m/**")
      .addResourceLocations("classpath:/m/")
      .setCachePeriod(20);
  }
}

다음과 같이 config패키지를 만들어 WebMvcConfigurer를 implements하여 리소스를 추가할 수 있다.

/m/ 으로 시작하는 요청이 오면 classpath:/m/ 에서 리소스를 찾는다.

 

HttpMessageConverters

HttpMessageConverters는 스프링에서 제공하는 인터페이스이며

HTTP요청을 객체로 변환시켜주거나 객체를 HTTP요청으로 변환해준다. 

{“username”:”ny”, “password”:”1234”} <-> User

@Controller
public class UserController {

  @PostMapping("/user")
  public @ResponseBody User create(@RequestBody User user) {
    return user;
  }
}

이때 ResponseBody와 RequestBody가 사용된다.  이처럼 객체로 데이터를 받을 수 있다.

HttpMessageConvert는 여러가지가 있으며 요청받은 데이터 형식에 따라 사용하는 HttpMessageConvert가 달라진다.

 

@RestController
public class UserController {

  @PostMapping("/user")
  public User create(@RequestBody User user) {
    return user;
  }
}

@RestController를 이용하면 @ResponseBody를 생략할 수 있다. RestControll에서는 자동적으로 해준다.

하지만 RequestBody는 써줘야 한다.

 

@Controller와 @RestController는 반환하는 형식이 다르다.

@Controller는 view이름을 반환하고 RestController는 객체를 반환한다.

 

 

@RestController
public class UserController {

    @PostMapping("/users/create")
    public User create(@RequestBody User user) {
        return user;
    }

}
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {

  @Autowired
  MockMvc mockMvc;

  @Test
  public void createUser_JSON() throws Exception{
    String userJson = "{\"username\":\"ny\", \"password\":\"1234\"}";
    mockMvc.perform(post("/users/create")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_JSON_UTF8)
                    .content(userJson))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$.username", is(equalTo("ny"))))
      .andExpect(jsonPath("$.password", is(equalTo("1234"))));
  }
}

다음과 같이 테스트 코드로 테스트 해볼 수 있다. 

 

 

ContentNegotiatingViewResolver

서버로 들어오는 요청중에 accept가 있다. 이 accept값에 따라 원하는 형태로 값을 리턴해준다.

위에서는 accept가 APPLICATION_JSON_UTF8 로 설정된 것을 볼 수 있다.

이 때 application/json형식으로 변환해주는 것이 ContentNegotiatingViewResolver이다.

 

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-xml</artifactId>
  <version>2.9.6</version>
</dependency>

이를 사용하기 위해 다음과 같은 의존성을 추가해야 한다.

 

@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {

  @Autowired
  MockMvc mockMvc;

  @Test
  public void createUser_XML() throws Exception{
    String userJson = "{\"username\":\"ny\", \"password\":\"1234\"}";
    
    mockMvc.perform(post("/users/create")
                    .contentType(MediaType.APPLICATION_JSON_UTF8)
                    .accept(MediaType.APPLICATION_XML)
                    .content(userJson))
      .andExpect(status().isOk())
      .andExpect(xpath("/User/username").string("ny"))
      .andExpect(xpath("/User/password").string("1234"));
  }
}

위의 테스트 코드는 요청은 json으로 보내고 응답은 xml로 받을 수 있다.

스프링 부트에서 테스트를 진행해 보자!

스프링 에서는 @SpringBootTest 을 이용해 테스트를 진행할 수 있다.

 

	 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

다음과 같은 의존성을 추가해야 테스트를 진행할 수 있다.

 

 

 

테스트를 해보기 위해 다음과 같이 hello 요청에 대한 처리를 하는 컨트롤러와 서비스를 작성해보자

 

@SpringBootTest

@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleControllerTest {

  @Test
  public hello() {

  }
}

 

먼저 가장 기본적인 테스트 코드이다. 따로 빈설정을 해주지 않아도 @SpringBootApplication이 알아서 해준다.

 

Mock

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class SampleControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    public void hello() throws Exception {
        mockMvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string("ny"))
                .andDo(print());

    }

}

먼저 MockMvc환경으로 테스트를 해보자. Mock 환경은 내장톰캣이 구동이 안되며

@MockBean을 이용해 서블릿 컨테이너를 띄우지 않고 dispatcherServlet에 요청을 보낸 것 처럼 테스트할 수 있다.

 

 

RANDON_PORT, DEFINED_PORT

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SampleControllerTest {

  @Autowired
  TestRestTemplate testRestTemplate; //내장톰캣서버에 요청 보내서 확인.. 서비스단까지감

  @Test
  public void hello() throws Exception{
    String result =testRestTemplate.getForObject("/hello", String.class);
     assertThat(result).isEqualTo("hellonewny");
  }

}

다음은 랜덤포트로 지정하여 TestRestTemplate를 이용한 방법이다.

Mock환경이 아니라 랜덤포트를 이용해 실제로 컨테이너가 구동되는 환경이다.

모든 빈이 다 등록되어 테스트 환경이 무거울수도 있다.

 

부분적으로 Mock 이용

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class SampleControllerTest {

  @Autowired
  TestRestTemplate testRestTemplate;

  @MockBean
  SampleService mockSampleService;  //컨트롤러만 테스트, 샘플컨트롤러빈을 여기서 만든 목빈으로 교체됨

  @Test
  public void hello() throws Exception{
    when(mockSampleService.getName()).thenReturn("newny");
  
    String result =testRestTemplate.getForObject("/hello", String.class);
    assertThat(result).isEqualTo("hellonewny");
  }

}

서비스까지 가지 않고 컨트롤러만 테스트해보고 싶다면 다음과 같이 SampleService 를 이용할 수 있다.

ApplicationContext에 SampleController빈이 MockBean으로 등록된다.

 

 

WebTestClient

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class SampleControllerTest {

  @Autowired
  WebTestClient webTestClient; 
  //스프링5에서 추가됨. 요청을 보내고 기다리는게 아닌 요청이오면 콜백 실행, async하게 실행됨

  @MockBean
  SampleService mockSampleService;

  @Test
  public void hello() throws Exception{
    when(mockSampleService.getName()).thenReturn("ny");

    webTestClient.get().uri("/hello").exchange()
      .expectStatus().isOk()
      .expectBody(String.class).isEqualTo("hellony");
  }
}

기존테스는 요청을 보내고 응답을 기다리는 sync방식이였다면 WebTestClient는 async하게 실행된다.

 

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

다음과 같은 의존성을 추가해야 WebTestClient를 이용할 수 있다.

 

슬라이싱테스트

모든빈을 다 등록하는 것이 아닌, 테스트 할 Bean만 등록하여 슬라이싱 테스트를 할 수 있다.

@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class) // 웹과 관련된 애들만 등록됨
public class SampleControllerTest {

  @MockBean
  SampleService mockSampleService; 

  @Autowired
  MockMvc mockMvc;

  @Test
  public void hello() throws Exception{
    when(mockSampleService.getName()).thenReturn("ny");

    mockMvc.perform(get("/hello"))
      .andExpect(content().string("hellony"));
  }
}

@WebMvcTest 을 이용해 테스트할 컨트롤러를 지정해준다. @WebMvcTest는 웹과 관련된 컨트롤러 들만

빈으로 등록되고 서비스나 레파지토리는 빈으로 등록되지 않는다.

따라서 @MockBean으로 SampleService 서비스빈을 등록해주어야 한다.

또한, WebMvcTest 는 반드시 MockMvc 로 테스트 해야한다.

 

 

테스트유틸 - OutputCapture

@RestController
public class SampleController {

  Logger logger = LoggerFactory.getLogger(SampleController.java)

  @Autowired
  private SampleService sampleService;

  @GetMapping("/hello")
  public String hello() {
    logger.info("ny")
    return "hello " + sampleService.getName();
  }
}
@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class)
public class SampleControllerTest {

  @MockBean
  SampleService mockSampleService; 

  @Autowired
  MockMvc mockMvc;

  @Test
  public void hello() throws Exception{
    when(mockSampleService.getName()).thenReturn("ny");

    mockMvc.perform(get("/hello"))
      .andExpect(content().string("hellony"));

    assertThat(outputCapture.toString()).contains("ny");
  }
}

outputCapture 테스트 유틸을 이용하면 콘솔에 찍히는 로그까지 테스트 할 수 있다.

로깅

스프링에서는 기본적으로 Commons Logging을 사용하며 내부에서 logback을 이용한다.

 

로거 설정

spring.output.ansi.enabled : always로 설정하면 컬러로 로그가 출력

logging.path : 로그 파일이 저장되는 경로

logging.file : 로그 파일 이름

logging.file.max-size : 저장할 로그 최대 용량

logging.level.패키지경로 : 패키지의 로그 레벨 지정

 

 

다음과 같이 로거를 찍을수도 있다.

 

커스텀로그 설정파일

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
    <logger name="org.springframework.web" level="DEBUG"/>
</configuration>

 

logback-spring.xml 파일을 생성하여 logback 설정을 커스텀 할 수 있다.

 

다음과 같이 패키지를 지정해줄 수 있다.

'Study > Spring boot' 카테고리의 다른 글

[Spring Boot] 스프링 웹 MVC - MessageConverters, ViewResolver  (0) 2020.12.19
[Spring Boot] 테스트  (0) 2020.12.19
[Spring Boot] 프로파일  (0) 2020.12.14
[Spring Boot] 외부설정  (0) 2020.12.13
[Spring Boot] SpringApplication  (0) 2020.12.12

프로파일

프로파일은 특정환경에서 설정값을 다르게 하고 싶을 때 사용한다.

각 프로파일 마다 설정값을 다르게 하여 Bean을 생성할 수 있다.

 

프로파일 사용

@Profile을 이용하여 각각 다른 프로파일을 설정해준다.

 

 

다음과 같이 hello 빈을 출력해보자. 이대로 실행하면 에러가 난다. 

어떤 프로파일을 사용할지 정의해주어야 한다.

 

application.properties에 어떤 프로파일을 활성화 할지 spring.profiles.active 정의한다.

 

실행해보면 프로파일을 prod로 설정한 빈 값이 출력되는 것을 볼 수 있다.

 

프로파일용 프로퍼티

프로파일용 프로퍼티를 따로 만들수도 있다.

application-{profile}.properties 형식으로 프로파일용 properties를 생성하여 관리할 수 있다.

 

 

spring.profiles.include 를 이용하면 다른 properties를 include 할 수 있다.

'Study > Spring boot' 카테고리의 다른 글

[Spring Boot] 테스트  (0) 2020.12.19
[Spring Boot] 로깅  (0) 2020.12.14
[Spring Boot] 외부설정  (0) 2020.12.13
[Spring Boot] SpringApplication  (0) 2020.12.12
[Spring Boot] 자동 설정  (0) 2020.12.07

+ Recent posts