Day94 java프로그래밍 기초(Spring web MVC)
1.페이지 컨트롤러 만드는 방법
1)RequestMapping
- Dispatcher Servlet(프론트 컨트롤러)가 페이지 컨트롤러를 찾기위해 붙이는 에노테이션이다.
- 컨트롤러에 URL을 매핑하여 설정한다.
- URL당 한개의 핸들러에만 연결할 수 있다.
- 한개의 request handler에 여러개의 URL을 매핑 할 수 있다.
requestMapping이 붙은 메서드를 요청핸들러 라고한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
@Controller @RequestMapping("/c01_3") // 핸들러에 적용될 기본 URL을 지정한다. public class Controller01_3 { @RequestMapping("h1") // 기본 URL에 뒤에 붙는 상세 URL. 예) /c01_3/h1 @ResponseBody public String handler() { return "h1"; } @RequestMapping("/h2") // 앞에 /를 붙여도 되고 생략해도 된다. 예) /c01_3/h2 @ResponseBody public String handler2() { return "h2"; } @RequestMapping("h3") // @ResponseBody public String handler3() { return "h3"; } @RequestMapping("h4") @ResponseBody public String handler4() { return "h4"; } @RequestMapping({"h5", "h6", "h7"}) @ResponseBody public String handler5() { return "h5,h6,h7"; } }
2. RequestMapping 구분하기
- 프론트 컨트롤에서서 요청이 들어오면 RequestMapping에 따라 요청핸들러가 결정되며, 요청매핑의 설정에 따라 결정된다.
- RequestMapping : 모든 HTTP 요청에 대해 요청핸들러를 호출한다.
- GetMapping : 요청메서드가 GET요청일때 요청핸들러를 호출한다.
PostMapping : 요청매서드가 POST요청일때 요청핸들러를 호출한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
@Controller @RequestMapping("/c02_2") public class Controller02_2 { // 테스트 방법: // => http://localhost:9999/eomcs-spring-webmvc/html/app1/c02_2.html @GetMapping // GET 요청일 때만 호출된다. @ResponseBody public String handler1() { return "get"; } @PostMapping // POST 요청일 때만 호출된다. @ResponseBody public String handler2() { return "post"; } @RequestMapping({RequestMethod.GET, RequestMethod.HEAD}) // GET과 HEAD요청일 때만 호출된다. @ResponseBody public String handler1() { System.out.println("handler1() 호출됨!"); return "get"; } }
3. 요청핸들러를 구분하는 방법
1)params
- GET요청에서 QueryString에 담긴 parmeter정보를 통해 매핑을 할 수 있다.
- Params의 항목으로 중복되어 Mapping을 하는 경우 에러가 발생한다.
- url?name=kim&age=20으로 들어왓는데 mapping이 mapping(name),mapping(age)로 된경우 요청핸들러 호출이 모호해진다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
@Controller @RequestMapping("/c03_1") public class Controller03_1 { @GetMapping(params = "name") @ResponseBody public String handler1() { return "handler1"; } @GetMapping(params = "age") @ResponseBody public String handler2() { return "handler2"; } // => 만약 이 요청 핸들러가 없다면, queryString에 name과 age가 동시에 들어오는 경우 에러발생 @GetMapping(params = {"age", "name"}) @ResponseBody public String handler3() { return "handler3"; } @GetMapping @ResponseBody public String handler4() { return "handler4"; } }
2)headers
- 요청헤더 중에서 특정 이름을 갖는 헤더가 있을 때, 요청핸들러를 지정할수 있다.
JS같은 프로그래밍으로 임의의 HTTP요청을 할때 헤더를 추가할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
@Controller @RequestMapping("/c03_2") public class Controller03_2 { // @GetMapping(headers = "name") @RequestMapping(method = RequestMethod.GET, headers = "name") @ResponseBody public String handler1() { return "handler1"; } // @GetMapping(headers="age") @RequestMapping(method = RequestMethod.GET, headers = "age") @ResponseBody public String handler2() { return "handler2"; } @GetMapping(headers = {"age", "name"}) @ResponseBody public String handler3() { return "handler3"; } @GetMapping @ResponseBody public String handler4() { return "handler4"; } }
3)produces
- Accept헤더 : client에서 서버에 요청할때 받고자 하는 콘텐트의 타입을 알려주는 헤더
produces설정을 통해 제공 받는 콘텐트 타입의 종류를 설정할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
@Controller @RequestMapping("/c03_3") public class Controller03_3 { @GetMapping(produces = "text/plain") @ResponseBody public String handler1() { return "handler1"; } @GetMapping(produces = "text/html") @ResponseBody public String handler2() { return "handler2"; } @GetMapping(produces = "application/json") @ResponseBody public String handler3() { return "handler3"; } @GetMapping @ResponseBody public String handler4() { return "handler4"; } }
4)consumes
- Content-Type : 클라이언트가 서버에 보내는 데이터 타입니다.
consumes 설정을 통해 서버가 받는 타입에 따라 매핑 설정이 가능하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
@Controller @RequestMapping("/c03_4") public class Controller03_4 { // 다음 메서드는 application/x-www-form-urlencoded 형식의 데이터를 소비한다. // => 즉 클라이언트의 HTTP 요청에서 Content-Type 헤더의 값이 위와 같을 때 // 이 메서드를 호출하라는 의미다. @PostMapping(consumes = "application/x-www-form-urlencoded") @ResponseBody public String handler1() { return "handler1"; } // 다음 메서드는 multipart/form-data 형식의 데이터를 소비한다. @PostMapping(consumes = "multipart/form-data") @ResponseBody public String handler2() { return "handler2"; } // 다음 메서드는 text/csv 형식의 데이터를 소비한다. @PostMapping(consumes = "text/csv") @ResponseBody public String handler3() { return "handler3"; } // 다음 메서드는 application/json 형식의 데이터를 소비한다. @PostMapping(consumes = "application/json") @ResponseBody public String handler4() { return "handler4"; } // 다음 메서드는 Content-Type 헤더가 없을 때 호출된다. @RequestMapping @ResponseBody public String handler5() { return "handler5"; } }
4. 요청핸들러의 아규먼트
1) 프로트 컨트롤러에서 받는 아규먼트
- 프론트컨트롤러에서 기본적으로 받을 수 있는 아규먼트는 다음과 같다.
- ServletRequest, ServletResponse
- HttpServletRequest, HttpServletResponse, HttpSession
- Map, Model
- PrintWriter
ServletContext는 의존 객체로 주입 받아야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
@Controller @RequestMapping("/c04_1") public class Controller04_1 { // ServletContext는 의존 객체로 주입 받아야 한다. // 요청 핸들러에서 파라미터로 받을 수 없다. @Autowired ServletContext sc; // 요청 핸들러에서 받을 수 있는 타입의 아규먼트를 선언해 보자! @GetMapping("h1") @ResponseBody public void handler1( // ServletContext sc, // ServletContext는 파라미터로 받을 수 없다. 예외 발생! // 의존 객체로 주입 받아야 한다. ServletRequest request, ServletResponse response, HttpServletRequest request2, HttpServletResponse response2, HttpSession session, Map<String, Object> map, // JSP에 전달할 값을 담는 임시 보관소 Model model, // Map과 같다. 둘 중 한 개만 받으면 된다. PrintWriter out // 클라이언트에게 콘텐트를 보낼 때 사용할 출력 스트림 ) { } }
2) RequestParam
- 클라이언트가 보낸 파라미터를 아규먼트로 사용 할 수 있다.
- GET요청에서는 quertyString에서 param을 받는다.
- POST요청에서는 URL 쿼리 스트링 또는 application/x-www-form-urlencoded 형식으로 전송되어야 한다.
요청 파라미터 이름과 메서드 파라미터(아규먼트)의 이름이 같다면 애노테이션을 생략해도 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
@Controller @RequestMapping("/c04_2") public class Controller04_2 { @GetMapping("h1") @ResponseBody public void handler1( PrintWriter out, ServletRequest request, @RequestParam(value = "name") String name1, @RequestParam(name = "name") String name2, // value와 name은 같은 일을 한다. @RequestParam("name") String name3, // value 이름을 생략할 수 있다. /* @RequestParam("name") */ String name // 생략가능 ) { } @GetMapping("h2") @ResponseBody public void handler2( PrintWriter out, @RequestParam("name1") String name1, // 애노테이션을 붙이면 필수 항목으로 간주한다. // 따라서 파라미터 값이 없으면 예외가 발생한다. String name2, // 애노테이션을 붙이지 않으면 선택 항목으로 간주한다. // 따라서 파라미터 값이 없으면 null을 받는다. @RequestParam(value = "name3", required = false) String name3, // required 프로퍼티를 false로 설정하면 선택 항목으로 간주한다. @RequestParam(value = "name4", defaultValue = "ohora") String name4 // 기본 값을 지정하면 파라미터 값이 없어도 된다. ) { } }
3) 도메인 객체로 요청 파라미터 값 받기
- 클라이언트가 보낸 요청 파라미터 값을 값 객체에 받을 수 있다.
- 프론트 컨트롤러는 매서드를 호출 할 때 객체의 인스턴스를 생성한후, 요청 파라미터와 일치하는 프로퍼티에 대해 값을 저장한다.
값 객체 안에 또 값 객체가 있을 때는 OGNL(ex. 내부객체클래스.프로퍼티명) 방식으로 요청 파라미터 값을 지정한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
@Controller @RequestMapping("/c04_3") public class Controller04_3 { @GetMapping(value = "h1", produces = "text/plain;charset=UTF-8") @ResponseBody public String handler1( String model, String maker, @RequestParam(defaultValue = "100") int capacity, // 프론트 컨트롤러가 String 값을 int로 변환해 준다. // 단 변환할 수 없을 경우 예외가 발생한다. boolean auto, // 프론트 컨트롤러가 String 값을 boolean으로 변환해 준다. // 단 변환할 수 없을 경우 예외가 발생한다. // "true", "false"는 대소문자 구분없이 true, false로 변환해 준다. // 1 ==> true, 0 ==> false 로 변환해 준다. 그 외 숫자는 예외 발생! Car car ) { return String.format("model=%s\n", model) + String.format("maker=%s\n", maker) + String.format("capacity=%s\n", capacity) + String.format("auto=%s\n", auto) + String.format("car=%s\n", car); } }
4) 프로퍼티 에디터 추가하기
- primitive type의 아규먼트는 자동으로 형변환을 해주지만, 그렇지 않은 경우는 에디터를 추가 해야한다.
- @InitBinder : 에디터는 Request Handler를 호출하기 전에 에디터를 설정해야하기 때문에 이 애노테이션을 통해 프론트컨트롤에 알려준다.
- 에디터 등록 : WebDataBinder.registerCustomEditor(class, new PropertyEditor())을 통해 등록한다.
- PropertyEditor : 객체는 PropertyEditorSupport를 상속받아 setAsText메서드를 통해 커스텀한다.
request handler의 아규먼트 개수 만큼 이 메서드를 호출한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
@Controller @RequestMapping("/c04_4") public class Controller04_4 { @GetMapping("h1") @ResponseBody public void handler1( PrintWriter out, String model, @RequestParam(defaultValue = "5") int capacity, boolean auto, Date createdDate // 프로퍼티 에디터를 설정하지 않으면 변환 오류 발생 ) { } @GetMapping("h2") @ResponseBody public void handler2(PrintWriter out, // 콤마(,)로 구분된 문자열을 Car 객체로 변환하기? // => String ===> Car 프로퍼티 에디터를 등록하면 된다. @RequestParam("car") Car car ) { } @InitBinder public void initBinder(WebDataBinder binder) { System.out.println("Controller04_4.initBinder()..."); binder.registerCustomEditor(java.util.Date.class ,new DatePropertyEditor()); binder.registerCustomEditor(Car.class, new CarPropertyEditor()); } class DatePropertyEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { try { setValue(java.sql.Date.valueOf(text)); } catch (Exception e) { throw new IllegalArgumentException(e); } } } class CarPropertyEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { String[] values = text.split(","); Car car = new Car(); car.setModel(values[0]); car.setCapacity(Integer.parseInt(values[1])); car.setAuto(Boolean.parseBoolean(values[2])); car.setCreatedDate(java.sql.Date.valueOf(values[3])); setValue(car); } } }
5) 글로벌 프로퍼티 에디터 추가하기
- InitBinder를 별도의 클래스에서 관리하며 모든 페이지 컨트롤러에 에디터를 사용한다.
@ControllerAdvice 에노테이션을 사용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
@ControllerAdvice public class GlobalControllerAdvice { @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(class, new PropertyEditor()); } class PropertyEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { try { //커스텀 setValue(/*커스텀 내용*/text); } catch (Exception e) { throw new IllegalArgumentException(e); } } } }
6) 요청핸들러의 아규먼트
클라이언트의 HTTP 요청 헤더를 받고 싶으면request handler의 아규먼트 앞에 @RequestHeader(헤더명) 애노테이션을 붙이면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
@Controller @RequestMapping("/c04_6") public class Controller04_6 { @GetMapping("h1") @ResponseBody public void handler1( PrintWriter out, @RequestHeader("Accept") String accept, @RequestHeader("User-Agent") String userAgent) { out.printf("Accept=%s\n", accept); out.printf("User-Agent=%s\n", userAgent); if (userAgent.matches(".*Edg.*")) { out.println("Edge"); } else if (userAgent.matches(".*Chrome.*")) { out.println("chrome"); } else if (userAgent.matches(".*Safari.*")) { out.println("safari"); } else if (userAgent.matches(".*Firefox.*")) { out.println("firefox"); } else { out.println("etc"); } } }
7) 요청핸들러의 쿠키
@CookieValue(쿠키명) 애노테이션을 request handler의 아규먼트 앞에 붙인다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
@Controller @RequestMapping("/c04_7") public class Controller04_7 { @GetMapping("h1") @ResponseBody public void handler1(PrintWriter out, HttpServletResponse response) { // 이 메서드에서 쿠키를 클라이언트로 보낸다. try { // 쿠키의 값이 ASCII가 아니라면 URL 인코딩 해야만 데이터가 깨지지 않는다. // URL 인코딩을 하지 않으면 ? 문자로 변환된다. response.addCookie(new Cookie("name1", "AB가각")); response.addCookie(new Cookie("name2", URLEncoder.encode("AB가각", "UTF-8"))); response.addCookie(new Cookie("name3", "HongKildong")); response.addCookie(new Cookie("age", "30")); } catch (Exception e) { e.printStackTrace(); } out.println("send cookie!"); } @GetMapping(value = "h2", produces = "text/plain;charset=UTF-8") @ResponseBody public String handler2(@CookieValue(value = "name1", required = false) String name1, @CookieValue(value = "name2", defaultValue = "") String name2, @CookieValue(value = "name3", defaultValue = "홍길동") String name3, @CookieValue(value = "age", defaultValue = "0") int age // String ===> int 자동 변환 ) throws Exception { String namex = new String(originBytes, "UTF-8"); return String.format(// "name1=%s\n name2=%s\n name2=%s\n name3=%s\n age=%d\n", // name1, name2, namex, name3, age); } }
5. 요청핸들러의 리턴값
1) ResponseBody
- 리턴 값이 클라이언트에게 보내는 콘텐트라면 메서드 선언부에 @ResponseBody를 붙인다.
리턴되는 콘텐트의 MIME 타입과 charset을 지정하고 싶다면 애노테이션의 produces 프로퍼티에 설정하라.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
@Controller @RequestMapping("/c05_1") public class Controller05_1 { @GetMapping("h1") @ResponseBody public String handler1() { return "<html><body><h1>abc가각간</h1></body></html>"; } @GetMapping(value = "h2", produces = "text/html;charset=UTF-8") @ResponseBody public String handler2() { return "<html><body><h1>abc가각간<h1></body></html>"; } @GetMapping("h3") @ResponseBody public String handler3(HttpServletResponse response) { // HttpServletResponse에 대해 다음과 같이 콘텐트 타입을 설정해봐야 소용없다. response.setContentType("text/html;charset=UTF-8"); return "<html><body><h1>abc가각간<h1></body></html>"; } @GetMapping("h4") public HttpEntity<String> handler4(HttpServletResponse response) { // HttpEntity 객체에 콘텐트를 담아 리턴할 수 있다. // 이 경우에는 리턴 타입으로 콘텐트임을 알 수 있기 때문에 // @ResponseBody 애노테이션을 붙이지 않아도 된다. HttpEntity<String> entity = new HttpEntity<>( "<html><body><h1>abc가각간<h1></body></html>"); return entity; } @GetMapping(value = "h5", produces = "text/html;charset=UTF-8") public HttpEntity<String> handler5(HttpServletResponse response) { // 한글을 제대로 출력하고 싶으면 위 애노테이션의 produces 속성에 콘텐트 타입을 지정한다. HttpEntity<String> entity = new HttpEntity<>( "<html><body><h1>abc가각간<h1></body></html>"); return entity; } @GetMapping("h6") public HttpEntity<String> handler6(HttpServletResponse response) { // 한글을 제대로 출력하고 싶으면, // 응답 헤더에 직접 Content-Type을 설정할 수 있다. HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "text/html;charset=UTF-8"); HttpEntity<String> entity = new HttpEntity<>( "<html><body><h1>abc가각간<h1></body></html>", headers); return entity; } @GetMapping("h7") public ResponseEntity<String> handler7(HttpServletResponse response) { HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "text/html;charset=UTF-8"); headers.add("BIT-OK", "ohora"); ResponseEntity<String> entity = new ResponseEntity<>( "<html><body><h1>abc가각간<h1></body></html>", headers, HttpStatus.OK // 응답 상태 코드를 설정할 수 있다. ); return entity; } }
2) view 컴포넌트(JSP) 쪽에 데이터 전달하기
- JSP가 꺼내 쓸 수 있도록 ServletRequest 객체에 직접 담는다.
- 맵 객체에 값을 담아 놓으면 프론트 컨트롤러가 JSP를 실행하기 전에 ServletRequest로 복사한다.
아규먼트에 Model 타입의 변수를 선언하면 프론트 컨트롤러는 모델 객체를 만들어 넘겨준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
@Controller @RequestMapping("/c05_3") public class Controller05_3 { @GetMapping("h1") public String handler1(ServletRequest request) { request.setAttribute("name", "홍길동"); request.setAttribute("age", 20); // auto-boxing: int ===> Integer 객체 request.setAttribute("working", true); // auto-boxing: boolean ===> Boolean 객체 return "/WEB-INF/jsp/c05_3.jsp"; } @GetMapping("h2") public String handler2(Map<String,Object> map) { map.put("name", "홍길동"); map.put("age", 20); // auto-boxing map.put("working", true); // auto-boxing return "/WEB-INF/jsp/c05_3.jsp"; } @GetMapping("h3") public String handler3(Model model) { model.addAttribute("name", "홍길동"); model.addAttribute("age", 20); // auto-boxing model.addAttribute("working", true); // auto-boxing return "/WEB-INF/jsp/c05_3.jsp"; } @GetMapping("h4") public ModelAndView handler4() { ModelAndView mv = new ModelAndView(); mv.addObject("name", "홍길동"); mv.addObject("age", 20); // auto-boxing mv.addObject("working", true); // auto-boxing mv.setViewName("/WEB-INF/jsp/c05_3.jsp"); return mv; } }
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.