3. Spring Framework 개발 따라하기-1
구조 설명이라고 하는게 맞는진 모르겠지만 하나하나씩 추가하며 어떤 파일들을 추가하며
제가 할 당시에 많이 쓰이던 라이브러리 들을 연동하겠습니다.
하나하나씩 추가하며 따라오시면 될것같습니다.

일단 위의 화면을 간단하게 설정 드리겠습니다.
다른 부분은 보통 기본적으로 구현하는 부분이며 스프링 기본적인 사용법 튜토리얼이라 생각하심 됩니다.
디비연동은 다음에 하겠습니다.
/src/main/webapp의 패키지 경로를 웹에서 띄울시 /(root)로 설정된 기본 설정입니다.
이부분의 설정을 조금씩 바뀔수도 있으니까 기억해두심 좋습니다.

일단 위와 같이 views 폴더에 login이란 폴더와 login폴더 안에 main이란 jsp파일을 만들겠습니다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 메인</title>
</head>
<body>
안녕 여긴 로그인 페이지
</body>
</html>
일단 위와 같이 만들어줬습니다.
http://localhost:8080/demo/login/main.jsp로 접속하여 파일을 확인하면 열리지 않는걸 확인 가능합니다.
이제 우리가 원하는 페이지를 띄우기 위해 Controller를 만들어 보겠습니다.

위와 같이 com.test.login이라는 package를 만들고 LoginController라는 java 파일을 만들었습니다.
@Controller
@RequestMapping(value="/login")
public class LoginController {
@RequestMapping(value = "/main")
public String login_main(Model model) {
return "login/main";
}
}
위와 같이 코드를 작성하였습니다.
하나하나 차츰 설명하는걸로 하겠습니다.
작성 후 http://localhost:8080/demo/login/main으로 접속을 해보겠습니다 잘 뜨는지

WARN : org.springframework.web.servlet.PageNotFound - No mapping found for HTTP request with URI [/demo/login/main] in DispatcherServlet with name 'appServlet' 와 같은 워닝이 문구를 확인할수 있습니다.
만든 컨트롤러가 정상동작을 하지 않는거 같습니다. 동작하게 만들어 봅시다.

servlet-context.xml을 열어봅니다.

24번째 줄을 수정하시면 됩니다. com.test.demo 패키지만 바라보게 되어있습니다.
이 부분을 수정하시면 됩니다.
<context:component-scan base-package="com.test" />
<context:component-scan base-package="com.test.*" />
<context:component-scan base-package="com.test.**" />
3가지중 하나로 바꾸시면 됩니다. 동작은 동일하게 가능합니다.
저장후 서버를 재시작 후 다시 해당 페이지에 접속을 하면 페이지가 잘 보입니다.
DB연동은 나중에 하고 MVC패턴에 대해 구현하려 합니다.
일단 만든 로그인엔 간단하게 로그인과 로그아웃이 있죠.
그럼 하나씩 차근차근 추가하며 어떻게 사용하는지(?) 차츰 설명하겠습니다.
DTO(Data Transfer Object) 파일을 생성해보겠습니다.
public class LoginDTO {
private String id;
private String password;
private String session_id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSession_id() {
return session_id;
}
public void setSession_id(String session_id) {
this.session_id = session_id;
}
}
com.test.login에 LoginDTO라는 java파일을 위와 같이 만들었습니다.
쉽게 데이터를 연동할때 사용할 데이터 객체를 선언하는 부분이라고 생각하시면 됩니다.
DTO = VO이긴 하지만 VO는 read only속성이 있다고 합니다.
DAO(Data Access Object) 파일을 생성해보겠습니다.
public interface LoginDAO {
public LoginDTO user_login(String id,String password);
public LoginDTO user_logout(String id, String password, String session_id);
}
LoginDAO라는 파일을 위와 같이 interface로 선언합니다. 일단 간단한 로그인과 로그아웃정도만 선언하겠습니다.
interface를 상속받아 구현하겠습니다.
@Repository("LoginDAO")
public class LoginDAOImpl implements LoginDAO {
@Override
public LoginDTO user_login(String id, String password) {
// TODO Auto-generated method stub
LoginDTO login_data = new LoginDTO();
if(id.equals("test") && password.equals("1111")) {
login_data.setId(id);
login_data.setPassword(password);
login_data.setSession_id("qwer1234");
} else {
login_data.setId(id);
login_data.setPassword(password);
login_data.setSession_id("");
}
return login_data;
}
@Override
public LoginDTO user_logout(String id, String password, String session_id) {
// TODO Auto-generated method stub
LoginDTO login_data = new LoginDTO();
if(id.equals("test") && password.equals("1111")) {
login_data.setId("");
login_data.setPassword("");
login_data.setSession_id("");
} else {
login_data.setId(id);
login_data.setPassword(password);
login_data.setSession_id(session_id);
}
return login_data;
}
}
로그인시 id, password가 맞다면 세션값을 내려주고 아니면 세션값을 빈값으로 내려줍니다.
로그아웃은 id, password, session_id가 맞으면 빈값으로 내려주고 아니면 받은 값을 반환합니다.
해당 DAO에는 사실 디비 연결부분을 처리해주시면 됩니다. 이번 포스팅에는 디비연동이 없으니 그냥 고정값으로만 처리했습니다.
이제 service를 선언하겠습니다.
public interface LoginService {
public LoginDTO user_login(String id, String password);
public LoginDTO user_logout(String id, String password, String session_id);
}
@Service("LoginService")
public class LoginServiceImpl implements LoginService {
// log 찍는용
private static final Logger logger = LoggerFactory.getLogger(LoginServiceImpl.class);
@Autowired
LoginDAOImpl loginDAO;
@Override
public LoginDTO user_login(String id, String password) {
// TODO Auto-generated method stub
LoginDTO user_data = loginDAO.user_login(id, password);
if(user_data.getSession_id().equals("")){
logger.info("로그인 실패");
}else {
logger.info("로그인 성공");
}
return user_data;
}
@Override
public LoginDTO user_logout(String id, String password, String session_id) {
// TODO Auto-generated method stub
LoginDTO user_data = loginDAO.user_logout(id, password, session_id);
if(user_data.getSession_id().equals("") && user_data.getId().equals("") && user_data.getPassword().equals("")){
logger.info("로그아웃 성공");
}else {
logger.info("로그아웃 실패");
}
return user_data;
}
}
interface로 선언 후 실제 구현을 합니다.
아까 만든 DAO를 의존성주입하여 사용합니다.
일단 log가 찍히게 만들었습니다. 여기서 해당 부분 서비스 로직을 구현하시면 됩니다.
@Controller
@RequestMapping(value="/login")
public class LoginController {
private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
@Autowired
LoginServiceImpl loginService;
@RequestMapping(value = {"/main","/"})
public String login_main(Model model, @RequestParam(value="typeVal", required = false) String typeVal, @RequestParam(value="msg", required = false) String msg) {
logger.info("login main page open");
if(msg != null && typeVal != null) {
logger.info(String.format("msg : %s, type : %s", msg,typeVal));
model.addAttribute("msg", msg);
model.addAttribute("typeVal", typeVal);
}
return "/login/main";
}
@RequestMapping(value = "/user_login",method = {RequestMethod.POST,RequestMethod.GET})
public String user_login(@RequestParam(value = "id",required = true, defaultValue = "") String id, @RequestParam(value = "password",required = true, defaultValue = "") String password) {
LoginDTO user_data = loginService.user_login(id, password);
logger.info(user_data.toString());
String msg = "";
if(user_data.getSession_id().isEmpty() == false) {
msg = "succ";
}else {
msg = "fail";
}
return String.format("redirect:/login/main?typeVal=login&msg=%s", msg);
}
@RequestMapping(value = "/user_logout", method = RequestMethod.POST)
public String user_logout(@RequestParam String id, @RequestParam String password, @RequestParam String session_id) {
LoginDTO user_data = loginService.user_logout(id, password, session_id);
logger.info(user_data.toString());
String msg = "";
if (user_data.getSession_id().isEmpty() && user_data.getId().isEmpty() && user_data.getPassword().isEmpty()) {
msg = "succ";
} else {
msg = "fail";
}
return String.format("redirect:/login/main?typeVal=logout&msg=%s", msg);
}
}
이제 위와 같이 controller를 변경합니다.
이제 로그인을 테스트 해보기 위해 /views/login/main.jsp를 수정합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 메인</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
안녕 여긴 로그인 페이지
<form action="${pageContext.request.contextPath}/login/user_login" method="post">
<input id="id" name="id">
<br>
<input id="password" name="password" type="password">
<br>
<input type="submit" value="확인">
</form>
</body>
</html>
서버를 재시작 하고 테스트를 해보시면 됩니다.

같은 페이지라 구분은 안가지만 서버 로그엔 잘 찍힙니다.
요즘엔 Front-End와 Back-End를 분리하여 개발을 많이 한다고 하던데 그래도 혹시나 웹과 연동하여 직접 데이터를 주고 받는다면 보통 JSTL을 많이 쓰게 될것입니다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 메인</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<c:choose>
<c:when test="${msg == 'succ' && typeVal == 'login'}">
로그인 성공함
</c:when>
<c:when test="${msg == 'fail'}">
로그인이든 로그아웃이든 뭔가 실패함
</c:when>
<c:otherwise>
<!-- 조건식 그외 -->
안녕 여긴 로그인 페이지
</c:otherwise>
</c:choose>
<form action="${pageContext.request.contextPath}/login/user_login" method="post">
<input id="id" name="id">
<br>
<input id="password" name="password" type="password">
<br>
<input type="submit" value="확인">
</form>
</body>
</html>
JSTL 선언을 하고 c: 이렇게 된 부분을 확인하시면 됩니다.
위에 사용한 부분은 if else if else 구조입니다.
초기화면 성공 실패 시 화면이 다른부분을 확인해보시면 됩니다.
이제 로그아웃을 붙이겠습니다.
로그아웃시 필요한 값은 id, password, session_id입니다.
페이지 이동이엿다면 model에서 addAttribute로 가능했겠지만 저는 redirect를 시키기 때문에 redirect를 시키는 방법을 추가하겠습니다.
구글링을 해보니 redirect시에 값을 넘기는 방법이 있더군요 그 방법으로 구현하겠습니다.
@RequestMapping(value = "/user_login", method = { RequestMethod.POST, RequestMethod.GET })
public String user_login(RedirectAttributes redirectAttributes,
@RequestParam(value = "id", required = true, defaultValue = "") String id,
@RequestParam(value = "password", required = true, defaultValue = "") String password) {
LoginDTO user_data = loginService.user_login(id, password);
logger.info(user_data.toString());
String msg = "";
if (user_data.getSession_id().isEmpty() == false) {
msg = "succ";
redirectAttributes.addFlashAttribute("login_info", user_data);
} else {
msg = "fail";
}
return String.format("redirect:/login/main?typeVal=login&msg=%s", msg);
}
위와 같이 RedirectAttributes가 추가되었습니다. 또한 값을 login_info라는 이름으로 로그인 정보를 넘겨줍니다.
원래는 addaddAttribute로 값을 전달주려 했으니 String만 지원하더라구요 Object를 전달하기 위해 addFlashAttribute를 사용하여 값을 넘겨줍니다.
(또한, addaddAttribute는 GET방식의 데이터 전송이라 하네요)
이제 값을 받기 위해서 redirect시키는 페이지도 변경해야겠죠?
찾아봤는데 백단에서 받는게 굉장히 귀찮더군요.. 그래도 잘 왔는지 로그는 찍어봐야 하니 Back-End에서 받아서 로그를 찍어보겠습니다.
@RequestMapping(value = { "/main", "/" }, method = { RequestMethod.POST, RequestMethod.GET })
public String login_main(Model model, @RequestParam(value = "typeVal", required = false) String typeVal,
@RequestParam(value = "msg", required = false) String msg, HttpServletRequest req) {
logger.info("login main page open");
if (msg != null && typeVal != null) {
logger.info(String.format("msg : %s, type : %s", msg, typeVal));
model.addAttribute("msg", msg);
model.addAttribute("typeVal", typeVal);
}
Map<String, ?> flashMap = RequestContextUtils.getInputFlashMap(req);
if (flashMap != null) {
LoginDTO login_info = (LoginDTO) flashMap.get("login_info");
logger.info("login_info : " + login_info.toString());
}
return "/login/main";
}
쫌 받는 방법이 귀찮지만 HttpHttpServletRequest을 사용해야하고 FlashMap을 받아와야 값을 받는게 가능하네요.
자료형이 다르다거나 하면 예외 처리가 귀찮을꺼 같네요...

login/main페이지에서 login_info라는 로그가 잘 찍히는걸 확인했습니다. 이제 웹페이지에 보여주겠습니다.
이미 redirectAttributes를 사용하여 attribute에 추가가 되어 있으므로 따로 처리해줄 필요는 없습니다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 메인</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<c:choose>
<c:when test="${msg == 'succ' && typeVal == 'login'}">
로그인 성공함
<c:out value="${login_info}" />
</c:when>
<c:when test="${msg == 'fail'}">
로그인이든 로그아웃이든 뭔가 실패함
</c:when>
<c:otherwise>
<!-- 조건식 그외 -->
안녕 여긴 로그인 페이지
</c:otherwise>
</c:choose>
<form action="${pageContext.request.contextPath}/login/user_login" method="post">
<input id="id" name="id">
<br>
<input id="password" name="password" type="password">
<br>
<input type="submit" value="확인">
</form>
</body>
</html>
c:out을 사용하여 정상적으로 값이 넘어왔는지 확인해보겠습니다.

위와 같이 보인다면 값은 정상적으로 넘어온걸 확인할 수 있습니다.
이제 해당 정보들을 사용하여 로그아웃으로 값을 넘겨보겠습니다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인 메인</title>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<c:choose>
<c:when test="${msg == 'succ' && typeVal == 'login'}">
로그인 성공함
<c:if test="${ !empty login_info }">
<form action="${pageContext.request.contextPath}/login/user_logout" method="post">
<input id="id" name="id" type="hidden" value='<c:out value="${login_info.id}" />'>
<input id="password" name="password" type="hidden" value='<c:out value="${login_info.password}" />'>
<input id="session_id" name="session_id" type="hidden" value='<c:out value="${login_info.session_id}" />'>
<input type="submit" value="로그아웃">
</form>
</c:if>
</c:when>
<c:when test="${msg == 'succ' && typeVal == 'logout'}">
로그아웃 성공함
</c:when>
<c:when test="${msg == 'fail'}">
로그인이든 로그아웃이든 뭔가 실패함
</c:when>
<c:otherwise>
<!-- 조건식 그외 -->
안녕 여긴 로그인 페이지
</c:otherwise>
</c:choose>
<form action="${pageContext.request.contextPath}/login/user_login" method="post">
<input id="id" name="id">
<br>
<input id="password" name="password" type="password">
<br>
<input type="submit" value="확인">
</form>
</body>
</html>
로그인을 성공하고 login_info가 재대로 넘어왔다면 값을 저장하고 로그아웃 버튼이 나오게 되어있습니다.
로그아웃관련 처리는 전에 처리해둔 코드가 정상동작 하나 확인해보시면 됩니다.
@RequestMapping(value = "/user_logout", method = RequestMethod.POST)
public String user_logout(@RequestParam String id, @RequestParam String password, @RequestParam String session_id) {
LoginDTO user_data = loginService.user_logout(id, password, session_id);
logger.info(user_data.toString());
String msg = "";
if (user_data.getSession_id().isEmpty() && user_data.getId().isEmpty() && user_data.getPassword().isEmpty()) {
msg = "succ";
} else {
msg = "fail";
}
return String.format("redirect:/login/main?typeVal=logout&msg=%s", msg);
}

로그인 성공 후 로그아웃 버튼을 누를시 id, password, session_id가 빈값으로 내려오는걸 확인할 수 있습니다.
이번 포스팅은 여기까지 하겠습니다.
다음엔 디비연동과 RESTful api관련 작업을 진행할거 같습니다.
Tiles는 고민중입니다.