War는 주로 톰캣을 별도로 설치할 때 사용, JSP를 쓸 때 사용, 내장 서버도 사용가능 하지만 주로 외부 서버에 배포하는 목적으로 사용
Jar는 별도의 톰캣 서버를 설치하는것이 아닌 내장 톰캣을 최적화해서 사용할 때 사용
운영 시스템에서는 System.out.println() 같은 시스템 콘솔을 사용해서 필요한 정보를 출력하지 않고, 별도의 로깅 라이브러리를 사용해서 로그를 출력
private Logger log = LoggerFactory.getLogger(getClass());
: slf4j에 있는것을 import해서 사용하기private static final Logger log = LoggerFactory.getLogger(Xxx.class)
: slf4j에 있는것을 import해서 사용하기@Slf4j
: 롬복이 제공하는 애너테이션 @Controller
는 반환 값이 String이면 뷰 이름으로 인식되어 뷰를 찾고 뷰가 랜더링 됨@RestController
는 반환 값으로 뷰를 찾는 것이 아니라, String이 바로 그대로 반환되어 HTTP 메시지 바디에 바로 입력application.properties
#전체 로그 레벨 설정(기본 info), root는 전체를 다 세팅(현재 나의 프로젝트 기본값을 세팅)
logging.level.root=info
#hello.springmvc 패키지와 그 하위 로그 레벨 설정(이 패키지에서는 이게 우선권을 가짐)
logging.level.hello.springmvc=debug
log.debug("data="+data)
log.debug("data={}", data)
요청이 왔을 때 어떤 컨트롤러가 매핑이 되어야 하는지?
@RequestMapping("/hello-basic")
public String helloString(){
log.info("helloBasic");
return "ok";
}
@RequestMapping(value = "/mapping-get-v1", method = RequestMethod.GET)
public String mappingGetV1() {
log.info("mappingGetV1");
return "ok";
}
@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
log.info("mapping-get-v2");
return "ok";
}
@RequestMapping(value = "/hello-basic", method = RequestMethod.GET)
@GetMapping(value = "/hello-basic")
로 축약 가능 /** * PathVariable 사용
* 변수명이 같으면 생략 가능
* @PathVariable("userId") String userId -> @PathVariable userId
* /mapping/userA
*/
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId")String data){
log.info("mappingPath userId={}",data);
return "ok";
}
/**
* PathVariable 사용 다중
*/
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long
orderId) {
log.info("mappingPath userId={}, orderId={}", userId, orderId);
return "ok";
}
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable String userId){
log.info("mappingPath userId={}",userId);
return "ok";
}
/**
* Content-Type 헤더 기반 추가 매핑 Media Type
* consumes="application/json"
* consumes="!application/json"
* consumes="application/*"
* consumes="*\/*"
* MediaType.APPLICATION_JSON_VALUE
*/
@PostMapping(value = "/mapping-consume", consumes = MediaType.APPLICATION_JSON_VALUE)
public String mappingConsumes() {
log.info("mappingConsumes");
return "ok";
}
/** * Accept 헤더 기반 Media Type
* produces = "text/html"
* produces = "!text/html"
* produces = "text/*"
* produces = "*\/*"
*/
@PostMapping(value = "/mapping-produce", produces = MediaType.TEXT_HTML_VALUE)
public String mappingProduces() {
log.info("mappingProduces");
return "ok";
}
@Slf4j //로그
@RestController //응답값을 뷰를 찾는것이 아닌 문자 그대로를 반환
public class RequestHeaderController {
@RequestMapping("/headers")
//애너테이션 기반의 컨트롤러는 유연하게 다양한 파라미터를 받아들일 수 있음
//리턴값도 유연하게 반환 가능(문자열, ModelAndView 등..)
public String headers(HttpServletRequest request,
HttpServletResponse response,
HttpMethod httpMethod, //HTTP 메서드 조회
Locale locale, //언어 정보
@RequestHeader MultiValueMap <String,String> headerMap,
//헤더를 한번에 다 받음
@RequestHeader("host") String host,
//헤더를 하나만 받고싶을 때
@CookieValue(value ="myCookie",required = false) String cookie
// value > 쿠키 이름, required > default가 true
){
log.info("request={}", request);
log.info("response={}", response);
log.info("httpMethod={}", httpMethod);
log.info("locale={}", locale);
log.info("headerMap={}", headerMap);
log.info("header host={}", host);
log.info("myCookie={}", cookie);
return "ok";
}
}
MultiValueMap<String, String> map = new LinkedMultiValueMap();
map.add("keyA", "value1");
map.add("keyA", "value2");
//[value1,value2] 배열로 반환
List<String> values = map.get("keyA");
클라이언트에서 서버로 요청 데이터를 전달할 때는 주로 다음 3가지 방법을 사용
@Slf4j
@Controller
public class RequestParamController {
@RequestMapping("/request-param-v1")
public void requestParamV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
String username=request.getParameter("username");
int age=Integer.parseInt(request.getParameter("age"));
log.info("username={}, age={}",username,age);
response.getWriter().write("ok");
}
}
@ResponseBody
// View 조회를 무시하고, HTTP message body에 직접 해당 내용 입력 > @RestController와 같은 효과
@RequestMapping("/request-param-v2")
public String requestParamV2(
@RequestParam("username")String memberName,
@RequestParam("age") int memberAge){
log.info("username={}, age={}",memberName,memberAge);
return "ok";
}
request.getParameter("username")
> @RequestParam("username") String memberName
public String requestParamV3(
@RequestParam String username,
@RequestParam int age)
public String requestParamV4(String username, int age)
public String requestParamRequired(
@RequestParam(required = true) String username,
@RequestParam(required = false) Integer age){
//age값이 파라미터로 들어오지 않으면 null이 입력되는데
//int는 기본형이라 null을 받지 못함 > Integer로 바꿔주면 사용 가능
log.info("username={}, age={}",username,age);
return "ok";
}
/request-param?username=
@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
@RequestParam(defaultValue = "guest") String username,
@RequestParam(defaultValue = "-1") int age){
log.info("username={}, age={}",username,age);
return "ok";
}
/request-param?username=
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap){
log.info("username={}, age={}",paramMap.get("username"),paramMap.get("age"));
return "ok";
}
요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣을 때 사용
롬복 @Data : @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor를 자동으로 적용
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributV1(@ModelAttribute HelloData helloData){
log.info("username={}, age={}",helloData.getUsername(),helloData.getAge());
return "ok";
}
스프링MVC는 @ModelAttribute 가 있으면
1. HelloData 객체를 생성
2. 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾음(getXXX, setXXX)
3. 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩)
예) 파라미터 이름이 username 이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력
4. 데이터의 타입이 잘못 입력되면 (int형인데 문자 입력하는 경우) 바인딩 오류 발생
프로퍼티
- 객체에 getUsername() , setUsername() 메서드가 있으면, 이 객체는 username 이라는 프로퍼티를 가지고 있다고 함
- username 프로퍼티의 값을 변경하면 setUsername() 이 호출되고, 조회하면 getUsername()이 호출
public String modelAttributV2(HelloData helloData){
HTTP message body에 데이터를 직접 담아서 요청
@PostMapping("/request-body-string-v1")
public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
//스트림은 바이트코드이기때문에 어떤 인코딩으로 해서 문자로 바꿀건지 지정
//지정 안하면 default를 사용
log.info("messageBody={}",messageBody);
response.getWriter().write("ok");
}
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException {
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}",messageBody);
responseWriter.write("ok");
}
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
//스프링이 알아서 문자형인것을 확인하고 httpBody에 있는 것을 문자로 바꿔서 넣어주는 HttpMessageConverter가 동작
String messageBody=httpEntity.getBody();
//http 메세지에 있는 바디를 꺼냄(string으로 변환하여)
log.info("messageBody={}",messageBody);
return new HttpEntity<>("ok");
}
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {
log.info("messageBody={}",messageBody);
return "ok";
}
요청 파라미터를 조회하는 기능 : @RequestParam , @ModelAttribute
HTTP 메시지 바디를 직접 조회하는 기능 : @RequestBody
@PostMapping("/request-body-json-v1")
public void requestBodyJsonV1(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream=request.getInputStream();
String messageBody= StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}",messageBody);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}",helloData.getUsername(),helloData.getAge());
response.getWriter().write("ok");
}
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
log.info("messageBody={}",messageBody);
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}",helloData.getUsername(),helloData.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData) {
log.info("username={}, age={}",helloData.getUsername(),helloData.getAge());
return "ok";
}
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
이 역할을 대신 해줌HelloData data
> @ModelAttribute HelloData data
/*(Accept : application/json)여야 json으로 받을 수 있음*/
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
log.info("username={}, age={}",data.getUsername(),data.getAge());
return data;
}
- @RequestBody 요청
JSON 요청 > HTTP 메시지 컨버터 > 객체- @ResponseBody 응답
객체 > HTTP 메시지 컨버터 > JSON 응답
스프링(서버)에서 응답 데이터를 만드는 방법은 크게 3가지
다음 경로에 파일이 들어있으면
src/main/resources/static/basic/hello-form.html
웹 브라우저에서 다음과 같이 실행
http://localhost:8080/basic/hello-form.html
src/main/resources/templates
@RequestMapping("/response-view-v1")
public ModelAndView responseViewV1(){
ModelAndView mav=new ModelAndView("response/hello")
.addObject("data","hello!");
return mav;
}
ModelAndView로 반환하는 경우
@RequestMapping("/response-view-v2")
public String responseViewV2(Model model){
model.addAttribute("data","hello!");
return "response/hello";
}
String을 반환하는 경우
//권장하지 않음
@RequestMapping("/response/hello")
//경로의 이름을 똑같이 넣어주면 return이 없어도 됨
//컨트롤러의 경로의 이름과 뷰의 논리적 이름이 같으면 요청온 경로가 논리적 뷰의 이름으로 그냥 실행됨
public void responseViewV3(Model model){
model.addAttribute("data","hello!");
}
Void를 반환하는 경우
정적 리소스나 뷰 템플릿을 거치지 않고, 직접 HTTP 응답 메시지를 전달하는 경우
@GetMapping("/response-body-string-v1")
public void responseBodyV1(HttpServletResponse response) throws IOException{
response.getWriter().write("ok");
}
@GetMapping("/response-body-string-v2")
public ResponseEntity<String> responseBodyV2(){
return new ResponseEntity<>("ok", HttpStatus.OK);
}
@ResponseBody
@GetMapping("/response-body-string-v3")
public String responseBodyV3(){
return "ok";
}
response.getWriter().write("ok")
@GetMapping("/response-body-json-v1")
public ResponseEntity<HelloData> responseBodyJsonV1(){
HelloData helloData=new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return new ResponseEntity<>(helloData,HttpStatus.OK);
}
@ResponseStatus(HttpStatus.OK) //상태코드 지정
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2(){
HelloData helloData=new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return helloData;
}
HTTP API처럼 JSON 데이터를 HTTP 메시지 바디에서 직접 읽거나 쓰는 경우 HTTP 메시지 컨버터를 사용하면 편리
@ResponseBody 사용 원리
*/*
(아무거나 다 됨)@RequestBody byte[] data
@ResponseBody return byte[]
으로 하면application/octet-stream application/octet-stream
로 적용됨*/*
@RequestBody String data
@ResponseBody return "ok"
면 쓰기 미디어타입은 text/plain
application/json
관련@RequestBody HelloData data
@ResponseBody return helloData
쓰기 미디어타입 application/json
관련HTTP 요청 데이터 읽기
1. HTTP 요청이 오고, 컨트롤러에서 @RequestBody, HttpEntity 파라미터를 사용
2. 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해 canRead() 를 호출
- @RequestBody 의 대상 클래스 타입을 지원하는지?
- HTTP 요청의 Content-Type 미디어 타입을 지원하는지?
3. canRead() 조건을 만족하면 read() 를 호출해서 객체 생성하고, 반환
HTTP 응답 데이터 생성
1. 컨트롤러에서 @ResponseBody , HttpEntity 로 값이 반환
2. 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해 canWrite() 를 호출
- return의 대상 클래스 타입을 지원하는가?
- HTTP 요청의 Accept 미디어 타입을 지원하는가?(더 정확히는 @RequestMapping 의 produces)
3. canWrite() 조건을 만족하면 write() 를 호출해서 HTTP 응답 메시지 바디에 데이터를 생성
RequestMappingHandlerAdapter는 @RequestMapping 을 처리하는 핸들러 어댑터
HandlerMethodReturnValueHandler
를 줄여서 ReturnValueHandler