1. 컨트롤러 클래스를 정의한다.
@Controller
public class UserController {
}
2. 요청핸들러 메소드를 정의한다.
@Controller
public class UserController {
public String info() { // 사용자 상세정보화면을 제공한다.
}
public String passwordChangeForm() { // 비밀번호 변경화면을 제공한다.
}
}
3. 요청 URL과 요청핸들러 메소드를 매핑한다.
@Controller
@RequesttMapping("/user") - 클래스 레벨의 URL 매핑, @RequestMapping 사용, 메소드 레벨의 URL 매핑과 더해진다.
public class UserController {
@GetMapping("/info") - 메소드 레벨의 URL 매핑, 실제 매핑은 "/user/info"다.
public String info() {
}
@GetMapping("/password") - 실제 매핑은 "/user/password"다.
public String passwordChangeForm() {
}
}
- 입력폼이 필요한 것은 PostMapping 사용
- 다른 경우에는 다 GetMapping 사용
- RequestMapping은 GetMapping / PostMapping과 상관없이 Mapping 하는 것.
4. 요청핸들러 메소드의 반환값을 결정한다.
@Controller
@GetMapping("/user")
public class UserController {
@GetMapping("/info")
public String info() {
return "user/info"; "/WEB-INF/views/user/info.jsp"
}
@GetMapping("/password")
public String passwordChangeForm() {
return "user/password-form"; "/WEB-INF/views/user/password-form.jsp"
}
@PostMapping("/password")
public String changePassword() {
return "redirect:password-success";
return "redirect:/user/password-success";
return "redirect:/password-success"; // 404 not found 에러 발생
}
@PostMapping("/password-success")
public String changePasswordSuccess() {
return "user/password-success";
}
}
- 재요청URL은 삭제/변경시 무조건 해야 한다.
- 절대경로는 무조건 localhost 뒤부터 무조건 적는다. (/까지 다 적어야 한다.)
- 재요청 URL은 상대경로, 절대경로 작성규칙에 맞게 작성해야 한다.
- 재요청 URL은 언제나 다른 요청핸들러 메소드를 요청하는 URL이어야 한다.
5. 요청핸들러 메소드 실행에 필요한 값을 전달받는 매개변수를 추가한다.
@Controller
@GetMapping("/user")
public class UserController {
// 로그인된 사용자의 상세정보화면을 제공하는 요청핸들러 메소드다.
// 로그인된 사용자 정보를 전달받아야 한다, JSP에 정보를 전달할 Model을 전달받아야 한다.
@GetMapping("/info")
public String info(@LoginUser LoginUserInfo loginUserInfo, Model model) {
return "user/info"; "/WEB-INF/views/user/info.jsp"
}
// 비밀번호를 변경하는 화면을 제공하는 요청핸들러 메소드다.
// 전달받을 정보가 없다.
@GetMapping("/password")
@LoginUser // 로그인 체크
public String passwordChangeForm() {
return "user/password-form"; "/WEB-INF/views/user/password-form.jsp"
}
// 이전 비밀번호와 새 비밀번호를 전달받아서 로그인한 사용자의 비밀번호을 변경하는 요청핸들러 메소드다.
// 로그인된 사용자의 비밀번호를 변경할 것이기 때문에 로그인된 사용자정보를 전달받아야한다.
// 비밀번호 변경폼에서 입력한 이전 비밀번호와 새 비밀번호를 전달받아야 한다.
@PostMapping("/password")
public String changePassword(@LoginUser LoginUserInfo loginUserInfo, String oldPassword, String password) {
return "redirect:password-success"; "localhost/user/password-success"를 재요청한다.
}
}
- 사용자 정보는 항상 세션에서 가지고 와야 한다.
6. 요청핸들러 메소드에 구현내용을 추가한다.
@Controller
@GetMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 로그인된 사용자의 상세정보화면을 제공하는 요청핸들러 메소드다.
// 로그인된 사용자 정보를 전달받아야 한다, JSP에 정보를 전달할 Model을 전달받아야 한다.
@GetMapping("/info")
public String info(@LoginUser LoginUserInfo loginUserInfo, Model model) {
// 업무로직 메소드 호출, 사용자 상세정보 획득
UserDetailDto dto = userService.getUserDetail(loginUserInfo.getId());
// Model에 사용자 상세정보 저장
model.addAttribute("user", dto);
return "user/info"; "/WEB-INF/views/user/info.jsp"
}
// 비밀번호를 변경하는 화면을 제공하는 요청핸들러 메소드다.
// 전달받을 정보가 없다.
@GetMapping("/password")
public String passwordChangeForm() {
return "user/password-form"; "/WEB-INF/views/user/password-form.jsp"
}
// 이전 비밀번호와 새 비밀번호를 전달받아서 로그인한 사용자의 비밀번호을 변경하는 요청핸들러 메소드다.
// 로그인된 사용자의 비밀번호를 변경할 것이기 때문에 로그인된 사용자정보를 전달받아야한다.
// 비밀번호 변경폼에서 입력한 이전 비밀번호와 새 비밀번호를 전달받아야 한다.
@PostMapping("/password")
public String changePassword(@LoginUser LoginUserInfo loginUserInfo, String oldPassword, String password) {
// 비밀번호를 변경시키는 업무로직 메소드 호출
userService.changePassword(loginUserInfo.getId(), oldPassword, password);
// 변경작업을 수행했기 때문에 재요청 URL을 반환
return "redirect:password-success"; "localhost/user/password-success"를 재요청한다.
}
}
1. 에러페이지를 만든다.
WEB-INF/views/error 폴더에 에러상황에 맞는 에러페이지를 생성한다.
WEB-INF/views/error/500.jsp
WEB-INF/views/error/404.jsp
WEB-INF/views/error/db.jsp
WEB-INF/views/error/app.jsp
- 에러페이지는 하나만 만들어도 OK!
2. 일관된 예외처리를 담당하는 @ControllerAdvice가 추가된 클래스를 정의한다.
@ControllerAdvice
public class ExceptionHandlerControllerAdvice {
}
3. 예외클래스별로 예외처리 메소드를 작성한다.
@ControllerAdvice
public class ExceptionHandlerControllerAdvice {
@ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(RuntimeException ex) {
ex.printStackTrace();
return "error/500";
}
@ExceptionHandler(DataAccessException.class)
public String handleDataAccessException(DataAccessException ex) {
ex.printStackTrace();
return "error/db";
}
@ExceptionHandler(ApplicationException.class)
public String handleApplicationException(ApplicationException ex) {
ex.printStackTrace();
return "error/app";
}
}
1. 로그인폼 페이지를 제공하는 요청핸들러 메소드를 작성한다.
@GetMapping("/login")
public String loginform() {
return "login-form";
}
2. WEB-INF/views 폴더에 로그인폼 페이지를 작성한다.
WEB-INF/views/login-form.jsp를 작성한다.
<form method="post" action="login">
아이디
<input type="text" name="id" />
비밀번호
<input type="password" name="password" />
<button type="submit">로그인</button>
</form>
3. 로그인 처리를 담당하는 요청핸들러 메소드를 작성한다.
@PostMapping("/login")
public String login() {
}
4. 요청핸들러 메소드의 반환값을 결정한다.
@PostMapping("/login")
public String login() {
return "redirect:home";
return "redirect:/home";도 가능하다.
}
5. 로그인폼 화면에 제출한 요청파라미터값을 전달받는 매개변수를 추가한다.
@PostMapping("/login")
public String login(String id, String password) {
// public String login(@Requestparam("id")String id, @Requestparam("password")String password) { 도 가능하다.
return "redirect:home";
}
6. 로그인처리를 수행하는 업무로직을 호출한다.
@PostMapping("/login")
public String login(String id, String password) {
// public String login(@Requestparam("id")String id, @Requestparam("password")String password) { 도 가능하다.
User user = userService.login(id, password);
return "redirect:home";
}
7. 로그인 인증이 완료된 사용자정보를 세션에 저장시킨다.
@PostMapping("/login")
public String login(String id, String password) {
User user = userService.login(id, password);
// LoginUserInfo는 아이디, 이름만 저장하는 객체다.
LoginUserInfo loginUserInfo = new LoginUserInfo(user.getId(), user.getName());
// 세션에는 LoginUserInfo객체를 속성으로 저장시킨다.
SessionUtils.setAttribute("loginUser", loginUserInfo);
///////////////////////////////////////////////////////////
JSP에서 로그인 정보 활용
// jsp에서 로그인된 사용자 정보 출력하기
${loginUser.name}
// jsp에서 로그인 여부 체크하기
<c: if test="${not empty loginUser}">
<a href="">새 글쓰기</a>
</c:if>
//////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
요청핸들러 메소드에서 로그인 정보 활용
// 로그인여부를 검사하게 하기
@GetMapping("/delete")
@LoginUser // 인터셉터가 @LoginUser 어노테이션을 감지하고, 세션에 로그인정보가 존재하는지 체크한다.
public String deleteForm() {
}
// 매개변수로 로그인된 사용자 정보전달받기
@PostMapping("/delete")
public String deleteUser(@LoginUser LoginUserInfo loginUserInfo, @RequestParam("password") String password) {
// 매개변수에 @LoginUser LoginUserInfo loginUserInfo로 추가하면
// 인터셉터가 @LoginUser 어노테이션을 감지하고, 세션에 로그인정보가 존재하는지 체크한다.
// ArgumentResolver가 세션에 저장된 로그인된 사용자정보를 매개변수로 전달한다.
}
///////////////////////////////////////////////////////////
return "redirect:home";
}
1. 로그아웃 처리를 담당하는 요청핸들러 메소드를 작성한다.
public String logout() {
}
2. 요청 URL과 요청핸들러 메소드를 매핑시킨다.
@RequestMapping("/logout")
public string logout() {
}
3. 요청핸들러 메소드의 반환값을 결정한다.
@RequestMapping("/logout")
public string logout() {
return "redirect:home";
}
4. 로그아웃 처리를 필요한 값을 전달받는 매개변수를 추가한다.
@RequestMapping("/logout")
public string logout() { // 필요한 값이 없어서 추가하지 않음
return "redirect:home";
}
5. 로그아웃처리를 수행하는 코드를 요청핸들러 메소드에 추가한다.
@RequestMapping("/logout")
public string logout() {
// 세션에 저장된 로그인 정보를 삭제한다.
SessionUtils.removeAttribute("loginUser");
return "redirect:home";
}
0. @LoginUser 어노테이션 정의하기
@Target({METHOD, PARAMETER})
@Retention(RUNTIME)
public @interface LoginUser {
boolean required() default true;
}
1. 요청핸들러 메소드의 매개변수를 분석하고, 적절한 값을 제공하는 ArgumentResolver 클래스 작성하기
public class LoginUserHandlerMethodArgumentResolver implments HandlerMethodArgumentResolver {
}
2. HandlerMethodArgumentResolver 인터페이스의 추상메소드를 구현클래스에 추가하기
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
// 이 메소드가 true를 반환하는 매개변수가 로그인정보를 전달받는 메소드다.
public boolean supportsParameter(MethodParameter parameter) { // supportsParameter가 true면, resolveArgument가 실행된다.
return false;
}
// 이 메소드가 반환하는 값이 로그인 정보로 매개변수에 전달된다.
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return null;
}
}
3. 추상메소드를 구현하기
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
// 이 메소드가 true를 반환하는 매개변수가 로그인정보를 전달받는 메소드다.
public boolean supportsParameter(MethodParameter parameter) {
// 요청핸들러 메소드가 @LoginUser를 가지고 있으면 true를 반환한다.
return parameter.hasParameterAnnotation(LoginUser.class); // LoginUser라는 어노테이션이 붙어있는 것만 true다.
}
// 이 메소드가 반환하는 값이 로그인 정보로 매개변수에 전달된다.
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
// 세션에 저장된 사용자정보를 반환한다.
return return SessionUtils.getAttribute("loginUser");
}
}
4. web-context.xml에 사용자정의 ArgumentResolver를 추가하기
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean class="com.sample.web.resolver.LoginUserHandlerMethodArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
5. 로그인된 사용자 정보를 요청핸들러 메소드에서 전달받기
@GetMapping("/user/info")
public String info(@LoginUser LoginUserInfo loginUserInfo, Model model) {
}
@PostMapping("/user/password")
public String changePassword(@LoginUser LoginUserInfo loginUserInfo, String oldPassword, String password) {
}
1. 로그인 상태에서만 요청할 수 있는 요청핸들러 메소드 혹은 매개변수에 @LoginUser를 추가한다.
@GetMapping("/user/delete")
@LoginUser
public String deleteform() {
}
@GetMapping("/user/info")
public String info(@LoginUser LoginUserInfo loginUserInfo) {
}
2. 요청핸들러 메소드의 실행 전처리/후처리를 담당하는 HanderInterceptor를 구현한 사용자정의 인터셉터를 클래스를 작성한다.
public class LoginCheckHandlerInterceptor implements HandlerInterceptor {
}
3. HandlerInterceptor 인터페이스의 추상 메소드를 추가한다.
public class LoginCheckHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 요청핸들러 메소드나, 메소드의 매개변수에 @LoginUser 어노테이션이 설정되어 있는지 체크한다.
// @LoginUser 어노테이션이 없으면 true를 반환한다. - 요청핸들러 메소드가 실행된다.
// @LoginUser 어노테이션이 있으면 로그인 여부를 체크 -> 로그인상태면 true를 반환한다. - 요청핸들러 메소드가 실행된다.
// -> 로그인상태가 아니면 예외를 발생시킨다. - 요청핸들러 메소드가 실행되지 않는다.
}
}
04. web-context.xml에 사용자정의 인터셉터를 등록한다.
<mvc:interceptors>
<!-- 사용자정의 인터셉터 등록 -->
<mvc:interceptor>
<!-- 모든 요청 URL에 대해서 사용자 정의 인터셉터가 실행되도록 설정한다. -->
<mvc:mapping path="/**/*"/>
<!-- 아래의 요청 URL에 대해서는 사용자 정의 인터셉터가 실행되지 않도록 설정한다. -->
<mvc:exclude-mapping path="/home"/>
<mvc:exclude-mapping path="/register"/>
<mvc:exclude-mapping path="/success"/>
<mvc:exclude-mapping path="/login"/>
<!-- 사용자정의 인터셉터 -->
<bean class="com.sample.web.interceptor.LoginCheckHandlerInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
CREATE TABLE SPRING_POSTS (
POST_NO NUMBER(5) CONSTRAINT SPRING_POST_NO PRIMARY KEY,
POST_TITLE VARCHAR2(255) NOT NULL,
POST_USER_ID VARCHAR2(100) NOT NULL CONSTRAINT SPRING_POST_USER_ID_FK REFERENCES SPRING_USERS (USER_ID),
POST_CONTENT VARCHAR2(1000),
POST_DELETED CHAR(1) DEFAULT 'N',
POST_READ_COUNT NUMBER(5) DEFAULT 0,
POST_COMMENT_COUNT NUMBER(5) DEFAULT 0,
POST_UPDATED_DATE DATE DEFAULT SYSDATE,
POST_CREATED_DATE DATE DEFAULT SYSDATE
);
CREATE TABLE SPRING_POST_COMMENTS (
COMMENT_NO NUMBER(5) CONSTRAINT SPRING_COMMENT_NO PRIMARY KEY,
COMMENT_USER_ID VARCHAR2(100) NOT NULL CONSTRAINT SPRING_COMMENT_USER_ID_FK REFERENCES SPRING_USERS (USER_ID),
COMMENT_CONTENT VARCHAR2(500) NOT NULL,
COMMENT_UPDATED_DATE DATE DEFAULT SYSDATE,
COMMENT_CREATED_DATE DATE DEFAULT SYSDATE,
COMMENT_POST_NO NUMBER(5) NOT NULL CONSTRAINT SPRING_COMMENT_POST_NO_FK REFERENCES SPRING_POSTS (POST_NO)
);
CREATE TABLE SPRING_POST_ATTACHED_FILES (
POST_NO NUMBER(5) NOT NULL CONSTRAINT SPRING_ATTACHED_FILE_POST_NO_FK REFERENCES SPRING_POSTS (POST_NO),
FILE_NAME VARCHAR2(255) NOT NULL
);
CREATE SEQUENCE SPRING_POSTS_SEQ NOCACHE;
CREATE SEQUENCE SPRING_COMMENTS_SEQ NOCACHE;
PostController
package com.sample.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.sample.web.login.LoginUser;
import com.sample.web.login.LoginUserInfo;
@Controller
@RequestMapping("/post")
public class PostController {
@GetMapping("/list")
public String list(@LoginUser LoginUserInfo loginUserInfo, Model model) {
return "post/list";
}
}
실행화면(비로그인 시)
실행화면(로그인 시)
- 일단 리스트 화면은 구현되었으나, 게시글 리스트 수정이 필요하다.
먼저,