[Spring] 정리(3)

박찬영·2024년 6월 29일

Spring

목록 보기
41/42

1. WAS

  • 정적 리소스는 웹 서버가 처리
  • 웹 서버는 애플리케이션 로직같은 동적인 처리가 필요하면 WAS에 요청을 위임
  • WAS는 중요한 애플리케이션 로직 처리 전담
    • ex) 서블릿, JSP, 스프링 MVC

자바는 서블릿 컨테이너 기능을 제공하면 WAS

2. 서블릿

@WebServlet(name = "helloServlet", urlPatterns = "/hello")
public class HelloServlet extends HttpServlet {
	
    @Override
    protected void service(HttpServletRequest request, 
    								HttpServletResponse reponse) 
              						throws ServletException, IOException {
                
    }

2.1 HttpServletRequest request

2.1.1 쿼리 파라미터
request.getParameter("username");	//단일 파라미터 조회
request.getParameterValues("username");	//복수 파라미터 조회
request.getParameterNames();	//파라미터 이름들 모두 조회
2.1.2 메세지 바디 - 단순 텍스트 & JSON
@WebServlet(name = "servlet", urlPatterns = "/request-body-json")
public class Servlet extends HttpServlet {
	
    private ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    										throws ServletException, IOException {
    
    	ServletInputStream inputStream = request.getInputStream();
        String messageBody = StreamUtils.copyToString
        							(inputStream, StandardCharsets.UTF_8);
        
        System.out.println("messageBody = " + messageBody);	//단순 텍스트
        
        HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
        System.out.println(helloData.getUsername());	//JSON
        System.out.println(helloData.getAge());		//JSON
2.1.3 임시 저장소

해당 HTTP 요청이 시작부터 끝날 때까지 유지되는 임시 저장소 기능

request.setAttribute(name, value)	//저장
request.getAttribute(name)	//조회

webapp 경로에 index.html을 두면 http://localhost:8080 호출시 index.html 페이지가 열린다.

2.2 HttpServletResponse response

2.2.1 header 설정
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
2.2.2 JSON 설정
@WebServlet(name = "servlet", urlPatterns = "/response-json")
public class Servlet extends HttpServlet {
	
    private ObjectMapper objectMapper = new ObjectMapper();
    
    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) 
    								throws ServletException, IOException {
    
    	response.setHeader("content-type", "application.json");
		response.setCharacterEncoding("utf-8");	
        
        HelloData data = new HelloData();
        data.setUsername("kim");
        data.setAge(20);
        
        //JSON -> {"username" : "kim", "age" : 20}
        String result = objectMapper.writeValueAsString(data);
        response.getWriter().write(result);

3. MVC 패턴

4. 스프링 MVC 구조

  1. 핸들러 조회 : 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러를 조회한다.
  2. 핸들러 어댑터 조회 : 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.
  3. 핸들러 어댑터 실행 : 핸들러 어댑터를 실행한다.
  4. 핸들러 실행 : 핸들러 어댑터가 실제 핸들러를 실행한다.
  5. ModelAndView 반환 : 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.
  6. viewResolver 호출 : 뷰 리졸버를 찾고 실행한다.
  7. View 반환 : 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.
  8. 뷰 렌더링 : 뷰를 통해서 뷰를 렌더링한다.

HTTP 요청이 오면 DispatcherServlet을 통해 적절한 핸들러와 어댑터를 반환하고 요청을 처리한다. 그 후 핸들러가 ModelAndView를 반환하고 viewResolver를 통해 실제 뷰로 변경하여 랜더링한다.

5. 요청 매핑

5.1 통합

/**
 * 클래스 단위 -> 메서드 단위
 * @RequestMapping 클래스 레벨과 메서드 레벨 조합
 */
 
@Controller
@RequestMapping("/springmvc/v2/members")
public class SpringMemberControllerV2 {

 	private MemberRepository memberRepository = MemberRepository.getInstance();
 
 	@RequestMapping("/new-form") 
    public ModelAndView newForm() {
 		return new ModelAndView("new-form");
 	}
    
 	@RequestMapping("/save")
 	public ModelAndView save(HttpServletRequest request, HttpServletResponse
response) {

		String username = request.getParameter("username");
 		int age = Integer.parseInt(request.getParameter("age"));
 
 		Member member = new Member(username, age);
 		memberRepository.save(member);
        
 		ModelAndView mav = new ModelAndView("save-result");
 		mav.addObject("member", member);
 		return mav;
 	}
    
 	@RequestMapping
 	public ModelAndView members() {
 		
        List<Member> members = memberRepository.findAll();
 
 		ModelAndView mav = new ModelAndView("members");
 		mav.addObject("members", members);
 		return mav;
 	}
    
}

5.2 실용적인 방식 (@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping)

/** * v3
 * Model 도입
 * ViewName 직접 반환
 * @RequestParam 사용
 * @RequestMapping -> @GetMapping, @PostMapping
 */
 
@Controller
@RequestMapping("/springmvc/v3/members")
public class SpringMemberControllerV3 {

 	private MemberRepository memberRepository = MemberRepository.getInstance();
 
 	@GetMapping("/new-form")
 	public String newForm() {
 		return "new-form";
 	}
 
 	@PostMapping("/save")
 	public String save(
 		@RequestParam("username") String username,
 		@RequestParam("age") int age,
 		Model model) {
        
	  ember member = new Member(username, age);
 	  mberRepository.save(member);
 
 	  model.addAttribute("member", member);
      return "save-result";
	}
    
 	@GetMapping
 	public String members(Model model) {
 		List<Member> members = memberRepository.findAll();
 		model.addAttribute("members", members);
 		return "members";
 	}

}
  • Model 파라미터

    • save(), members()를 보면 Model을 파라미터로 받는 것을 확인할 수 있다. 스프링 MVC도 이런 편의 기능을 제공한다.
  • ViewName 직접 반환

    • 뷰의 논리 이름을 반환할 수 있다.
  • @RequestParam 사용

    • 스프링은 HTTP 요청 파라미터를 @RequestParam으로 받을 수 있다.
      • @RequestParam("username")은 request.getParameter("username")와 거의 같은 코드라 생각하면 된다.
  • @RequestMapping -> @GetMapping, @PostMapping

    • @RequestMapping은 URL만 매칭하는 것이 아니라, HTTP Method도 함께 구분할 수 있다.
      • @RequestMapping(value = "/new-form", method = RequestMethod.GET)도 가능

6. 스프링 MVC - 기본 기능

6.1 @RequestMapping

@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
 		 @RequestParam("username") String memberName,
 		 @RequestParam("age") int memberAge) {
         
 	log.info("username={}, age={}", memberName, memberAge);
 	return "ok";
}
  • @RequestParam : 파라미터 이름으로 바인딩
  • @RequestParam("username") String memberName == request.getParameter("username")
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
 		 @RequestParam String username,
 		 @RequestParam int age) {
         
 	log.info("username={}, age={}", memberName, memberAge);
 	return "ok";
}
  • HTTP 파라미터 이름이 변수 이름과 같으면 @RequestParam(name="xx") 생략가능
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(String username, int age) {
 	log.info("username={}, age={}", memberName, memberAge);
 	return "ok";
}
  • String, int, Integer 등의 단순 타입이면 @RequestParam도 생략 가능
  • @RequestParam 애노테이션을 생략하면 required=false을 적용
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
 		 @RequestParam(required = true) String username,
 		 @RequestParam(required = false) int age) {
         
 	log.info("username={}, age={}", memberName, memberAge);
 	return "ok";
}
  • @RequestParam.required

    • 파라미터 필수 여부
    • 기본값이 파라미터 필수(true)이다.
  • 주의 - 기본형에 null 입력

    • /request-param-required?username=
    • null을 int에 입력하는 것은 불가능
    • 따라서 null을 받을 수 있는 Integer로 변경하거나, 또는 defaultValue 사용
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
 		 @RequestParam(defaultValue = "guest") String username,
 		 @RequestParam(defaultValue = "-1") int age) {
         
 	log.info("username={}, age={}", memberName, memberAge);
 	return "ok";
}
  • 파라미터에 값이 있는 경우 defaultValue를 사용하면 기본 값을 적용할 수 있다.
@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";
}
  • 파라미터를 Map, MultiValueMap으로 조회할 수 있다.
  • 파라미터 값이 2개 이상이면 @RequestParam MultiValueMap 사용

6.2 @ModelAttribute

@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
 	log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
 	return "ok";
}
  • 실제 개발을 하면 요청 파라미터를 받아서 필요한 객체를 만들고 그 객체에 값을 넣어주어야 한다.
    • 스프링은 이 과정을 완전히 자동화해주는 @ModelAttribute 기능을 제공한다.
  • HelloData 객체가 생성되고, 요청 파라미터의 값도 모두 들어가 있다.
  • 실행
    • HelloData 객체를 생성한다.
    • 요청 파라미터의 이름으로 HelloData 객체의 프로퍼티를 찾는다. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩)한다.
      • ex) 파라미터 이름이 username이면 setUsername() 메서드를 찾아서 호출하면서 값을 입력한다.
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(HelloData helloData) {
 	log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
 	return "ok";
}
  • @ModelAttribute는 생략할 수 있다.
  • 스프링은 해당 생략시 다음과 같은 규칙을 적용한다.
    • String, int, Integer 같은 단순 타입 = @RequestParam
    • 나머지 = @ModelAttribute

6.3 HttpEntity

@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
 	String messageBody = httpEntity.getBody();
 	log.info("messageBody={}", messageBody);
 
 	return new HttpEntity<>("ok");
}
  • HttpEntity는 HTTP header, body 정보를 편리하게 조회
    • 메세지 바디 정보를 직접 조회
    • 요청 파라미터를 조회하는 기능과 관계 없음 (@RequestParam X, @ModelAttribute X)
  • 응답에서도 HttpEntity 사용 가능
    • 메세지 바디 정보를 직접 반환
    • 헤더 정보 포함 가능
    • view 조회 X
  • HttpEntity를 상속받은 객체들도 같은 기능을 제공한다.
    • RequestEntity
      • HttpMethode, url 정보 추가, 요청에서 사용
    • ResponseEntity
      • HTTP 상태 코드 설정 가능, 응답에서 사용
//JSON 정보를 @ModelAttribute처럼 바로 객체로 변환
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
 	HelloData data = httpEntity.getBody();
 	log.info("username={}, age={}", data.getUsername(), data.getAge()); 
    return "ok";
}

6.4 @RequestBody & @ResponseBody

@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
 	log.info("messageBody={}", messageBody);
 	return "ok";
}
  • @RequestBody

    • HTTP 메세지 바디 정보를 직접 조회 (@RequestParam 관계X, @ModelAttribute 관계X)
    • 헤더 정보가 필요하다면 HttpEntity를 사용하거나 @RequestHeader를 사용하면 된다.
  • 요청 파라미터 vs HTTP 메세지 바디

    • 요청 파라미터를 조회하는 기능 : @RequestParam, @ModelAttribute
    • HTTP 메세지 바디를 직접 조회하는 기능 : @RequestBody
  • @ResponseBody

    • 응답 결과를 HTTP 메세지 바디에 직접 담아서 전달할 수 있다.
    • view X
//JSON
@ResponseBody
@PostMapping("/request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messageBody) throws
IOException {
 	HelloData data = objectMapper.readValue(messageBody, HelloData.class);
 	log.info("username={}, age={}", data.getUsername(), data.getAge());
 	return "ok";
}
  • @RequestBody를 사용해서 HTTP 메세지에서 데이터를 꺼내고 messageBody에 저장한다.
  • 문자로 된 JSON 데이터인 messageBody를 objectMapper를 통해서 자바 객체로 변환한다.
//JSON 정보를 @ModelAttribute처럼 바로 객체로 변환
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {
	log.info("username={}, age={}", data.getUsername(), data.getAge());
 	return "ok";
}
  • @RequestBody HelloData data
  • @RequestBody에 직접 만든 객체를 지정할 수 있다.
  • @RequestBody는 생략 불가능
//객체를 HTTP 메세지 바디에 직접 반환
@ResponseBody
@PostMapping("/request-body-json-v5")
public HelloData requestBodyJsonV5(@RequestBody HelloData data) {
 	log.info("username={}, age={}", data.getUsername(), data.getAge());
 	return data;
}
  • @ResponseBody를 사용하면 해당 객체를 HTTP 메세지 바디에 직접 넣어줄 수 있다.
  • 물론 이 경우에도 HttpEntity를 사용해도 된다.
  • @RequestBody 요청
    • JSON 요청 -> HTTP 메세지 컨버터 -> 객체
  • @ResponseBody 응답
    • 객체 -> HTTP 메세지 컨버터 -> JSON 응답

6.5 @RestController

@RestController
public class MappingController {

 	private Logger log = LoggerFactory.getLogger(getClass());
    
    @ResponseBody
 	@RequestMapping("/hello-basic")
 	public String helloBasic() {
 		log.info("helloBasic");
 		return "ok";
 	}
    
}
  • @RestController : 클래스 레벨

  • @ResonseBody : 메서드 레벨

  • @RestController 애노테이션을 사용하면, 해당 컨트롤러에 모두 @ResponseBody가 적용되는 효과가 있다.

  • @Controller는 반환 값이 String이면 뷰 이름으로 인식된다. 그래서 뷰를 찾고 뷰가 렌더링 된다.

  • @RestController는 반환 값으로 뷰를 찾는 것이 아니라, HTTP 메세지 바디에 바로 입력한다.따라서 실행 결과로 ok 메세지를 받을 수 있다.

6.6 @PathVariable

@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String data) {
 	log.info("mappingPath userId={}", data);
 	return "ok";
}
//다중
@GetMapping("/mapping/users/{userId}/orders/{orderId}")
public String mappingPath(@PathVariable String userId, @PathVariable Long
orderId) {
 	log.info("mappingPath userId={}, orderId={}", userId, orderId);
 	return "ok";
}
  • @RequestMapping은 URL 경로를 템플릿화 할 수 있는데, @PathVariable을 사용하면 매칭 되는 부분을 편리하게 조회할 수 있다.
  • @PathVariable의 이름과 파라미터 이름이 같으면 생략할 수 있다.
    • @PathVariable("userId") String userId -> @PathVariable String userId

6.7 그 외

6.7.1 params : 특정 파라미터가 있거나 없는 조건 추가
@GetMapping(value = "/mapping-param", params = "mode=debug")
public String mappingParam() {
 	log.info("mappingParam");
 	return "ok";
}
6.7.2 headers : 파라미터 매핑과 비슷하지만, HTTP 헤더를 사용
@GetMapping(value = "/mapping-header", headers = "mode=debug")
public String mappingHeader() {
 	log.info("mappingHeader");
 	return "ok";
}
6.7.3 consume : HTTP 요청 Content-Type 조건 추가
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
 	log.info("mappingConsumes");
 	return "ok";
}
6.7.4 produce : HTTP 요청 Accept 조건 추가
@PostMapping(value = "/mapping-produce", produces = "text/html")public String mappingProduces() {
 	log.info("mappingProduces");
 	return "ok";
}

참고
HTTP 응답 - 정적 리소스, 뷰 템플릿
src/main/resources/static 경로에 보관하면 리소스 서비스 제공

7. HTTP 메세지 컨버터

8. 서비스 제공 흐름

profile
블로그 이전했습니다 -> https://young-code.tistory.com

0개의 댓글