저자 Ed Burns
Michael Jouravlev는 2004년 8월에 관련 업계에 큰 영향을 미쳤던 Redirect After Post라는 기사에서, 수많은 웹 애플리케이션에서 발생하는 문제를 설명했습니다. 그는 이 문제를 다음과 같이 설명했습니다.
모든 대화형 프로그램은 사용자 입력을 받고 결과를 표시하는 두 가지 기본적인 기능을 제공합니다. 웹 애플리케이션은 각각 POST와 GET이라는 두 가지 HTTP 메소드를 사용하여 이 동작을 구현합니다. 이 간단한 프로토콜은 애플리케이션이 POST 요청에 대한 응답으로 웹 페이지를 반환할 때 중단됩니다. 서로 다른 브라우저의 특성과 결합된 POST 메소드의 특성으로 인해 종종 사용자 경험의 질이 낮아지고 서버 애플리케이션의 상태가 잘못될 수 있습니다.
이 문제를 해결하기 위해, Jouravlev는 자신이 POST-REDIRECT-GET 또는 간단히 줄여 PRG 패턴이라 부른 기법을 설명했습니다. 이 패턴의 규칙은 다음과 같습니다.
- POST에 대한 응답으로 절대 페이지를 표시하지 않음
- 항상 GET을 사용하여 페이지를 로드함
- REDIRECT를 사용하여 POST에서 GET으로 탐색
JSF(JavaServer Faces) 기술의 이전 버전들은 페이지 탐색을 할 때마다 POST를 사용했기 때문에 위 규칙 중 첫 번째 규칙을 위반했습니다. JSF 사용 애플리케이션에서 한 페이지에서 다른 페이지로 탐색할 때, JSF 프레임워크가 서블릿 API의 RequestDispatcher.forward( ) 메소드를 통해 POST 요청을 전달했습니다. 이로 인해 포스트백 요청에 대한 응답으로 새 Faces 페이지가 렌더링되어 브라우저로 반환됩니다.
사실, Struts를 포함하여 가장 많이 사용하는 자바 서블릿 기반 웹 프레임워크는 이 접근 방법을 이용해 탐색합니다. HTTP 신봉자들은 당연히 이 접근 방법이 PRG 패턴의 첫 번째 규칙을 위반한다는 점을 지적합니다. JSF는 첫 번째 규칙을 어기지 않았을 뿐 아니라, JavaServer Faces 2.0까지는 다른 어떤 방법을 통해 이 규칙을 위반하기도 무척 어려웠습니다. JBoss의 Seam 팀에서 JSF 기술을 제공해준 덕분에, 지금은 JSF로 PRG 작업을 하기가 훨씬 쉬워졌습니다.
이 테크팁에서는 JSF 2.0에서 PRG 패턴을 구현하는 방법을 설명합니다. 이 팁의 내용은 곧 출간될 필자와 Neil Griffin의 공저인 JavaServer Faces 2.0: The Complete Reference의 PRG 및 JSF 2.0 섹션을 개작한 것입니다.
PRG가 아닌 예
사용자 등록을 처리하는 간단한 JSF 2.0 애플리케이션을 검사하는 것부터 시작해봅시다. 이 첫 번째 예에서는 애플리케이션이 PRG 패턴을 구현하지 않습니다. 이 애플리케이션의 첫 페이지는 다음과 같이 register.xhtml 파일에서 코딩됩니다.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>A Simple JavaServer Faces Registration Application</title>
</h:head>
<h:body>
<h:form>
<h2>JSF Registration App</h2>
<h4>Registration Form</h4>
<table>
<tr>
<td>First Name:</td>
<td>
<h:inputText label="First Name"
id="fname" value="#{userBean.firstName}"
required="true"/>
<h:message for="fname" />
</td>
</tr>
<tr>
<td>Last Name:</td>
<td>
<h:inputText label="Last Name"
id="lname" value="#{userBean.lastName}"
required="true"/>
<h:message for="lname" />
</td>
</tr>
... additional table rows not shown.
</table>
<p><h:commandButton value="Register" action="confirm" /></p>
</h:form>
</h:body>
</html>
|
이 페이지에는 사용자가 성과 이름을 입력하는 텍스트 필드가 있습니다. 등록 버튼도 표시됩니다. 사용자가 등록 버튼을 누르면 JSF 탐색 규칙 시스템에서 확장명이 현재 페이지와 동일하고 파일 이름이 confirm인 애플리케이션 내부의 페이지를 찾습니다. confirm.xhtml이 존재하는 경우 JSF는 그 파일에 있는 탐색 구성요소를 사용하여 다음 페이지를 탐색합니다. 다음은 confirm.xhtml 파일입니다.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>A Simple JavaServer Faces Registration Application</title>
</h:head>
<h:body>
<h:form>
<h2>JSF Registration App</h2>
<h4>Registration Confirmation</h4>
<table>
<tr>
<td>First Name:</td>
<td>
<h:outputText value="First Name" value="#{userBean.firstName}"
</td>
</tr>
<tr>
<td>Last Name:</td>
<td>
<h:outputText label="Last Name" value="#{userBean.lastName}"
</td>
</tr>
... additional table rows not shown.
</table>
<p><h:commandButton value="Edit" action="register" /></p>
<p><h:commandButton value="Confirm" action="#{userBean.addConfirmedUser}" /></p>
</h:form>
</h:body>
</html>
|
confirm.xhtml 파일에는 편집 버튼과 확인 버튼에 대한 마크업이 포함됩니다. 사용자가 편집 버튼을 클릭하면 register.xhtml 페이지로 다시 돌아갑니다. 사용자가 확인 버튼을 클릭하면 어떤 작업이 호출됩니다. 확인 버튼을 클릭하면 메소드의 논리에서 프로그램 방식으로 결과를 결정하는 addConfirmedUser( )라는 작업 메소드가 지정됩니다. 다음은 addConfirmedUser( ) 메소드가 들어 있는 UserBean.java 파일입니다.
package com.jsfcompref.model;
... imports
@ManagedBean
@SessionScoped
public class UserBean {
... properties and methods
public String addConfirmedUser() {
boolean added = true; // actual application may fail to add user
FacesMessage doneMessage = null;
String outcome = null;
if (added) {
doneMessage = new FacesMessage("Successfully added new user");
outcome = "done";
} else {
doneMessage = new FacesMessage("Failed to add new user");
outcome = "register";
}
FacesContext.getCurrentInstance().addMessage(null, doneMessage);
return outcome;
}
|
이 간단한 예에서 addConfirmedUser( )는 페이지에 Successfully added new user라는 메시지가 표시되는 원인이 되고 그 결과로 "done"을 반환합니다. addConfirmedUser( ) 메소드가 "done"을 결과로 반환하면 사용자에게는 done.xhtml 페이지가 표시됩니다. 이것은 JSF 2.0의 새로운 기능인 암시적 탐색의 예입니다. 적용되는 모든 규칙을 확인한 후 일치하는 탐색 케이스를 찾지 못한 경우 탐색 처리기가 작업 결과가 보기 id에 해당하는지 여부를 확인합니다. 작업 결과와 일치하는 보기를 찾은 경우 일치하는 보기에 대한 암시적 탐색이 이루어집니다. 여기서 결과는 "done"이고 일치하는 보기는 done.xhtml이므로, 사용자에게는 done.xhtml 페이지가 표시됩니다. 암시적 탐색을 통해 faces-config.xml 파일에 탐색 규칙을 추가하는 수고를 덜 수 있습니다.
다음은 done.xhtml 페이지입니다.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>A Simple JavaServer Faces Registration Application</title>
</h:head>
<h:body>
<h:form>
<h2>JSF Registration App</h2>
<h4>Registration Confirmation</h4>
<h:messages />
<table>
<tr>
<td>First Name:</td>
<td>
<h:outputText value="First Name" value="#{userBean.firstName}"
</td>
</tr>
</table>
</h:form>
</h:body>
</html>
|
매개 변수 보기를 사용하는 POST-REDIRECT-GET
매개 변수 보기는 간단하고 단정적으로 수신 요청 매개 변수 값을 보기 내부의 특수한 구성요소에 매핑하는 방법입니다. 이런 매핑은 보기의 새로운 <f:metadata> 섹션 내에서 새로운 <f:viewParam> 구성요소를 사용하여 지정됩니다. 다음 예를 생각해보십시오.
<f:metadata>
<f:viewParam name="foo" value="#{bean.foo}"/>
</f:metadata>
|
이 예에서는 이름이 "foo"인 요청 매개 변수의 값이 #{bean.foo}의 속성에 자동으로 할당되는 것을 보여줍니다. 따라서 GET 요청의 경우는 다음과 같습니다.
JSF가 요청을 처리하기 시작할 때 #{bean.foo} 속성의 값은 bar로 설정됩니다.
매개 변수 보기는 JBoss Seam에서 볼 수 있는 페이지 매개 변수 기능과 비슷하지만, JSF 2.0에서 구현된 이 기능은 핵심적인 JSF 사양과 긴밀히 통합되어 있으므로 더욱 쉽게 사용할 수 있고 더 강력합니다. 다른 간단한 예를 살펴봅시다.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>A Simple JavaServer Faces 2.0 View</title>
</h:head>
<h:body>
<h:form>
<p>First Name:< <h:inputText id="fname"
value="#{userBean.firstName}" /> </p>
<p><h:commandButton value="submit"
action="page02?faces-redirect=true&includeViewParams=true" /></p>
</h:form>
</h:body>
</html>
|
<h:commandButton> 요소에 action="page02?faces-redirect=true"가 있습니다. URL을 정의하는 인터넷 표준에서 ? 문자가 있다는 것은 그 URL의 나머지 부분이 & 또는 &로 구분되는 name=value 쌍의 목록이 될 것임을 가리키며, 이 목록은 URL에 대한 요청과 함께 서버로 전송되어야 합니다. 이것을 쿼리 문자열이라고 합니다. JSF는 여기서 ? 문자의 의미를 빌리는데, 그 의미는 인터넷 표준에서 URL과 정확히 같습니다. JSF가 서버 쪽에서 결과를 구문 분석할 때 인식하는 두 가지 특수한 쿼리 문자열 매개 변수가 있습니다. faces-redirect 쿼리 문자열은 탐색 시스템에 이 암시적 탐색 케이스를 마치 <redirect/> 요소를 포함한 실제 <navigation-case> 요소인 것처럼 취급해야 한다고 알려줍니다. 다른 특수 쿼리 문자열 매개 변수인 includeViewParams는 탐색 처리기에 탐색을 수행할 때 매개 변수 보기를 포함하라고 알려줍니다. 그러나 어떤 매개 변수 보기가 포함되어야 할까요? 탐색을 수행할 때 포함할 매개 변수 보기는 to-view-id 페이지에서 선언됩니다. 이 경우에는 암시적 탐색을 사용하고 있으므로, 아래에 표시된 것처럼 암시적 to-view-id는 page02.xhtml입니다.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:metadata>
<f:viewParam name="fname> value="#userBean.firstName}"/>
</f:metadata>
<h:head>
<title>A Simple JavaServer Faces 2.0 View</title>
</h:head>
<h:body>
<h:form>
<p> Hello #{userBean.firstName}.</p>
</h:form>
</h:body>
</html>
|
탐색 처리기가 매개 변수 보기를 포함시켜야 한다고 선언하는 해당 탐색 케이스(암시적 또는 명시적)를 발견하면 from-view-id 및 to-view-id 페이지의 매개 변수 보기를 살펴보고 일치 및 복사 알고리즘을 수행하여 매개 변수 보기를 새 페이지로 전달합니다. 이 경우에는 navigation-case가 리디렉션도 요청했습니다.
이제 등록 예를 살펴봅시다. 이번에는 매개 변수 보기로 PRG를 수행하도록 구현했습니다. register.xhtml 페이지는 다음과 같은 모습입니다.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:metadata>
<f:viewParam name="fname" value="#{userBean.firstName}" />
<f:viewParam name="lname" value="#{userBean.lastName}" />
<f:viewParam name="sex" value="#{userBean.sex}" />
<f:viewParam name="dob" value="#{userBean.dob}">
<f:convertDateTime pattern="MM-dd-yy" />
</f:viewParam>
<f:viewParam name="mail" value="#{userBean.email}" />
<f:viewParam name="sLevel" value="#{userBean.serviceLevel}" />
</f:metadata>
<h:head>
<title>A Simple JavaServer Faces Registration Application</title>
</h:head>
<h:body>
<h:form>
<h2>JSF Registration App</h2>
<h4>Registration Form</h4>
<table>
<tr>
<td>First Name:</td>
<td>
<h:inputText label="First Name"
id="fname" value="#{userBean.firstName}"
required="true"/>
<h:message for="fname" />
</td>
</tr>
... remaining table rows omitted, they are the same as the original
</table>
<!-- The query parameters on the action attribute cause JSF to do the
POST REDIRECT GET pattern -->
<p><h:commandButton value="Register"
action="confirm?faces-redirect=true&includeViewParams=true" /></p>
</h:form>
</h:body>
</html>
|
이전의 매개 변수 보기 예에서는 to-view-id 페이지에 나타난다고 말했습니다. 이 예는 사용자가 보는 첫 페이지이지만, 이 예에서도 그렇습니다. 이 특정 애플리케이션에서는 사용자가 register.xhtml 페이지와 confirm.xhtml 페이지 사이를 오갈 수 있습니다. 따라서 사용자가 confirm.xhtml 페이지에 있을 때 to-view-id는 register.xhtml 페이지이고 그 반대도 마찬가지입니다. 따라서 <f:viewParams>는 두 페이지에 모두 있습니다. to 페이지로 넘어가려는 from 페이지에 있는 모든 입력 구성요소에 대해 <f:viewParam>이 필요합니다. dob 속성의 <f:viewParam> 내에 있는 <f:convertDateTime>에도 주목하십시오. 탐색이 수행될 때 값을 넘기기 위해 변환기를 호출해야 하기 때문에 이것이 필요합니다. 입력 필드에 정의된 명시적 변환기가 있는 경우에는 그에 해당하는 <f:viewParam>에도 명시적 변환기가 하나 있어야 합니다. 마지막으로, 암시적 탐색에서 이제는 눈에 익은 추가 쿼리 매개 변수인 confirm?faces-redirect=true&includeViewParams=true를 볼 수 있습니다.
UserBean.java 파일의 변경 내용을 살펴봅시다.
package com.jsfcompref.model;
... omits imports
@ManagedBean
@RequestScoped
public class UserBean {
... additional properties omitted
public String addConfirmedUser() {
boolean added = true; // actual application may fail to add user
FacesMessage doneMessage = null;
String outcome = null;
if (added) {
doneMessage = new FacesMessage("Successfully added new user");
outcome = "done?faces-redirect=true&includeViewParams=true";
} else {
doneMessage = new FacesMessage("Failed to add new user");
outcome = "register?faces-redirect=true&includeViewParams=true";
}
FacesContext.getCurrentInstance().addMessage(null, doneMessage);
return outcome;
}
|
이 코드의 유일한 변경 사항은 Bean을 요청 범위에 포함시키고 쿼리 매개 변수를 암시적 탐색 문자열에 추가한 것입니다. 이 경우에는 includeViewParams=true 매개 변수가 추가되어 to-view-id page에서 어떤 매개 변수 보기를 선언하더라도 탐색에 포함됩니다.
다음은 confirm.xhtml 페이지입니다.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:metadata>
<f:viewParam name="fname" value="#{userBean.firstName}" />
<f:viewParam name="lname" value="#{userBean.lastName}" />
<f:viewParam name="sex" value="#{userBean.sex}" />
<f:viewParam name="dob" value="#{userBean.dob}">
<f:convertDateTime pattern="MM-dd-yy" />
</f:viewParam>
<f:viewParam name="mail" value="#{userBean.email}" />
<f:viewParam name="sLevel" value="#{userBean.serviceLevel}" />
</f:metadata>
<h:head>
<title>A Simple JavaServer Faces Registration Application</title>
</h:head>
<h:body>
<h:form>
<h2>JSF Registration App</h2>
<h4>Registration Confirmation</h4>
<table>
<tr>
<td>First Name:</td>
<td>
<h:outputText value="First Name" value="#{userBean.firstName}"
</td>
</tr>
... additional rows omitted, they are the same as the original.
</table>
<p><h:commandButton value="Edit"
action="register?faces-redirect=true&includeViewParams=true" /></p>
</h:form>
<h:form>
<h:inputHidden value="#{userBean.firstName}" />
<h:inputHidden value="#{userBean.lastName}"/>
<h:inputHidden value="#{userBean.sex}" />
<h:inputHidden value="#{userBean.dob}">
<f:convertDateTime pattern="MM-dd-yy" />
</h:inputHidden>
<h:inputHidden value="#{userBean.email}" />
<h:inputHidden value="#{userBean.serviceLevel}" />
<p><h:commandButton value="Confirm"
action="#{userBean.addConfirmedUser}" /></p>
</h:form>
</h:body>
</html>
|
register.xhtml 페이지에서와 같이, 페이지 맨 위에 <f:metadata> 섹션과 작업 문자열에 추가 쿼리 매개 변수가 필요합니다. 여기서 새로운 점은 추가적인 <h:form> 요소 및 <h:inputHidden> 요소와 확인 버튼이 이 새로운 형태로 바뀌었다는 사실입니다. 이것은 매개 변수 보기로서 이 페이지로 전달되는 값을 일반적인 형태의 전송 매개 변수로 다음 페이지로 넘겨야 하기 때문에 필요합니다. 하지만, register.xhtml 페이지에 있는 것처럼 일반적인 입력 필드는 없습니다. 따라서 값을 넘기기 위해 숨겨진 필드를 사용합니다. dob 필드에서 <f:convertDateTime>이 계속 필요하다는 점도 주의하십시오.
마지막으로, 다음은 done.xhtml 페이지입니다.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<f:metadata>
<f:viewParam name="fname" value="#{userBean.firstName}" />
<f:viewParam name="lname" value="#{userBean.lastName}" />
<f:viewParam name="sex" value="#{userBean.sex}" />
<f:viewParam name="dob" value="#{userBean.dob}">
<f:convertDateTime pattern="MM-dd-yy" />
</f:viewParam>
<f:viewParam name="email" value="#{userBean.email}" />
<f:viewParam name="sLevel" value="#{userBean.serviceLevel}" />
</f:metadata>
<h:head>
<title>A Simple JavaServer Faces Registration Application</title>
</h:head>
<h:body>
<h:form>
<h2>JSF Registration App</h2>
<h4>Registration Confirmation</h4>
<h:messages />
<table>
<tr>
<td>First Name:</td>
<td>
<h:outputText value="First Name" value="#{userBean.firstName}"
</td>
</tr>
... additional rows omitted
</table>
</h:form>
</h:body>
</html>
|
이 done.xhtml과 원본 파일 사이의 유일한 차이점은 지금은 눈에 익은 <f:metadata> 섹션입니다.
샘플 코드 실행
이 팁에는 PRG를 구현하는 샘플 애플리케이션이 제공됩니다. 다음 지침에서는 Maven 2 소프트웨어 프로젝트 관리 도구를 사용하여 샘플 애플리케이션을 작성한 다음 글래스피쉬 v3 프리뷰 애플리케이션 서버에 배포합니다.
- 글래스피쉬 v3 프리뷰 애플리케이션 서버의 최신 promoted build 또는 nightly build를 아직 다운로드하지 않았으면 다운로드하십시오.
- Maven 2를 아직 다운로드하지 않았으면 다운로드하십시오.
- 샘플 애플리케이션 패키지인 PostRedirectGet.zip을 다운로드합니다.
- 샘플 애플리케이션 패키지의 압축을 풉니다. 매개 변수 보기를 사용하는 PRG 애플리케이션의 코드가 들어 있는
prgViewParams 폴더가 보일 것입니다.
prgViewParams 디렉토리로 이동하고 다음 Maven 명령을 입력하여 PRG 애플리케이션을 위한 WAR 파일을 만듭니다.
prgViewParams 디렉토리에 새로 생성된 target 하위 디렉토리에 prgViewParams.war 파일이 보일 것입니다.
- 다음 명령을 입력하여 글래스피쉬 v3 프리뷰 애플리케이션 서버를 시작합니다.
<GFv3_inst>/bin/asadmin start-domain
|
여기서 <GFv3_inst>는 글래스피쉬 v3 프리뷰 애플리케이션 서버를 설치한 곳입니다.
- 샘플 애플리케이션을 배포합니다. 배포 방법 중 한 가지는
prgViewParams.war 파일을 <GFv3inst>/domains/domain1/autodeploy 디렉토리에 복사하는 것입니다.
- 브라우저를 열고 URL http://localhost:8080/prgViewParams에 액세스하여 애플리케이션을 실행합니다. 그림 1에 표시된 양식이 보일 것입니다.
그림 1. 등록 페이지 |
- 양식에 적당히 정보를 입력하고 등록 버튼을 클릭합니다. 그림 2에 표시된 것과 비슷한 페이지가 나타날 것입니다.
그림 2. 등록 페이지를 통한 등록 |
- 확인 버튼을 클릭합니다. 그림 3에 표시된 것과 비슷한 페이지가 나타날 것입니다.
그림 3. 확인 페이지 |
추가 자료
자세한 내용은 다음 자료를 참조하십시오.
저자 정보
Ed Burns는 썬 마이크로시스템즈의 엔지니어입니다. Ed는 1994년부터 NCSA Mosaic, Mozilla, Sun Java Plugin, Jakarta Tomcat 등 다양한 클라이언트 및 서버측 웹 기술 관련 업무를 해왔으며 최근에는 JavaServer Faces 관련 업무를 담당하고 있습니다. Ed는 현재 JavaServer Faces의 스펙 부문 공동 리더입니다. JavaServer Faces: The Complete Reference의 공동 저자이며, Secrets of the Rockstar Programmers의 저자입니다. 또한 곧 출간될 JavaServer Faces 2.0: The Complete Reference의 공동 저자이기도 합니다. Ed Burns의 블로그를 둘러보십시오.
---------------------------------------------------------------------------------------
원문 : Using CDI and Dependency Injection for Java in a JSF 2.0 Application
http://blogs.sun.com/enterprisetechtips ··· njection
댓글을 달아 주세요