바로 앞 포스트에서 Handler와 Handler Adapter에서 우선순위가 가장 높은 것은 RequestMapping
어노테이션이 있는것이라고 했는데 이 RequestMapping
을 의미함.
@Controller // @RequestMapping 애노테이션이 아니므로 빈 이름으로 핸들러를 인식하게됨
public class SpringMemberFormControllerV1 {
@RequestMapping("/springmvc/v1/members/new-form")
public ModelAndView process() {
return new ModelAndView("new-form");
}
}
위의 클래스는 @Controller
로 정의되어 있으므로 @RequestMapping
어노테이션으로 핸들러를 찾지못해서 빈 이름으로 핸들러로 인식됨.
그리고 @RequestMapping
어노테이션 때문에 핸들러 어댑터로 인식돼서 handle()
호출될때 해당 메소드가 실행됨
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
/**
* Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
* if view resolution is required.
* @since 4.2
* @see #createInvocableHandlerMethod(HandlerMethod)
*/
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
...
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
...
invocableMethod.invokeAndHandle(webRequest, mavContainer);
...
}
}
참고로 @RequestMapping
은 따로 http method를 설정해놓지 않으면 get,post,put 등 모든 방식에서 똑같이 작동함
HTTP 요청 파라미터를 @RequestParam
으로 받을 수 있음.
@RequestParam(...)
은 request.getParameter(...)
와 똑같이 동작함.
get, post form 방식 모두 지원됨
package=일괄=java의 jar 툴을 이용하여 생성된 압축 파일이며, 쉽게 말해 어플리케이션을 쉽게 배포하고 동작시킬 수 있도록 있도록 관련 파일(리소스, 속성파일 등)들을 패키징(packaging)해주는 것
WAR=Web Application Archive=웹 어플리케이션 압축=.war파일로 패키징됨
JAR=Java Archive=자바 압축=.jar파일로 패키징됨
war과 jar이 비슷해서 특정한 용도(jsp, 외부 서버)가 아니라면 요즘은 스프링 부트에서 추천하는 jar을 많이 사용하는 추세임.
welcome page=
localhost:8080/
을 실행시 보여지는 페이지
/resources/static/
위치에 index.html
파일을 두면 웰컴 페이지로 처리해줌/webapp/
에 index.html
을 두면 웰컴 페이지로 처리해줌log=일지
기존에는 System.out.println()
로 체크했지만 만약 많은 사용자들이 몰리면서 무분별하게 이런 출력문이 많이 발생한다면 성능 하락이 발생할 수 있음. 그리고 운영과 개발 단계에서 꼭 필요한 출력문만 출력된다면 성능면에서나 개발면에서 훨씬 편하게 작업할 수 있을텐데... -> 로그 기능
spring-boot-starter
라이브러리에 기본적으로 들어있으므로 별도의 라이브러리가 필요하지 않음.@Slf4j // 로그 애노테이션
@RestController
public class LogTestController {
@RequestMapping("/log-test")
public String logTest() {
String name = "Spring";
System.out.println("LogTestController.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);
// 해당 로그 레벨이 아니여서 로그가 보이지 않아도 문자열+문자열이면 해당 문자열 연산을 하기 때문에 낭비됨.
// 따라서 로그 사용시 문자열+문자열은 사용하지 말기
log.debug("String concat log=" + name);
return "ok"; // @RestController덕분에 jsp 이름이 아니라 문자열이 http 메시지 body에 그대로 반환돼서 보임
}
}
로그 레벨
TRACE > DEBUG > INFO > WARN > ERROR
일반적으로 개발 서버는 debug 레벨이고, 운영 서버는 info 레벨임.
application.properties
# 전체 로그 레벨 설정(기본적으로 전체 로그는 info 레벨로 설정되어 있음)
# 더 높은 레벨로 바꾸면 라이브러리의 로그도 나오므로 전체 로그는 왠만하면 수정하지 말고 밑에처럼 프로젝트의 패키지 로그 레벨을 수정하자
logging.level.root=info
# hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=debug
@RequestParam
어노테이션@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String userId) {
// 이름이 같으므로 (@PathVariable String userId)도 가능
log.info("mappingPath userId={}", userId);
return "ok";
}
@RequestParam(required = true) Integer age
해당 파라미터가 필수로 있어야 된다고 알리는 속성으로,
localhost:8080/request-param?username=
처럼 파라미터 이름만 있고 값이 없으면 값이 ""로 들어감. 그러나 파라미터 이름도 없으면 null로 들어가므로 int같은 형을 사용하면 안되고 Integer을 사용해야됨
@RequestParam(required = true, defaultValue = "guest")
파라미터를 직접 입력안하면 해당 파라미터의 값에 defaultValue
의 값이 들어감.
required=true
라서 null값 때문에 기본형이 신경쓰일때 defaultValue
를 쓰면 null 값이 들어갈 일이 없어서 좋음.
headers를 건드리는 거라서 postman을 이용해야지 테스트가 훨씬 수월함
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
log.info("mappingConsumes");
return "ok";
}
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
log.info("mappingProduces");
return "ok";
}
@RequestParam
과 똑같은 역할을 하지만, @RequestPamram
은 int,String같은 기본형에 사용하고 @ModelAttribute
는 직접 만든 객체에 사용함
@Data
class HelloData {
private String username;
}
...
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
...
}
여기서 @ModelAttribute
는 요청된 파라미터의 이름(username)으로 HelloData
객체의 프로퍼티(username)를 찾음. 그리고 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력(바인딩)함.
HttpMessageConverter(http 메시지 컨버터)=springmvc가 http body 메시지를 원하는 문자나 객체로 자동변환해줌
get방식이나 post form 방식으로 전달된 파라미터가 아닌(@ModelAttribute
,@RequestParam
), 그 외 text,json,xml 등 모든 방식으로 전달된 http message body 데이터를 받아서 원하는 형식으로 데이터를 바꿔주는 http 메시지 컨버터가 중간에서 동작하므로 httpEntity
나 @RequestBody
, @ResponseBody
등을 사용하면 편하게 데이터를 사용할 수 있음.
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");
}
@RequestBody
@ResponseBody // response 결과로 뷰를 보여주는게 아니라 바디에 넣어서 심플하게 전달해줌(string을 넣어도 되고 객체를 넣어도 되고..)
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
// public HelloData requestBodyStringV4(@RequestBody HelloData data) {
log.info("messageBody={}", messageBody);
// return data;
return "ok";
}
웹 브라우저에 정적인 html,js,css를 제공할때 사용하는데 src/main/resources/static
에 들어있음.
리소스의 경로대로 url에서 접근 가능함
웹 프라우저에 동적인 html을 제공할때 사용하는데 src/main/resources/templates
에 들어있음.
thymeleaf가 여기에 해당됨.
정적 리소스와 다르게 직접 url에서 접근할 수 없고 컨트롤러에서만 접근 가능함.
참고로 컨트롤러에 @ResponseBody
가 없으면 view resolver가 실행돼서 리턴된 값으로 뷰를 찾고 렌더링하고, 있으면 http message body에 리턴된 값이 전달돼서 보여짐
ThymeleafViewResolver
와 필요한 스프링 빈들을 등록함.application.properties
에 prefix,suffix값이 jsp처럼 들어가는데, thymeleaf는 자동으로 설정돼서 뷰 리졸버가 해당 뷰를 찾고 렌더링할 수 있게 해줌. spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
@RestController
=@Controller
+@ResponseBody
=뷰 템플릿을 사용하는 것이 아니라 HTTP message body에 직접 데이터를 입력함
대상 클래스 타입과 미디어 타입(content-type)을 체크해서 적합한 메시지 컨버터가 호출되는데, 숫자가 작을수록 우선순위 높음.
ByteArrayHttpMessageConverter
(클래스 타입: byte[] , 미디어타입: */*
)StringHttpMessageConverter
(클래스 타입: String, 미디어타입: */*
)MappingJackson2HttpMessageConverter
(클래스 타입: 객체 또는 HashMap, 미디어타입 application/json
)content-type: application/json // 서버가 request로 받은 데이터의 타입
@RequestMapping
void hello(@RequetsBody String data) {
...
}
대상 클래스 타입이 String이고 미디어 타입이 application/json
이므로 2번인 StringHttpMessageConverter
가 메시지 컨버터로써 사용됨=json 형식으로 받아진 데이터를 String 타입으로 메시지 컨버터가 자동 변환해줌
참고로
@ResponseBody
를 사용하면 http body에 문자 내용을 직접 반환하므로 view가 필요없음. 그래서 이 경우에는viewResolver
대신에HttpMesssageConverter
가 동작함
프론트에서 전달된 데이터를 컨트롤러에서 받거나 보낼때 동작하므로 핸들러 어댑터
-핸들러
사이에서 동작할 확률이 높음. 더 정확하게는 애노테이션 기반의 컨트롤러인, @RequestMapping
을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter
이 동작할때 필요에 따라 동작함.
ArgumentResolver
: HttpServeletRequest
,Model
,@RequestParam
,@ResponseBody
같은 다양한 애노테이션을 파라미터를 처리할 수 있게 도와줌.RequestMappingHandlerAdapter
가 컨트롤러를 호출할때 필요한 파라미터를 ArgumentResolver
에게 요청하고 해당 파라미터가 필요할때 ArgumentResolver
에서 만들어진 파라미터를 호출해서 사용함
// 해당 인터페이스를 부모로 한, 다양한 어노테이션을 지원하는 클래스들이 존재함
public interface HandlerMethodArgumentResolver {
// 해당 파라미터를 지원하는지 체크
boolean supportsParameter(MethodParameter parameter);
// 지원하면 실제 객체를 생성해서 어댑터에 전달됨
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory
binderFactory) throws Exception;
}
ReturnValueHandler
: 컨트로러에서 string으로 뷰 이름을 반환해도 뷰를 찾아주는 역할핸들러가 @RequestBody
와 @ResponseBody
를 사용하는 예제로, 필요에 따라 ArgumentResolver
들이 필요한
객체를 생성하기 위해 HttpMessageConverter
를 사용한다는 것을 알 수 있음.