@GetMapping, @PostMapping 등등의 어노테이션을 사용하면, 편리하게 축약해서 사용할 수 있다.
배열을 만들어 여러개의 url을 매핑하는 것도 가능하다.
2) 경로 변수 사용 - @PathVariable
@GetMapping("/movies/{movieId}")
public String readMovie(@PathVariable("movieId") Long data){}
//변수명 같으면 생략 가능
@GetMapping("/movies/{movieId}")
public String readMovie(@PathVariable Long movieId){}
restApi를 사용하면 "/movies/1" 등과 같이 url에 id와 같은 변수를 사용한다.
@PathVariable 을 사용하면 경로 변수를 컨트롤러 메소드 함수 인자로 편하게 받아올 수 있다.
ReqeustMappingHandler에서 헤더, 기본값을 꺼내는 방법은 파라미터로 다음과 같은 값을 넣어주면 된다.
HttpservletRequest request
HttpServletResponse response
HttpMethod method
Locale locale
@RequestHeader("host")
@CookieValue(value="cookieName", required=false)
이외에도 여러가지 값들을 메소드 파라미터로 넣을 수 있고, 사용할 수 있다.
3. Http 요청 조회
1) 쿼리 파라미터 & HTML Form - @RequestParam
request parameter 형식으로 데이터가 전달되어 같은 방법으로 둘 다 조회 가능하다.
HttpServletRequest - request.getParameter()
@RequestParam
// url = "/movies?movieName=parasite&stars=200
@RequestMapping("/movies")
public String requestParam(
@RequestParam("movieName") String movieName,
@RequestParam("stars") int stars){}
//변수명 같으면 생략 가능
@RequestMapping("/movies")
public String requestParam(
@RequestParam String movieName,
@RequestParam int stars){}
//어노테이션도 사실 생략 가능 -> 헷갈리니까 잘 안씀
@RequestMapping("/movies")
public String requestParam(String movieName, int stars){}
//필수값 지정, 기본은 true
// "/movies?stars=200 오류 x , movieName = null
// "/movies?movieName=&stars=200 오류 x, movieName에 빈 문자열 들어감
// "/movies?movieName=parasite 400 오류
@RequestMapping("/movies")
public String requestParam(
@RequestParam(required=false) String movieName,
@RequestParam int stars){}
쿼리 파라미터 혹은 Html form으로 정보가 넘어오는 경우 @RequestParam 어노테이션을 자주 사용한다.
이것도 변수명이 같으면 생략이 가능하며 String, int 단순 타입이면 어노테이션도 사실 생략 가능하다.
필수 파라미터 여부를 (required = false, true)로 설정 가능하다.
기본은 true 이고 true로 설정해놓은 파라미터에 값이 없으면 400 Bad Request가 발생한다.
false로 설정해놓은 파라미터가 요청에 없을시 null이 지정된다.
파라미터 이름만 사용, 값 입력 x → null이 아니라 빈문자열이 들어간다..
int로 파라미터 받으면 null 설정이 안되서 required=false여도 요청에 없으면 500 오류가 발생한다.
<그외>
defaultValue 옵션 : 기본값 설정 (빈 문자도 기본값으로 해줌)
맵으로도 조회 가능 (@RequestParam Map<String,Object>)
MultiValueMap을 이용하면 파라미터 하나로 여러 값 받아오기가 가능하다.
2) 쿼리 파라미터 & HTML Form - @ModelAttribute
// url = "/movies?movieName=parasite&stars=200
public class MovieRequest{
String movieName;
int stars;
}
@RequestMapping("/movies")
public String modelAttribute(@ModelAttribute MovieRequest request){}
//어노테이션 생략 가능
@RequestMapping("/movies")
public String modelAttribute(MovieRequest request){}
@ModelAttribute를 사용하면 객체를 파라미터로 받아올 수 있다.
스프링 mvc는 @ModelAttribute가 있으면 MovieRequest 객체를 생성한 후
요청 파라미터 이름과 객체의 프로퍼티를 비교해 값을 바인딩한다.
이것도 어노테이션을 생략해도 된다.
그러나 String, int, Integer 등의 타입이 파라미터에 있는데 생략하면 @RequestParam을 적용시킨다.
후술할 ArgumentResolver 으로 세팅 혹은 예약된 경우 아닌 클래스의 경우만 ModelAttribute 적용한다.
3) 단순 텍스트 (text, xml, json 등등) - @RequestBody
Http Body에 값이 들어오는 경우는 앞의 두 파라미터로 꺼내오기가 불가능하다.
ServletInputStream 사용해서 직접 꺼내오기
HttpEntity,RequestEntity를 이용해서 꺼내오기
@RequestBody 사용하기
// url = "/movies
public class MovieRequest{
String movieName;
int stars;
}
@RequestMapping("/movies")
public String modelAttribute(@RequestBody String text){}
@RequestMapping("/movies")
public String modelAttribute(@RequestBody MovieRequest request){}
@RequestBody 어노테이션을 사용하면 String이나 JSON 형식으로 Http 바디에 담겨오는 정보들을 HttpMessageConverter가 알아서 변환해서 잘 매핑해준다.
결론은
파라미터 조회 : RequestParam or ModelAttribute
바디 조회 : RequestBody
헤더 조회 : @RequestHeader 또는 HttpEntity 사용하자.
4. HTTP 응답
1) 정적 리소스(html)
spring boot에서 정적 리소스 반환 기능을 자동으로 제공한다.
resource/static 폴더에 html 저장하고 url에 파일 경로를 입력하면 html파일이 전달된다.
2) 뷰 템플릿(동적인 html)
@Controller
public TestController{
@GetMapping("/movies")
public String getMovie(){
return "movie-list"; //뷰 이름 반환
}
@GetMapping("/movies/1")
public String getMovieDetail(Model model){
String data = "movie-data";
model.addAttribute("data",data); //model 사용해 정보 전달
return "response/movie-detail.html"; // 뷰 경로 반환
}
}
타임리프등 뷰 템플릿을 사용할 시에는 resource/templates 폴더에 저장한 후
컨트롤러에서 String 으로 파일경로 혹은 이름을 반환하면 된다.
model.addAttribute()로 모델에 값을 넣어 템플릿에 전달이 가능하다.
Controller가 String 반환할경우 ResponseBody이 없으면 ViewResolver가 경로나 뷰 이름으로 뷰를 찾아 렌더링해준다.
3) 바디에 직접 입력(JSON)
@Controller
public TestController{
@ResponseBody
@GetMapping("/movies")
public String getMovies(){
return "movies"; // 문자열 그대로 반환
}
@GetMapping("/movies/1")
public ResponseEntity<String> getMovieDetail(){
return new ResponseEntity<>("ok", HttpStatus.ok);
}
}
ResponseBody : 그냥 String 또는 객체를 반환한다. (객체의 경우 JSON 형식으로 반환된다)
ResponseEntity : String,객체 + status code 반환를 반환한다.
RestController 쓰면 (ResponseBody + Controller) 를 한 것과 같이 작동한다.
5. Http 메세지 컨버터
Http Body에서 정보 읽거나 Body에 정보를 입력해서 반환할때 스프링 부트에선 메세지 컨버터를 사용한다.
인터페이스로 만들어져있으며다양한 종류의 HttpMessageConverter 가 존재한다. (우선순위 존재)
ByteArrayHttpMassageConverter : 바이트 단위로 받아오기 가능
StringHttpMessage : 문자(String)으로 데이터 처리 (text/plain)
MappingJacksonConverter : 객체로 데이터 처리, 주로 JSON
Spring boot는 Accept 헤더(클라이언트의 해석 타입) , 컨트롤러 반환 타입등의 정보를 조합해서 컨버터를 선택한다.
요청 처리 순서
Http 요청 + @RequestBody or HttpEntity, RequestEntity
모든 컨버터 대상 canRead() 메소드 호출 (대상 클래스 타입, medeaType, Accept type 지원하나 확인)
read()로 객체 생성, 정보 입력하고 반환
6. 요청 매핑 핸들러 어뎁터의 구조
HttpMethodConverter는 그러면 어디서 동작할까?
어노테이션 기반 컨트롤러에 요청이 왔을때 DispatcherServlet은 RequestMappingHandler와 어댑터를 불러온다.
Auditing을 하는 이유가 로그를 추적하고 확인하기 편하게 하려고 라는 내용을 들었다.
그렇다면 로그를 어떤 식으로 남겨서 어떻게 활용하는 걸까??
1. Slf4j 와 그 외 로깅 라이브러리
스프링 부트로 프로젝트를 만들었을때 기본으로 설치되어 있는 springboot-starter 라이브러리는 slf4j 라이브러리를 포함한다.
SLF4J는 이름을 왜 이따구로 지었는지는 잘 모르겠지만 다양한 로깅 라이브러리에 대한 추상 레이어를 제공하는 인터페이스이다.
추후에 로깅 라이브러리를 변경해도 코드를 수정하지 않아도 되도록 지원해준다.
Log4j, Logback 등의 로깅 라이브러리를 연결해 사용할 수 있으며
실무에선 스프링부트에서 기본 제공하는 Logback 많이 사용한다.
2. 활용하는 법과 주의점
@RestController
public class UserController{
private final Logger logger = LoggerFactory.getLogger(getClass());
@GetMapping("/users")
public ResponseEntity<FindAllUserResponse> findAllUserName(){
//생략
logger.info("[INFO] find All User Name {}", data); // GOOD
logger.info("[INFO] find All User Name " + data); // BAD!!
return "ok";
}
}
slf4j 라이브러리의 Logger를 LoggerFactory를 통해 주입 받은 후 사용하면 된다.
@Slf4j 어노테이션으로 연관관계 주입 과정을 간략화 할 수도 있다.
이때 로거 안에 + 연산자 등을 이용해 문자열 연산을 넣으면
로깅 시 불필요한 연산으로 인한 메모리 및 자원 낭비, 성능 저하를 유발할 수 있다.
실제 로그는 다음과 같은 방식으로 출력된다. (데이터는 넣지 않았다)
실행시간 로그레벨 프로세스ID —[쓰레드명] 클래스명 : 메세지
3. 로그의 종류
로그의 종류는 심각도에 따라 5가지로 나뉜다.
trace < debug < info < warn < error
trace, debug는 디버깅용 출력 메세지이며 trace가 좀 더 상세한 메세지를 제공한다.
info는 정보성 메세지이며
warn은 처리 가능한 문제 혹은 향후 에러 원인이 될 수 있는 문제 로그이며
error은 실행 중 발생한 에러를 나타낸다.
application.properties, yml 파일에서 콘솔에 출력할 logging 레벨 설정이 가능하다.
trace(전부 보기), debug(개발), info(운영시,기본) 으로 보통 설정한다.
4. 실무에서 어떻게 사용할까?
1) logback 설정파일 만들기
예전에 장고로 api를 개발할때 하루에 한 번씩 자동으로 수행되는 크론을 사용했었다.
이때 하루에 한 번 수행되는 것이 맞는지 테스트하기 위해, 실행될때마다 cron.log 파일에 로그를 찍어 확인했던 기억이 난다.
logback또한 파일에 로그를 따로 기록하는 것이 가능하며, 콘솔에 출력되는 로그의 형식을 바꾸는 것도 가능하다.
/src/resources 폴더에 logback-spring.xml 파일을 만들어 설정이 가능하다.
@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest{
@Mock
private UserRepository userRepository;
@InjectMocks
private UserServiceImpl userService;
@Test
public void 유저_단건_조회(){
//given
User user = new User("test1", "test1234", "kim");
given(userRepository.findById(any(Long.class))).willReturn(Optional.of(user));
Long userId = 1L;
//when
User result = userService.findOne(userId);
//then
assertThat(result.getAccountId()).isEqualTo("test1");
}
}
service에서는 Mockito를 사용했다.
@Mock을 이용해 가짜 repository를 만들고
@InjectMocks를 이용해 userService 객체에 만들어놓은 Mock 객체를 주입했다.
이때 스프링을 이용해서 하는 테스트가 아니기 때문에 service가 인터페이스라면 작동하지 않는다.
실제 구현되는 serviceImpl 객체를 이용해서 테스트해야한다.
마찬가지로 given을 이용해 repository의 응답을 대체했다.
3) Repository 테스트
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(AppConfig.class)
public class UserRepositoryTest{
@Autowired
private UserRepository userRepository;
@Test //유저 생성
public void test_save(){
//given
User user = new User("test1", "test1234!", "kim");
//when
User saveUser = userRepository.save(user);
//then
assertThat(saveUser.getAccountId()).isEqualTo("test1");
}
}
DataJpaTest를 사용했다.
이때 Auditing, queryDSL등을 활용하기 위해 AppConfig 클래스를 따로 만들어줬으므로
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/**
* Swagger springdoc-ui 구성 파일
*/
@Configuration
@EnableJpaAuditing
public class OpenApiConfig {
@Bean
public OpenAPI openAPI() {
Info info = new Info()
.title("도마잎 API Document")
.version("v0.0.1")
.description("API 명세서입니다.");
return new OpenAPI()
.components(new Components())
.info(info);
}
}