Day 90

ChangWoo·2023년 1월 10일
0

중앙 HTA

목록 보기
34/51

Spring MVC로 웹 애플리케이션 구현하기

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";
	}

ArgumentResolver로 요청핸들러 메소드의 매개변수를 분석해서 적절한 값을 매개변수로 전달하기

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";
	}

}

실행화면(비로그인 시)

실행화면(로그인 시)

  • 일단 리스트 화면은 구현되었으나, 게시글 리스트 수정이 필요하다.

먼저,

게시글 등록 구현

profile
한 걸음 한 걸음 나아가는 개발자

0개의 댓글