강좌/Spring @MVC

005. FrontController 패턴 1/2 - 의역: 출입구 패턴, 분배기 패턴

여름나라겨울이야기 2013. 2. 27. 22:04
728x90

FrontController 왈 "나를 통하지 않고서는 그 누구도 웹서버 안 정보에 접근할 수 없다."


ExpertSpringMVCFrontController.zip



이번 강좌에서는 FrontController 패턴이 개발자의 삶을 어떻게 향상 시키는가를 살펴보도록 하겠습니다.  앞의 강좌까지 충분히 이해하시는 분들이라면 이제는 장황한 말 보다는 코드로 대화하는 것이 더 좋을 것 같군요.  새로운 지식은 기존 지식을 발판 삼아 더해갈 때 가장 쉽게 얻어짐으로 최대한 기존 소스를 활용/수정해서 작성하도록 하겠습니다.


기존 소스의 보존을 위해 새로운 DWP 를 만들었습니다.  첨부 파일명 처럼 ExpertSpringMVCFrontController 라는 이름으로.. 헥헥 길다.. ㅡㅡ;


이제 여러분 각자가 만들 구조를 일단 표로 보시죠(저는 이미 만들었다는 거.. ^^v)


 MVC Class / Interface

 Package / 경로

 설명

 M

 Member.java

 com.heaven.spring.model

 도메인 모델 정보를 소유하고 있으며 이전 강좌와 동일

 V

 Member.jsp

 WebContent

 사용자와 대화를 담당, 이건 강좌의 NeoMember.jsp 와 동일
 C  MemberController.java  com.heaven.spring.controller

 이전 강좌의 NeoMemberServlet.java 를 수정

 FrontController.java

 com.heaven.spring.controller

 HttpServlet 을 상속하여 doGet(...) 오버라이딩

 Controller.java  com.heaven.spring.controller

 interface 입니다. 이것을 통해 나중에 FrontController 와 MemberController 사이에 쉽게 IoC/DI 하실 수 있겠죠!


먼저 도메인 모델 역할을 담당하는 Member.java 입니다.  MVC 구조에 맞게 패키지를 조정해 주었습니다.

지난 강좌의 NeoMember.java 를 잘 C&P 하시면 됩니다.  패키지명과 클래스명만 리팩토링 해주시면 됩니다.

package com.heaven.spring.model;

public class Member {
	public static final boolean FEMALE = true;
	public static final boolean MALE = false;
	
	private String name;
	private boolean gender;
	private int age;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public boolean isGender() {
		return gender;
	}
	public void setGender(boolean gender) {
		this.gender = gender;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	public String toString() {
		String msg = null;
		
		if(gender == FEMALE) {
			switch(age / 10) {
				case 0: 	msg = "귀엽냐?"; break;
				case 1: 	msg = "이쁘냐?"; break;
				case 2: 	msg = "이쁘냐?"; break;
				case 3: 	msg = "이쁘냐?"; break;
				case 4: 	msg = "이쁘냐?"; break;
				case 5: 	msg = "이쁘냐?"; break;
				case 6: 	msg = "곱냐?"; break;
				default: 	msg = "곱냐?"; break;
			}
		} else {
			switch(age / 10) {
				case 0: 	msg = "귀엽냐?"; break;
				case 1: 	msg = "잘 생겼냐?"; break;
				case 2: 	msg = "잘 생겼냐?"; break;
				case 3: 	msg = "돈 잘 버냐?"; break;
				case 4: 	msg = "밤에.. 음..?"; break;
				case 5: 	msg = "밤에.. 음..?"; break;
				case 6: 	msg = "재산 많냐?"; break;
				default: 	msg = "재산 많냐?"; break;
			}			
		}
		return msg;
	}
}

이번에는 뷰의 역할을 담당할 Member.jsp 입니다.  역시 바로 전에 만든 NeoMember.jsp 를 C&P 하시고 약간만 수정해 주시면 됩니다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>나는 회원이다!!!</title>
</head>
<body>
<c:forEach var="member" items="${model}">
<b>${member.name}</b> ${member}<br/>
</c:forEach>
</body>
</html>

기존에 비해서 어디가 변했을 까요?  거의 변화를 못 느끼시겠죠?

<title> 태그에 좀 더 의미(?) 있는 내용을 넣어줬습니다.  그리고 정말 중요한 변화는 11 라인에 있습니다.  숙제입니다.  10초만 찾아보세요.  정답 보시는 법은 이제 아시죠?  아실거예요!!! ^^?

정답: items="${members}" 를 items="${model}" 로 변경했습니다.

좀 더 일관성이 있게 다수의 View 를 다루기 위한 선택이었구요.  Spring MVC 의 아이디어를 미리 차용해 왔습니다.


그럼 이제 정말 중요한 변화를 겪은 소스의 차례이군요.  바로 NeoMemberServlet.java 에서 MemberController.java 로 변경된 소스입니다.  얼마나 중대한 변화를 겪었냐 하면 HttpServlet 클래스를 상속하지 않는 코드가 되었습니다.  이건 무슨 의미가 되느냐 하면 웹 기술과 독립적인 존재가 되었다는 것입니다.  지난 강좌에서는 MVC 중에서 Model 만 플랫폼에 독립적인 존재였었는데 이제는 Controller 부분의 일부를 SoC 기준으로 분리했더니 MemberController.java 도 플랫폼에 독립적인 존재가 되었다는 것입니다(여기서 또 고수님들 태클이... 이 다음에 다음에!!! 시간되면 MemberService.java 로 바꾼다니깐요. 지금은 말고요 ㅡㅡ;).  이렇게 독립적인 존재가 되면 단위 테스트가 무척이나 쉬워지는 부수적인 효과를 가져옵니다.  중요한 변화라 주절 주절했네요.  소스 보겠습니다.

package com.heaven.spring.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.heaven.spring.model.Member;

public class MemberController implements Controller {
	public Map<string, object> handle() {
		Member juliet = new Member();
		juliet.setName("줄리엣");
		juliet.setGender(Member.FEMALE);
		juliet.setAge(18);
		
		Member romeo = new Member();
		romeo.setName("놈임요");
		romeo.setGender(Member.MALE);
		romeo.setAge(19);
		
		Member seoyoung = new Member();
		seoyoung.setName("이서영");
		seoyoung.setGender(Member.FEMALE);
		seoyoung.setAge(32);
		
		Member samjae = new Member();
		samjae.setName("이삼재");
		samjae.setGender(Member.MALE);
		samjae.setAge(62);
				
		// JSP 에 전달할 자료를 준비합니다.
		// 여기서는 Member 객체들을 List 에 담고 있습니다.
		List<member> members = new ArrayList<member>();
		
		members.add(juliet);
		members.add(romeo);
		members.add(seoyoung);
		members.add(samjae);

		Map<string, object=""> modelAndView = new HashMaplt;string, object="">();
		
		modelAndView.put("model", members); // List<member> 의 인스턴스를 전달
		modelAndView.put("view", "Member"); // View 를 선택하기 위한 문자열 전달
		
		return modelAndView;
	}
}

오웃 너무 많이 변화됬나요?  그렇다면 모니터를 2 분할 하셔서서 http://expert0226.tistory.com/entry/MVCModel2 를 띄운 후에 중간쯤 스크롤 하셔서 두 소스를 비교 분석하시면서 보시길.. 더 좋은 건 출력해서.. 그것도 칼라풀하게.. 제가 지난 시간에 얼마나 빨갱이, 초록이, 보라돌이, 파랭이, 노랭이 색칠하느라 힘들었다구요. ㅡㅜ


지금 비교해서 보시고 있는 거죠?  그럼 MemberController.java 에는 이전 코드의 빨갱이에 해당하는 코드만 남았다는 사실을.. 그리고 Map 객체를 사용해서 결과를 return 하는 부분이 달라졌음을 아실 수 있습니다.  그러나 진정 우리에게 필요한 건 뭐?  바로 바로 소스 비교툴이죠.  winMerge 를 사용해 봤습니다.

winMerge 다운로드: http://sourceforge.net/projects/winmerge/?source=dlp

winMerge 사용방법: 구골링, 네이흠 ^^;


STS 에서도 다른 2 개의 파일을 비교할 수 있다면 좋을텐데.. 가능한가요? 혹시 아시는 분?



딱 봐도 좋으다 좋으다.  양쪽이 같으면 흰색 배경, 같은 라인에 다른 소스가 있으면 노란 배경,  한쪽에만 있으면 회색 배경.. WinMerge 를 무료 오픈 소스로 제공해 주신 개발팀에 감사드립니다.  한글화까지.. 감동이야.. T^T


자자 그만 감동하고 비교! 비교!  좌측이 기존의 NeoMemberServlet.java 이고 오른쪽이 새로워진 MemberController.java 입니다.  제일 중요한 변화는 뭐라구요?  이제 MemberController 는 HttpServlet 의 서브 클래스가 아니라구요.  import 문자 깔끔해 진거 보소.  아래 아이들 다 어디갔어? - 황현희 버전


import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


자바는 다중 상속을 지원하지 않는데 MemberContoller 는 그 무엇도 슈퍼 클래스로 갖지 않으니. 내가 원하는 슈퍼 클래스가 있다면 그 슈퍼 클래스를 상속할 수 있게 되었어요.  doGet() 메소드도 오버라이딩할 필요가 없다구요.  개발자가 생각하기에 좀 더 의미있는 메소드명(handle 얼마나 이름이 좋은지)을 주면 된다구요. T^T  감격 안 되시나요?  그럼 계속 강좌를...



아래 쪽에 RequestDispatcher 관련 부분도 없어졌습니다.  일반적으로 많이 사용하는 Map 을 반환하게만 했습니다.


제가 이야기한데로 지난 강좌에 빨갱이 부분만, 위의 이미지 좌측에서 흰 배경인 부분만 우측 소스로 그대로 옮겨 왔습니다. ^^


음 그런데 HttpServlet 을 상속하지 않는 대신 Controller 인터페이스를 구현하네요.  뭘까요?  너무 깊이 고민하지 마세요.  제가 만들어졌요.   나 이뻐용?  뿌잉~ 뿌잉~ 뿌지직??? 헙!!!!


쿨락~  무슨 일 있었나요?  자자 이상했던 뿌~에 신경 끄시고 Contoller 인터페이스 소스 보시죠.

package com.heaven.spring.controller;

import java.util.Map;

public interface Controller {
	Map<string, object=""> handle();
}

큭 정말 별거 없네요.  이 별거 아닌 걸 제가 왜 만들었을까요?  바로 바로 FrontController 를 깔끔하게 만들고저 선경지명으로 만들었습니다(독백: ㅋㅋ 내가 2시간 동안 삽질한 사실은 이 글 읽으시는 분들은 전혀 모르시겠지.. ㅋㅋㅋ  마치 나는 한 코드 한 코드 실수도 없이 완벽한 것 처럼 위장할 수 있는 이 묘미...)


드디어 지난 강좌에는 없던 FrontController 입니다.

package com.heaven.spring.controller;

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

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FrontController extends HttpServlet {
	@Override
	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		Controller controller = null;
		Map<string, object=""> modelAndView = null;		
		String url = req.getRequestURI().toLowerCase();
		
		if(url.contains("member")) {
			// ToDo: 아악 DI 하고 싶어!!!
			controller = new MemberController();
		} else if(url.contains("item")) {
			// ToDo: 아악 DI 하고 싶어!!!
			controller = new ItemController();
		} else {
			// ToDo: 아악 DI 하고 싶어!!!
			controller = new NobodyController();
		}
		
		modelAndView = controller.handle();
		
		// View 에 준비한 자료를 HttpServletRequest 를 통해 저장해 둡니다.
		req.setAttribute("model", modelAndView.get("model"));
		
		// 화면 처리를 위한 View 를 선정합니다.
		String viewFile = "/" + (String)modelAndView.get("view") + ".jsp";
		
		// View 에게 화면 처리를 의뢰합니다.
		RequestDispatcher view = req.getRequestDispatcher(viewFile);
		view.forward(req, resp);
	}
}

음 위의 소스에서 사라졌던 import 문들이랑 RequestDispatcher 부분이 이곳으로 옮겨져 왔군요.  당연하겠죠.  FrontController 는 기존의 Sevlet 을 대신해 전체 요청을 제어를 해 줄 진정한 빅브라더이니까요.  FrontController 를 자세히 분석해 보시기 바랍니다.  그러면 아래 같은 역활을 하고 있다는 것을 아실 수 있을 겁니다.


1. 사용자(브라우저)의 요청에 따라 그 요청의 세부 사항을 처리할,  Controller 인터페이스를 구현한 객체를 생성한다.

2. 위 1 에서 생성된 객체의 handle() 메서도를 호출해서 modelAndView 맵 객체를 반환 받는다.

3. 반환 받은 modelAndView 맵 객체를 이용해 뷰 이름을 획득한다.

4. 위 3 에서 얻은 뷰 이름을 이용해 RequestDispatcher 에게 사용자 화면 생성을 의뢰한다.


음 이게 몇 줄 째인가요? 오늘 강의 길다.. 힘드시죠.  쉬었다 할까요?  잠시 커피 한 잔의 여유를...


5분 뒤에 씁니다(믿거나? 말거나!)


자 그럼 이제 우리의 마수를 펴칠 또 하나의 파일이 있었으니 뭘까요?  두둥 DD(Deploy Descriptor : 배포 서술자) 인 web.xml 입니다.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns="http://java.sun.com/xml/ns/javaee" 
	xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
		http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
	id="WebApp_ID" version="2.5">
	<servlet>
		<servlet-name>FrontController</servlet-name>
		<servlet-class>com.heaven.spring.controller.FrontController</servlet-class>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>FrontController</servlet-name>
		<url-pattern>/controller/*</url-pattern>
	</servlet-mapping>
</web-app>

설명이 필요한가요?  설명이 필요하시다면 이 강좌 카테고리 첫번째 강좌부터 다시 복습하시고....

설정 내용을 보니


http://웹서버:웹서버포트/프로젝트명/controller/


이하 호출되는 모든 요청을 FrontController 클래스로 보내주는 군요.


그럼 실행 전에 모두 모두 저장하시고 아래 사항들을 정검하셔야죠.


아래 전체 개발 과정에서 한번만 해주시면 됩니다.

- 프로젝트 이름 오른쪽 버튼 Build Path > Configure Build Path... 선택하시고
   Soruce 탭에서 Default output folder 를 다음과 같이 설정했는지 확인 하셨나요?

         프로젝트명/WebContent/WEB-INF/classes

   올바른 예는 아래와 같습니다.



- jstl.jar 와 standard.jar 를 WebContent/WEB-INF/lib 에 추가하셨나요?



java 확장자 소스가 변경 되거나 web.xml 이 변경되었을 때 마다 위 해주셔야 합니다.

- STS 상단 메뉴에서 Project > Build Project 하셨나요?

  매번 Build 하기 귀찮으시면 저 처럼 Project > Build Automatically 를 체크해 두시면 되겠죠.





자 그럼 이제 드디어 프로젝트명 오른쪽 버튼 Run as > Run on Server...

그리고 브라우저를 열어 예상하시는 바로 그 주소를 입력합니다.


http://웹서버:웹서버포트/프로젝트명/controller/Member



잘 나오시나요?  에러가 쏟아지실 거예요.  그게 정상이예요.. 제가 첨부해 드린 소스랑 비교하시면서, 면밀히 검토해 보시고,  또 설정은 잘 하셨나 다시 한번 정검해 보세요.  다들 그러면서 크는 거예요.


이 강좌 준비하면서 여기까지 와서 브라우저로 띄우는데만 두 시간.. ㅡㅜ

코드 리팩토링 하는데만 또 두 시간... 저 오늘 회사 안 갔...

그리고 강좌 올리는데 세 시간.. 회사 안 갔다고...

앗.. 오늘은 회사간다고.. 강좌 쓰다가 날짜가 변경.. ㅡㅜ


잉.. 많이 쓴 것 같은데 미리 보기 하니 이미지들을 제외하면 스크롤이 얼마 안 되네요?

회사 안 간.. 김에 FrontController 마저 끝내고 한 동안 강좌를 좀 멀리해야겠네요.

몰래 공부하고 오겠다는 뜻은 아닐 겁.... 저도 초보라니깐요.. ㅡㅜ

제 게임 닉이 뭐라고요?  초보람보.. 람보에 집중하지 마시고 초보에 집중하시라구요.

강좌하면서 오히려 제가 제일 많이 배우고 있습니다.

토비님 책을 제일 많이 배우신 분도 아마 토비님 일 겁니다. 쿨락...


그럼 FrontContoller 패턴이라 했으니 이제 마구 잡이로 몇 가지 파일을 추가해 보겠습니다.  위 쪽에 패키지 익스플로러(한영키가 이제 귀찮은...)에 보이는 파일들 입니다.  궁금하셨죠?  궁금하셨다고 해주세요.  제발요! 플맀쯔!  야메때9다42????? 야메 강좌가 설마 그 야메? ^^?


MVC 모델 M 하나 추가용.

package com.heaven.spring.model;

public class Item {
	private String name;
	private String brand;
	private double price;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}	
}

MVC 모델이니 V 도 추가용. 아 MVC 각각의 요소가 꼭 1 vs 1 vs 1 은 아닙니다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>나는 제품이다!!!</title>
</head>
<body>
<c:forEach var="item" items="${model}">
제품명: <b>${item.name}</b> 제조사: ${item.brand} 가격: ${item.price}<br/>
</c:forEach>
</body>
</html>

MVC 모델의 C 도 추가용

package com.heaven.spring.controller; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.heaven.spring.model.Item; public class ItemController implements Controller { @Override public Map<string, object> handle() { Item myPhone = new Item(); myPhone.setName("나의 아이뻐"); myPhone.setBrand("사과"); myPhone.setPrice(400000.0); Item myCar = new Item(); myCar.setName("나의 리오"); myCar.setBrand("배고파"); myCar.setPrice(30000000.0); Item myTablet = new Item(); myTablet.setName("나의 패드"); myTablet.setBrand("파인애플"); myTablet.setPrice(8000000.0); // JSP 에 전달할 자료를 준비합니다. // 여기서는 Item 객체들을 List 에 담고 있습니다. List<item> items = new ArrayList<item>(); items.add(myPhone); items.add(myCar); items.add(myTablet); Map<string, object> modelAndView = new HashMap<string, object>(); modelAndView.put("model", items); // List<item> 의 인스턴스를 전달 modelAndView.put("view", "Item"); // View 를 선택하기 위한 문자열 전달 return modelAndView; } }

다 저장하시고 build project 그리고 run on server, 브라우저에 /controller/Item 으로 주소 변경 확인

web.xml 이요?  FrontController 이 모든 요청을 다 처리하도록 설정해 줬잖아요!!! 
FrontController 요?  코드 보세요.  제가 이미 Item, Nobody 주소 처리하도록 만들어 두었습니다.

저 선견지명 있다니깐요.  믿어봐요.. 좀...



흠 그리고 NobodyController.java 가 보이고, Nobody.jsp 가 보이네요.  딱 봐도 뭐가 MVC 의 View 이고 Controller 인지 감이 오시죠.  Model 이요.  이 경우에는 필요없다고 판단 안 만들었습니다.



오우 오우 Model 없어도... 앙?? 그런데 주소가.. 주소가.. 무서워요... 왠 "쇠도끼".. 저거 Nobody 되야 하는 거 아님...이라고 생각하셨다면... FrontController.java 소스 면밀히 다시 봐주세요.

면밀히 다시 보셨어요.  그럼 이번에는 주소창에 ItemNo1 이라고 넣어봐 주세요.  기대하는 결과가 나왔나요?



오오 이렇게 접기 하는 거구나.. 처음 해봤어요. ㅡㅡ;


Nobody 관련 VC 는 각자 구현하는게 과제이구요.  과제는 메일로 제출해 주세요.  과제 제출하시면 제가 검토해 드리는데.. 단, 첨부 파일에 주변에서 가장 이쁜 츠자 브로마이드를 같이...


자.. 결론 좀 내어보고 끝내겠습니다.


FrontController 패턴이 들어옴으로 인해 Web MVC 무엇이 좋아졌나요?



경고 - 생각 안해 보시고 저의 의견을 펼치시면 상처를 받으실 수도 있습니다.


그리고 말이죠... MVC 구현인데.. M 은 POJO 중에서도 도메인 모델 그것도 아주 경량의 도메인 모델...  C 는... V 는.. 흠 JUnit 으로 테스트 코드 구현은??? 흠흠.. 그건 숙제예요.  다음 강좌 보시기 전까지 단위 테스트 코드를 추가해서 제출해주세요.


응?  아까 과제 이미 냈다구요?  그건 과제고 이건 숙제잖아요. 퍼~~억.. 왜 때려.. ㅡㅜ

나 좋차고 이러나... (솔직히 제가 젤루 좋긴해요.  Spring MVC 가 이제야 머리 속에서 정리되는 느낌이랄까!!!)

그래서 여러분 좋차고 하는 거니까... 이제 레포트를 내 드립니... 텨... 후다닥 =3 =3 ===3


아 제가 튀면서 하는 소리 들어보세요. 아래는 인용 부호는 없지만 튀면서 하는 소리입니다.


혹시라도 먼저 책 보신 분들은 이 허접 FrontController 를 조금 더 멋지게 구현해 보고 싶지 않으세요.  위에 보니 나름 중복도 아직 보이네요.. 토비님 스타일로 끝장을 보고 싶으신 분들 있나요?  그러니까


- 리플렉션을 이용하면 FC(프론트 컨트롤러) 의 doGet() 메소드 안에 if 문을 없앨 수 있을 것 같습니다.

- 각 콘트롤러 마다 중복 되는 ArrayList, Map 부분을 SoC(사회 간접 자본??? 아니죠 ^^;) 할 수 있을 것 같다.

- Exception 처리를 해주면 좋겠다..


이런 증상을 겪는 분들 정말 정말 한국 개발자의 미래를 이끌어 가실 분들입니다.


그럼 분들에게서는 아래 링크를 통해 TED 동영상을 꼭 감상하시길 부탁드립니다.  10분 짜리 이니 부담도 없습니다.


http://www.ted.com/talks/lang/ko/thomas_thwaites_how_i_built_a_toaster_from_scratch.html


다 보시고 난 후 아래 접힌 부분을 펴서 읽어주세요.



음 강좌가 이전 Spring 3.0 강좌랑 다르죠..  제가 너무 달라져서 기대에 못 미치면 댓글로 지도편달해 주시면 겸허히 고민해보고 수용할 부분만 수용하도록 하는 걸로..  결론 댓글 내놔요.


제가 믿는 걸 여러분도 믿으셨으면 좋겠네요.  저는 성경의 여러 구절 중에서도 딱 두 구절을 온전히 믿습니다.

창세기 1장 1절 "태초에 하나님이 천지를 창조하시니라." - 요한계시록 마지막 장 마지막 절 "아멘"

할렐루야

반응형