본문 바로가기
4. Backend Development/1. Spring Framework

7. Spring boot

by H232C 2020. 9. 24.

1. Spring Boot란? 스프링 기반 어플리케이션을 빠르고 쉽게 만들 수 있으며 컨벤션 설정을 제공하여 스프링이 자동으로 설정되도록 한다. 더불어 써드파티 라이브러리를 설정을 제공하여 다양한 기능을 사용할 수 있다. (톰캣 라이브러리를 이용한 내장톰캣 이용 등)

2. Spring Boot 실행 환경 : JDK 8 버전 이상, Servlet 3.1 버전 이상의 환경에서 사용 가능하다.

3. Spring Boot 자동 설정
- @SpringBootConfiguration 애노테이션 내부에 @EnableAutoConfiguration 애노테이션 존재
- Bean은 두 단계로 나누어 읽힌다.
- 1단계 : @ComponentScan (@Component, @Repository, @Service ... 애노테이션을 빈 등록)
- 2단계 : @EnableAutoConfiguration (Spring-boot-autoconfigure 프로젝트 META-INF > spring.factories에 기재된 클래스를 빈으로 등록)

Maven의 Spring-boot-autoconfigure , spring.factories 화면

- spring.factories에 등록되어 있는 클래스는 모두 @Configuration 애노테이션을 갖고 있음
- 클래스 내에 @ConditionalOnXxxYyyZzz 등의 애노테이션이 존재한다면 특정 상황 및 조건에 따라 빈 생성 여부와 순서를 변경한다는 의미이다.
- @EnableAutoConfiguration 시 등록되는 Bean중 Servlet, Tomcat 관련 빈이 존재하므로 @SpringBootApplication(@ConponentScan, @EnableAutoConfiguration) 애노테이션이 등록된 메인 클래스를 실행하는 것으로 내장 톰캣 및 서블릿을 이용할 수 있다.

@SpringBootConfiguration 화면

4. 내장 서블릿 컨테이너 : @SpringBootApplication 애노테이션 없이 내장 서블릿 컨테이너 구현하기

package com.starter2.starter2;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@SpringBootApplication
public class Starter2Application {

  public static void main(String[] args) throws LifecycleException {

//    SpringApplication.run(Starter2Application.class, 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("<html><head><title>");
        writer.println("hey, Tomcat");
        writer.println("</title></head>");
        writer.println("<body><h1>hello tomcat</h1></body>");
        writer.println("</html>");
      }
    };

    String servletName = "helloServlet";
    tomcat.addServlet("/", servletName, servlet); // "/" 컨텍스트 패스에 위치하는 servletName에 servlet을 추가함
    context.addServletMappingDecoded("/", servletName); // "/hello" 요청이 오면 servletName을 보여주겠다.

    tomcat.start();
    tomcat.getServer().await();
  }
}

5. Maven으로 Jar파일 만들기
- set JAVA_HOME="JAVA_PATH"
- mvwn package
- ./target/*.jar 파일 확인
- java -jar 파일명.jar

6. 서블릿
- 서블릿에 대한 이해 : 웹프로그래밍에서 클라이언트의 요청을 처리하고 그 결과를 다시 클라이언트에게 전송하는 Servlet 클래스의 구현 규칙을 지킨 자바 프로그래밍 기술, 자바를 사용하여 웹을 만들기 위해 필요한 기술! 클라이언트가 어떤 요청을 하면 그에 대한 결과를 다시 전송해야 하는데 이러한 역할을 하는 자바 프로그램! 즉 서블릿이다. 서블릿은 자바로 구현된 CGI라고 흔히 말한다. (동적인 페이지를 생성하는 어플리케이션을 CGI라고 함, Common Gateway Interface; CGI)

- 서블릿의 특징 : 클라이언트의 요청을 동적으로 작동하는 웹 어플리케이션 컴포넌트
html을 사용하여 요청에 응답함, HTTP 프로토콜 서비스를 지원하는 javax.servlet.http.HttpServlet 클래스를 상속받는다. UDP보다 속도가 느리다. HTML 변경시 Servlet을 재컴파일해야 하는 단점이 있다.


- 서블릿 컨테이너? 서블릿을 관리해주는 컨테이너
우리가 서버에 서블릿을 만들었다고 해서 스스로 작동하는 것이 아니고, 서블릿을 관리해주는 것이 필요한데 그러한 역할을 하는 것이 바로 서블릿 컨테이너 입니다. 예를 들어, 서블릿이 어떠한 역할을 수행하는 정의서라고 보면, 서블릿 컨테이너는 그 정의서를 보고 수행한다고 볼 수 있습니다. 서블릿 컨테이너는 클라이언트의 요청(Request)을 받아주고 응답(Response)할 수 있게, 웹서버와 소켓을 만들어 통신하며 대표적인 예로 톰캣(Tomcat)이 있습니다. 톰캣은 실제로 웹서버와 통신하여 JSP(자바 서버 페이지)와 Servlet이 작동하는 환경을 제공해줍니다.

- 서블릿 컨테이너 역할
웹서버와의 통신 지원 : 서블릿 컨테이너는 서블릿과 웹서버가 손쉽게 통신할 수 있게 해줍니다. 일반적으로 우리는 소켓을 만들고 listen, accept 등을 해야하지만 서블릿 컨테이너는 이러한 기능을 API로 제공하여 복잡한 과정을 생략할 수 있게 해줍니다. 그래서 개발자가 서블릿에 구현해야 할 비지니스 로직에 대해서만 초점을 두게끔 도와줍니다.

- 서블릿 생명주기(Life Cycle) 관리 서블릿 컨테이너는 서블릿의 탄생과 죽음을 관리합니다. 서블릿 클래스를 로딩하여 인스턴스화하고, 초기화 메소드를 호출하고, 요청이 들어오면 적절한 서블릿 메소드를 호출합니다.  또한 서블릿이 생명을 다 한 순간에는 적절하게 Garbage Collection(가비지 컬렉션)을 진행하여 편의를 제공합니다.

- 멀티쓰레드 지원 및 관리
서블릿 컨테이너는 요청이 올 때 마다 새로운 자바 쓰레드를 하나 생성하는데, HTTP 서비스 메소드를 실행하고 나면, 쓰레드는 자동으로 죽게됩니다. 원래는 쓰레드를 관리해야 하지만 서버가 다중 쓰레드를 생성 및 운영해주니 쓰레드의 안정성에 대해서 걱정하지 않아도 됩니다.

- 선언적인 보안 관리
서블릿 컨테이너를 사용하면 개발자는 보안에 관련된 내용을 서블릿 또는 자바 클래스에 구현해 놓지 않아도 됩니다. 일반적으로 보안관리는 XML 배포 서술자에 다가 기록하므로, 보안에 대해 수정할 일이 생겨도 자바 소스 코드를 수정하여 다시 컴파일 하지 않아도 보안관리가 가능합니다.

- 서블릿 생명주기
1. 클라이언트의 요청이 들어오면 컨테이너는 해당 서블릿이 메모리에 있는지 확인하고, 없을 경우 init()메소드를 호출하여 적재합니다. init()메소드는 처음 한번만 실행되기 때문에, 서블릿의 쓰레드에서 공통적으로 사용해야하는 것이 있다면 오버라이딩하여 구현하면 됩다. 실행 중 서블릿이 변경될 경우, 기존 서블릿을 파괴하고 init()을 통해 새로운 내용을 다시 메모리에 적재합니다.

2. init()이 호출된 후 클라이언트의 요청에 따라서 service()메소드를 통해 요청에 대한 응답이 doGet()가 doPost()로 분기됩니다. 이때 서블릿 컨테이너가 클라이언트의 요청이 오면 가장 먼저 처리하는 과정으로 생성된 HttpServletRequest, HttpServletResponse에 의해 request와 response객체가 제공됩니다.

3. 컨테이너가 서블릿에 종료 요청을 하면 destroy()메소드가 호출되는데 마찬가지로 한번만 실행되며, 종료시에 처리해야하는 작업들은 destroy()메소드를 오버라이딩하여 구현하면 됩니다.

7. 내장 서블릿 컨테이너
- 웹 서버 미사용 : application.properties > spring.main.web-application-type=none
- 포트 변경 : application.properties > server.port=7070 (포트를 7070으로 변경)
- 랜덤 포트 : application.properties > server.port=0

- HTTPS 설정 (추후 수정)
키스토어 만들기 : keytool -genkey -alias spring -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 4000

8. 스프링 부트 원리
- spring-boot-dependencies (Spring Boot에서 사용하는 모든 의존성이 존재함)
- pom.xml 에서 특정 의존성 지정 가능
- 자동 설정 : @SpringBootApplication (@EnableAutoConfiguration, @ComponentScan)
- 내장 웹서버 : Tomcat, Jetty..

9. SpringApplication (추후 수정)

10. 외부 설정
- 사용할 수 있는 외부 설정 : properties (application.properties) 
스프링부트가 자동으로 로드하는 규약
h232ch.name = 'h232ch' (Key/Value 현태로 선언) 이후 클래스 내에서 변수로 사용 가능

@Component
public class SampleRunner implements ApplicationRunner {

    @Value("${h232ch.name}")
    private String name;

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

- test/resources/application.context 파일이 product보다 우선시 됨 ->
테스트코드 작동시 (프로젝트 설정 -> 모듈 -> test/resource 폴더를 Test resource로 지정 필요)
- 위 방법은 비추함 -> @TestPropertySource(properties="key=value")
- 프로퍼티 변수가 많을시 application.properties 생성 후 ->
@TestPropertySource(locations = "classpath:/test.properties")로 사용
- application.properties 지정 가능 위치 (높은게 낮은걸 덮어씀)
file:./config/   -> Root/config 디렉토리
file:./   -> Root 디렉토리
classpath:/config/  -> Jar 디렉토리 (Target)
classpath:/   -> Jar 디렉토리 (Target)
properties 우선순위는 다양하여 테스트의 성격에 맞춰 잘 써야함

- YAML
- 환경변수
- 커맨드 라인 아규먼트

- 프로퍼티 우선순위
1. 유저 홈 디렉토리에 있는 spring-boot-dev-tools.properties -> 스프링부트 데브툴 의존성 -> 리로딩 도움 역할
2. 테스트에 있는 @TestPropertySource
3. @SpringBootTest 애노테이션의 properties 애트리뷰트
4. 커맨드 라인 아규먼트(java -jar snapshot --test.name=h232ch) -> application.properties 보다 우선순위가 높기 때문에 오버라이드 됨
5. SPRING_APPLICATION_JSON(환경변수 또는 시스템 프로퍼티)에 들어있는 프로퍼티
6. ServletConfig 파라미터
7. ServletContext 파라미터
8. java:comp/env JNDI 애트리뷰트
9. System.getProperties() 자바 시스템 프로퍼티
10. OS 환경변수
11. RandomValuePropertySource
12. JAR 밖에 있는 특정 프로파일용 application properties
13. JAR 안에 이는 특정 프로파일용 application properties
14. JAR 밖에 있는 application properties
15. JAR 안에 있는 application properties (application.propertiess)
16. @PropertySource

- application.properties Bean으로 등록 (추후 수정)
-  융통성 있는 바인딩 (application.properties에 아래와 같이 적어도 매칭을 해줌 -> full_name == fullname)
context-path (케밥)
context_path (언더스코어)
contextPath (캐멀)
CONTEXTPATH

- 프로퍼티 타입 컨버전 : 스프링부트에서 제공하는 컨버전 기능 -> applicatoin.properties 변수 -> Int, string 등등의 형으로 알맞게 변환하여 변수에 주입

@Component
@ConfigurationProperties("h232ch") // application.properties 내에 h232ch.으로 시작하는 변수에 대해서 범위 적용
public class shProperties {

    private String name;
    private int age;
    private String fullName;


    @DurationUnit(ChronoUnit.SECONDS)
    private Duration sessionTimeout = Duration.ofSeconds(30);

--

# shRunner.class

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

    // sessionTimeout이 Int -> Duration으로 컨버전이 일어난다 

 

11. 프로퍼티 값 검증
- @Validated
- JSR-303 (@NotNull, ...)

@Component
@ConfigurationProperties("sh")
@Validated // 값을 검증 JSR-393 구현체

public class shProperties {

    @NotEmpty // @Validated 기능으로 @NotEmpty 사용 가능
    private String name;
    @Size(min=0, max=100) // @Validated 기능으로 @SIze 사용 가능
    private int age;
    private String fullName;

 

12. 프로파일 : 특정한 프로파일에서만 빈을 등록, 애플리케이션 동작을 특정 프로파일에서 다르게 동작할떄 사용

# BaseConfiguration

@Profile("prod") 
@Configuration
public class BaseConfiguration {

    @Bean  // 이빈은 "prod" 프로파일 설정이 되어야 사용됨
    public String hello() {
        return "Hello";
    }
}

# TestConfiguration


@Profile("test")
@Configuration
public class TestConfiguration {
    @Bean // 이빈은 "test" 프로파일 설정이 되어야 사용됨
    public String hello() {
        return "Hello";
    }
}

- application.properties -> switch 역할 : spring.profiles.active=prod or test를 수정해서 사용
- @Profile 애노테이션 사용
@Configuration, @Component
- 어떤 프로파일을 활성화 할것인가?
application.properties > spring.profiles.active
- 프로파일용 프로퍼티 : application-{profile}.properties -> 기본 application.properties 보다 우선순위가 높음
- application-prod.properties > h232ch.name=h232ch
- application-test-properties > h232ch.name=h232ch_TEST > 우선순위가 높음

13. 로깅
- 로깅 퍼사드 VS 로거
로깅 퍼사드 :
Commons Logging(문제가 많았음), SLF4j( Commons Logging 대안으로 새롭게 생긴 라이브러리) : 실제 로깅을 하는게 아니라, 로거 API를 추상화해놓은 Interfaces 이다. 주로 프레임워크 개발자들이 사용함 > 로깅 퍼사드(Commons Loging, SLF4j)를 이용해서 아래의 로거 종류를 선택할 수 있음
- JUL, Log4J2, Logback

- 스프링부트는 Commons Logging을 쓰고있다. (스프링 버전 1에서 Commons Logging을 Exclusion 시키고 SLF4j를 넣으면 구현은 가능하나 디펜던시 설정을 명확히 해야함) 하지만 스프링 버전 5부터 Spring-JCL을 추해서 SLF4j or Log4J2로 변경하는 기능을 추가했다.
스프링부트는 Commons Logging을 쓴다. 이는 Logback으로 연결된다. 결국 우리는 Logback을 쓰고있는것이다. -> Common Logging(JUL, Log4J2 사용시 SLF4j로 보낸다.) -> SLF4j -> Logback (SLF4j의 구현체)

- 스프링 부트 로깅
기본포맷
--debug (일부 핵심 라이브러리만 디버깅 모드로) VMoptions에 -Ddebug 옵션추가 -> embedded container, hibernate, spring boot만 debug 모드에서 수행
 --trace (전부 다 디버깅 모드로)
컬러 출력 : application.properties > spring.output.ansi.enabled=always
파일 출력 : application.properties > logging.file or logging.path
로그 레벨 조정 : logging.level.패키지  = 로그레벨 (ex. logging.level.com.starter.springinit=DEBUG)
스프링 프레임워크에 Debug 모드 적용 : logging.level.org.springframework=DEBUG

14. 테스트
- @SpringBootTest : 메인의 @SpringBootApplication을 찾아가서 모든 빈스캔을 다 해서 테스트용 application context를 생성하면서 다넣어줌 -> MockBean이 존재하는경우 해당 빈을 교체까지 함
@RunWith(SpringRunner.class)와 함꼐 사용해야 함
- webEnvironmnet(@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK or RAND.. NONE)
Dispatcher 서블릿에 요청을 보내는 것과 비슷하게 실행할 수 있음
MockMVC라는 클라이언트를 꼭 사용해야 함(MockMVC가 클라이언트 되어 요청을 보내는 역할을 함)
- RANDOM_PORT, DEFINE_PORT : 내장 톰캣 사용함
- NONE : 서블릿 환경 제공 하지 않음

- MockBean : ApplicationContext에 들어있는 빈을 mock으로 만든 객체로 교체함, 모든 @Test 마다 자동으로 리셋됨
- 슬라이스 테스트 (각 기능별 테스트를 하고자 할때)
레이어 별로 잘라서 테스트하고 싶을때
@JsonTest (Json 요청/응답만 따로 테스트할수 있음)
@WebMvcTest (특정 계층 테스트시 사용 -> Cotroller 테스트시 사용
Service는 Mocking 해서 사용해야 함 -> 많이 사용됨) -> @WebMvcTest 적용시 @AutoConfigureMockMVC가 적용됨

@RunWith(SpingJunit4ClassRunner.class) : Junit 라이브러리의 @RunWith는 제이유닛 내장 실행기(runner) 대신 SpingJunit4ClassRunner.class를 참조하여 테스트를 실행, Junit의 BlockJunit4ClassRunner를 커스터마이징한 클래스로, 스프링 테스트 컨택스트 프레임워크의 모든 기능을 제공

@SpringRunner : SpringJunit4ClassRunner를 상속한 클래스로, 사실상 클래스 이름만 짧게 줄인 동일한 클래스

@SpringBootTest : 일반적인 스프링 부트 기반의 테스트 클래스에 붙이는 어노테이션. 속성을 추가해서 애플리케이션에 따라 설정을 다르게 할 수 있음 (해당 애노테이션을 이용할 때는 @AutoConfiureMockMVC 애노테이션 추가해줘야 Mock 사용가능)

@WebMvcTest(SampleController.class)
@RunWith(SpringRunner.class)
@AutoConfigureMockMvc // Mock Bean을 사용할 수있도록 함 -> MockMvc는 서블릿 기능의 Bean이다.
public class SampleControllerTest {

    @MockBean
    SampleService mockSampleService;

    @Autowired
    MockMvc mockMvc;

    @Test
    public void hello() throws Exception {
        when(mockSampleService.getName()).thenReturn("Kim");
        mockMvc.perform(get("/hello")) // perform으로 요청 테스트
                .andExpect(status().isOk()) // status, contenst, print 등은 모두 MockMvcResultHandlers에서 가져와야 함
                .andExpect(content().string("Hello Kim"))
                .andDo(print());
    }

 

15. 테스트 유틸
- OutputCapture : 
코드에 Logger 빈을 생성하여 logging 작업을 할때 테스트 코드에 로깅을 찍는 기능을 한다 (유용한 기능)

@RunWith(SpringRunner.class)
@AutoConfigureMockMvc // Mock Bean을 사용할 수있도록 함 -> MockMvc는 서블릿 기능의 Bean이다.
@WebMvcTest(SampleController.class)
public class SampleControllerTest {


  @Rule
    public OutputCapture outputCapture = new OutputCapture();

    @MockBean
    SampleService mockSampleService;

    @Autowired
    MockMvc mockMvc;

    @Test
    public void hello() throws Exception {
        when(mockSampleService.getName()).thenReturn("Kim");
        mockMvc.perform(get("/hello")) // perform으로 요청 테스트
                .andExpect(status().isOk()) // status, contenst, print 등은 모두 MockMvcResultHandlers에서 가져와야 함
                .andExpect(content().string("Hello Kim"))
                .andDo(print());

        assertThat(outputCapture.toString())
                .contains("Kim")
                .contains("Captured Kim sysout");
    }

 

16. Spring-Boot-Devtools : 스프링부트가 제공하는 옵셔널(선택) 툴
 - 캐시 설정을 개발 환경에 맞게 변경 : 캐시를 다 끄고(브라우저에서 즉시적용), 
세션 퍼시스턴트를 킴 등 개발환경에 맞춰서 바꿔줌
- 클래스패스에 있는 파일이 변결될 때마나다 자동으로 재시작
- 라이브 리로드? 리스타트 했을때 브라우저 자동 리프레시 하는 기능
(브라우저의 익스텐션 기능으로 설치해야 함 : 라이브 리로드) : 프론트엔드 개발자들이 많이 애용하는 기능

17. 스프링 웹 MVC
- Spring WebMVC 기능 (@GetMapping, PostMapping...등) : 서블릿 기능의 Dispatcher 기능을 제공해줌
- 스프링 MVC 확장 (MVC 기능을 추가설정 하고싶을때) : Configuration + WebMvcConfigurer
- 스프링 MVC 재정의 : @Configuration + @EnableWebMvc (@EnableWebMvc 사용시 스프링부트의 자동설정 기능이 빠지기 때문에 수동으로 모든 설정을 해줘야해서 비추하는 방법)

package com.example.demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
//@EnableWebMvc 
public class WebConfig implements WebMvcConfigurer { // WebMvcConfigurer 인터페이스 기능일 오버라이딩하여 재정의 후 사용
    


- HttpMessageConverters (Springframework에서 제공하는 인터페이스면서 SpringMvc의 일부이다.)
HTTP 요청 본문을 객체로 변경하거나, 객체를 HTTP 응답 본문으로 변경할 때 사용
{“username”:”h232ch”, “password”:”123”} <-> User
@ReuqestBody, @ResponseBody

package com.example.demo.user;

import org.springframework.web.bind.annotation.*;

@RestController
public class UserController {

    @GetMapping("/hello2")
    public String hello2(){
        return "hello2";
    }

    @PostMapping("/user")
    public @ResponseBody User create(@RequestBody User user) { // 요청바디를 User 객체로 받는다.
        return user; // Json 데이터가 바디에 존재한다면 HttpMessageConverter가 User 객체로 변환하여 넣어줌
        // Return 시에도 HttpMessageConverter가 User 객체를 Json 객체로 변환하여 넣어줌
        // 그냥 문자열 하나일 경우 StringMessageConverter가 사용됨
        // @RestController 사용시 public "@ResponseBody" <- 생략 가능
        // @RestController : HttpMessageConverter를 사용하게 함
    }

 

package com.example.demo.user;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringRunner.class) // junit spring runner 역할
@WebMvcTest(UserController.class) // Servlet 기반 테스트 수행
public class UserControllerTest {

    @Autowired
    MockMvc mockMvc;

    @Test
    public void hello2() throws Exception {
        mockMvc.perform(get("/hello2"))
                .andExpect(status().isOk())
                .andExpect(content().string("hello2"));
    }

    @Test
    public void createUser_JSON() throws Exception {

        String userJson = "{\"username\":\"h232ch\", \"password\":\"123\"}";

        mockMvc.perform(post("/users/create")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON)
            .content(userJson))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.username", is(equalTo("h232ch"))))
                .andExpect(jsonPath("$.password", is(equalTo("123"))))
                .andDo(print());
    }

}

- 뷰 리졸버 설정 제공 (ContentNegotiationgViewResolver -> SpringMVC에 내장된 기능으로 자동적용됨 : View Resolver 중 하나, 들어오는 액셉트 헤더에 따라 응답이 달라지게 함)
액셉트 헤더라는건 브라우저가 어떤타입의 응답의 본문을 원하는지 서버에게 알려주는 역할을 함
어떠한 요청이 들어오면 그 요청을 응답할수 있는 모든 뷰를 찾고 최종으로 액셉트 타입과 비교해서 뷰를 선택하여 리턴함, 클라이언트가 어떤 뷰를 원하느냐 판단하는 기준은 액셉트 헤더이다. 경우에 따라 엑셉트 헤더를 제공하지 않는 경우도 있다. 그럴경우 ?format=pdf 등의 타입을 제공해서 뷰를 알려주기도 함

- 정적 리소스 지원 (기본 리소스 위치)
classpath:/static
classpath:/public
classpath:/resources/
classpath:/META-INF/resources
- "hello.html" > /static/hello.html 존재시 hello.html 응답해줌 (ResourceHttpRequestHandler가 처리함)
- ex. "/hello.html" > /static/hello.html
- spirng.mvc.static-path-pattern : 매핑 설정 변경 가능 (application.properties에 지정)
- spring.mvc.static-path-pattern=/static/**
- spring.mvc.static-locations : 리소스 찾을 위치 변경 가능 (비추)


- 웹 JAR (Client에서 사용하는 Jquery, Vuew.js, Angular 등등 webjar로 추가 가능)

<!-- https://mvnrepository.com/artifact/org.webjars.bower/jquery -->
<dependency>
    <groupId>org.webjars.bower</groupId>
    <artifactId>jquery</artifactId>
    <version>3.3.1</version>
</dependency>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
Hello Static Resource
<script src="/webjars/jquery/3.3.1/dist/jquery.min.js"></script>
<script>
    $(function() {
        alert("ready");
        });
</script>


</body>
</html>

웹JAR 맵핑 “ /webjars/**”
버전 생략하고 사용하려면 : webjars-locator-core 의존성 추가

<!-- https://mvnrepository.com/artifact/org.webjars/webjars-locator-core -->
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>webjars-locator-core</artifactId>
    <version>0.35</version>
</dependency>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
Hello Static Resource
<!--<script src="/webjars/jquery/3.3.1/dist/jquery.min.js"></script> -->
<script src="/webjars/jquery/dist/jquery.min.js"></script>
<script>
    $(function() {
        alert("ready");
        });
</script>


</body>
</html>

 

- index 페이지
웰컴 페이지 (정적 or 동적 페이지를 보여주는 방법 존재)
기본 리소스 위치에
classpath:/static/index.html
classpath:/public/index.html
classpath:/resources/index.html
classpath:/META-INF/resources/index.html
index.html 찾아보고 있으면 제공, index.템플릿 찾아보고 있으면 제공, 둘다 없으면 에러페이지 출력

- 파비콘 (https://favicon.io -> favicon.ico 제작후 resources/static 디렉토리 아래 넣어줌)
파비콘이 안 바뀔 때? stackoverflow.com/questions/2208933/how-do-i-force-a-favicon-refresh

 

How do I force a favicon refresh?

I have a Grails application running locally using its own tomcat and I have just changed the favicon for a new one. Problem is that I can not see it in any browser. The old favicon shows up or I ...

stackoverflow.com

- Thymeleaf : 템플릿 엔진
의존성 추가: spring-boot-starter-thymeleaf

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

템플릿 파일 위치: /src/main/resources/template/
예제 : github.com/thymeleaf/thymeleafexamples-stsm/blob/3.0-master/src/main/webapp/WEB-INF/templates/seedstartermng.html

 

thymeleaf/thymeleafexamples-stsm

Spring Thyme Seedstarter Manager - Companion application for the "Thymeleaf + Spring 3" tutorial downloadable at the Thymeleaf website: http://www.thymeleaf.org/documentation.html - thym...

github.com

package com.example.demo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class SampleController1 {

    @GetMapping("/hello3")
    public String hello3(Model model){ // hello3 은 뷰 이름이 됨, Model 데이터를 받을 model 매개변수
        model.addAttribute("name", "h232ch");
        return "hello3"; // 응답의 본문이 아니다. RestController가 아니기 때문
        // return 값은 뷰 이름을 던진다. 데이터를 처리하고 가야할 뷰 이름 -> 즉 템플릿 디렉토리 아래 hello3.html을 찾아가게 함
    }
}
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@RunWith(SpringRunner.class)
@WebMvcTest(SampleController1.class)
public class SampleController1Test {

    @Autowired
    MockMvc mockMvc;

    @Test
    public void hello3() throws Exception {
//        요청 : "/hello3"
//        응답
//        - 모델 name : h232ch
//        - 뷰 이름 : hello

        mockMvc.perform(get("/hello3"))
                .andExpect(status().isOk())
                .andDo(print())
                .andExpect(view().name("hello3"))
                .andExpect(model().attribute("name", is("h232ch")));
    }
<!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>

 

- ExceptionHandler (SpringApplication에 이미 Error Handler 기능이 등록되어 있다. 우리가 springApplication.run에서 보는 에러 메세지는 모두 이 ErrorHandler가 뿌려줌 -> WhiteLabe Error 등)
- ex) crul 에러 메세지 ({"timestamp":"2020-08-03T08:30:08.731+0000","status":404,"error":"Not Found","message":"No message available","path":"/fd"}) : 해당 에러도 ErrorHandler에 저장되어있는 폼으로 나가는 것이다.

package com.example.demo;

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;
    }
}
package com.example.demo;

public class SampleException extends RuntimeException {
  
}
package com.example.demo;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class SampleController2 {

    @GetMapping("/hello4")
    public String hello4(){
        throw new SampleException(); // 에러 발생 유발 (런타임에러를 임플리먼트하는 클래스)
        // 에러 발생시 @ExceptionHandler가 SampleException 처리 수행
    }

    @ExceptionHandler(SampleException.class)
    public @ResponseBody AppError sampleError(SampleException e){
        AppError appError = new AppError();
        appError.setMessage("error.app.key");
        appError.setReason("IDK IDK IDK");
        return appError;
    }
}


- 커스텀 에러 페이지
상태 코드값에 따라 에러 페이지 보여주기
src/main/resources/static | template/error/
404.html, 5xx.html 등 작성시 해당 코드의 오류 발생시 해당 페이지를 보여줌

- Spring HATEOAS

- CORS (Cross Origin Resource Sharing)
SOP (Single Origin Policy)와 CORS (SOP를 우회하기위한 기술)
Single-Origin Policy : 같은 오리진에만 요청을 보낼수있다. (기본적으로 오리진이 다르면 서로 호출이 안됨 -> SOP가 기본임)
Cross-Origin Resource Sharing : 서로다른 오리진끼리 리소스를 쉐어할 수 있다 (스프링프레임워크에서 제공하는 표준) -> 부트에서는 빈설정 없이 바로 사용 가능
Origin? 아래의 세가지를 조합한것이 하나의 오리진이다.
(http:test.org -> http://test.org:8080 호출이 안됨 -> CORS로 해결)
URI 스키마(http, https), hostname(localhost.. test.org), port(8080, 18080)

package com.starter2.starter2;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer { // 스프링부트가 제공하는 MVC기능을 다 사용하면서 확장사용


    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 전체를 CORS 허용
//        registry.addMapping("/hello10") // hello10만 허용
                .allowedOrigins("http://localhost:18080");
    }
}
@SpringBootApplication
@RestController
//@EnableConfigurationProperties(shProperties.class) 자동으로 등록되어 있음
public class Starter2Application {

//  @CrossOrigin(origins = "http://localhost:18080") // Cross 도메인 설정 (이제 18080에서 8080으로 요청 가능)
  @GetMapping("/hello10")
  public String hello10(){
    return "Hello10";
  }

'4. Backend Development > 1. Spring Framework' 카테고리의 다른 글

8. Spring WEB MVC  (0) 2020.09.28
6. DispatcherServlet  (0) 2020.09.01
5. Servlet  (0) 2020.09.01
4. MacOS Security Management System  (0) 2020.07.21
3. Spring Framework 기반 웹 프로젝트  (0) 2020.06.09

댓글