클라이언트가 요청을 보내면 다른 곳으로 다시 요청을 보내게 하므로 요청 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()를 사용해야 함
}