JAVA

JSP: FrontController 직접 구현해보기

docc 2021. 7. 26. 11:02

Spring 의 FrontController 이해하기

// FronctController.java

package com.stone.mvc.control;

import java.io.IOException;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.RequestDispatcher;
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 com.stone.mvc.util.ViewResolver;


//FrontController
@WebServlet("*.do")
public class FrontController extends HttpServlet {
    
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. uri path 만 제거
		String uri= request.getRequestURI();
		String 요청작업DotDo=uri.substring(5);//"/mvc/"5글자 다음 인덱스인 6째부터(zero base면 5가 6번째임)
		while(true) {
			// 2. 요청작업 구하기
			int lastIdx=요청작업DotDo.indexOf(".do");//".do" 시작인덱스
			String 요청작업=요청작업DotDo.substring(0,lastIdx);//".do"문자열 제거
			
			// 3. 요청과 컨트롤 객체 매핑
	        IControl control= ControlProvider.getControlProvider().getControl(요청작업);
			//해당 커트롤러 요청처리
			ModelAndView 모델과지정된뷰명 = control.process(request, response);
			Set<Entry<String,Object> >  entry들= 모델과지정된뷰명.model.entrySet();
			for(Entry<String,Object> entry  : entry들 ){
				request.setAttribute(entry.getKey(), entry.getValue());
			}
			
			// 4. 경로에 따른 제어			
			if(모델과지정된뷰명.viewName.contains("redirect:/")) {
				요청작업DotDo = 모델과지정된뷰명.viewName.substring("redirect:/".length());//"redirect:/"문자열 제거
				response.sendRedirect(요청작업DotDo);
				break;//이미 제어권이 넘어가므로 큰 의미는 없다.
			}
			if(모델과지정된뷰명.viewName.contains("forward:/")) {
				요청작업DotDo = 모델과지정된뷰명.viewName.substring("forward:/".length());//"forward:/"문자열 제거
				System.out.println(요청작업DotDo);
				continue;//요청작업 구하기부터 다시함
			}
			// 5. view로 forward
			RequestDispatcher rd =request.getRequestDispatcher(ViewResolver.getJspViewName(모델과지정된뷰명.viewName));
			rd.forward(request, response);		
			break;//포워드시 이미 제어권이 넘어가므로 큰 의미는 없다.
		}		
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request,response );
	}

}
// ControlProvider.java

class BasicControlProvider {
	
	HashMap<String, IControl> controls = new HashMap<String, IControl>();
	
	BasicControlProvider(){
		controls.put("prepare_board", new 게시물등록준비());
		controls.put("add_board", new 게시물등록());
        // 추가사항
		controls.put("prepare_member", new 회원등록준비());
		controls.put("add_member", new 회원등록());
		controls.put("info_member", new 회원정보준비());
		controls.put("inquery_address", new 우편번호조회준비());
		controls.put("inquery_id", new 아이디중복검사준비());
	}
	
	public IControl getControl(String work) {
		return controls.get(work);
	}
}

public class ControlProvider {
	
	static BasicControlProvider controlProvider = new BasicControlProvider();
	
	public static BasicControlProvider getControlProvider() {
		return controlProvider;
	}
}

회원등록

>> 회원등록준비.java

public class 회원등록준비 implements IControl{

	public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
		ModelAndView mv = new ModelAndView();
		
		HttpSession session = request.getSession();
		// 로그인해서 없는 카테고리지만 어떻게 주소를 찾아 들어올 경우 돌려보내기
		if(!(session.getAttribute("no") == null)) {
			mv.setViewName("index");
		}
				
		mv.setViewName("회원등록창");
		return mv;
	}
}

>> 회원등록창.jsp

<body>

<form action="add_member" onsubmit="return 회원등록하다()" method="post" enctype="multipart/form-data">
<h1>회원등록</h1>
<!--기초내용 -->
성명 <input type = "text" name = "name" id="name"><br>
// 추가사항
프로필<img id="myimg"/><br>
	<input type="file" id="profile" name="profile"/>

우편번호 <input type = "text" name = "post" id="post" readonly="readonly"><input type="button" onclick="조회창을띄우다()" value="조회"><br>
주소 <input type = "text" name = "address" id="address" readonly="readonly"> <br>
상세주소 <input type = "text" name = "detailaddress"> <br>
전화번호 <input type="text" name="tel"/><br>
이메일 <input type="text" name="email1"/>@
 <select name="email2">
 	<option value="google.com">google.com</option>	
 	<option value="naver.com">naver.com</option>	
 	<option value="daum.com">daum.com</option>	
 </select>
<br>
아이디 <input type="text" name="id" id="id" readonly="readonly"> <input type="button" onclick="아이디중복검사창을띄우다()" value="조회"><br>
패스워드 <input type="password" name="password" id="password1"/><br>
패스워드재입력 <input type="password" id="password2"/><br>
<input type="submit" value="등록" />
</form>

</body>

<script>
/* 우편번호,주소 */
function 조회창을띄우다(){
	window.open("inquery_address","","width=450,height=300");
}

function 주소를받다(우편번호, 주소){
	let txt우편번호 = document.querySelector("#post");
	txt우편번호.value = 우편번호;
	
	let txt주소 = document.querySelector("#address");
	txt주소.value = 주소;
}

/* 아이디 */
function 아이디중복검사창을띄우다(){
	window.open("inquery_id","","width=450,height=300");
}

function 아이디를받다(id){
	let txtID = document.querySelector("#id");
	txtID.value = id;
}

/* 필수입력, 비밀번호확인 */
function 회원등록하다(){
	// 필수 입력확인
	var txt성명 = document.querySelector("#name");
	if(txt성명.value == ""){
		alert("성명은 필수 입력입니다.");
		return false;
	}
	
	var txt아이디 = document.querySelector("#id");
	if(txt아이디.value == ""){
		alert("아이디은 필수 입력입니다.");
		return false;
	}
	
	var txt패스워드 = document.querySelector("#password1");
	if(txt패스워드.value == ""){
		alert("패스워드은 필수 입력입니다.");
		return false;
	}
	
	// 비밀번호 동일 확인
	var txt패스워드2 = document.querySelector("#password2");
	if(txt패스워드.value != txt패스워드2.value){
		alert("패스워드가 동일하지 않습니다.");
		return false;
	}
	
	return true;
}
    // 추가사항
	function 그림파일읽어출력하기(event){
		var 그림파일 = event.target.files[0];
		
		if(!그림파일.type.match('image.*')){
			alert("그림파일을 선택해주세요.");
		}
		
		var 파일리더 = new FileReader();
		파일리더.onload = function(event2){
			var myimg = document.querySelector("#myimg");
			img.src = event2.currentTarget.result;
		}
		파일리더.readAsDataURL(그림파일);
	}
	
	document.querySelector("#myfile").addEventListener("change",그림파일읽어출력하기);
</script>

>> 회원등록.java

public class 회원등록 implements IControl{

	public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
		Member member = null;
		
		String folder = "D:\\dojihye\\testFolder";
		File file = new File(folder);

		if(!file.exists()) {
			try {
				file.mkdir();
			} catch (Exception e) {
				System.err.println("이미 존재하는 폴더입니다.");
			}
		}
		// try 문안에서와 밖에서 사용해야하니 위에서 선언
		ModelAndView mv = new ModelAndView();
		MemberDAO dao = new MemberDAO();
		
		
		try {
			// MultipartRequest 가 null 일때 String folder 즉, 폴더지정을 잘했는지 확인
			MultipartRequest mr = new MultipartRequest(request, folder, 1024*1204*5,"utf-8",new DefaultFileRenamePolicy());
			member = new Member();
			member.setName(mr.getParameter("name"));
			member.setPost(mr.getParameter("post"));
			member.setAddress(mr.getParameter("address"));
			member.setDetailaddress(mr.getParameter("detailaddress"));
            // 이메일 주의
			member.setEmail(mr.getParameter("email1")+"@"+mr.getParameter("email2"));
			member.setTel(mr.getParameter("tel"));
			member.setId(mr.getParameter("id"));
			member.setPassword(mr.getParameter("password"));
			member.setState(mr.getParameter("state"));
			member.setSize(mr.getParameter("size"));
			
			File pro = mr.getFile("profile");
			// input 태그에 name 이 profile 인지 확인. 아니면 가져오는 것도 없어서 길이도 없으니 오류
			byte[] profile = new byte[(int) pro.length()];
			FileInputStream is = new FileInputStream(pro);
			is.read(profile);
			member.setProfile(profile);
			
		} catch (Exception e) {
			// profile 에 이미지 미등록시 오류가 나니 없어도 save 하도록 똑같이 코드작성
//			e.printStackTrace();
			mv.addObject("member", member);
			mv.setViewName("회원등록결과통보");
			dao.save(member);
			
			return mv;
		}

		dao.save(member);
		
		mv.addObject("member", member);
		mv.setViewName("회원등록결과통보");
		
		return mv;

  }
}

>> 회원등록결과통보.jsp

<body>
${member.name} 님 회원등록 감사합니다! <br />
<a href="index?no=-1">index</a>
</body>

>> 인덱스화면가기.java

public class 인덱스화면가기 implements IControl{

	public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
		ModelAndView mv = new ModelAndView();
		HttpSession session = request.getSession();
		
		int no = 0;
		if(!(session.getAttribute("no") == null)){
			no = Integer.valueOf((String)session.getAttribute("no"));
		}

		mv.addObject("no", no);
		mv.setViewName("index");
		return mv;
	}

}

>> index.jsp

<body>
	index 입니다.
	<br />
	<%if(request.getParameter("msg") != null){%>
	${msg}
	<% 	}%>
	<%if(login){%>
	<a href="info_member">회원정보보기</a>
	<% 	}%>
</body>

회원정보

>> 회원정보준비.java

	public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
		ModelAndView mv = new ModelAndView();
		HttpSession session = request.getSession();
		
		int no = (Integer)session.getAttribute("no");
		
		if(no>0) {
			MemberDAO dao = new MemberDAO();
			Member member = dao.findByNo(no);
			
			mv.addObject("member", member);
			mv.setViewName("회원정보보기");	
		}else {
			mv.addObject("msg", "로그인이 필요합니다.");
			mv.setViewName("index");
		}
		
		return mv;
	}

>> 회원정보보기.jsp

<body>
name: ${member.name} <br />
profile: <img src="profile?no=${member.no}"> <br />
post: ${member.post} <br />
address: ${member.address} <br />
detailaddress: ${member.detailaddress} <br />
tel: ${member.tel} <br />
email: ${member.email} <br />
id: ${member.id} <br />
password: ${member.password} <br />
rdate: ${member.rdate} <br />
state: ${member.state} <br /> 
size: ${member.size} <br />
</body>

>> 프로필보기.java

public class 프로필보기 implements IControl{

	public ModelAndView process(HttpServletRequest request, HttpServletResponse response) {
		int no = Integer.valueOf(request.getParameter("no"));
		
		ModelAndView mv = new ModelAndView();
		
		mv.addObject("no", no);
		mv.setViewName("profile");
		
		return mv;
	}

}

>> profile.jsp

<%@ page trimDirectiveWhitespaces="true" %>
<%
	// 바이너리로 응답 (html 응답) 모든 파일은 다 바이너리(byte)규격 이다.
	int no = Integer.valueOf(request.getParameter("no"));
	MemberDAO dao = new MemberDAO();
	Member member = dao.findByNo(no);
	
	response.setContentType("image/jpeg");
	ServletOutputStream os = response.getOutputStream();
	try{
		byte[] profile = member.getProfile();

		os.write(profile);
		
	}catch(Exception e){
		// 회원 이미지가 없어 null 예외 뜰 경우 images 폴더에 저장해둔 기본 유저이미지를 읽어 write한다.
		String path = request.getRealPath("images");
		File file = new File(path + "\\hoteIindexImg.png");
		byte[] image = new byte[(int)file.length()];	//일단 file 크기만큼의 byte 배열을 만든다.
		FileInputStream 입력스트림 = new FileInputStream(file);
		입력스트림.read(image);

		os.write(image);
	}
%>

우편번호 API (다음 무료 API)

https://postcode.map.daum.net/guide

원하는 폼에 예제파일보기 누르고 코드 그대로 복사해 넣으면 끝!

>> 조회창 예시

<h1>우편번호조회</h1>
<input type="text" id="sample4_postcode" placeholder="우편번호">
<input type="button" onclick="sample4_execDaumPostcode()" value="우편번호 찾기"><br>
<input type="text" id="sample4_roadAddress" placeholder="도로명주소">
<input type="text" id="sample4_jibunAddress" placeholder="지번주소">
<span id="guide" style="color:#999;display:none"></span>
<input type="text" id="sample4_detailAddress" placeholder="상세주소">
<input type="text" id="sample4_extraAddress" placeholder="참고항목">
<input type="button" onclick="주소값주기()" value="확인"/>

<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
    //본 예제에서는 도로명 주소 표기 방식에 대한 법령에 따라, 내려오는 데이터를 조합하여 올바른 주소를 구성하는 방법을 설명합니다.
    function sample4_execDaumPostcode() {
        new daum.Postcode({
            oncomplete: function(data) {
                // 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.

                // 도로명 주소의 노출 규칙에 따라 주소를 표시한다.
                // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
                var roadAddr = data.roadAddress; // 도로명 주소 변수
                var extraRoadAddr = ''; // 참고 항목 변수

                // 법정동명이 있을 경우 추가한다. (법정리는 제외)
                // 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
                if(data.bname !== '' && /[동|로|가]$/g.test(data.bname)){
                    extraRoadAddr += data.bname;
                }
                // 건물명이 있고, 공동주택일 경우 추가한다.
                if(data.buildingName !== '' && data.apartment === 'Y'){
                   extraRoadAddr += (extraRoadAddr !== '' ? ', ' + data.buildingName : data.buildingName);
                }
                // 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
                if(extraRoadAddr !== ''){
                    extraRoadAddr = ' (' + extraRoadAddr + ')';
                }

                // 우편번호와 주소 정보를 해당 필드에 넣는다.
                document.getElementById('sample4_postcode').value = data.zonecode;
                document.getElementById("sample4_roadAddress").value = roadAddr;
                document.getElementById("sample4_jibunAddress").value = data.jibunAddress;
                
                // 참고항목 문자열이 있을 경우 해당 필드에 넣는다.
                if(roadAddr !== ''){
                    document.getElementById("sample4_extraAddress").value = extraRoadAddr;
                } else {
                    document.getElementById("sample4_extraAddress").value = '';
                }

                var guideTextBox = document.getElementById("guide");
                // 사용자가 '선택 안함'을 클릭한 경우, 예상 주소라는 표시를 해준다.
                if(data.autoRoadAddress) {
                    var expRoadAddr = data.autoRoadAddress + extraRoadAddr;
                    guideTextBox.innerHTML = '(예상 도로명 주소 : ' + expRoadAddr + ')';
                    guideTextBox.style.display = 'block';

                } else if(data.autoJibunAddress) {
                    var expJibunAddr = data.autoJibunAddress;
                    guideTextBox.innerHTML = '(예상 지번 주소 : ' + expJibunAddr + ')';
                    guideTextBox.style.display = 'block';
                } else {
                    guideTextBox.innerHTML = '';
                    guideTextBox.style.display = 'none';
                }
            }
        }).open();
    }
    
    function 주소값주기(){
    	var 우편번호 = document.querySelector("#sample4_postcode").value;
    	var 도로명주소 = document.querySelector("#sample4_roadAddress").value + document.querySelector("#sample4_extraAddress").value;
    	var 지번주소 = document.querySelector("#sample4_jibunAddress").value;
    	var 상세주소 = document.querySelector("#sample4_detailAddress").value;
    	
    	opener.주소를받다(우편번호, 도로명주소, 지번주소, 상세주소);
    	close();
    }
</script>