개발하는 삶
[Spring] 스프링 MVC 본문
MVC 패턴
- 프로젝트를 구성할 때 controller 가 실행되면 controller가 model 을 통해 데이터를 가져와서 view 에 데이터를 표현함
DispatcherServlet
- HttpServlet 을 상속받고 서블릿으로 동작
- DispatcherServlet < FrameworkServlet < HttpServletBean < HttpServlet
- 서블릿이 호출되면 FrameworkServlet(HttpServlet 의 하위) 의 service()를 오버라이딩 받고,
- DispacherServlet.doDispatch() 호출함
- 핸들러 조회, 핸들러 어댑터 조회/실행, 뷰 렌더링 호출
- 모든 경로(urlPatterns="/")에 대해서 매핑함
- 더 자세한 경로가 우선순위가 높아 그것에 덮어질수있음
SpringMVC 구조
동작 순서
- 핸들러 조회
- 핸들러 어댑터 조회
- 핸들러 어댑터 실행
- 핸들러 실행
- 핸들러 어댑터는 핸들러에게 받은 ModelAndView 반환
- viewResolver 호출 해 View의 논리주소 -> 물리주소
- View 반환
- 뷰 렌더링
@RequestMapping
- URL이 호출될 시, Controller의 요청정보(ex. 메서드)가 작동하도록 매핑할 때 사용하는 애노테이션
- @Controller과 같이 쓰임
- @RequestMapping("url") : 해당 URL이 호출시, 아래의 클래스/메서드가 호출됨
- HTTP method : URL 설정, 요청방식(GET, POST, DELETE, PATCH) 설정
- GET : 리소스 조회 (xxx.png 입력 -> 그림파일 요청. 데이터 받기용)
- POST : 요청 데이터 처리, 데이터 등록 (회원가입 후 회원정보 등록)
- PUT : 서버에게 리소스의 업데이트/리소스 생성 요청 (회원정보 수정 - 모든 데이터)
- PATCH : 서버에게 리소스 업데이트 요청. 리소스 일부 변경. (회원정보 수정 - 부분 데이터)
- DELETE : 서버에게 리소스 삭제 요청
- 예시
- @RequestMapping(value= "경로", method=RequestMethod.GET)
- method 값 없으면 HTTP 메서드 모두 허용 (GET, HEAD, POST ...)
- @GetMapping("경로") 으로 바꿀 수 있음
- value : 경로
- 클래스/메서드 서로 조합 가능
- ex) 클래스에 붙일 땐 서로 중복/공통된 url을 넣고, 메서드에 붙일 땐 다른 url을 넣어줌
- HTTP method : URL 설정, 요청방식(GET, POST, DELETE, PATCH) 설정
- 스프링에서 주로 사용하는 핸들러 매핑과 핸들러 어댑터
- RequestMappingHandlerMapping : 핸들러 매핑
- @RequestMapping, @Controller 가 클래스에 붙어있는 경우에 매핑정보로 인식
- RequestMappingHandlerAdapter : 핸들러 어댑터
- RequestMappingHandlerMapping : 핸들러 매핑
InternalResourceViewResolver
- 뷰 리졸버 이용
- jsp를 처리할 수 있는 뷰를 반환
- spring.mvc.view.prefix (예시 = /WEB-INF/jsp/ ), spring.mvc.view.suffix (예시 = .jsp) 설정
- InternalResourceView 반환 (뷰 반환)
ModelAndView
- 모델과 뷰 정보를 담아 반환
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
//Get 요청 방식의 API 생성
@GetMapping("/new-form")
public String newForm() {
return "new-form";
}
//Post 요청 방식의 API 생성
@PostMapping("/save")
public String save(
//@RequestParam(파라미터값) : HTTP 요청 파라미터를 입력받음(request.getParameter("username"))
@RequestParam("username") String username,
@RequestParam("age") int age,
Model model) {
Member member = new Member(username, age);
memberRepository.save(member);
model.addAttribute("member", member);
return "save-result";
}
//Get 요청 방식의 API 생성
@GetMapping
public String members(Model model) {
List<Member> members = memberRepository.findAll();
model.addAttribute("members", members);
return "members";
}
}
로깅
- 필요한 정보를 출력할 때 사용
- 로깅 라이브러리 사용 : SLF4J 등
//@Slf4j : 롬복 있으면 사용가능. log.error 이렇게 log만 적어도 됨. 로그 선언 하는법 (1)
//@RestController : 반환 값을 HTTP response body에 데이터를 담아 요청을 완료함
//(@Controller와 @ResponseBody가 합쳐진 어노테이션 느낌)
@RestController
public class LogTestController {
//출력하기 위해 로그 선언. 로그 선언 하는법 (2)
private final Logger log = LoggerFactory.getLogger(getClass());
@RequestMapping("/log-test")
public String logTest() {
String name = "Spring";
//로그 출력 테스트 - 시간,로그레벨,프로세스ID,쓰레드명,클래스명,로그메시지
//로그레벨 : TRACE > DEBUG > INFO > WARN > ERROR
//개발 서버 : debug 출력
//운영 서버 : info 출력
log.trace("trace log={}", name);
log.debug("debug log={}", name);
log.info(" info log={}", name);
log.warn(" warn log={}", name);
log.error("error log={}", name);
//로그를 사용하지 않아도 a+b 계산 로직이 먼저 실행됨, 이런 방식으로 사용하면 X
log.debug("String concat log=" + name);
return "ok";
}
}
//로그 레벨 설정 application.properties
//기본 info
logging.level.root=info
//hello.springmvc 패키지와 그 하위로그 레벨 설정
logging.level.hello.springmvc=debug
요청 매핑 - 경로 변수 사용
XXXMapping("경로와 경로변수") + @PathVariable("경로변수")
- @PathVariable 의 경로변수 = mapping의 경로변수
- 요즘엔 이 방식을 선호함
- 게시판 번호 /7 이런식으로 나타내거나, url에 있는 값 그대로 출력하고 싶을때 사용
@GetMappingg("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
log.info("mappingPath userId={}", data);
return "ok";
}
XXXMapping("경로", produces = "text/html(예시)")
- HTTP 요청의 Content-Type 헤더를 기반으로 미디어 타입으로 매핑함
- 잘 안씀
@GetMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
log.info("mappingProduces");
return "ok";
}
//아래는 예시
produces = "text/plain"
produces = {"text/plain", "application/*"}
produces = MediaType.TEXT_PLAIN_VALUE
produces = "text/plain;charset=UTF-8"
HTTP 요청 - 헤더 정보 조회
- 웹사이트 기본값 설정하기
- @RequestHeader
- 특정 HTTP 헤더 조회하기
- MultiValueMap
- 키의 중복 허용. 값만 다르게 하면 됨. (키를 가져올 때 키-값 리스트 형태로 저장/반환)
- 키와 값으로 이루어짐
- @CookieValue
- @CookieValue(value="쿠키이름", required=false) value 속성을 전달받을 쿠키의 이름을 지정
- required = true : value 속성의 이름을 가진 쿠키가 존재한다는 뜻
@Slf4j
@RestController
public class RequestHeaderController {
@RequestMapping("/headers")
public String headers(HttpServletRequest request,
HttpServletResponse response,
HttpMethod httpMethod, //HTTP 메서드 조회
Locale locale, //Locale = 웹사이트 언어 정보 (다국어 지원용으로 쓰임) (기본값: 한국 ko_kr)
//모든 HTTP 헤더를 MultiValueMap 형식으로 조회
@RequestHeader MultiValueMap<String, String> headerMap,
//특정 HTTP 헤더를 조회
@RequestHeader("host") String host,
//특정 쿠키를 조회
@CookieValue(value = "myCookie", required = false) String cookie
) {
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";
}
}
HTTP 요청 파라미터 - @RequestParam
- 파라미터 이름으로 바인딩
// 방법 (1)
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(
@RequestParam("username") String membername,
@RequestParam("age") int memberage) {
log.info("username={}, age={}", username, age);
return "ok";
}
// 방법 (2)
// HTTP 파라미터 이름이 참조변수 이름과 같으면 ("파라미터") 생략 가능
@ResponseBody
@RequestMapping("/request-param-v3")
public String requestParamV3(
@RequestParam String username,
@RequestParam int age) {
log.info("username={}, age={}", username, age);
return "ok";
}
- 파라미터 필수여부, 기본값
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
// 파라미터 필수 여부 (기본값은 true)
// true 면 무조건 값이 할당되어야 한다는 의미
@RequestParam(required = true) String username,
// defaultValue = 파라미터에 값이 없는 경우 기본값 설정
@RequestParam(required = false, defaultValue = "guest") Integer age) {
log.info("username={}, age={}", username, age);
return "ok";
}
- 파라미터 Map으로 조회
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap) {
//받은 값을 paramap에 저장해서 출력
log.info("username={}, age={}", paramMap.get("username"), paramMap.get("age"));
return "ok";
}
HTTP 요청 파라미터 - @ModelAttribute
- @RequestParam : String, 래퍼클래스, int(기본형) 등 기본형 타입에 사용 적합
- @ModelAttribute : 객체 등의 나머지 타입들 사용 (argument resolver 로 지정해둔 타입 외)
- 두개 다 생략이 가능하지만 정해놓은 타입에 따라 자동으로 적용이 되기 때문에, 생략하지 않는 걸 추천
//HelloData.class
//요청 파라미터를 받을 객체
//@Data는 롬복 애노테이션. getter,setter 등을 불러와줌
@Data
public class HelloData {
private String username;
private int age;
}
//modelAttributeV1 메서드
//매개 변수에 @ModelAttribute를 넣어 HelloData를 불러옴
//@ModelAttribute : 사용자가 요청해 전달한 값을 오브젝트 형태로 매핑해주는 어노테이션
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
HTTP 요청 메시지 - 단순 텍스트
- HTTP message body에 데이터를 직접 담아 요청
- 요청 파라미터를 조회하는 기능 : @RequestParam, @ModelAttribute
- HTTP message body를 직접 조회하는 기능 : @RequestBody
//Stream을 이용하기
/**
* 스프링 MVC는 다음 파라미터를 지원한다
* InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
* OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력
*/
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter)
throws IOException {
// 스프링에 내장된 유틸클래스.스트림->문자열로반환(HTTP 요청 메시지 바디 스트림 ,utf-8 형식으로)
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
// HTTP 응답 메시지 출력
responseWriter.write("ok");
}
//HttpEntity 이용하기
/**
* HttpEntity: HTTP header, body 정보를 편리하게 조회
* - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*
* 응답에서도 HttpEntity 사용 가능
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*/
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
//요청받은 메시지 바디를 문자열로 받음
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new HttpEntity<>("ok"); //응답
}
@RequestBody, @ResponseBody 이용
/**
* @RequestBody
* - 메시지 바디 정보를 직접 조회(@RequestParam X, @ModelAttribute X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*
* @ResponseBody
* - 메시지 바디 정보 직접 반환(view 조회는 X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*/
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
//요청 메시지 바디 출력
log.info("messageBody={}", messageBody);
//응답 메시지 바디 출력
return "ok";
}
HTTP 요청 메시지 - JSON
- JSON 데이터 형식 조회
- @RequestBody 요청 : JSON 요청 -> HTTP 메시지 컨버터 -> 객체
- @ResponseBody 응답 : 객체 -> HTTP 메시지 컨버터 -> JSON 응답
//기존 JSON 코드
/**
* {"username":"hello", "age":20}
* content-type: application/json
*/
@Slf4j
@Controller
public class RequestBodyJsonController {
//ObjectMapper을 통해 JSON 라이브러리 사용
private ObjectMapper objectMapper = new ObjectMapper();
//요청,응답 받기
@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 data = objectMapper.readValue(messageBody, HelloData.class);
//객체 속 저장된 username,age 출력
log.info("username={}, age={}", data.getUsername(), data.getAge());
//요청이 완료되면 응답메시지 출력
response.getWriter().write("ok");
}
}
//@RequestBody, @ResponseBody 를 이용해 간추린 것
/**
* @RequestBody
* HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*
* @ResponseBody
* - 모든 메서드에 @ResponseBody 적용
* - 메시지 바디 정보 직접 반환(view 조회X)
* - HttpMessageConverter 사용 -> StringHttpMessageConverter 적용
*/
....
private ObjectMapper objectMapper = new ObjectMapper();
@ResponseBody //getWriter() 과정 생략해줌
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws IOException {
//@RequestBody : stream으로 받은 요청사항 과정을 다 알아서 해줌
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok"; //응답
}
//@RequestBody에 직접만든 객체 지정 - 최종 간추리기
/**
* @RequestBody 생략 불가능(@ModelAttribute 가 적용되어 버림)
* HttpMessageConverter 사용 -> MappingJackson2HttpMessageConverter (contenttype: application/json)
*
*/
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
출처
'Spring' 카테고리의 다른 글
[Spring] 로그인, 스프링 시큐리티 (0) | 2022.08.18 |
---|---|
[Spring] HTTP 요청/응답 (0) | 2022.07.23 |
[Spring] HTTP Method (0) | 2022.07.11 |
[Spring] 웹서버 (0) | 2022.07.09 |
[Spring] 스코프, 프록시 (0) | 2022.07.06 |