클라이언트가 요청을 보내면 다른 곳으로 다시 요청을 보내게 하므로 요청 2번, 응답 2번이 된다.
=> 브라우저가 자동으로 재요청을 보내기 때문에 URL이 바뀜
다른 페이지로 페이지가 자체적으로 forward요청을 보내기 때문에 클라이언트는 한번의 요청으로 한번의 응답을 받을 수 있다.
=> 브라우저의 내부에서 forward요청이 처리되기 때문에 URL이 바뀌지 않음
출처: https://goodgid.github.io/Redirect-vs-Forwarding/
@RequestMapping("/download")
public String download(HttpServletRequest request,
@RequestParam(required=false, defaultValue="") String type) {
List<User> userList = getUserList();
request.setAttribute("data", userList);
if(type.equals("pdf")){
return "forward:/pdfView";
} else if(type.equals("csv")){
return "forward:/csvView";
}
return "forward:/txtView";
}
Cookie cookie = new Cookie("id", "asdf"); // name이 id, value가 asdf인 쿠키 생성
cookie.setMaxAge(60*60*24); // 유효기간 설정(초 단위)
response.addCookie(cookie); // 응답에 쿠키 추가
쿠키의 구분을 위해 name설정에 주의.
Cookie cookie = new Cookie("id", ""); // 변경할 쿠키와 같은 이름의 쿠키 생성
cookie.setValue(URLEncoder.encode("남궁성")); // 값 변경
cookie.setDomain("www.fastcampus.co.kr"); // 도메인의 변경
cookie.setPath("/ch2"); // 경로의 변경
cookie.setMaxAge(60*60*24*7); // 유효기간의 변경
response.addCookie(cookie); // 응답에 쿠키 추가
쿠키의 유효기간을 0으로 셋팅하면 됨.
쿠키의 구분을 위해 name은 중요하지만 value는 관계없음.
Cookie cookie = new Cookie("id", ""); // 변경할 쿠키와 같은 이름의 쿠키 생성
cookie.setMaxAge(0); // 유효기간을 0으로 설정(삭제)
response.addCookie(cookie); // 응답에 쿠키 추가
Cookie[] cookies = request.getCookies(); // 쿠키 읽기
for(Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
System.out.printf("[cookie]name=%s, value=%s%n", name, value);
}
쿠키가 2개 이상일 수 있으므로 배열로 반환받음. (이때 쿠키가 없으면 null값이 반환됨)
서로 관련된 요청들을 하나로 묶은 것 - 쿠키를 기반으로 하되 서버에 저장됨.
브라우저마다 개별 저장소(session객체)를 서버에서 제공.
key-value형태로 이루어진 map을 저장방식으로 사용함.
쿠키로 세션id를 보내면 서버에서 확인 후 응답헤더에 세션id를 보내고 다음 요청부터 둘의 id가 같으면 서버의 세션저장소를 사용할 수 있음.
세션속성
session = "true"
: 세션이 없을 때 세션생성o
session = "false"
: 세션이 없을 때 세션생성x
=> 세션이 이미 있을때 false를 준다고 있는 세션이 사라지지는 않고, 새로 만들지는 않는다는 의미.(=기존의 세션에 영향x)
쿠키(Cookie) | 세션(HttpSession) |
---|---|
브라우저에 저장 | 서버에 저장 |
서버 부담 X | 서버에 부담 O |
보안에 불리 | 보안에 유리 |
서버 다중화에 유리 | 서버 다중화에 불리 |
Spring에서의 예외처리
1) 컨트롤러 메서드 내에서 try-catch로 처리
2) 컨트롤러의 @ExceptionHandler메서드가 처리
3) @ControllerAdvice클래스의 @ExceptionHandler메서드가 처리
4) 예외 종류별로 뷰 지정 - SimpleMappingExceptionResolver
5) 응답 상태 코드별로 뷰 지정 -<error-page>
@ExceptionHandler
어노테이션을 이용해 예외처리 가능.
@Controller
public class ExceptionController {
@ExceptionHandler(NullPointException.class)
public String catcher2(Exception ex, Model m) {
m.addAttribute("ex", ex);
return "error";
}
@ExceptionHandler(Exception.class)
public String catcher(Exception ex, Model m) {
m.addAttribute("ex", ex)
return "error";
}
@RequestMapping("/ex")
public String main() throws Exception {
throw new Exception("예외가 발생했습니다.");
}
@RequestMapping("/ex2")
public String main() throws Exception {
throw new NullPointException("예외가 발생했습니다.");
}
}
=> try-catch문을 사용하듯이 메서드를 만들어 각 오류상황에 대응할 수 있도록 한다.
단, @ExceptionHandler
어노테이션을 통한 예외처리는 메서드가 속해있는 컨트롤러 내부의 오류에만 대응한다.
모든 컨트롤러에서의 오류에 대응하려면 핸들링 클래스를 만들어 클래스에 @ControllerAdvice
어노테이션을 적용한다.
→ @ControllerAdvice
: 전역에서의 오류를 잡아 처리하는 어노테이션. 특정 패키지만 지정하는것도 가능함. 예외 처리 메서드가 중복된 경우, 컨트롤러 내의 예외 처리 메서드가 우선됨.
참고링크 - @ControllerAdvice, @ExceptionHandler를 이용한 예외처리 분리, 통합하기
응답 메시지의 상태 코드를 변경할 때 사용.
1) 예외 처리 메서드에서의 사용 : 예외처리에 성공했다면 예외상황임에도 상태코드 200(요청처리 성공)가 전송되므로 에러사항을 클라이언트에게 알리기 위해 사용됨.
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
@ExceptionHandler({NullPointerException.class, ClassCastExcepion.class})
public String catcher2(Exception ex, Model m){
m.addAttribute("ex", ex);
return "error";
}
클라이언트쪽 에러일때 4xx, 서버쪽 에러일때 5xx 등 상태코드를 지정하여 보여줌.
2) 사용자 정의 예외클래스에서의 사용 :
@ResponseStatus(HttpStatus.BAD_REQUEST)
class MyException extends RuntimeExcption {
MyException(String msg){
super(msg);
}
MyException(){
this();
}
}
공통적인 전처리 과정에 사용됨.
출처: https://velog.io/@hyunjong96/Sprin-Spring-MVC-DispatcherServlet
DispatcherServlet
이 Controller
뿐만 아니라 서블릿과 같은 다양한 대상을 호출할 수 있도록 DispatcherServlet
과 Controller
의 연결을 느슨하게 하기 위해 HandlerAdepter
를 사용함.
서블릿의 Filter : 요청 전후의 전처리 및 후처리
스프링의 Intercepter : Filter보다 발전된 개념으로, 역할은 유사함.
타입 변환, 데이터 검증 작업을 함.
BindingResult
에 결과를 저장하여 반환.(에러 발생시에도 에러라는 결과를 저장함.)
매개변수에 선언할때 반환 결과를 저장할 객체의 바로 뒤에 위치해야 한다.
양방향 타입 변환 (String → 타입, 타입 → String)
특정 타입이나 이름의 필드에 적용 가능.
단방향 타입 변환(타입A → 타입B)
PropertyEditor
의 단점을 개선(stateful → stateless)
싱글톤으로 사용가능.
타입 변환 서비스를 제공.
여러 Converter
를 등록 가능.
WebDataBinder
에 DefaultFormattingConversionService
가 기본 등록.
모든 컨트롤러 내에서의 변환 : ConfigurableWebBindingInitializer
를 설정하여 사용.
특정 컨트롤러 내에서의 변환 : 컨트롤러에 @InitBinder
가 붙은 메서드를 작성.
양방향 타입 변환 (String → 타입, 타입 → String)
바인딩할 필드에 적용 : @NumberFormat
, @DateTimeFormat
entity클래스에서 필드를 지정할때 @DateTimeFormat
어노테이션을 이용하면 입력받은 문자열을 Date타입으로 변환하기에 편리함.
@DateTimeFormat(pattern="yyyy/MM/dd")
private Date birth;
@NumberFormat(pattern="###,###")
BigDecimal salary;
}
=> "2023/08/16"형태의 문자열을 Date타입으로 변환, 천 단위마다 콤마가 붙은 문자열을 숫자형으로 변환.
객체를 검증하기 위한 인터페이스.
객체 검증기(validator) 구현에 사용함.
하나의 Validator로 여러 객체를 검증할 때, 글로벌 Validator로 등록.
1) 글로벌 Validator로 등록하는 방법
- Servlet-constext.xml -
<annotation-driven validator="globalValidator"/>
<beans:bean id="globalValidator" class="com.fastcampus.ch2.GlobalValidator"/> // 빈 등록
2) 글로벌 Validator와 로컬 Validator를 동시에 적용하는 방법
@InitBinder
public void toDate(WebDataBinder binder) {
SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(df, false));
binder.addValidators(new UserValidator()); // 로컬 validator를 WebDataBinder에 등록
// 신규등록이 아닌 추가등록이기 때문에 setValidator()가 아니라 addValidators()를 사용해야 함
}