컨트롤러 객체
- 사용자의 요청을 처리
- 사용자에게 데이터를 제공
- 서비스 객체를 의존한다.
web-context.xml<context:component-scan base-package="com.sample.web />
- com.sample.web 패키지 및 그 하위 패키지에서 @Controller, @ControllerAdvice, @RestController, @RestControllerAdvice, @Repository, @Service, @Configuration, @Component 등의 어노테이션이 부착된 클래스를 전부 스캔해서 객체 생성
서비스 객체
- 업무로직 수행
- 매퍼객체를 의존한다.
service-context.xml<context:component-scan base-package="com.sample.service />
- com.sample.service 패키지 및 그 하위 패키지에서 @Controller, @ControllerAdvice, @RestController, @RestControllerAdvice, @Repository, @Service, @Configuration, @Component 등의 어노테이션이 부착된 클래스를 전부 스캔해서 객체 생성
매퍼객체
- 데이터 베이스 엑세스 수행
- SqlSessionFactory를 의존한다.
database-context.xml<bean id="dataSource" class="DriverManagerDataSource"> <bean id="sqlSessionFactory" class="SqlSessionFactoryBean"> <mybatis-spring:scan base-package="com.sample.mapper />
- com.sample.mapper 패키지의 모든 매퍼 인터페이스를 스캔해서 인터페이스를 구현한 매퍼 인스턴스(인스턴스 구현객체)를 생성하고 스프링의 빈으로 등록시킨다.
DriverManagerDataSource
- 데이터베이스와 연결된 Connection을 관리 제공
SqlSessionFactory
- SqlSession 제공
- DriverManagerDataSource을 의존한다.
MapperScannerConfigurer
- 매퍼 인터페이스를 구현한 매퍼 객체를 생성하고 스프링 컨테이너에 등록
- SqlSessionFactory를 의존한다.
/register 요청이 오면 TomCat은 요청객체 / 응답객체를 생성한다.
DispathcerServlet
HandlerMapping
HandlerAdapter
ModelAndView를 DispatcherServlet에게 반환한다.
전처리 작업을 수행한다.
전처리 작업 : 요청핸들러 메소드의 매개변수 분석
후처리 작업을 수행한다.
후처리 작업 : ModelAndView 객체를 생성하고 반환한다.
ViewResolver
RedirectView
success.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<title>애플리케이션</title>
</head>
<body>
<c:set var="menu" value="register" />
<%@ include file="common/navbar.jsp" %>
<div class="container">
<div class="row mb-3">
<div class="col-12">
<div class="border p-3 bg-light">
<h1 class="mb-4">회원가입 완료</h1>
<p>회원가입이 완료되었습니다.</p>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
</body>
</html>
HomeController
@GetMapping("/success")
public String success() {
return "success";
}
실행결과
Homecontroller
/*
* 값이 적기 때문에 하나씩 적어준다. (id와 패스워드)
* 요청핸들러 메소드의 매개변수가 기본자료형 혹은 String형인 경우,
* - 매개변수의 이름과 동일한 이름으로 요청파라미터값을 조회해서 매개변수로 전달한다.(input박스 이름과 똑같아야 한다!)
* - 매개변수의 타입이 기본자료형 타입인 경우, 해당 타입으로 형변환해서 전달한다.(int로 설정시 글자가 와도 숫자로 변경해준다.)
* - 매개변수의 타입이 기본자료형(정수, 실수, 문자, 불린)일 때,
* 요청파라미터값이 존재하지 않으면 오류가 발생한다.
* 요청파라미터값을 해당 타입으로 변환할 수 없을 때 오류가 발생한다.
* @RequestParam
* 요청파라미터값을 요청핸들러의 매개변수와 매핑시키는 어노테이션이다.
* 주요 속성
* name : 요청파라미터의 이름을 지정한다.
* required : 기본값은 true다. false로 지정하면 name에 지정한 요청파라미터값이 없어도 오류가 발생하지 않는다.
* defaultValue : name에 지정한 요청파라미터값이 존재하지 않을 때 매개변수로 대입되는 기본값을 설정한다.
* defaultValue의 값은 문자열로 설정되지만 매개변수로 대입될 대는 해당 타입으로 형변환된다.
* 예시
* page / rows / sort가 와야해! 그런데 없으면 1 / 10 / date가 될거야! (무조건 있어야 하는 값은 사용하지 않고 있어도 되고 없어도 되는 값에만 사용!)
* public String list(@RequestParam(name = "page", required = false, defaultValue = "1") int page,
* @RequestParam(name = "rows", required = false, defaultValue = "10") int rows,
* @RequestParam(name = "sort", "required = false, defaultValue = "date") String sort,
* String opt,
* String keyword)
*
* 요청핸들러 메소드의 매개변수로 가능한 객체 및 어노테인션
* HttpServletRequest 요청객체
* HttpServletResponse 응답객체
* HttpSession 세션객체
* WebRequest Spring이 제공하는 객체다. / 요청객체가 가지고 있는 정보 대부분을 제공하는 객체다.
* TimeZone 시간정보
* Locale 지역정보(국가, 언어)
* InputStream 클라이언트와 연결된 읽기 전용 스트림
* OutputStrean 클라이언트와 연결된 쓰기 전용 스트림
* Reader 클라이언트와 연결된 텍스트 읽기 전용 스트림
* Writer 클라이언트와 연결된 텍스트 쓰기 전용 스트림
* @RequestParam 요청파라미터와 매개변수를 매핑시키는 어노테이션
* @PathVariable 요청 URL 경로에 포함된 파라미터값과 매개변수를 매핑시키는 어노테이션
* @ModelAttribute 요청파라미터와 해당값을 저장하는 객체를 매핑시키는 어노테이션
* @RequestBody 요청메세지의 바디부 정보와 매개변수를 매핑시키는 어노테이션
* @Valid 요청파라미터값의 유효성 여부를 검증시키는 어노테이션
* Model 뷰에 전달할 정보를 저장하는 객체
* Errors 요청파라미터값의 유효성 검증 결과를 저장하는 객체
* BindingResult 요청파라미터값의 유효성 검증 결과를 저장하는 객체
* SessionStatus 세션에 저장된 정보를 삭제하는 객체
* 기본자료형 요청파라미터값을 전달받는다.
* String 요청파라미터값을 전달받는다.
* 사용자정의 객체 요청파라미터값을 전달받는다.
*/
@PostMapping("/login")
public String login(String id, String password, HttpSession session) {
User user = userService.login(id, password);
session.setAttribute("loginUser", user);
return "redirect:home";
}
login-form.jsp
<script type="text/javascript">
$(function() {
$("#form-register").submit(function() {
let id = $("#form-register :input[name=id]").val();
let password = $("#form-register :input[name=password]").val();
if (id == "") {
alert("아이디는 필수입력값입니다.");
return false;
}
if (password == "") {
alert("비밀번호는 필수입력값입니다.");
return false;
}
return true;
})
})
</script>
UserService.java
/*
* Controller의 요청파라미터와는 달리 매개변수와 다른 이름을 사용해도 상관없다!
*/
public User login(String userId, String password) {
User savedUser = userMapper.getUserById(userId);
if (savedUser == null) {
throw new ApplicationException("아이디 혹은 비밀번호가 올바르지 않습니다.");
}
if ("Y".equals(savedUser.getDeleted())) {
throw new ApplicationException("탈퇴처리된 사용자 계정으로 로그인할 수 없습니다.");
}
if (!savedUser.getPassword().equals(password)) {
throw new ApplicationException("아이디 혹은 비밀번호가 올바르지 않습니다.");
}
return savedUser;
}
navbar.jsp
<c:if test="${not empty loginUser }">
<span class="navbar-text"><strong class="text-white">${loginUser.name }</strong>님 환영합니다.</span>
</c:if>
<ul class="navbar-nav">
<c:choose>
<c:when test="${not empty loginUser }">
<li class="nav-item"><a class="nav-link" href="/logout">로그아웃</a></li>
</c:when>
<c:otherwise>
<li class="nav-item"><a class="nav-link ${menu eq 'login' ? 'active' : '' }" href="/login">로그인</a></li>
<li class="nav-item"><a class="nav-link ${menu eq 'register' ? 'active' : '' }" href="/register">회원가입</a></li>
</c:otherwise>
</c:choose>
실행결과 (로그인 전)
실행결과 (없는 사용자로 로그인 시)
실행결과 (로그인 시)
SessionUtil
package com.sample.utils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
/**
* HttpSession 객체에 속성을 저장하고, 삭제하는 기능이 제공되는 유틸리티 클래스다.
* @author lee_e
*
*/
public class SessionUtils {
/**
* HttpSession객체에 속성을 추가한다.
* @param name 속성명
* @param value 속성값(객체)
*/
public static void setAttribute(String name, Object value) {
getRequestAttributes().setAttribute(name, value, RequestAttributes.SCOPE_SESSION);
}
/**
* HttpSession객체서 지정된 속성을 삭제한다.
* @param name 속성명
*/
public static void removeAttribute(String name) {
getRequestAttributes().removeAttribute(name, RequestAttributes.SCOPE_SESSION);
}
/**
* HttpSession 객체에서 지정된 속성을 반환한다.
* @param name 속성명
* @return 속성값(객체)
*/
public static Object getAttribute(String name) {
return getRequestAttributes().getAttribute(name, RequestAttributes.SCOPE_SESSION);
}
private static RequestAttributes getRequestAttributes() {
return RequestContextHolder.getRequestAttributes();
}
}
HomeController
@PostMapping("/login")
public String login(String id, String password) {
User user = userService.login(id, password);
// HttpSession 객체에 속성으로 로그인 절차가 완료된 사용자 정보를 저장한다.
SessionUtils.setAttribute("loginUser", user);
return "redirect:home";
}
HomeController
/*
* 요청 URL 매핑하기
* 요청URL과 요청핸들러 메소드를 매핑시키는 어노테이션
* @RequestMapping : 요청방식에 상관없이 요청URL을 기준으로 매핑시킨다.
* @GetMapping : 요청방식이 GET 방식이고 요청 URL이 일치하는 요청핸들러 메소드와 매핑시킨다.
* @PostMapping : 요청방식이 POST 방식이고 요청 URL이 일치하는 요청핸들러 메소드와 매핑시킨다.
* @PutMapping : 요청방식이 PUT 방식이고 요청 URL이 일치하는 요청핸들러 메소드와 매핑시킨다.
* @DeleteMapping : 요청방식이 DELETE 방식이고 요청 URL이 일치하는 요청핸들러 메소드와 매핑시킨다.
*
* 요청방식
* GET : 서버에서 정보를 조회하는 요청
* POST : 서버에 정보를 추가하는 요청
* PUT : 서버의 정보를 변경하는 요청
* DELETE : 서버의 정보를 삭제하는 요청
* * 일반적인 웹 애플리케이션에서는 GET, POST 두가지 방식을 사용한다.
* * REST 방식의 웹 어플리케이션에서는 GET, POST, PUT, DELETE 방식을 전부 사용한다.
*/
@RequestMapping("/logout")
public String logout() {
// HttpSession객체에서 로그인된 사용자 정보를 삭제한다.
SessionUtils.removeAttribute("loginUser");
return "redirect:home";
}
실행결과 (로그아웃 전)
실행결과 (로그아웃 후)
기존과의 차이점
- Util을 사용하여 HttpSession을 적지 않아도 된다. (=HttpSession에서 값을 가지고 오지 않아도 RequestContextHolder를 통해 Sessino객체에 값을 주고 / 가지고 올 수 있게 되었다.)
자바스크립트의 변수
var
--------------------------------------------------------------------------------------------------------------------------
변수의 범위가 함수다. (y와 z가 블록 안에서만 아닌, 함수에서 적용된다. 그래서 블록 밖에서도 사용 가능하다.)
--------------------------------------------------------------------------------------------------------------------------
function a() {
var x = 10;
if (x > 5) {
var y = 20;
var z = x*y;
console.log(x, y, z); // 10, 20, 200 출력된다.
}
console.log(x, y, z); // 10, 20, 200 출력된다.
}
--------------------------------------------------------------------------------------------------------------------------
변수 호이스팅(변수 끌어올리기)이 적용된다
--------------------------------------------------------------------------------------------------------------------------
function a() {
console.log(x, y, z); // 오류가 발생하지 않는다 undefined, undefined, undefined가 출력된다.
var x = 10;
if (x > 5) {
var y = 20;
var z = x*y;
console.log(x, y, z);
}
console.log(x, y, z);
}
// 자바스크립트 컴파일러는 위의 소스코드를 아래와 같이 변경한다.
function a() {
var x, y, z; // 함수내에서 선언된 모든 변수의 선언을 함수의 첫번째 라인으로 끌어올린다.
console.log(x, y, z);
x = 10;
if (x > 5) {
y = 20;
z = x*y;
console.log(x, y, z);
}
console.log(x, y, z);
}
--------------------------------------------------------------------------------------------------------------------------
변수명이 중복되어도 오류가 발생하지 않는다.
--------------------------------------------------------------------------------------------------------------------------
function x() {
var x = 10;
console.log(x); // 10이 출력된다.
var x = 20;
console.log(x); // 20이 출력된다.
}
let
--------------------------------------------------------------------------------------------------------------------------
변수의 범위가 블록이다. (블록 안에서 실행된 함수는 블록 안에서 사라진다. / 블록 밖에서 사용할 수 없다.)
--------------------------------------------------------------------------------------------------------------------------
function a() {
let x = 10;
if (x > 5) {
let y = 20;
let z = x*y;
console.log(x, y, z); // 10, 20, 200 출력된다.
}
console.log(x, y, z); // 오류 발생, y, z가 변수는 존재하지 않는다.
}
--------------------------------------------------------------------------------------------------------------------------
변수 호이스팅이 적용되지 않는다.
--------------------------------------------------------------------------------------------------------------------------
function a() {
let x = 10;
if (x > 5) {
let y = 20;
let z = x*y;
console.log(x, y, z); // 10, 20, 200 출력된다.
}
console.log(x, y, z); // 오류 발생, y, z가 변수는 존재하지 않는다.
}
--------------------------------------------------------------------------------------------------------------------------
변수명이 중복되면 오류가 발생한다.
--------------------------------------------------------------------------------------------------------------------------
function x() {
let x = 10;
console.log(x);
let x = 20; // 변수명 중복으로 문법 오류가 발생한다.
console.log(x);
}
const
--------------------------------------------------------------------------------------------------------------------------
한번 할당된 값을 변경할 수 없다. (같은 값을 써야할 때 사용)
--------------------------------------------------------------------------------------------------------------------------
const x = 10;
x = 100; // 오류가 발생한다. 값을 변경할 수 없다.
navbar.jsp
<li class="nav-item"><a class="nav-link ${menu eq 'user' ? 'active' : '' }" href="/user/info">내정보 보기</a></li>
실행결과
LoginUserInfo.java
package com.sample.web.login;
/*
* 로그인된 사용자의 정보를 담는다.
* id와 이름만 가지고 있다.
*/
public class LoginUserInfo {
private String id;
private String name;
public LoginUserInfo(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
}
HomeController
@PostMapping("/login")
public String login(String id, String password) {
// 아이디, 비밀번호 로그인 검증
User user = userService.login(id, password);
// HttpSession 객체에 로그인 절차가 완료된 사용자 정보를 저장한다.
LoginUserInfo loginUserInfo = new LoginUserInfo(user.getId(), user.getName());
// HttpSession 객체에 속성으로 로그인 절차가 완료된 사용자 정보를 저장한다.
SessionUtils.setAttribute("loginUser", loginUserInfo);
return "redirect:home";
}
UserDetailDto.java
package com.sample.dto;
import java.sql.Date;
import java.util.List;
import com.sample.vo.UserRole;
public class UserDetailDto {
private String id;
private String name;
private String email;
private String tel;
private Date createdDate;
private List<UserRole> userRoles;
public UserDetailDto() {}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public Date getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
public List<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(List<UserRole> userRoles) {
this.userRoles = userRoles;
}
@Override
public String toString() {
return "UserDetailDto [id=" + id + ", name=" + name + ", email=" + email + ", tel=" + tel + ", createdDate="
+ createdDate + ", userRoles=" + userRoles + "]";
}
}
UserService.java
public UserDetailDto getUserDetail(String userId) {
// 사용자 정보 조회
User user = userMapper.getUserById(userId);
// 사용자의 권한 정보 조회
List<UserRole> userRoles = userRoleMapper.getUserRolesByUserId(userId);
// 사용자 정보와 권한정보를 모두 저장하는 UserDetailDto 객체 생성
UserDetailDto userDetailDto = new UserDetailDto();
// User객체의 값을 UserDetailDto객체로 복사하기
BeanUtils.copyProperties(user, userDetailDto);
// 사용자권한정보를 UserDetailDto객체에 저장하기
userDetailDto.setUserRoles(userRoles);
return userDetailDto;
}
UserController
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.dto.UserDetailDto;
import com.sample.exception.ApplicationException;
import com.sample.service.UserService;
import com.sample.utils.SessionUtils;
import com.sample.web.login.LoginUserInfo;
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/info")
public String info(Model model) {
LoginUserInfo loginUserInfo = (LoginUserInfo) SessionUtils.getAttribute("loginUser");
if (loginUserInfo == null) {
throw new ApplicationException("로그인 정보가 존재하지 않습니다.");
}
UserDetailDto userDetailDto = userService.getUserDetail(loginUserInfo.getId());
model.addAttribute("user", userDetailDto);
return "user/detail";
}
}
웹과 관련된 코드는 Controller에 DB 엑세스 코드는 Service에 나눠서 구현했다.
표현계층은 서버와 직접적인 영역이 있다.
프론트엔드 -> 표현계층 -> 벡엔드 -> 표현계층 -> 프론트엔드 순서로 실행된다.
그러나 서비스는 웹이 아닌 곳에서도 보여져야 하기 때문에 로그인 체크를 Controller에 작성하였다.
싱글턴 객체에서 멤버변수는 무조건 read-only여야 한다. (꺼내서 사용하는 것만 가능! / 변경 금지!)
detail.jsp
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<title>애플리케이션</title>
</head>
<body>
<c:set var="menu" value="user" />
<%@ include file="../common/navbar.jsp" %>
<div class="container">
<div class="row mb-3">
<div class="col-12">
<h1 class="border bg-light p-2 fs-4">내 정보</h1>
</div>
</div>
<div class="row mb-3">
<div class="col-3">
<div class="card">
<div class="card-header">
메뉴
</div>
<div class="card-body">
<div class="list-group">
<a href="/user/info" class="list-group-item list-group-item-action active">내 정보 보기</a>
<a href="/user/password" class="list-group-item list-group-item-action">비밀번호 변경</a>
<a href="/user/delete" class="list-group-item list-group-item-action">탈퇴하기</a>
</div>
</div>
</div>
</div>
<div class="col-9">
<p>내 정보를 확인하세요.</p>
<table class="table table-bordered">
<colgroup>
<col width="15%">
<col width="35%">
<col width="15%">
<col width="35%">
</colgroup>
<tbody>
<tr>
<th>아이디</th>
<td>${user.id }</td>
<th>가입일</th>
<td><fmt:formatDate value="${user.createdDate }"/></td>
</tr>
<tr>
<th>이름</th>
<td>${user.name }</td>
<th>접근권한</th>
<td>
<c:forEach var="userRole" items="${user.userRoles }">
<c:choose>
<c:when test="${userRole.roleName eq 'ROLE_GUEST' }">
<span class="badge text-bg-primary">${userRole.roleName }</span>
</c:when>
<c:when test="${userRole.roleName eq 'ROLE_USER' }">
<span class="badge text-bg-warning">${userRole.roleName }</span>
</c:when>
<c:when test="${userRole.roleName eq 'ROLE_ADMIN' }">
<span class="badge text-bg-success">${userRole.roleName }</span>
</c:when>
</c:choose>
</c:forEach>
</td>
</tr>
<tr>
<th>이메일</th>
<td>${user.email }</td>
<th>전화번호</th>
<td>${user.tel }</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
</body>
</html>
실행결과