728x90
1. WAS 작동 큰 그림
- 서블릿이라는게 http message의 실제 필요한 부분만 logic에 사용할 수 있도록 반복 작업을 다 수행해준다.
- request, response 라는 것을 이용해서 요청온 정보중 원하는 부분만 가지고 올 수 있고 내가 보내고 싶은 정보를 보낼 수 있다.
2. 서블릿
2.1. 기본
- 임시저장소
- request.setAttribute(name, value)
- request.getAttribute(name)
- 세션관리
- request.getSession(create: true)
- 요청
- queryParams, html
- api
- text
- json
2.2. 요청
- queary params, html form 조회 메서드
// http://localhost:8080/request-param?username=hello&age=20&username=bye
// key-value 형식
// 1. 단일 파라미터 조회
String username = request.getParameter("username"); // 중복된 key있을 시 1st 꺼냄
String username = request.getParameter("age");
// 2. 파라미터 이름들 모두 조회(중복 key는 1st만 꺼냄)
Enumeration<String> parameterNames = request.getParameterNames(); //username,age 다꺼냄
request.getParameterNames()
.asIterator().forEachRemaining(paramName->sout(paramName)) // 전체 params 각각 꺼냄
// 3. 파라미터를 Map 으로 조회
Map<String, String[]> parameterMap = request.getParameterMap();
// 4. 복수 파라미터 조회
String[] usernames = request.getParameterValues("username"); // 중복된 key가 있을때만 사용가능
- HTTP message body - API 메시지 바디_단순 텍스트
- getInputStream()
- StreamUtils.copyToString()
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletInputStream inputStream = request.getInputStream();
// http message body의 내용을 bytetypecode로 읽어온다.
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
// bytecode를 string으로 변환, 변환시 항상 encoding 정보를 알려줘야함
System.out.println("messageBody = " + messageBody);
}
- HTTP message body - API 메시지 바디_JSON
- JSON -> Object로 변경해서 읽어야한다.
= JSON도 JS의 Object nation(?)으로 JS의 객체이기 때문 - 방법
- json 객체를 모방한 java의 객체
- new ObjectMapper() - json이랑 java 객체 mapping 용도
- getInputStream()
- StreamUtils.copyToString()
- objectMapper.readValue
- json 객체를 모방한 java의 객체
- JSON -> Object로 변경해서 읽어야한다.
// json 형태의 객체를 받아올 java의 객체를 미리 준비
@Getter @Setter
public class HelloData {
private String username;
private int age;
}
@WebServlet(name = "requestBodyJsonServlet", urlPatterns = "/request-body-json")
public class RequestBodyJsonServlet extends HttpServlet {
// 1. json data를 파싱해서 사용할 수 있는 java object로 변환하기 위한 Jackson library, ObjectMapper 제공
// json 변환 library: Jackson, Gson
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 2. http meassage body에서 data 꺼내는 작업
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
System.out.println("messageBody = " + messageBody);
// 3. 꺼낸 data(=json)을 java 객체에 담아서 파싱하기
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
// object로 변환된 data 출력
System.out.println("helloData.getUsername() = " + helloData.getUsername());
System.out.println("helloData.getAge() = " + helloData.getAge());
response.getWriter().write("ok");
}
}
2.3. 응답
- 응답
- query, html
- http api
- text
- json
- 기본 정보
@WebServlet(name = "responseHeaderServlet", urlPatterns = "/response-header")
public class ResponseHeaderServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// status-line
response.setStatus(HttpServletResponse.SC_OK); //http 응답코드
// response-headers
response.setHeader("Content-Type ", "text/plain;charset=utf-8");
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("my-header", "woowee"); // 내가 만든 임의의 header
// header 편의 method
content(response);
cookie(response);
redirect(response);
PrintWriter writer = response.getWriter();
writer.println("hello");
}
// Content 편의 method
private void content(HttpServletResponse response) {
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
// response.setContentLength(2); // 생략시 자동 생성
}
// Cookie 편의 method
private void cookie(HttpServletResponse response) {
Cookie cookie = new Cookie("myCookie", "good");
cookie.setMaxAge(600);
response.addCookie(cookie);
}
// redirect 편의 method
private void redirect(HttpServletResponse response) throws IOException {
//Status Code 302
// Location: /basic/hello-form.html
// response.setStatus(HttpServletResponse.SC_FOUND); // 302
// response.setHeader("Location", "/basic/hello-form.html");
response.sendRedirect("/basic/hello-form.html");
}
}
- query, html
PrintWriter writer = response.getWriter();
writer.println("hello");
@WebServlet(name = "responseHtmlServlet",urlPatterns = "/response-html")
public class ResponseHtmlServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<body>");
writer.println("안녕");
writer.println("</body>");
writer.println("</html>");
// 그냥 writer.println("안녕")만 해도 html로 나타남. ContentType에서 html이라고 알려주기 때문
}
}
- http api - json 응답
- java 객체 생성 -> 이 객체를 json 객체로 변경할 것이다.
- new ObjectMapper()
- objectMapper.writeValueAsString(java 객체)
- getWriter에 넣기
- java 객체 생성 -> 이 객체를 json 객체로 변경할 것이다.
- json 요청 받을 때 비교해보기
- new ObjectMapper() - json이랑 java 객체 mapping 용도
- getInputStream()
- StreamUtils.copyToString()
- objectMapper.readValue
@WebServlet(name = "responseJsonServlet", urlPatterns = "/response-json")
public class ResponseJsonServlet extends HttpServlet {
// 1. objectMapper 생성
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
// java class 객체에 http 응답 메시지 body에 넣을 data 담기
HelloData data = new HelloData();
data.setUsername("woowee");
data.setAge(26);
// 2. java 객체를 json 형태의 string으로 담기
String result = objectMapper.writeValueAsString(data);
// http message body에 넣기
response.getWriter().write(result);
}
}
3. mvc 프레임워크 만들기
4. 스프링 mvc 구조 이해
- ViewResolver
- (1순위) BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능에 사용)
- (2순위) InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.
- view 반환
- String : view 이름만 반환
- ModelAndView : ("view이름", model값)
- mv.addObject("member", member);
@Controller
public class SpringMemberSaveControllerV1 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@RequestMapping("springmvc/v1/members/save")
public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
// dispatcher servlet 작동 때 받아온 req, res를 params로 가지고 온 것
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelAndView mv = new ModelAndView("save-result");
// mv.getModel().put("member", member);
mv.addObject("member", member); // 더 깔끔한 방법
return mv;
}
}
3.1. mvc 기본 기능
- 2가지 방식
- ModelAndView 반환 방식
- modelAndView 생성하면서 초기화시, 논리 path 넣기
- mv.addOject() 으로 view에 전달할 리소스 넣기
- mv.getModel().put("member", member); 이것도 가능한데 위의 방법이 좋음
- String 논리url 반환 방식
- dispatcherServlet에서 이미 만든 Model 객체를 이용(model.addAttribute())해서 view로 전달할 리소스 넣기
- String으로 논리 url만 반환
- request params를 직접 받는 방법 : @RequestParam()
- ModelAndView 반환 방식
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@RequestMapping(value = "/new-form" , method = RequestMethod.GET)
public String newForm(){
return "new-form";
}
@PostMapping("/save")
public String save(@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";
}
// model에 직접 저장
@GetMapping
public String members(Model model) {
List<Member> members = memberRepository.findAll();
model.addAttribute("members", members);
return "members";
}
}
4. mvc 기본 기능 - 핵심
4.1. 로그
- slf4j 로그
# application.properties
# 전체 로그 레벌 설정(default 값으로 info)
logging.level.root=info
#hello.springmvc 패키지와 하의 로그 레벨 설정
logging.level.hello.springmvc=debug # debug부터 high level log 모두 출력됨. trace 출력 안됨
로그 단계: TRACE > DEBUG > INFO > WARN > ERROR
@Slf4j
@RestController
public class LogTestController {
@RequestMapping("/log-test")
public String logTest(){
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);
return "ok";
}
}
4.2. @requestMapping
@RestController
public class MappingController {
/**
* 편리한 축약 애노테이션 (코드보기)
* @GetMapping
* @PostMapping
* @PutMapping
* @DeleteMapping
* @PatchMapping
*/
@GetMapping(value = "/mapping-get-v2")
public String mappingGetV2() {
log.info("mapping-get-v2");
return "ok";
}
/**
* PathVariable 사용
* 변수명이 같으면 생략 가능
* @PathVariable("userId") String userId -> @PathVariable userId * 이름과 params가 같으면 생략가능
*/
@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";
}
4.3. http 요청 - 기본, 헤더 조회
- multivalueMap: 동일 key에 여러 value를 배열로 받는 다.
MultiValueMap<String, String> map = new LinkedMultiValueMap();
map.add("keyA", "value1");
map.add("keyA", "value2");
//[value1,value2]
List<String> values = map.get("keyA");
- header 정보 - cookie를 핵심으로 보기
@Slf4j
@RestController
public class RequestHeaderController {
@RequestMapping("/headers")
public String header(
HttpServletRequest request,
HttpServletResponse response,
HttpMethod httpMethod,
Locale locale, // 언어
@RequestHeader MultiValueMap<String, String> headerMap, // 모든 헤더 다 받을 때
@RequestHeader("host") String host, // 필수 헤더 받을 때, 헤더 key 값을 넣는다.
@CookieValue(value = "myCookie", required = false) String cookie){
}
}
4.4. 요청 params - query, Html form, http api
- query, form
- @RequestParam : 기본형인 경우
- @ModelAttribute : 참조변수인 경우
- @RequestParam
- 어지간 하면 생략 말기
- default랑 required 사용 경우 존재
- params 여러개 받을 때 Map || MultiMap으로 받을 수 있다.
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
// default가 true : 해당 request params가 꼭 들어와야함
@RequestParam(required = true) String username,
@RequestParam(required = false) Integer age){ // int는 안됨. false인 경우 null을 반환하기 때문. 그래서 객체타입으로 null 받아야함.
log.info("username={}, age={}", username, age);
return "ok";
}
@ResponseBody
@RequestMapping("/request-param-default")
public String requestParamDefault(
// false에서 값이 없거나 true에서 값이 안들어오면 default가 넘어감, null 뿐만이 아니라 빈문자열도 defaultValue 적용한다.
@RequestParam(required = true, defaultValue = "guest") String username,
@RequestParam(required = false, defaultValue = "-1") int age){
log.info("username={}, age={}", username, age);
return "ok";
}
@ResponseBody
@RequestMapping("/request-param-map")
// 해당 requestParams를 통채로 Map으로 가져온다. multiMap도 있음
public String requestParamMap(@RequestParam Map<String, Object> paramMap){
log.info("username={}, age={}", paramMap.get("username"),paramMap.get("age"));
return "ok";
}
- @ModelAttribute
- 객체로 담을 class 생성 필요 - lombok @Data - toString, getter, setter 존재
- modelAttribute의 추가 기능
- @ModelAttribute가 method()영역에 존재시, 그 자체를 항상 view로 보낸다.
참고 글 : 2. thymeleaf - 스프링 통합과 폼 - validation 의 @bindingResult와 같이 쓰인다
참고 글 : 스프링 mvc2 - 2 검증 1 (Validation)
- @ModelAttribute가 method()영역에 존재시, 그 자체를 항상 view로 보낸다.
@Slf4j
@Controller
public class RequestParamController {
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData){
log.info("username={}, age={}", helloData.getUsername(),helloData.getAge());
log.info("helloData={}",helloData);
return "ok";
}
@RequestMapping("/model-attribute-v2")
public String modelAttributeV2(HelloData helloData){ // 생략가능
log.info("username={}, age={}", helloData.getUsername(),helloData.getAge());
log.info("helloData={}",helloData);
return "ok";
}
}
- http message body : json, text
- Http message body 조회 기능
- HttpEntity<>
generics 사용 안할 시, object로 반환 - @RequestBody -> 생략 하면 안됨
spring이 요청 params 조회 애노테이션 생략으로 판단
- HttpEntity<>
@Slf4j
@Controller
public class RequestBodyStringController {
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException{
String body = httpEntity.getBody();
return new HttpEntity<>("ok");
}
/*
public HttpEntity<String> requestBodyStringV3(RequestEntity<String> httpEntity) throws IOException{
String body = httpEntity.getBody();
return new ResponseEntity<>("ok", HttpStatus.CREATED);
}
*/
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
return "ok";
}
}
- json
- @RequestBody
- HttpEntity<HelloData> data
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData helloData) {
log.info("messageBody={}", helloData);
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> data) {
HelloData helloData = data.getBody();
log.info("messageBody={}", helloData);
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return helloData; // http message converter를 통해서 바뀜
}
- query, form data를 view로 보내는건 쉬우니깐 생략
- body - json, text
- 방법
- ResponseEntity<>("보낼 data", 상태코드)
- @ResponseBody
@ResponseStatus
package hello.springmvc.basic.response;
@Slf4j
@Controller
public class ResponseBodyController {
// 1. 문자열 처리
@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";
}
// 2. json 처리
@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) // responseEntity와 달리 상태코드 설정 못하기 때문
@ResponseBody
@GetMapping("/response-body-json-v2")
public HelloData responseBodyJsonV2(){
HelloData helloData = new HelloData();
helloData.setUsername("userA");
helloData.setAge(20);
return helloData;
}
}
5. redirect , PRG
스프링 MVC 6 - thymeleaf, @ModelAttribute, PRG, Redirection
- modelAttribute
@PostMapping("/add")
public String addItemV4(Item item){
itemRepository.save(item);
return "basic/item"; // 해당 url로도 상세화면이 가는 이유는 @ModelAttribute가 basic/item에 직접 값을 넣어주기 때문
}
- PRG
package hello.itemservice.web.basic;
@Controller
@RequestMapping("/basic/items") // 공통부분
@RequiredArgsConstructor // 생성자 주입 자동으로 해주는 lombok
public class BasicItemController {
private final ItemRepository itemRepository;
@PostMapping("/add")
public String addItemV6(Item item, RedirectAttributes redirectAttributes){
Item savedItem = itemRepository.save(item);
redirectAttributes.addAttribute("itemId", savedItem.getId());
redirectAttributes.addAttribute("status", true);
// 추가기능, url 마지막에 query 형식으로 ?status=true 추가됨
return "redirect:/basic/items/{itemId}";
}
}
- RedirectAttribute 기능 - data가 view로 넘어가는 것이 아니고 Url redirection 정보로 사용된다.
- url 인코딩 기능 수행
redirectAttributes.addAttribute("itemId", savedItem.getId());
- 추가 기능까지 수행
redirectAttributes.addAttribute("status", true);
- url 인코딩 기능 수행
- addFlashAttribute
- 그냥 Model로 data 넘겨도 되는데 회원가입 완료 알림창 같은 경우 model에 담게되면 이동시마다 계속 나타나게 된다.
- 위의 메서드 사용시, data가 session에 담긴후, view에서 한번만 수행된다.
- 회원가입 완료 알림창 같은 경우 사용된다.