56일차 공부 회원가입 페이지
어제에 이어서
4) Business Logic(업무처리)을 처리해 줄 Service 클래스를 디자인
=>싱글톤 패턴을 적용하고 템플릿 메소드 패턴(인터페이스 -> 클래스 : 가독성 과 유지보수를 위해서)을 적용
=>Service 에서는 Dao를 사용한다.
Dao 인스턴스가 필요하다.
=>service 패키지에 User의 요청을 처리해 줄 메소드의 원형을 선언하는 인터페이스를 생성
service.UserService
package service;
//User 에 대한 처리를 하기 위한 메소드 선언을 위한 인터페이스
//메뉴판과 유사한 역할을 수행합니다.
public interface UserService {
}
=>UserService 인터페이스의 메소드를 구현할 UserServiceImpl 클래스를 생성
//User의 요청을 처리할 메소드를 구현할 클래스
public class UserServiceImpl implements UserService {
//Service 에서 사용할 UserDao 변수
private UserDao userDao;
private UserServiceImpl() {
userDao = UserDao.sharedInstance();
}
private static UserService userService;
public static UserService sharedInstance() {
if(userService == null) {
userService = new UserServiceImpl();
}
return userService;
}
}
5) 사용자의 요청을 받아서 필요한 서비스를 호출하고 서비스의 결과를출력할 View를 결정해 주는 역할을 수행하는 Controller 클래스를 생성
=> HttpServlet 클래스를 상속해서 만들어야 한다.
=> 처리할 URL 패턴을 설정해야 한다.
=> Service 인스턴스를 소유하고 있어야 한다.
=> controller.UserController를 생성하고 URL 패턴은 user 디렉토리 패턴으로 설정
package controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import service.UserService;
import service.UserServiceImpl;
@WebServlet("/user/*")
public class UserController extends HttpServlet {
private static final long serialVersionUID = 1L;
private UserService userService;
public UserController() {
super();
userService = UserServiceImpl.sharedInstance();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
String contextPath = request.getContextPath();
String command = requestURI.substring(contextPath.length());
String method = request.getMethod();
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
6) 여기까지가 하나의 서비스를 구현하기 위한 기본 작업
=> 여기까지는 프레임워크를 사용하면 클래스를 만들고 변수를 생성하는 부분까지만 직접하고 나머지는 프레임워크가 구현을 해준다.
6. 시작 페이지 만들기
=> 대규모 웹 애플리케이션에서는 모든 요청이 Controller를 거치도록 하는 것을 권장
=> web.xml 의 welcome-file list 라는 항목이 시작 요청이 왔을 때 보여지는 페이지를 설정하는데 여기에는 실제 존재하는 파일명이 설정되어야 한다.
=> Controller는 url 패턴으로 설정하고 web.xml은 파일이름으로 설정하기 때문에 시작 요청을 처리하는게 어렵다.
=> web.xml 파일에 설정한 시작 페이지가 바로 Controller의 요청으로 포워딩하거나 리다이렉트 되도록 만든다.
이때 Controller가 index.html 도 같이 처리하는 경우가 많다.
1) WebContent 디렉토리에 시작페이지로 사용할 index.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>
<!-- 바로 user/main으로 포워딩 하도록 해주는 코드
전자 정보 프레임워크가 만들어주는 프로젝트에서 index.jsp에 보면 이런 코드가 존재한다. -->
<jsp:forward page="user/main"></jsp:forward>
</body>
</html>
2) UserController가 index.html
@WebServlet({"/index.html", "/user/*"})
3) UserController 클래스의 doGet 메소드를 수정
if(command.equals("/index.html")) {
response.sendRedirect("./");
}
//단순 페이지 이동은 포워딩
else if(command.equals("/user/main")) {
RequestDispatcher dispatcher = request.getRequestDispatcher("../member/main.jsp");
dispatcher.forward(request, response);
}
4) WebContent 디렉토리에 member 디렉토리를 생성하고 main.jsp 파일을 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
시작
</body>
</html>
5) web.xml 파일에서 welcom-file-list 수정
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
6) 프로젝트를 실행해서 main.jsp 파일이 출력되는지 확인
7. 회원가입 - 데이터 삽입이다.
=> 어떤 항목을 입력받을 것인가 ? email, password, nickname, image
=> 입력받아야 하는 항목 중에서 유일무이한 값이 있는지 확인? email, nickname
유일무이하게 저장되어야 하는 값은 중복 검사를 해야한다.
=> 입력받는 값 중에서 값에 제한이 있는 항목은 있는가?
값에 제한이 있으면 radio, checkbox, select 를 이용해야 하고
필수 항목 중에서 하나만 선택해야 햐는 것은 radio,
다중 선택이 가능한 것은 checkbox 나 select로 만드는 것이 좋다.
필수 선택일 때는 기본값을 선택 해주는 것이 좋다.
=> 입력을 해서 전송을 할 때는 유효성 검사를 해주어야 한다.
=> 하나의 서버를 이용해서 웹 브라우저, 모바일 앱, PC 앱을 전부 처리하고자 하면 웹 브라우저에서 폼의 데이터를 전송할 때 ajax를 이용한다.
1) 회원가입 요청을 생성 - 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>
<!-- jsp 파일에서 서버에 요청하는 경우는 상대 경로를 이용하지 않는 것이 좋다. 절대 경로쓰기
css나 js의 링크 그리고 이미지나 동영상 및 사운드의 링크도 동일한 방법으로 설정하자 -->
<a href="${pageContext.request.contextPath}/user/register">회원가입</a><br/>
</body>
</html>
2) UserController 클래스에서 /user/register 요청이 GET방식으로 오면 처리하는 코드를 doGet 메소드에 추가
else if(command.equals("/user/register")) {
RequestDispatcher dispatcher = request.getRequestDispatcher("../member/register.jsp");
dispatcher.forward(request, response);
}
3) member 디렉토리에 register.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>
<!-- ajax로 요청하는 경우는 method가 필요없고 id만 설정 - 웹만 할때는 method를 post로 설정
입력하는 폼에 파일이 존재하는 경우는 enctype을 설정해야 한다. 설정안하면 파일 업로드 안된다. -->
<form method="post" id="registerform" enctype="multipart/form-data">
<div id="msg">유효성 검사 내용 출력</div>
이메일<input type="text" name="email" id="email"/>
<div id="emailmsg"></div>
비밀번호<input type="password" name="password" id="password"/>
<div id="passwordmsg"></div>
비밀번호확인<input type="password" name="password1" id="password1"/>
<div id="passwordmsg"></div>
별명<input type="text" name="nickname" id="nickname"/>
<div id="nicknamemsg"></div>
이미지<input type="file" name="image" id="image"/><br/>
<!-- 웹만 한다면 type은 submit -->
<input type="button" value="회원가입" id="mainbtn"/>
<input type="button" value="로그인" id="loginbtn"/>
</form>
</body>
</html>
4) UserDao 클래스에 email 중복검사와 nickname 중복검사를 수행해 줄 메소드를 생성
=> return 타입을 String, int, boolean으로 해도 된다.
email이나 nickname 존재 여부를 알려줄 수 있어야 한다.
=> 존재하면 false, 그렇지 않으면 true를 리턴하도록 작성
package dao;
public class UserDao extends AbstractDao {
//Singleton 패턴을 적용하기 위한 코드
//인스턴스를 하나만 생성하는 디자인 패턴
//모든 곳에서 공유할 데이터를 갖는 클래스나
//Entry point(출입구)에 해당하는 클래스 또는
//서버에서 클라이언트의 요청을 처리하는 클래스는
//인스턴스가 1개이면 됩니다.
private UserDao() {}
private static UserDao userDao;
public static UserDao sharedInstance() {
if(userDao == null) {
userDao = new UserDao();
}
return userDao;
}
//email 중복 검사를 위한 메소드
public boolean emailcheck(String email) {
boolean result = false;
connect();
try {
//sql 생성
//email을 대문자로 변경해서 비교
pstmt = con.prepareStatement("select email from user where upper(email) = ?");
//데이터바인딩
pstmt.setString(1, email.toUpperCase());
//SQL 실행
rs = pstmt.executeQuery();
if(rs.next()) {
result = false;
}else {
result = true;
}
}catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
close();
return result;
}
//nickname 중복 검사를 위한 메소드
public boolean nicknamecheck(String email) {
boolean result = false;
connect();
try {
//sql 생성
pstmt = con.prepareStatement("select nickname from user where upper(nickname) = ?");
//데이터바인딩
pstmt.setString(1, email.toUpperCase());
//SQL 실행
rs = pstmt.executeQuery();
if(rs.next()) {
result = false;
}else {
result = true;
}
}catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
close();
return result;
}
}
5) Dao의 2개의 메소드를 테스트
=> 프로젝트를 선택하고 마우스 우클릭하고 [Build -Path] - [Configure Build Path]를 선택한 후 [Libraries]탭 선택
[Add Library]를 선택하고 보이는 창에서 JUnit을 선택한 후 버전을 선택하고 apply
=> index.jsp
6) Service 인터페이스에 이메일과 닉네임중복 체크를 위함 메소드를 선언
=> Service 가 하는 일은 클라이언트가 전달해준 정보를 받아서 처리를 한 후 저장을 해주는 것
=> 이 때 클라이언트의 정보는 HttpSeverletRequest 클래스의 인스턴스에 있고 응답을 하고자 할 때는 HttpSeverletResponse 클래스의 인스턴스를 이용하면 된다.
=> 기본 모양을 통일
public void 메소드이름(HttpServletRequest request, HttpServletResponse response)
=> 메소드 2개 선언
public void emailCheck(HttpServletRequest request, HttpServletResponse response);
public void nicknameCheck(HttpServletRequest request, HttpServletResponse response);
7) ServiceImpl 클래스에 이메일과 닉네임 중복체크를 위한 메소드를 구현
@Override
public void emailCheck(HttpServletRequest request, HttpServletResponse response) {
//1. 파라미터 읽기
try {
request.setCharacterEncoding("utf-8");
}catch(Exception e) {
System.out.println("Service:" + e.getMessage());
e.printStackTrace();
}
String email = request.getParameter("email");
//2. 별도의 작업을 수행해야 하면 처리
//암호화, 파일 업로드, 파라미터를 다른 자료형으로 변환
//업무처리에 필요한 알고리즘
//3. DAO 작업이 필요하면 호출할 DAO 메소드의 매개변수를 생성
//4. DAO의 메소드를 호출해서 결과를 변수에 저장
boolean result = userDao.emailCheck(email);
//5. 단순 웹 페이지를 위한 서버의 경우는 결과들을 request 나 session에 저장
//REST API 서버의 경우는 JSONObject 클래스의 객체를 만들어서 저장한 후 request에 저장한다.
//웹페이지를 위한 로그인의 경우에만 session에 저장하던디 데이터베이스에 로그인 여부를 저장해 놓는다.
JSONObject object = new JSONObject();
object.put("result", result);
//request에 저장
request.setAttribute("result", object);
}
@Override
public void nicknameCheck(HttpServletRequest request, HttpServletResponse response) {
//1. 파라미터 읽기
try {
request.setCharacterEncoding("utf-8");
}catch(Exception e) {
System.out.println("Service:" + e.getMessage());
e.printStackTrace();
}
String nickname = request.getParameter("nickname");
//2. 별도의 작업을 수행해야 하면 처리
//암호화, 파일 업로드, 파라미터를 다른 자료형으로 변환
//업무처리에 필요한 알고리즘
//3. DAO 작업이 필요하면 호출할 DAO 메소드의 매개변수를 생성
//4. DAO의 메소드를 호출해서 결과를 변수에 저장
boolean result = userDao.nicknameCheck(nickname);
//5. 단순 웹 페이지를 위한 서버의 경우는 결과들을 request 나 session에 저장
//REST API 서버의 경우는 JSONObject 클래스의 객체를 만들어서 저장한 후 request에 저장한다.
//웹페이지를 위한 로그인의 경우에만 session에 저장하던디 데이터베이스에 로그인 여부를 저장해 놓는다.
JSONObject object = new JSONObject();
object.put("result", result);
//request에 저장
request.setAttribute("result", object);
//기억해야 할 것은 파라미터 이름(nickname)
//결과를 저장할 때 사용한 속성이름과 데이터
//request나 session에 저장한 이름
}
8) UserController 클래스에 email 중복체크와 nickname 중복체크를 처리할 코드를 doGet 메소드에 작성
else if(command.equals("/user/emailcheck")) {
//System.out.println("요청 도달");
userService.emailCheck(request, response);
RequestDispatcher dispatcher = request.getRequestDispatcher("../member/emailcheck.jsp");
dispatcher.forward(request, response);
}
else if(command.equals("/user/nicknamecheck")) {
//System.out.println("요청 도달");
userService.nicknameCheck(request, response);
RequestDispatcher dispatcher = request.getRequestDispatcher("../member/nicknamecheck.jsp");
dispatcher.forward(request, response);
}
9) member 디렉토리에 emailcheck.jsp 파일을 만들고 결과를 출력
<%@ page language="java" contentType="text/json; charset=UTF-8"
pageEncoding="UTF-8"%>
${result}
10) member 디렉토리에 nicknamecheck.jsp 파일을 만들고 결과를 출력
<%@ page language="java" contentType="text/json; charset=UTF-8"
pageEncoding="UTF-8"%>
${result}
11) REST API 서버를 만들 경우 테스트
브라우저에 서버주소 뒤에 요청 URL?파라미터이름=파라미터값&파라미터이름=파라미터값..
http://localhost:8080/User/user/emailcheck?email=lsb5212@naver.com 입력
결과가 출력되지 않아서 디버깅
=> 요청이 Controller 까지 도달하는지 확인
doGet 메소드의 요청을 처리하는 부분에 콘솔에 출력하는 코드를 추가
System.out.println("요청 도달");
콘솔에 메시지가 보이면 요청에는 아무런 문제가 없음
이 메시지가 안보이면 URL을 확인
=> 요청이 Service메소드까지 도달하는지 확인
Service의 메소드에 콘솔에 출력하는 코드를 추가
콘솔에 이 메시지가 안보이면 Controller에서 Service의 메소드를 호출하지 않은 것이다.
=> 요청이 DAO 메소드까지 도달하는지 확인
DAO의 메소드에 콘솔에 출력하는 코드를 추가
파라미터를 출력하는 코드를 출력
콘솔에 메세지가 안보이면 Service에서 DAO의 메소드를 호출하지 않은 것이다.
메시지는 보이는데 null로 보이면 파라미터 전달이 잘못된 것이므로 Service에 가서 파라미터를 읽는 부분과 만드는 부분을 확인
=> DAO 메소드에서 결과를 출력
결과가 출력되지 않으면 예외가 발생했을 가능성이 높음 - SQL을 잘못 입력햇거나 ?에 값을 제대로 바인딩하지 않았기 때문이다.
결과는 출력되었다면 그 결과를 제대로 return하고 있는지 확인
=> ServiceImpl의 메소드에서 DAO 의 결과를 저장한 변수를 확인하고 그 값을 JSONObject에 삽입했는지 그리고 JSONObject를 request에 무엇이라고 저장했는지 확인
=> 출력하는 jsp 파일에서 확인
12) 위의 내용을 웹에서 사용할 때 수행할 내용 register.jsp 파일에 작성
=> 이 경우는 전부 자바스크립트로 작업
=> 외부에 자바스크립트 파일을 만들어서 사용할려고 register.jsp파일에 자바스크립트 링크를 추가
=> js/register.js 파일의 경로
<script src="자바스크립트 파일 경로"></script>
<!-- js/register.js 파일의 링크를 설정 -->
<script src="${pageContext.request.contextPath}/member/js/register.js"></script>
13) member 안에 js 폴더를 만들고 register.js 파일에 작성
//스크립트 링크가 body 위에 있다면 window의 load 이벤트 안에 작성
window.addEventListener('load', function(event){
//id를 가지고 필요한 객체들을 찾아오기
var registerform = document.getElementById("registerform");
var msg = document.getElementById("msg");
var email = document.getElementById("email");
var emailmsg = document.getElementById("emailmsg");
var password = document.getElementById("password");
var password1 = document.getElementById("password1");
var passwordmsg = document.getElementById("passwordmsg");
var nickname = document.getElementById("nickname");
var nicknamemsg = document.getElementById("nicknamemsg");
var image = document.getElementById("image");
var registerbtn = document.getElementById("registerbtn");
var mainbtn = document.getElementById("mainbtn");
var loginbtn = document.getElementById("loginbtn");
//mainbtn을 클릭하면 메인 화면으로 이동
mainbtn.addEventListener("click", function(event){
location.href = "../";
});
//loginbtn을 클릭하면 login 으로 이동하도록 작성
loginbtn.addEventListener("click", function(event){
location.href = "login";
});
//email 중복검사 통과 여부를 저장할 변수
var emailcheck = false;
//email을 작성하고 포커스가 떠나면 중복검사를 수행
email.addEventListener("focusout", function(event){
if(email.value.trim().length == 0){
emailmsg.innerHTML = "이메일을 입력하고 넘어가세요!!";
emailmsg.style.color = 'red';
//검사하지 않도록 리턴
return;
}
//ajax 요청 객체를 생성
var request = new XMLHttpRequest();
//요청 생성
request.open('get',
'emailcheck' + '?' + 'email=' + email.value, true);
//요청을 전송
request.send('');
//결과를 받기 위한 부분 생성
request.addEventListener('load', function(event){
//결과를 파싱
var data = JSON.parse(event.target.responseText);
if(data.result == true){
emailmsg.innerHTML = "사용 가능한 이메일";
emailmsg.style.color = "blue";
//email 중복 검사를 통과했다고 표시
emailcheck = true;
}else{
emailmsg.innerHTML = "사용 중 인 이메일";
emailmsg.style.color = "red";
//email 중복 검사를 통과 못했다고 표시
emailcheck = false;
}
})
});
//닉네임 중복 검사 통과여부를 저장할 변수
var nicknamecheck = false;
//닉네임을 작성하고 포커스를 옮길 때
nickname.addEventListener(
"focusout", function(event){
//입력한 내용이 없을 때는 검사하지 않음
if(nickname.value.trim().length < 1){
nicknamemsg.innerHTML = "닉네임을 입력하세요";
nicknamemsg.style.color = 'red';
nicknamecheck = false;
return;
}
var request = new XMLHttpRequest();
request.open('get',
'nicknamecheck?nickname='+nickname.value,
true);
request.send('');
//데이터를 가져왔을 때 호출될 메소드를 설정
request.addEventListener(
'load', function(event){
var data =
JSON.parse(event.target.responseText);
if(data.result == true){
//메시지 출력
nicknamemsg.innerHTML = '사용 가능한 별명';
nicknamemsg.style.color = 'green';
//표시
nicknamecheck = true;
}else{
//메시지 출력
nicknamemsg.innerHTML = '이미 사용 중 인 별명';
nicknamemsg.style.color = 'red';
//표시
nicknamecheck = false;
}
});
});
});
** 템플릿 메소드 패턴 : 인터페이스 -> 클래스
=> C, C++, Objective-C : header(.h, .hpp 등) -> 구현체(c, cpp, m, mm)
** URL 패턴
=> 웹 서버를 만들 때는 중요
/* : 모든 요청을 처리
/디렉토리/* : 디렉토리로 시작하는 요청을 전부 처리
*.확장자 : 확장자로 끝나는 요청을 전부 처리
/경로 : 경로에 해당하는 요청만 처리
=> 디렉토리 패턴과 확장자 패턴은 같이 사용할 수 없다.
/디렉토리/*.확장자 : 에러
=> spring 에서는 / 패턴이 추가되는데 /는 .jsp를 제외한 모든 요청을 처리한다.
** Web에서 비동기 처리 - ajax
=> javascript만 이용
var 변수명(request) = new XMLHttpRequest();
변수명.open("전송방식", url, 비동기여부); //특별한 경우기 아니면 비동기로 처리
변수명.send(파라미터); //요청을 전송 - 전송방식이 post일 때는 send에 파라미터를 보내지만
// get방식을 때는 url뒤에 ?를 하고 파라미터이름=값&파라미터이름=값 의 형태로 전송
// 요청에 응답했을 때 호출되는 콜백 메소드를 등록
변수명.addEventListener('load', function(event)) {
// e.target.responseText가 서버가 응답한 결과 - json
// e.target.responseXML 이 XML로 응답한 결과
// 응답이 오면 그 결과를 파싱해서 사용
// json parsing을 할 때는 JSON.parse(문자열)
});
=> 편리하게 작업하고자 하는 경우에는 javascript 라이브러리 - jQuery
$.ajax({
url:url,
data:(파라미터이름:값, 파라미터이름:값),
dataType:'json',
success:function(data){
성공했을 때 수행할 내용
}
})
** BackEnd 개발자가 자바스크립트 학습
=> HTML에 만든 태그를 자바스크립트 객체로 변환하는 방법
=> 이벤트 처리
=> ajax
** FrontEnd 개발자는 자바스크립트를 문법부터 충실하게 공부를 해야 한다.
Web Front End 개발자는 당연히 해야하는 것이고 모바일 Front End 개발자도 hybrid app 개발을 위해서는 자바스크립트를 해야 한다.
** 스크립트 언어의 특징
=> 줄단위로 해석하면서 실행하기 때문에 상단에 에러가 있으면 하단의 문장이 실행이 안된다.
하단에 에러가 있는 경우는 에러가 발생하기 전까지는 실행이 된다.
=> 자바스크립트, 파이썬, 루비, 코틀린, 스위프트
ajax url : nicknamecheck?nickname= 값