JAVA 웹개발 과정 국비 55일차 정리

55일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


1. Dynamic Web Project

수정 사항은 보기 쉽게 commit 하여 github에 올려두었다. 블로그와 같이 보면 이해가 쉬울 것이다.

1) FrontController 패턴 적용

현재 있는 DeleteProc, inputProc, ModifyProc, OutputProc 서블렛을 모두 하나로 합쳐서 하나의 서블릿(ContactController)이 제어할 수 있게끔 만든다.

  1. ContactController 생성
  2. ContactController 어노테이션 "*.con" 으로 변경
  3. Front 페이지 요청 모두 .con 형식으로 변경 (주의할 점 : 서블릿은 대문자, ~.con은 소문자로)
  4. ContactController에 if-else if문 작성하여 각 기능을 정리한다.
  5. 기존 DeleteProc, inputProc, ModifyProc, OutputProc 서블렛을 삭제한다.

이것으로 첫번째 Dynamic Web Project는 끝났다..!

2. FrontController 패턴

1) FrontController 패턴이란?

각각의 기능을 대표하는 하나의 컨트롤러를 만들어서 그 servlet으로 데이터의 입력을 제어한다.

현재 프로젝트의 servlets 패키지에는 삭제기능을 담당하는 DeleteProc.java, 입력 기능을 담당하는 InputProc.java, 수정 기능을 담당하는 ModifyProc.java, 출력 기능을 담당하는 OutputProc.java가 존재한다.

그렇다면 매번 기능을 추가할 때 마다 servlet을 하나씩 추가해줘야되는가? 라는 의문이 생긴다.
이런 불편함을 해결하기 위해 FrontController 패턴을 도입한다.
예를 들면 어떤 웹 사이트가 있다고 한다면
처음으로 멤버쉽, 게시판, 상품 등의 큰 카테고리로 나누고 그 카테고리 안에서 기능을 세부적으로 나누는 것이다.

결론은 기능 하나당 서블릿 하나씩이 아닌, 카테고리 별로 컨트롤러를 만들어주는 것이다.

2) 어노테이션 수정

서블릿의 어노테이션은 접속하는 클라이언트가 요청하는 URL과 매핑되는 이름이다.
예를 들어, index.html에서 a태그 또는 form&button 태그로 OutputProc을 요청하면 OutputProc어노테이션의 서블릿으로 이동된다.

현재 C,R,U,D 의 각 기능을 모두 ContactController로 보낼 예정인데, 각각의 기능들을 모두 ContactController의 이름으로 보낼 수는 없다. 기능이 분리되지 않기 때문이다.
따라서 어노테이션을 "*.con"으로 바꿔주면 어떤 이름 뒤에든 .con만 붙으면 이 서블릿으로 오게끔 만들 수 있다.
주의할 점은 파일의 이름으로 찾을 때는 어노테이션에 /를 붙여주고, *.con 이런식으로 받게 되면 /를 붙이지 않는다는 것이다.

3) 사용자가 요청한 기능 알아내기

클라이언트의 모든 요청이 이제 ContactController로 모이게 된다.
클라이언트의 요청 url 값을 알아야 어떤 요청을 하는지 알고, 그 요청에 따라서 각 기능을 동작시킬 수 있다.

  • request.getRequestURI : 사용자가 요청한 전체 주소 (/Day_01_03/output.con)
  • request.getContextPath : 환경 경로(=프로젝트 명) (/Day_01_03)
  • 전체주소 - 환경경로 : 사용자가 요청한 기능 (/output.con)

전체 주소값에서 프로젝트명은 없는 편이 더 좋기 때문에 환경경로를 따로 추출해서 빼주면 사용자가 요청한 기능만 얻어낼 수 있다.

String uri = request.getRequestURI();
System.out.println("사용자가 요청한 주소 : " + uri);
String ctxPath = request.getContextPath();
System.out.println("프로젝트명 : " + ctxPath);
String cmd = uri.substring(ctxPath.length());
System.out.println("사용자가 요청한 기능 : " + cmd);

4) Controller에서 각 기능 분리하기

사용자가 요청한 기능을 알았으니 이제 if-else if문으로 각각의 기능들을 받아 동작시켜준다.
try-catch문은 각각 있을 필요가 없으므로 전체 if문에 감싸준다.

try {
	if(cmd.equals("/output.con")) {
		List<ContactDTO> list = dao.selectAll();
		request.setAttribute("list", list);
		request.getRequestDispatcher("outputView.jsp").forward(request, response);
	}else if(cmd.equals("/input.con")) {
		// 입력 기능..
	}else if(cmd.equals("/modify.con")) {
		// 수정 기능..
	}else if(cmd.equals("/delete.con")) {
		// 삭제 기능..
	}
}catch(Exception e) {
	e.printStackTrace();
	response.sendRedirect("error.html");
}

5) 확장성을 위한 모든 페이지 Controller 거치게 만들기

현재 프로그램은 input을 누르면 inputform.html로 넘어가서 inputform.html에서 데이터를 입력한 뒤 input.con으로 이동해서 입력 기능을 수행하는 형태이다.
잘못된 코드는 아니지만 클라이언트가 inputform.html로 이동하는데 아무런 제약이 없다.
후에 프로젝트가 더 커지게 되면 A페이지에서 B페이지로 이동할 때 이 사용자가 B라는 페이지를 볼 자격이 있는지 검사할 필요가 있기 때문에 Controller를 꼭 거쳐서 이동할 수 있도록 만들어준다.
즉, 모든 페이지가 Controller를 거쳐서 이동할 수 있게끔 해야한다.

	}else if(cmd.equals("/inputForm.con")){
		// 자격 증명, 기록 남기기 등..
		response.sendRedirect("inputForm.html");
	}

3. 게시판 만들기

웹 개발자의 가장 기본이라고 할 수 있는 게시판 만들기를 진행한다.
필요한 라이브러리는 jstl과 ojdbc이다.
board 이름의 폴더로 github에 올려놓았다.

1) 로그인 페이지 구현

  • index.jsp 생성하여 로그인 화면 꾸며주기

테이블을 이용하여 간단하게 로그인 화면을 생성했다.
로그인과 회원가입 버튼을 만들었다.

2) 회원가입 페이지 구현

  • webapp 폴더 > member 폴더 생성 후 signup.jsp 페이지 생성
  • 로그인 페이지에서 회원가입 버튼을 누르면 회원가입 페이지로 이동
  • 다이렉트 이동은 막는다.

어려웠던 점 ①

로그인 화면에는 로그인 버튼과 회원가입 버튼이 있는데, form 태그에 id와 pw를 묶어주면 두 버튼이 모두 form태그의 action으로 이동해버리는 문제가 있다.
회원가입 버튼은 아무 데이터를 가져가지 않아도 되므로 a 태그로 이동시키고 type=button을 줘서 form태그 이동을 막는다., 로그인은 id와 pw를 가지고 이동해야하므로 form 태그 이동을 이용한다.

<td colspan=2 align=center><button>로그인</button>
<a href="signup.mem"><button type="button">회원가입</button></a>

어려웠던 점 ②

유효성 검사 기능이 있는 회원가입 페이지를 예전 js 수업때 만들었던 것을 가져왔는데, 동작하는데는 문제 없는데 F12로 검사하니 계속 jquery-3.6.0.min.js:2 Uncaught TypeError: ((S.event.special[o.origType] || {}).handle || o.handler).apply is not a function 오류가 떴었다.

focus와 blur 이벤트로 input 창에 마우스를 focus 하면 유효성 검사 문구가 떴다가 blur되면 문구가 사라지는 기능을 구현했는데 마우스를 input창에 focus할 때마다 위의 에러 문구가 계속 추가되는 것이였다.
찾아보니 이벤트 등록에 관한 오류였어서 강사님의 도움을 받아 해결할 수 있었다.

오류가 생긴 부분

let nameCheckEvent = $("#input-name").on("input",nameCheck);
$("#input-name").on("focus",nameCheckEvent);

nameCheckEvent 변수에 이벤트를 넣는 코드가 에러의 발생 원인이였다.
input이 발생할 때 nameCheck 이벤트를 등록하고, focus가 발생할 때 이 이벤트 자체를 가져려는 의도였지만 nameCheckEvent가 함수가 아니여서 제대로 된 코드가 아니다.
단순하게 focus될 때와 input이 발생할 때 두 상황 모두에 nameCheck 이벤트를 등록해줌으로 해결했다.

$("#input-name").on("input",nameCheck);
$("#input-name").on("focus",nameCheck);

3) ID 중복 검사 팝업창 구현

  • 중복검사 버튼을 누르면 팝업창 띄우기
  • 아이디가 입력되지 않은 상태에서 버튼을 누르면 alert으로 경고창 띄우기
  • 중복검사를 위한 dao 클래스, DB table 만들기
  • isIdExist메소드 결과값을 idCheckView에 구현하기
  • 사용 가능한 아이디일 때, 사용을 누르면 적은 id가 그대로이고, 취소를 누르면 적은 id가 사라진다.

① window.onload의 제이쿼리 문법

$(function(){})을 head 태그 안에 적어주면 js의 window.onload와 같이 window의 로딩이 완료되었을 때 실행이 된다. 제이쿼리의 문법이다.
window.open을 이용해 팝업창을 띄운다.

② 서버에 값을 보내는 또다른 방법

form태그의 method를 get방식으로 지정하고 submit을 하면(type=button으로 이동은 막음) 주소값의 ? 뒤에는 key값과 value값이 주소값?key=value 형태로 나오게 된다. 개발자가 인위적으로 주소값 뒤에 파라미터를 지정하여 서버에 값(key, value)을 보낼 수 있다.

$(function(){
	$("#id-check").on("click",function(){
		window.open("idCheck.mem?id="+$("#input-id").val(),"","width=300px,height=200px,top=200px,left=200px");
	})
})

③ 팝업창의 parent

iframe의 경우 parent의 개념이, 팝업창에서에는 opener이다.
열려진 팝업창을 기준으로 opener를 호출하면, 팝업창을 연 대상을 가리킨다.

$("#close").on("click",function(){
	window.close();
	opener.document.getElementById("input-id").value="";
})

4) Table 생성

member 객체들의 데이터를 저장하는 member 테이블을 생성한다.

CREATE TABLE Member(
id VARCHAR(20) PRIMARY KEY,
pw VARCHAR(128) NOT NULL,
name VARCHAR(20) NOT NULL,
phone VARCHAR(20),
email VARCHAR(100),
zipcode VARCHAR(5),
address1 VARCHAR(100),
address2 VARCHAR(100),
signup_date DATE DEFAULT SYSDATE NOT NULL
);

5. eclipse template 설정

매번 jsp파일에 jquery나 jstl 코드를 적어주기 번거롭다면, 이클립스 설정에서 jsp 파일을 생성할 때마다 자동으로 적어주는 코드를 추가하면 된다.

Window > Preferences > 검색창에 template > JSP Files 아래 Templates 클릭 > New JSP File (html 5) 클릭 후 Edit.. > Pattern에 추가하려는 코드 붙여넣기

6. 절대경로와 상대경로

중복검사 버튼을 누르면 사용자가 요청한 기능은 /Board/member/idCheck.mem 이다.
subString으로 ContextPath를 제거해준다고 해도 /member/idCheck.mem이 된다.
/member가 붙은 것일까?
현재 member 폴더 안에 존재하는 회원가입 페이지 signup.jsp에서 idCheck.mem을 호출하는 상황이기 때문이다.

  1. 절대경로
    • 대상이 존재하는 경로를 시작부터 끝까지 다 알려주는 것
    • 주소값 앞에 /를 붙여준다.
    • 절대경로로 signup.jsp에서 idCheck.mem을 부른다면?
      • 프로젝트의 제일 꼭대기부터 idCheck.mem을 찾겠다
      • 주소창에는 localhost:8080/idCheck.mem으로 나타남.
  2. 상대경로
    • 현재 내가 위치한 곳에서 상대를 부르는 것
    • 주소값 앞에 아무것도 붙이지 않는다.
    • 상대경로로 signup.jsp에서 idCheck.mem을 부른다면?
      • 나랑 같은 폴더(member) 안에 있는 idCheck.mem을 부르는 것
      • 주소창에는 localhost:8080/Board/member/idCheck.mem으로 나타남.

상대경로로 url을 사용하는 순간 내가 어떤폴더에서 어떤 요청을 하냐에 따라서 계속 url이 바뀐다. 그렇게되면 controller에서 if문을 사용하기 어렵다는 단점이 있기 때문에 절대경로를 사용하는 것이 좋다.
상대경로를 쓰고싶다면 상위폴더로 가는 ../을 사용하여 내가 위치한 곳의 상위 폴더를 빼버려야 한다. 그러나 폴더안에 폴더안에 파일이 있다면? 같은 상황이 되기 때문에 마찬가지로 추천하지 않는다.

절대경로를 사용한다는 것은 알겠는데.. 그럼 /Board/member/idCheck.mem를 다 적어주어야하는 걸까?
현재는 프로젝트 명을 명시하기 때문에 프로젝트 꼭대기부터 /idCheck.mem을 찾으려고 하는데, 없기 때문에 404 에러가 뜨는 것이다.
서버 설정에서 프로젝트 명을 명시하지 않겠다고 바꿔주면(아래 2번 방법) /idCheck.mem만 쓰더라도 더이상 오류가 나지 않고 때려맞춰서(?) 잘 맞게 찾아 들어간다.

즉, 정리하자면 아래 순서를 따라가면 경로 표시에 대한 에러는 없다.

  1. 절대경로로 표시 : /idCheck.mem
  2. Servers 폴더에 server.xml에서 path="/Board"를 찾아서 path=""로 변경

7. 소스 코드

Dynamic Web Project
Board

댓글남기기