스프링 mvc 기본

bo04·2022년 6월 21일
0

spring-mvc1

목록 보기
9/9

어노테이션

@RequestMapping

바로 앞 포스트에서 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 등 모든 방식에서 똑같이 작동함

@RequestParam

HTTP 요청 파라미터를 @RequestParam으로 받을 수 있음.
@RequestParam(...)request.getParameter(...)와 똑같이 동작함.
get, post form 방식 모두 지원됨

프로젝트

package

package=일괄=java의 jar 툴을 이용하여 생성된 압축 파일이며, 쉽게 말해 어플리케이션을 쉽게 배포하고 동작시킬 수 있도록 있도록 관련 파일(리소스, 속성파일 등)들을 패키징(packaging)해주는 것

war

WAR=Web Application Archive=웹 어플리케이션 압축=.war파일로 패키징됨

  • 웹 어플리케이션을 위한 포맷이기 때문에 웹 관련 자원만 포함하고 있어서 이를 실행하려면 따로 외부 서버(톰캣)가 필요함.
  • 내장 서버(톰캣)도 사용가능하긴하지만 주로 외부 서버에 배포할때 사용함
  • 이미 구조가 WEB-INF 및 META-INF 디렉토리로 정해져있어 이 구조를 바탕으로 사용해야됨
  • jsp를 사용할때 사용하는 패키지

jar

JAR=Java Archive=자바 압축=.jar파일로 패키징됨

  • java 어플리케이션이 동작할 수 있도록 자바 프로젝트를 압축한 파일이라 JRE만 있으면 프로젝트가 실행 가능함.
  • war의 구조가 정해진것과 다르게 jar은 원하는 구조로 구성해서 사용할 수 있음.

war과 jar이 비슷해서 특정한 용도(jsp, 외부 서버)가 아니라면 요즘은 스프링 부트에서 추천하는 jar을 많이 사용하는 추세임.

welcome page

welcome page=localhost:8080/을 실행시 보여지는 페이지

  • package jar를 사용: /resources/static/위치에 index.html 파일을 두면 웰컴 페이지로 처리해줌
  • package war를 사용: /webapp/index.html 을 두면 웰컴 페이지로 처리해줌

log

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 값이 들어갈 일이 없어서 좋음.

content-type 조건 매핑

headers를 건드리는 거라서 postman을 이용해야지 테스트가 훨씬 수월함

  • consumes
    request로 오는 데이터의 content-type을 확인해서 맞으면 매핑함
@PostMapping(value = "/mapping-consume", consumes = "application/json")
public String mappingConsumes() {
  log.info("mappingConsumes");
  return "ok";
}
  • produces
    response로 보내는 데이터의 content-type을 확인해서 맞으면 매핑함
@PostMapping(value = "/mapping-produce", produces = "text/html")
public String mappingProduces() {
  log.info("mappingProduces");
  return "ok";
}

@ModelAttribute

@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를 호출해서 파라미터의 값을 입력(바인딩)함.

HttpEntity<..>, @RequestBody

HttpMessageConverter(http 메시지 컨버터)=springmvc가 http body 메시지를 원하는 문자나 객체로 자동변환해줌

get방식이나 post form 방식으로 전달된 파라미터가 아닌(@ModelAttribute,@RequestParam), 그 외 text,json,xml 등 모든 방식으로 전달된 http message body 데이터를 받아서 원하는 형식으로 데이터를 바꿔주는 http 메시지 컨버터가 중간에서 동작하므로 httpEntity@RequestBody, @ResponseBody 등을 사용하면 편하게 데이터를 사용할 수 있음.

  • HttpEntity<...>
    request로 온 http header와 body 둘다 조회할 수 있는 클래스로, request 조회도 가능하지만 response로 보내는 것도 가능함
@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
    request로 온 http message body만 조회할 수 있음
@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에 리턴된 값이 전달돼서 보여짐

  • thymeleaf
    thyemeleaf 의존성을 추가하면 스프링 부트가 자동으로 ThymeleafViewResolver와 필요한 스프링 빈들을 등록함.
    그리고 application.properties에 prefix,suffix값이 jsp처럼 들어가는데, thymeleaf는 자동으로 설정돼서 뷰 리졸버가 해당 뷰를 찾고 렌더링할 수 있게 해줌.
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

@RestController

@RestController=@Controller+@ResponseBody=뷰 템플릿을 사용하는 것이 아니라 HTTP message body에 직접 데이터를 입력함

httpMessageConverter

  • http header의 content-type: request로 보내져서 서버가 받은 데이터의 타입
  • http accept: 받는 수많은 데이터 타입중에서 서버가 해석할 수 있는 데이터 타입

스프링 부트 기본 메시지 컨버터

대상 클래스 타입과 미디어 타입(content-type)을 체크해서 적합한 메시지 컨버터가 호출되는데, 숫자가 작을수록 우선순위 높음.

  1. ByteArrayHttpMessageConverter (클래스 타입: byte[] , 미디어타입: */*)
  2. StringHttpMessageConverter (클래스 타입: String, 미디어타입: */*)
  3. 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가 동작함

HttpMessageConverter가 동작하는 위치

프론트에서 전달된 데이터를 컨트롤러에서 받거나 보낼때 동작하므로 핸들러 어댑터-핸들러사이에서 동작할 확률이 높음. 더 정확하게는 애노테이션 기반의 컨트롤러인, @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를 사용한다는 것을 알 수 있음.

참고

0개의 댓글