이번 프로그램에서는 Servlet 프로그래밍의 전체적인 흐름과 페이지간 데이터 요청 및 응답을 중점적으로 확인해 볼 것이다.
웹 상에서 보여줄 회원 가입 폼을 아래와 같이 만들어주고, 여러 가지 기능들을 다뤄볼 것이다.
회원 가입에 필요한 정보들을 입력받고, 다음과 같은 유효성 검사를 해보도록 하겠다. (유효성 검사란 정해진 형식의 데이터만 입력 가능하도록 제한하는 기능을 말한다)
● 아이디 입력시 8자리가 넘지 않도록 하며, 첫번째 글자는 영문 소문자가 되도록 한다.
● 비밀번호 일치 여부를 비고란에 출력한다.
● 우편번호 검색을 위한 api를 갖다 써본다. (Daum에서 제공)
● 생년월일을 구하기 위한 알고리즘을 AJAX 문으로 구현해본다.
● 라디오박스, 체크박스, text area를 갖다넣고 submit 버튼으로 위의 값들을 모두 전달해본다.
Front 상에서 입력 받은 값들을 Servlet에 매핑한 후 다음 화면에다가 입력받은 값들을 간단하게 출력해보는것 까지 해볼 것이다.
우선 환경설정 파일(web.xml)에 서블릿 매핑부터 해주도록 하겠다.
(서블릿 매핑의 방법은 web.xml을 통한 방법과 @WebServlet("/URL") 어노테이션을 통한 매핑이 있는데 어노테이션은 다음 프로그램에 사용해봄)
web.xml 파일에 <servlet> 태그와 <servlet-mapping> 태그를 추가시켜준다.
<servlet-class>에는 서블릿 작업을 해줄 클래스의 패키지와 클래스를 명시해준다.
<url-pattern>에는 실제 서블릿 매핑 이름이 들어간다. 서블릿 매핑 시 사용되는 가상의 이름이며 클라이언트가 브라우저에게 요청할 때와 사용할 때 반드시 '/'(슬래시)로 시작한다.
먼저, 회원가입 폼을 html로 구성을 해보겠다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>User Registration</title>
<style>
table, td, th {
border : 1px solid black;
border-collapse: collapse;
margin: 20px auto;
}
td {
width: 150px;
height: 50px;
padding: 5px;
font-size: 20px;
/* text-align: center; */
}
input , select {
font-size: 20px;
}
.data_ui {
/* width: 250px; */
height: 50px;
}
button {
font-size: 15px;
margin: 5px;
}
#sending {
text-align: center;
}
input.poster :disabled {
background: gray;
}
#form {
font-size: 30px;
}
#message {
color: red;
}
</style>
<script type="text/javascript" src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script> <!-- 다음에서 제공하는 api (우편번호) -->
<script type="text/javascript" src="https://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$('#month').on('change',function(){
let year = $('#year').val();
let month = $('#month').val();
let days = new Date(parseInt(year),month,0).getDate();
alert(days);
let day_of_month = "<select id='days' name='days'><option>--일--</option>"
for(let i=0;i<days;i++){
day_of_month += `<option value='${i+1}'>${i+1}</option>`;
}
day_of_month += '</select>';
$('#days').html(day_of_month);
});
$('#btn_check_id').on('click',function(e){
let user_id = $('#user_id').val();
let alpha = 'abcdefghijklmnopqrstuvwxyz';
let message = "";
if(user_id!=''&&user_id.length<=8&&alpha.indexOf(user_id.charAt(0))>=0){
alert(user_id);
$.ajax({
url : "/MemberSampleComplete/memberController",
type : 'get',
data : {'user_id':user_id},
success : function(data){
let flag = JSON.parse(data).result;
if(flag){
$('#message').html('사용할 수 없는 아이디입니다.');
} else {
$('#message').html('사용할 수 있는 아이디입니다.');
}
$('#flag').val("true");
},
error : function(){
},
complete : function(){
}
});
} else {
message += 'id가 비어있거나 형식에 맞지 않습니다.';
$('#message').html(message);
}
e.preventDefault();
});
let checkId = function(){
return $('#flag').val();
}
// 비밀번호
$("#user_password_check").change(function(){
if ($(this).val() === $("#user_password").val()) {
message = '비밀번호 일치';
$('#pwdMessage').css("color","blue");
$('#pwdMessage').html(message);
} else {
message = '비밀번호가 일치하지 않습니다.';
$('#pwdMessage').css("color","red");
$('#pwdMessage').html(message);
}
});
let validateData = function(){
let flag = false;
if(checkId()=='true'){
flag = true;
}
return flag;
}
$('#sending').on('click',function(e){
$('#postal').attr('disabled',false); // textbox가 disabled 되어있으면 서블릿으로 값 못넘겨줌
$('#addr1').attr('disabled',false);
//$('#addr2').attr('disabled',false);
let result = validateData();
result = true;
if(!result){
e.preventDefault();
}
});
$('#find_zipcode').on('click',function(e){
new daum.Postcode({
oncomplete : function(data) {
//alert('address api ' + JSON.stringify(data)); /* data는 JSON 객체로 넘어온것. 이걸 보려면 stringify */
$('#postal').val(data.zonecode);
$('#addr1').val(data.address);
$('#addr2').attr('disabled',false);
$('#addr2').focus();
}
}).open();
/*$('#postal').val("12345");
$('#addr1').val('부산시 해운대구 좌동');
$('#addr2').val('해운대시티');*/
e.preventDefault();
});
});
</script>
</head>
<body>
<form action="/MemberSampleComplete/memberController" method="get">
<table>
<tr><th colspan="5" id="form">회원가입양식</th></tr>
<tr><th>구분</th><th class="data_ui" colspan="2">데이터입력</th><th>유효성검사</th><th>비고</th></tr>
<tr>
<td>아이디</td>
<td colspan="2">
<input type="text" id="user_id" name="userId">
<button id="btn_check_id">아이디확인</button>
</td>
<td>8자리 , 첫글자 영문소문자</td><td id="message"></td></tr>
<tr><td>비밀번호</td><td colspan="2"><input type="password" id="user_password" name="pwd1"></td><td rowspan="2">비밀번호 일치</td><td><input type="hidden" id="flag" value="false"></td></tr>
<tr><td>비번확인</td><td colspan="2"><input type="password" id="user_password_check"></td><td id="pwdMessage"></td></tr>
<tr><td>이름</td><td colspan="2"><input type="text" id="user_name" name="userName"></td><td>필수입력</td><td></td></tr>
<tr><td>우편번호</td><td colspan="2"><input type="text" id="postal" name="postal" disabled><button id="find_zipcode">우편번호찾기</button></td><td rowspan="3">필수입력</td><td></td></tr>
<tr><td>주소1</td><td colspan="2"><input type="text" id="addr1" name="addr1" size="35" disabled></td><td></td></tr>
<tr><td>주소2</td><td colspan="2"><input type="text" id="addr2" name="addr2" size="35" disabled></td><td></td><tr>
<tr>
<td>생년월일</td>
<td colspan="2">
<input type="text" placeholder="년도" size="5" id="year" name="year">
-
<select id="month" name="month">
<option>-- 월 --</option>
<option value='1'>1</option>
<option value='2'>2</option>
<option value='3'>3</option>
<option value='4'>4</option>
<option value='5'>5</option>
<option value='6'>6</option>
<option value='7'>7</option>
<option value='8'>8</option>
<option value='9'>9</option>
<option value='10'>10</option>
<option value='11'>11</option>
<option value='12'>12</option>
</select>
-
<span id="days"></span>
</td><td>년도는 1900~2050, 월은 1~12, 일은 1~31</td><td>2개중 하나 선택</td></tr>
<tr>
<td>성별</td>
<td colspan="2">
<input type="radio" name="gender" value="남" checked>남
<input type="radio" name="gender" value="여">여
</td><td>필수 선택</td><td></td></tr>
<tr>
<td>관심분야</td>
<td colspan="2">
<input type="checkbox" name="interests" value="문학">문학
<input type="checkbox" name="interests" value="어학">어학
<input type="checkbox" name="interests" value="정보IT">정보IT<br>
<input type="checkbox" name="interests" value="과학">과학
<input type="checkbox" name="interests" value="수학">수학
<input type="checkbox" name="interests" value="예술">예술
</td><td>2~3개 사이 선택</td><td></td>
</tr>
<tr><td>자기소개</td><td colspan="2"><textarea cols="50" rows="5" name="introduction"></textarea></td><td></td><td></td></tr>
<tr><td colspan="5" id="sending"><input type="submit" value="가입"> <input type="reset"></td></tr>
</table>
</form>
</body>
</html>
html의 내용은 테이블 구성하는 것이 전부이다.
테이블 내부 셀을 span 태그로 조절을 잘 해주는 것이나 각각의 input type에 맞는 컴포넌트들을 넣어주는것 정도이다.
javascript 태그 내에서 logic 처리가 필요한 부분에 있어서는 id값을 두도록 한다.
또한, 서블릿에 요청할 데이터들은 name을 두도록한다. (이유는 서블릿 파트에서 설명)
이제 Input 별로 validation check에 대한 logic을 살펴보겠다.
먼저 ID이다.
아이디에 대한 row의 4번째 td에 message라는 id를 삽입시켜놓는다.
스크립트란을 보자
아이디 확인 버튼을 눌렀을 때, textbox에 담긴 user_id와 소문자 확인을 위한 변수 alpha, 출력할 message를 생성한다.
user_id가 null이 아니면서 + user_id 길이가 8보다 작고 + user_id의 첫 글자가 alpha에 포함될 때 ajax를 통해 서블릿에 값을 넘길 것이다.
※ 스크립트 상의 데이터를 서블릿으로 넘기는 방법으로는 AJAX문을 통해 값을 보내는 방식과 form 태그를 사용하여 submit을 통해 보내는 방식이 있는데 이 프로그램에서는 AJAX 사용하겠다. (AJAX 구닥다리라 요즘에는 사용 안하는 추세인듯함 → history 관리 안되는 문제 + 보안적인 문제)
매핑된 URL을 통해 get 방식으로 컨트롤러에 user_id란 데이터를 보내준다.
id 사용 가능한지(중복체크)에 대한 로직은 자바에서 구현을 딱히 안했기 때문에 (DB 사용안하고 있고, 데이터 넘겨주고 받고에 대한 결과 확인이 목적이기에) success에 대한 부분은 대충 사용 가능한 아이디라 가정하고 넘어간다. 내부의 flag는 최종 가입버튼 submit시에 실행되게 할 것인가를 위한 것이다.
validation check가 유효하지 않을 시에는 비고란에 message를 하나 출력시켜준다.
코드 맨 아래 e.preventDefault();는 버튼 눌렀을 때 화면이 자기 멋대로 튀지 않게 해주는 것이다. 처음 스크립트문 작성할 때 이놈때매 좀 해맸다.
다음은 비밀번호 일치에 대한 유효성 판별이다.
위와 같이 두 개의 텍스트 박스에 같은 값 "1234"를 넣어준다면 비밀번호 일치를 띄우고,
다른 값을 넣는다면 비밀 번호 불일치 문구를 띄워준다.
user_password_check에 값을 입력하고 포커스가 textbox에서 벗어났을때 change() 메서드가 발생하는데, this의 value(=user_password_check)와 user_password의 value를 비교하여 메시지와 색깔을 설정해주고 출력해준다.
이름은 별다른 로직이 필요하지 않으므로 패스한다.
다음은 우편번호이다.
텍스트 박스는 disable로 막아놓고 우편번호찾기 버튼을 누르면 Daum의 Postcode Service API를 불러와서 주소를 받아올 수 있도록 하고 주소 2에 상세 주소를 입력할 수 있도록 disable을 풀어주도록 할 것이다.
<script type="text/javascript" src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script> <!-- 다음에서 제공하는 api (우편번호) -->
api 사용을 위해서는 위와 같이 src를 불러와야 한다.
해운대구를 치고 첫번째 데이터를 선택해 보겠다.
선택한 값들이 우편번호 텍스트 박스와 주소1 박스에 담기고 주소2에 상세 주소를 입력할 수 있게 한다.
그럼 해당 API의 다이얼로그로부터 값을 받아오는 코드를 살펴보겠다.
먼저 daum.PostCode가 생성되고 작업 처리 후 실행하는 oncomplete(=콜백 함수)에서 일을 처리해 줄것인데, 원하는 값들이 api로부터 어떤식으로 넘어오는지 alert를 통해 확인해 보겠다.
JSON 형식으로 값이 날라오는 것을 알 수 있고, 원하는 값인 우편번호와 주소1은 "zonecode", "address"라는 키 값으로 각각 날아옴을 확인할 수 있다.
텍스트 박스의 각 value를 data.zonecode, data.address로 세팅해주고, 주소2의 disabled를 풀어준 후 focus를 둔다.
다음은 생년월일 선택이다.
년도와 월을 입력받으면 그에 맞게 일자를 선택할 수 있도록 일자의 selectbox를 띄워줄 것이다.
예를 들어, 2024년의 경우 윤년이므로 2월의 일수는 29일까지 있다.
이 경우, 29 까지 선택할 수 있게 로직을 짜줄 것이다.
month 박스에서 월을 선택했을 때, year와 month 값을 가진 변수를 선언해주고, days에 Date() 클래스를 생성하여 해당 Date를 알아온다. getDate() 메서드는 해당 월의 마지막 날짜를 반환한다.
day_of_month에 id와 name을 가진 select 태그를 넣어주고, 해당 월의 일자만큼 반복문을 돌려 option을 채워 넣어준다.
이러면 일자의 select box가 완성된다.
성별(radio button), 관심분야(check box), 자기소개(text area) 같은 경우 특별한 로직은 없고, radio button과 check box는 각 component마다 같은 name으로 묶는다 정도의 특징이 있다.
textarea는 cols와 rows에 값을 줘서 사이즈를 원하는 만큼 정할 수 있다.
마지막으로 가입 버튼이다.
가입 버튼을 누르면 모든 데이터를 Back으로 보내야 하는데, 여기서 중요한게 disabled된 우편번호란과 주소1란을 풀어줘야 한다. (안 풀어주면 값 안넘어감)
result는 아이디와 비밀번호 유효성 검사를 해주고 true이면 진행하게 해주는 용이다. (true로 진행)
이걸로 Front 파트는 완료이다.
가입을 누르고 URL을 확인해본다면
이런식으로 url에 넘긴 데이터들이 박혀있다.
이는 get 방식의 특징이며, 보안에 문제가 있다. (이번 프로그램에서만 GET 방식을 써보겠다)
GET과 POST방식의 차이 확인 ↓
https://suldenlion.tistory.com/111
package bitedu.bipa.member.controller;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import bitedu.bipa.member.vo.TestVO;
public class MemberController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=UTF-8");
String userId = req.getParameter("userId"); // key 값 무조건 name, id 안되는듯
String pwd = req.getParameter("pwd1");
String name = req.getParameter("userName");
String[] address = new String[3];
address[0] = req.getParameter("postal");
address[1] = req.getParameter("addr1");
address[2] = req.getParameter("addr2");
int[] date = new int[3];
date[0] = Integer.parseInt(req.getParameter("year"));
date[1] = Integer.parseInt(req.getParameter("month"));
date[2] = Integer.parseInt(req.getParameter("days"));
char gender = req.getParameter("gender").charAt(0);
String[] interests = req.getParameterValues("interests");
String introduction = req.getParameter("introduction");
TestVO test = new TestVO(userId, pwd, name, address, date, gender, interests, introduction);
System.out.println(test);
req.setAttribute("test", test);
//resp.sendRedirect("./member/view_update.jsp?user_id="+userId);
//resp.sendRedirect("./member/view_update.jsp");
RequestDispatcher rd = req.getRequestDispatcher("./member/view_update.jsp");
rd.forward(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
System.out.println("post method");
this.doGet(req, resp);
}
public void test() {
// String id = req.getParameter("user_id");
// boolean flag = false;
// if(id.equals("admin")) {
// flag = true;
// }
// String result = "{\"result\":"+flag+"}"; //json
// PrintWriter out = resp.getWriter();
// out.print(result);
// out.close();
}
}
Servlet에 매핑해 둔 MemberController 클래스이다.
HttpServlet을 상속받고, 대표 메서드인 doGet()과 doPost()를 Override 받는다.
doGet()을 보면,
resp.setContentType("text/html; charset=UTF-8");
이는 Response를 보낼 화면의 인코딩을 UTF-8로 설정해주는 것이다.
Front에서 입력했던 id, password, name ... 등을 각 타입에 맞게
req.getParameter("name에 해당하는 키 값");
메서드를 통해 값을 받아온다.
기본적으로 String 형태로 받으므로 적절히 parsing 해주면 된다.
getParameter에서는 위에서 말했던 name 태그 값으로 가져오는데, id 값으로는 가져오지 못한다.
마지막으로 test라는 데이터들의 묶음 객체를 하나 만들어 setAttribute() 해준다.
req.setAttribute("test", test);
이를 해줌으로써, 웹에서 test 객체를 가져올 수 있다.
이제 Response를 받을 URL을 설정해줘야 하는데, sendRedirect와 RequestDispatcher의 두가지 방식이 있다.
● sendRedirect = 브라우저가 response에 따라 서버로 지정된 경로를 다시 요청하여 페이지를 호출
● RequestDispatcher = 페이지 호출없이 jsp 파일 내에서 다른 파일로 요청을 보내고 바로 응답을 받는 것
즉, RequestDispatcher는 클라이언트로부터 들어온 요청을 Servlet 내에서 원하는 자원으로 요청을 넘기는 기능인데 해당 자원으로 이동할 때 request 객체와 response 객체를 공유하려면 forward() 메서드를 쓴다.
rd.forward(req, resp);
(sendRedirect로 페이지를 불러온다면 기본적으로 값을 넘겨주지 않고 GET방식처럼 URL에 데이터를 포함해서 넘겨줘야 하는듯 하다)
//resp.sendRedirect("./member/view_update.jsp?user_id="+userId);
이로써 Servlet 파트도 끝이다.
마지막으로 view_update.jsp 라는 페이지에 값을 띄워줘보고 마무리 해보겠다.
<%@page import="bitedu.bipa.member.vo.TestVO"%>
<%@page import="java.util.Arrays"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>수정화면</title>
</head>
<body>
<h1>수정화면</h1>
<!-- scriptlet -->
<%
//String id = request.getParameter("user_id");
//out.print("<h1>"+id+"</h1>");
TestVO test = (TestVO)request.getAttribute("test");
String id = test.getId();
String pwd = test.getPwd1();
String name = test.getName();
String[] address = test.getAddress();
String s_address = Arrays.toString(address);
int[] date = test.getDate();
String s_date = Arrays.toString(date);
char gender = test.getGender();
String[] interests = test.getInterests();
String s_interests = Arrays.toString(interests);
String introduction = test.getIntroduction();
%>
<br>
<h1><%=id%> <%=pwd%> <%=name%> <%=s_address%> <%=s_date%> <%=gender%> <%=s_interests%> <%=introduction%> </h1>
</body>
</html>
view_update.jsp 이다
이제 「scriptlet」이라는 태그를 써서 자바 코드를 삽입시켜주도록 할 것이다.
<% ... %> 형태로 사용한다.
코드 맨 위의 java에서의 import 문 같은 경우도 scriptlet을 써서 받아온다.
scriptlet 내부는 그냥 자바와 같다.
Servlet에서 넘어온 객체를 getAttribute로 받아주고 해당 객체의 getter 들을 사용해서 각 값을 선언한다.
화면에 출력 해보기 위해 html의 h1태그에다가 <%=변수명%>과 같은 형식으로 출력시켜본다.
잘 나오는 것을 확인할 수 있다.
'Spring' 카테고리의 다른 글
페이지네이션 (Pagination) - Servlet 프로그래밍 (0) | 2023.07.08 |
---|---|
Servlet 프로그래밍 (동적 Library CRUD 프로그램) (1) | 2023.07.02 |
Servlet, WAS에 대한 정리 (동적 웹 프로그래밍) (0) | 2023.06.30 |
단축 URL API 설계 (네이버) (0) | 2022.11.09 |
Test Coverage 노트 (0) | 2022.11.08 |
댓글