실무에서 사용하는 Spring MVC 구조 & 순서 & 사용 완벽 정리

길셔·2022년 3월 9일
0

스프링(Spring)

목록 보기
10/10
post-thumbnail


순서

1. spring MVC 정의
2. Controller에서의 Request
3. Controller에서의 Response
3.5. 그 외 요청,응답 파라미터
4. Spring MVC 핵심
5. Spring 동작 순서
6. Spring_Controller 동작 이해
7. ViewResolver (뷰 리졸버)
8. @RequestMapping
9. Parameter
10. 서버에서 클라이언트로 응답
11. HTTP 메세지 컨버터
12. 요청 매핑 핸들러 어댑터 구조
13. 로깅( Logging )




1 Spring MVC 패턴사용

  • servlet에서 모든 로직(비즈니스, 출력 로직)을 만들다 보니 개발자가 작성하기 힘듬
  • 템플릿 엔진으로 비즈니스,출력 로직을 모두 작성, 근데 가독성 굉장히 떨어짐
  • controller, view로 나눠서 MVC 모델1을 사용
  • 근데 Controller에서 비즈니스로직+호출까지 담당하니 가독성 떨어짐
  • 그래서 Controller,Service,DTO,DAO,View 작업을 나눈 MVC 모델2를 사용
  • 현재는 MVC 하면 모델2
  • 함께 사용할 템플릿 엔진 : JSP, Thymeleaf



2 Controller에서의 Request

  • 이 객체는 임시 저장소 기능, 세션관리 기능을 해준다.
  • 저장소 : request.setAttribute(name, value), request.getAttribute(name)
  • 세션 : request.getSession(create: true)
  • 이런식으로 request 객체를 통해서 요청 or 응답할 데이터를 각 메소드를 통해서 요청할 수도, 응답할 수도 있다.

1) Http 요청 데이터

ㄱ. GET - 쿼리 파라미터

  • url?username=hello&age=20으로 메세지 바디없이 url에 쿼리 파라미터를 포함해서 전달
    • URL 뒤에 ?로 쿼리 파라미터를 시작, &로 파라미터를 구분해서 보낸다.
  • content-type이 없다.
  • 주로 검색, 필터, 페이징 등에서 사용

ㄴ. POST T-HTML Form

  • http 요청시 'content-type: application/x-www-form-urlencoded'으로 전달
  • 메세지 body에 쿼리 파라미터로 전달 username=hello&age=20
    • 웹 브라우저가 결과를 캐시하고 있어서 과거 작성했던 HTML 결과가 보이는 경우가 있기 때문에 웹브라우저의 새로고침을 하면된다. OR 서버 재시작 필수
    • 클라이언트에서는 get과 post를 전달하는 방식에 차이가 있지만 서버는 똑같이 쿼리 파라미터로 받기 때문에 둘다 request.getParameter()로 받으면 된다.
  • 주로 회원 가입, 상품 주문, HTML Form 사용

ㄷ. HTTP Message Body

  • HTTP API에서 주로 사용, JSON, XML, TEXT
  • 데이터 형식은 주로 JSON 사용
  • POST, PUT, PATCH (근데 주로 POST만 사용)
  • body에 데이터를 InputStream을 사용해서 직접 읽을 수 있다.
// inputstream으로 byte 코드를 반환
 ServletInputStream inputStream = request.getInputStream();

// 반환 받은 byte 코드를 utf_8 charset으로 지정해서 문자로 담기
 String messageBody = StreamUtils.copyToString(inputStream,StandardCharsets.UTF_8);

// body 출력
 System.out.println("messageBody = " + messageBody);

// 응답
 response.getWriter().write("ok");

  • JSON 결과를 파싱해서 사용할 수 있는 자바 객체로 변환하려면 Jackson, Gson 같은 JSON 변환 라이브러리를 추가해서 사용해야 한다. (스프링 부트는 기본으로 Jackson 라이브러리( ObjectMapper )를 함께 제공한다.



3 Controller에서의 Response

  • response 객체에 HTTP 응답 메시지 생성, HTTP 응답코드 지정, 헤더 생성, 바디 생성
    • .setHeader("Content-Type", "text/plain;charset=utf-8")처럼 헤더를 지정 가능
    • .setContentType("text/plain")등으로 Content 메서드를 정의 가능
    • Cookie 클래스로 쿠키를 생성하고, .setaddCookie(cookie)로 쿠키 지정 가능
    • .sendRedirect("url") 사용으로, url redirect 가능

1) HTTP 응답 데이터

  • 단순 텍스트 응답 : writer.println("ok");

  • HTML 응답
    • HTML로 응답시 content-type을 text/html로 지정

  • HTTP API에서는 주로 MessageBody JSON 응답
    • HTTP 응답으로 JSON을 반환할 때는 content-type을 application/json 로 지정

// 반환할 데이터 타입 지정
 response.setHeader("content-type", "application/json");

//Jackson 라이브러리가 제공하는 메서드를 사용해서 json 객체를 문자로 변경
//{"username":"kim","age":20}
 String result = objectMapper.writeValueAsString(data);
 response.getWriter().write(result);



3_ 그 외 파라미터

1. 스프링 MVC는 HTTP 응답 메세지의 바디 내용을 직접 조회, 출력이 가능하다.

  • InputStream(Reader)
  • OutputStream(Writer)

2. HttpMessageConvert

  • 요청 : HttpEntity< String > httpEntity
    • 이렇게 받으면 http의 body 내용을 httpmessage 컨버터가 동작해서 담아준다.
    • 사용 : httpEntity.getBody(); , httpEntity.getHeader();

  • 응답 : : HttpEntity< String >
    • HttpEntity<>(""); 담아 return
    • 당연히 view 조회 x

  • HttpEntity를 상속받은 RequestEntity, ResponseEntity도 있다.
    • url의 정보를 얻거나, 상태코드를 넣어서 응답할 수 있다.

  • @RequestBody
    • 파라미터에 '@RequestBody String messageBody'를 사용
    • http messagebody를 읽어서 변수에 넣는다.
    • @RequestBody 객체 파라미터
      • @RequestBody에 직접 만든 객체를 지정할 수 있다.(@RequestBody HelloData data)
      • 생략 x

  • @ResponseBody
    • 클래스에 어노테이션을 붙이면 응답코드에 return ""의 String 내용이 들어간다.

  • 이렇게 httpmessage body를 직접 조회하는 기능은 @RequestParam, @ModelAttribute 처럼 요청 파라미터를 직접 조회하는 기능과는 전혀 관계가 없다.



4 Spring MVC 핵심

1)

  • DispacherServlet 서블릿 등록
  • 부모 클래스에서 HttpServlet 을 상속 받아서 사용
  • 스프링 부트는 DispacherServlet 을 서블릿으로 자동으로 등록하면서 모든 경로( urlPatterns="/" )에 대해서 매핑한다.

2)

  • 서블릿이 호출되면 HttpServlet 이 제공하는 serivce() 가 호출된다.
  • 스프링 MVC는 DispatcherServlet 의 부모인 FrameworkServlet 에서 service() 를 오버라이드 해두었다.
  • FrameworkServlet.service() 를 시작으로 여러 메서드가 호출되면서 DispacherServlet.doDispatch() 가 호출된다.

3)

  • DispacherServlet.doDispatch() 동작 순서 (이거만 이해해도 완벽)
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

  HttpServletRequest processedRequest = request;
  HandlerExecutionChain mappedHandler = null;
  ModelAndView mv = null;

// 1. 핸들러 조회
  mappedHandler = getHandler(processedRequest);
  
  if (mappedHandler == null) {
    noHandlerFound(processedRequest, response);
    
    return;
}

// 2. 핸들러 어댑터 조회 - 핸들러를 처리할 수 있는 어댑터
  HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// 3. 핸들러 어댑터 실행 -> 4. 핸들러 어댑터를 통해 핸들러 실행 -> 5. ModelAndView 반환
  mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

}



private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

// 뷰 렌더링 호출
  render(mv, request, response);
}


protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {

  View view;
  String viewName = mv.getViewName();

// 6. 뷰 리졸버를 통해서 뷰 찾기, 7. View 반환
  view = resolveViewName(viewName, mv.getModelInternal(), locale, request);

// 8. 뷰 렌더링
  view.render(mv.getModelInternal(), request, response);
}

  • URL 요청 받으면 controller를 찾고, service의 비즈니스 로직을 실행하고, model에 값을 담아서, 뷰 리졸버로 뷰를 찾아서 반환해서, 물리적인 view를 출력하는 모든 과정을 여기서 진행한다. Spring 핵심이라고 할 수 있다.



5 spring 동작 순서

  • 이 흐름이 spring 동작순서의 전부이다.

1. 핸들러 조회: 핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회한다.

2. 핸들러 어댑터 조회: 핸들러를 실행할 수 있는 핸들러 어댑터를 조회한다.

3. 핸들러 어댑터 실행: 핸들러 어댑터를 실행한다.

4. 핸들러 실행: 핸들러 어댑터가 실제 핸들러를 실행한다.

5. ModelAndView 반환: 핸들러 어댑터는 핸들러가 반환하는 정보를 ModelAndView로 변환해서 반환한다.

6. viewResolver 호출: 뷰 리졸버를 찾고 실행한다.

  • JSP의 경우: InternalResourceViewResolver 가 자동 등록되고, 사용된다.

7. View 반환: 뷰 리졸버는 뷰의 논리 이름을 물리 이름으로 바꾸고, 렌더링 역할을 담당하는 뷰 객체를 반환한다.

  • JSP의 경우 InternalResourceView(JstlView) 를 반환하는데, 내부에 forward() 로직이 있다.

8. 뷰 렌더링: 뷰를 통해서 뷰를 렌더링 한다.




6 spring_controller 동작 이해


  • 예시를 위한 Controller 클래스
@Component("/springmvc/old-controller")
public class OldController implements Controller {
 
	 @Override
 	 public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
 		
        System.out.println("OldController.handleRequest");
 
 		return null;
 	 }
}
  • Controller 인터페이스를 통해 controller 클래스를 구현하고 @Component로 해당 url을 스프링 빈으로 등록했고, 이제 이 빈 이름으로 url을 매핑한다.

  • 이때!! HandlerMapping과 HandlerAdapter는 아래 우선순위에 따라 순서대로 찾아 동작한다.


1. HandlerMapping

0 = RequestMappingHandlerMapping : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = BeanNameUrlHandlerMapping : 스프링 빈의 이름으로 핸들러를 찾는다.


2. HandlerAdapter

0 = RequestMappingHandlerAdapter : 애노테이션 기반의 컨트롤러인 @RequestMapping에서 사용
1 = HttpRequestHandlerAdapter : HttpRequestHandler 처리
2 = SimpleControllerHandlerAdapter : Controller 인터페이스(애노테이션X, 과거에 사용) 처리

  • 그럼 위의 OldController 클래스의 동작 순서를 보면


1. 핸들러 매핑으로 핸들러 조회

  1. HandlerMapping 을 순서대로 실행해서, 핸들러를 찾는다.
  2. 이 경우 빈 이름으로 핸들러를 찾아야 하기 때문에 이름 그대로 빈 이름으로 핸들러를 찾아주는
    BeanNameUrlHandlerMapping 가 실행에 성공하고 핸들러인 OldController 를 반환한다.

2. 핸들러 어댑터 조회

  1. HandlerAdapter 의 supports() 를 순서대로 호출한다.
  2. SimpleControllerHandlerAdapter 가 Controller 인터페이스를 지원하므로 대상이 된다.

3. 핸들러 어댑터 실행

  1. 디스패처 서블릿이 조회한 SimpleControllerHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
  2. SimpleControllerHandlerAdapter 는 핸들러인 OldController 를 내부에서 실행하고, 그 결과를 반환한다.

- 만약 Controller 인터페이스를 대신해서 implements HttpRequestHandler를 구현하면

1. 핸들러 매핑으로 핸들러 조회는 위와 같고


2. 핸들러 어댑터 조회

  1. HandlerAdapter 의 supports() 를 순서대로 호출한다.
  2. HttpRequestHandlerAdapter 가 HttpRequestHandler 인터페이스를 지원하므로 대상이 된다.

3. 핸들러 어댑터 실행

  1. 디스패처 서블릿이 조회한 HttpRequestHandlerAdapter 를 실행하면서 핸들러 정보도 함께 넘겨준다.
  2. HttpRequestHandlerAdapter 는 핸들러인 MyHttpRequestHandler 를 내부에서 실행하고, 그 결과를 반환한다.
  • 추가설명으로 @RequestMapping을 사용하면 우선순위가 제일 높다.



7 ViewResolver

1 = BeanNameViewResolver : 빈 이름으로 뷰를 찾아서 반환한다. (예: 엑셀 파일 생성 기능에 사용)
2 = InternalResourceViewResolver : JSP를 처리할 수 있는 뷰를 반환한다.


  • 순서

1. 핸들러 어댑터 호출

  • 핸들러 어댑터를 통해 컨트롤러에서 return 한 논리 뷰 이름을 획득한다.

2. ViewResolver 호출

  • 전달받은 뷰 이름으로 viewResolver를 순서대로 호출한다.
  • BeanNameViewResolver 는 전달받은 뷰 이름의 스프링 빈으로 등록된 뷰를 찾아야 하는데 없다.
  • InternalResourceViewResolver 가 호출된다.

3. InternalResourceViewResolver

  • 이 뷰 리졸버는 InternalResourceView 를 반환한다.

4. 뷰 - InternalResourceView

  • InternalResourceView 는 JSP처럼 포워드 forward() 를 호출해서 처리할 수 있는 경우에 사용한다.

5. view.render()

  • view.render() 가 호출되고 InternalResourceView 는 forward() 를 사용해서 JSP를 실행한다.


8 RequestMapping

  • 실무에서 가장 많이 사용하는 방식의 컨트롤러이다.

  • @RequestMapping은 RequestMappingHandlerMapping, RequestMappingHandlerAdapter을 줄여서 사용한다.

  • 코드로 빠르게 이해!!

@Controller
public class SpringMemberFormControllerV1 {
 
  @RequestMapping("/springmvc/v1/members/new-form")
  public ModelAndView process() {
    return new ModelAndView("new-form");
  }
}

1. @Controller

  • 스프링이 자동으로 스프링 빈으로 등록한다. (내부에 @Component 애노테이션이 있어서 컴포넌트
    스캔의 대상이 됨)
  • 스프링 MVC에서 애노테이션 기반 컨트롤러로 인식한다.

2. @RequestMapping

  • 요청 정보를 매핑한다. 해당 URL이 호출되면 이 메서드가 호출된다. 애노테이션을 기반으로 동작하기 때문에, 메서드의 이름은 임의로 지으면 된다.

3. ModelAndView

  • 모델과 뷰 정보를 담아서 반환하면 된다.

4. RequestMappingHandlerMapping

  • 스프링 빈 중에서 @RequestMapping 또는 @Controller 가 클래스 레벨에 붙어 있는 경우에 매핑 정보로 인식한다.
  • 그래서 @Controller 대신에 아래와 같이 작성해도 똑같이 동작한다.

@Component //컴포넌트 스캔을 통해 스프링 빈으로 등록
@RequestMapping
public class SpringMemberFormControllerV1 {
  @RequestMapping("/springmvc/v1/members/new-form")
  public ModelAndView process() {
 
  return new ModelAndView("new-form");
  }
}
  • 만약 controller에서 url이 중복되면
@Controller
@RequestMapping("/springmvc/v2/members")
  • 처럼 클래스 단위로 매핑을 먼저하고 메서드 단위로 매핑하게 만들면 된다.

5. @RequestMapping를 개발자 편의에 맞춰 개발가능하도록 HTTP Method도 구분해서 제공하는 편의 기능이 있다.

1) url 매칭과 메소드 구분

  • @RequestMapping(value = "/new-form", method = RequestMethod.GET)

2) url의 get 요청 시

  • @GetMapping("url")

3) url의 post 요청 시

  • @PostMapping("url")

6. consumes

  • http 요청 받는 content-type을 지정할 수 있다.
  • @RequestMapping(value="url", consumes ="application/json")

7. produces

  • http 요청의 accept를 통해 매핑
  • @RequestMapping(value="url", produces ="text/html")


9 parameter

  • Spring은 HTTP 요청 파라미터를 @RequestParam으로 받을 수 있다.

  • @RequestParam("username") = request.getParameter("username")

  • @RequestParam("username") String username

  • @RequestParam String username


  • 요청 파라미터의 이름과 동일 시 어노테이션 제거 가능
    • String username
    • String,int ...등의 단순 타입일 때만 가능

  • 파라미터의 필수 여부

    • @RequestParam(required = false) String username ( 아무것도 작성안하면 됌 or true로 작성하면 필수 )

    • 근데 파라미터 필수값을 없을때 ""빈 문자로 처리되서 등록되기 때문에 주의해서 사용

    • int는 null을 받을 수 없기 때문에 Integer로 바꿔서 받을 수 있게 만들어야 함


  • defaultValue
    • 값이 없으면(빈문자도 포함) 자동으로 정해둔 값을 넣는다.
    • @RequestParam(defaultValue = "guest")

  • 파라미터를 Map으로 받기
    • @RequestParam(Map<String, Object> paramMap 으로 받을 수도 있다.
    • 그럼 paramMap.get("")으로 꺼낼 수 있다.

  • @ModelAttribute
    • 파라미터를 받아서 담을 model에 바로 넣을 수 있다.
    • @ModelAttribute ModelData helloData
    • @ModelAttribute를 생략도 가능하다.

  • 파라미터를 조회할 때 주의사항
    • @RequestParam은 int,String 같은 기본 타입을 조회해서 찾고
    • 그 외 나머지는 @ModelAttribute를 조회해서 찾는다.
    • 단, HttpServletResponse 같은 argument resolver 같은 정해진 경우에는 생략할 수 없다.


10 서버에서 클라이언트로 응답

1) 정적 리소스

  • 웹 브라우저에 정적인 HTML,CSS,Javascript을 제공할 때는 '정적 리소스'를 사용한다
  • 해당 파일을 변경없이 그대로 서비스 하는 것

2) 뷰 템플릿

  • 웹 브라우저에 동적인 HTML을 제공할 때는 뷰 템플릿을 사용한다.
  • 뷰 템플릿을 거쳐서 HTML이 생성되고, 뷰가 동적으로 응답을 만들어서 전달
    • JSP에서 제공하는 ${} 문법을 통해 <%= request.getAttribute("member")%>로 모델에 저장한 객체를 대신 꺼낼 수 있다.

3) HTTP 메세지 사용

  • HTTP API를 제공하는 경우에는 HTML이 아니라 데이터를 전달해야 하므로, HTTP 메세지 바디에 JSON 같은 형식으로 데이터를 실어 보낸다.

  • @ResponseBody가 있으면 뷰 리졸버를 실행하지 않고, HTTP 메세지 바디에 직접 return ""의 문자가 들어간다.

  • return type에 String이 아니라 직접 만든 data객체를 넣고 return해도 보내진다.

  • 동적으로 상태코드도 넣고 싶으면
    • return new ResponseEntity(data,HttpStatus.ok);
    • 클래스에 @ResponseBody,@ResponseStatus(HttpStatus.ok)를 붙이고, return에 data를 넣으면 된다.

  • @ResponseBody를 클래스에 붙이면 controller 메소드 전체가 responseBody가 된다.
    • 또는 @Controller와 @Responseody를 합친 @RestController로 붙이면된다.



11 HTTP 메세지 컨버터

  • 컨트롤러 메소드에 @ResponseBody가 붙어 있으면 viewResolver 대신 HttpMessageConverter가 동작


  • 기본 메시지 컨터버

    0 = ByteArrayHttpMessageConverter
    1 = StringHttpMessageConverter
    2 = MappingJacksonHttpMessageConverter

  • 기본 byte[] 데이터를 처리 :ByteArrayHttpMessageConverter

  • 기본 문자를 처리 : StringHttpMessageConverter
    • 요청

      • 클래스 타입 : String
      • 미디어 타입 : 아무거나 상관x

  • 기본 객체 처리 : MappingJacksonHttpMessageConverter
    • 요청
      • 클래스 타입 :객체 또는 HashMap
      • 미디어 타입, application/json

  • 응답 시 클라이언트의 Http Accept 헤더와 서버의 controller 반환 타입 정보를 조합해서 HttpMessageConverter가 선택된다.

  • HttpMessageConverter 적용 기준
    • HTTP 요청 : @RequestBody, HttpEntity(RequestEntity)
    • HTTP 응답 : @ResponseBody, HttpEntity(ResponseEntity)


12 요청 매핑 핸들러 어댑터 구조

  • @RequestMapping 을 처리하는 핸들러 어댑터인 RequestMappingHandlerAdapter (요청 매핑 헨들러 어뎁터)에서 파라미터를 처리한다.
  • 빨간 표시되는 부분에는 아래와 같은 구조가 있다.

  • 이렇게 파라미터를 유연하게 처리할 수 있는 이유가 바로 ArgumentResolver 덕분이다.

  • 애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdaptor 는 바로 이 ArgumentResolver 를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성한다. 그리고 이렇게 파리미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다

  • ReturnValueHandler

    • HandlerMethodReturnValueHandler 를 줄여서 ReturnValueHandle 라 부른다.
    • ArgumentResolver 와 비슷한데, 이것은 응답 값을 변환하고 처리한다.
    • 컨트롤러에서 String으로 뷰 이름을 반환해도, 동작하는 이유가 바로 ReturnValueHandler 덕분이다.


  • HTTP 메시지 컨버터 위치

  • 요청

    • @RequestBody 를 처리하는 ArgumentResolver 가 있고, HttpEntity 를 처리하는
      ArgumentResolver 가 있다. 이 ArgumentResolver 들이 HTTP 메시지 컨버터를 사용해서 필요한
      객체를 생성하는 것이다.

  • 응답

    • 우 @ResponseBody 와 HttpEntity 를 처리하는 ReturnValueHandler 가 있다. 그리고
      여기에서 HTTP 메시지 컨버터를 호출해서 응답 결과를 만든다.

  • 스프링 MVC는

    • @RequestBody @ResponseBody 가 있으면 RequestResponseBodyMethodProcessor (ArgumentResolver)
    • HttpEntity 가 있으면 HttpEntityMethodProcessor (ArgumentResolver)를 사용한다.


13 로깅


  • 로그 호출
Private Logger log = LoggerFactory.getLogger(getClass());
private static final Logger log = LoggerFactory.getLogger(Xxx.class)
// lombok 사용
@Slf4j

  • LEVEL: TRACE > DEBUG > INFO > WARN > ERROR

    • 로그 레벨 설정
    • application.properties

#전체 로그 레벨 설정(기본 info)
logging.level.root=info
#hello.springmvc 패키지와 그 하위 로그 레벨 설정
logging.level.hello.springmvc=debug
  • 개발 서버는 debug 출력

  • 운영 서버는 info 출력

  • 로그 사용법

    • log.debug("data="+data)
      로그 출력 레벨을 info로 설정해도 해당 코드에 있는 "data="+data가 실제 실행이 되어 버린다.
      결과적으로 문자 더하기 연산이 발생한다.
    • log.debug("data={}", data)
      로그 출력 레벨을 info로 설정하면 아무일도 발생하지 않는다. 따라서 앞과 같은 의미없는 연산이
      발생하지 않는다.

  • 로그의 장점
  • 쓰레드 정보, 클래스 이름 같은 부가 정보를 함께 볼 수 있고, 출력 모양을 조정할 수 있다.
  • 로그 레벨에 따라 개발 서버에서는 모든 로그를 출력하고, 운영서버에서는 출력하지 않는 등 로그를 상황에
    맞게 조절할 수 있다.
  • 시스템 아웃 콘솔에만 출력하는 것이 아니라, 파일이나 네트워크 등, 로그를 별도의 위치에 남길 수 있다.
    특히 파일로 남길 때는 일별, 특정 용량에 따라 로그를 분할하는 것도 가능하다.
  • 성능도 일반 System.out보다 좋다. (내부 버퍼링, 멀티 쓰레드 등등) 그래서 실무에서는 꼭 로그를 사용해야 한다.
profile
까먹지말자

0개의 댓글