Petstore
源码记纵记
(3)
-商业逻辑处理
(
下
)
( 欧宣修 )
图文并茂版请参考
http://www.javatwo.net/JavaPaper/Petstore-3_business_logic.pdf
接续上期 ...
我们已了解 SignOnFilter 在 Web tier 处理登入工作的步骤,它需要透过 EJB tier 从数据库读取资料进行比对,所以接下来探讨在 EJB tier 的运作情形,从图 14 、 15 可找出实际对应的 EJB ,从图上面可知此 EJB 的属性是 Local Stateless
Session Bean ,这很少见,大部份的书介绍到 Local Bean 的用法都用在 Entity Bean ,由此可知 Local Bean 的用法亦可用在 Session Bean 。
SignOnEJB ,源码在 Petstore_home/src/components/signon/src/com/sun/j2ee/blueprints/signon/ejb/SignOnEJB.java ,请读者顺便加上侦察程序代码:
public class SignOnEJB implements SessionBean {
private static final String USER_HOME_ENV_NAME =
"java:comp/env/ejb/local/User";
private InitialContext ic = null;
private UserLocalHome ulh = null;
public void ejbCreate() throws CreateException {
try {
ic = new InitialContext();
// 取得 UserLocalHome Reference ,它是代表使用者基本资料的 Local Entity Bean
ulh = (UserLocalHome) ic.lookup(USER_HOME_ENV_NAME);
} catch (NamingException ne) {
throw new EJBException("SignOnEJB Got naming exception! " +
ne.getMessage());
}
}
/**
* 此函数由 SignOnFilter 呼叫,依使用者帐号找出对应的 User 实体
*(instance) ,然后呼叫 User 实体的密码比对函数- user.matchPassword()
* business method used to check if a user is allowed to sign on
*/
public boolean authenticate(String userName, String password) {
// 请加入侦察程序代码,方便稍候程序验证
System.out.println("SignOnEJB 执行 authenticate() 进行使用者验证
userName="+userName+", password="+password);
try {
UserLocal user = ulh.findByPrimaryKey(userName);
return user.matchPassword(password);
} catch (FinderException fe) {
return false; // User not found, so authentication failed.
}
}
以下略 ...
图 16 SignOnEJB 的 EJB Reference
UserEJB ,源码在
Petstore_home/src/components/signon/src/com/sun/j2ee/blueprints/signon/user/ejb/
UserEJB.java ,它是 Local Entity Bean ,对应数据库中实际资料表-
UserEJBTable 在约 88 列可看到 SignOnEJB 所呼叫的函数,请读者顺便加
上侦察程序代码:
public boolean matchPassword(String password) {
// 请加入侦察程序代码,方便稍候程序验证
System.out.println("UserEJB 执行 matchPassword() 进行密码比对 ");
return password.equals(getPassword());
}
图 17 UserEJB 为 Entity Bean
点选图 17 右下角 ”Deployment Settings” 钮,可见到图 18 画面,
选择左下角 ”Method Implementation Queries” 之 ”Container Methods”
之 ”createRow” ,即可看到图 18 之 SQL Query :
图 18 UserEJB 对应资料表为 UserEJBTable
由上述所列程序代码及图,读者应该可以了解使用者登入在 EJBztier 的运作情形,在 UserEJB 也看到一个不一样的用法,就是在 Entity Bean 上使用商业逻辑- matchPassword() 函数,在大部份的 EJB 书籍或文件都告诉我们商业逻辑应该用 Session Bean 去实作,资料则用 Entity Bean 来实作,但在这里密码比较的商业逻辑是非常简单的,只用了一行程序就解决了,所以也不需为了它另外再做一个 Session Bean ,
也许读者会问为什么不将此函数写在 SignOnEJB ?笔者认为密码比对不只在登入流程上会使用,在使用者基本资料编辑时也可能会用到,所以还是放在 UserEJB 比较适合: ) 我们能够知道使用者基本资料是存于 UserEJBTable ,我们要如何知道此资料表所存内容是什么?请参阅注 4 。
现在请重复第一阶段验证步骤将程序重新编辑及部署,可发现程序如我们所预期般执行!
图 19 第二阶段程序验证结果
第三阶段
大家还记得在 SignOnFilter 之 validateSignOn() 函数,验证成功会将
SIGNED_ON_USER 变量 ( 对应实际变量为 j_signon) 的值设为真值 (true) :
hreq.getSession().setAttribute(SIGNED_ON_USER, new Boolean(true));
当我们再次从首页进入使用者基本资料浏览页,会再度给 SignOnFilter 拦截,从 Session 取出 SIGNED_ON_USER 变量 ( 对应实际变量为 j_signon) ,经判断为真值 (true) , SignOnFilter 则会放行转导至使用者基本资料浏览画面 (customer.do) ,读者可参考下列程序代码加入侦察码,在约 125 列 doFilter() 函数:
boolean signedOn = false;
if (hreq.getSession().getAttribute(SIGNED_ON_USER) != null) {
signedOn
=((Boolean)hreq.getSession().getAttribute(SIGNED_ON_USER)).booleanValue();
// 加入侦察码
System.out.println("signedOn="+signedOn);
} else {
hreq.getSession().setAttribute(SIGNED_ON_USER, new Boolean(false));
}
// jump to the resource if signed on
// 若已登入过,则结束此 Filter 工作,进入 Filter chain ,以本例来说,它为
Filter chain 中最后一个 Filter ,所以就是不做任何事,让使用者进入他的目的画面
if (signedOn) {
// 加入侦察码
System.out.println(" 使用者已登入过! ");
chain.doFilter(request,response);
return;
}
参考前面叙述重新编译部署后执行,可得下图预期结果:
图 20 第三阶段程序验证结果
customer.do
到这里相信读者能对 Petstore 登入流程控管有更深入的了解,通过登入流程就到达了使用者基本数据浏览画面 (customer.do) , *.do 与前二篇介绍的 *.screen 不同, *.screen 代表的是一个画面,如 main.screen 代表首页,它可由多个 .jsp 所组成; *.do 代表的是一个动作, customer.do 代表对使用者基本资料相关动作,如新增、修改、删除,它会透过 EJB tier 与资料库互动,最后得到的结果也是要展现,运用 *.screen 的机制组成结果画面,所以我们可以这样想象 *.screen 是名词, *.do 是动词,以本例来说,只是读取资料,虽然运用到 *.do ,但并没有任何动作,为了能让读者了解整个架构,还是在此稍事说明。
请开 ?deploytool ,点选左边窗格 Files > Applications > PetstoreEAR > PetstoreWAR > MainServlet ,选择右边 Alias 页,可发现处理 *.do 即是 MainServlet
图 21 *.do 对应 MainServlet
点选 General 页可找到实际对应类别,源码在
Petstore_home/src/waf/src/controller/com/sun/j2ee/blueprints/waf/controller/web/MainServlet.java
,在约 79 列可找到初始函数:
public void init(ServletConfig config) throws ServletException {
// 读取预设语系,值为 ”en_US”
String defaultLocaleString = config.getInitParameter("default_locale");
defaultLocale = I18nUtil.getLocaleFromString(defaultLocaleString);
this.context = config.getServletContext();
String requestMappingsURL = null;
try {
// 读取 mapping.xml
requestMappingsURL =
context.getResource("/WEB-INF/mappings.xml").toString();
} catch (java.net.MalformedURLException ex) {
System.err.println("MainServlet: initializing ScreenFlowManager
malformed URL exception: " + ex);
}
// 将 mappings.xml 转成 HashMap 类别并存入 ServletContext
urlMappings = URLMappingsXmlDAO.loadRequestMappings(requestMappingsURL);
context.setAttribute(WebKeys.URL_MAPPINGS, urlMappings);
eventMappings = URLMappingsXmlDAO.loadEventMappings(requestMappingsURL);
context.setAttribute(WebKeys.EVENT_MAPPINGS, eventMappings);
//ScreenFlowManager 初始化并存入 ServletContext
getScreenFlowManager();
//RequestProcessor 初始化并存入 ServletContext
getRequestProcessor();
}
图 22 MainServlet 实际对应类别
MainServlet 在初始化时会读取预设语系及对应设定档 (mapping.xml) ,及初始化 ScreenFlowManager 、 RequestProcessor ,预设语系设定在 MainServlet 之 init. Parameter 页,值为 ”en_US”( 英语系 ) 。
图 23 MainServlet 之 init. Parameter 设定
对应设定文件位置在
Petstore_home/src/apps/petstore/src/docroot/WEB-INF/mappings.xml
,我们可在约 95 行找到 customer.do 的相关设定
<url-mapping url="customer.do" screen="customer.screen" >
<web-action-class>com.sun.j2ee.blueprints.petstore.controller.web.actions.CustomerHTMLAction</web-action-class>
</url-mapping>
这一段卷标 (tag) 有三个参数:
url: 标示对应动作
web-action-class: 实际执行类别
screen: 结果显示画面
*.do 的运作机制是 Servlet 接收到 request ,从 mappings.xml 找到对应的 url ,
RequestProcessor 将对应的 web-action-class 初始化并执行, ScreenFlowManager
将执行结果传回给 *.screen ,再运用本系列前两篇所介绍 *.screen 机制组成实际画面,响应给使用者,大致的流程是这样,实际上更复杂,因牵涉到 Web tier 与 EJB tier 间沟通,但以本例来说, RequestProcessor 对应 web-action-class(CustomerHTMLAction) 并没有做什么事,后续 RequestProcessor 接收到结果显示画面 (customer.screen) ,则进行转导 (forward) 动作。
mappings.xml 读取后转成 HashMap 类别存入 ServletContext 供所有进入
Petstore 系统的人使用,接着将 ScreenFlowManager 及 RequestProcessor 初始
化并存入 ServletContext 。
( 欧宣修 )
图文并茂版请参考
http://www.javatwo.net/JavaPaper/Petstore-3_business_logic.pdf
接续上期 ...
我们已了解 SignOnFilter 在 Web tier 处理登入工作的步骤,它需要透过 EJB tier 从数据库读取资料进行比对,所以接下来探讨在 EJB tier 的运作情形,从图 14 、 15 可找出实际对应的 EJB ,从图上面可知此 EJB 的属性是 Local Stateless
Session Bean ,这很少见,大部份的书介绍到 Local Bean 的用法都用在 Entity Bean ,由此可知 Local Bean 的用法亦可用在 Session Bean 。
SignOnEJB ,源码在 Petstore_home/src/components/signon/src/com/sun/j2ee/blueprints/signon/ejb/SignOnEJB.java ,请读者顺便加上侦察程序代码:
public class SignOnEJB implements SessionBean {
private static final String USER_HOME_ENV_NAME =
"java:comp/env/ejb/local/User";
private InitialContext ic = null;
private UserLocalHome ulh = null;
public void ejbCreate() throws CreateException {
try {
ic = new InitialContext();
// 取得 UserLocalHome Reference ,它是代表使用者基本资料的 Local Entity Bean
ulh = (UserLocalHome) ic.lookup(USER_HOME_ENV_NAME);
} catch (NamingException ne) {
throw new EJBException("SignOnEJB Got naming exception! " +
ne.getMessage());
}
}
/**
* 此函数由 SignOnFilter 呼叫,依使用者帐号找出对应的 User 实体
*(instance) ,然后呼叫 User 实体的密码比对函数- user.matchPassword()
* business method used to check if a user is allowed to sign on
*/
public boolean authenticate(String userName, String password) {
// 请加入侦察程序代码,方便稍候程序验证
System.out.println("SignOnEJB 执行 authenticate() 进行使用者验证
userName="+userName+", password="+password);
try {
UserLocal user = ulh.findByPrimaryKey(userName);
return user.matchPassword(password);
} catch (FinderException fe) {
return false; // User not found, so authentication failed.
}
}
以下略 ...
图 16 SignOnEJB 的 EJB Reference
UserEJB ,源码在
Petstore_home/src/components/signon/src/com/sun/j2ee/blueprints/signon/user/ejb/
UserEJB.java ,它是 Local Entity Bean ,对应数据库中实际资料表-
UserEJBTable 在约 88 列可看到 SignOnEJB 所呼叫的函数,请读者顺便加
上侦察程序代码:
public boolean matchPassword(String password) {
// 请加入侦察程序代码,方便稍候程序验证
System.out.println("UserEJB 执行 matchPassword() 进行密码比对 ");
return password.equals(getPassword());
}
图 17 UserEJB 为 Entity Bean
点选图 17 右下角 ”Deployment Settings” 钮,可见到图 18 画面,
选择左下角 ”Method Implementation Queries” 之 ”Container Methods”
之 ”createRow” ,即可看到图 18 之 SQL Query :
图 18 UserEJB 对应资料表为 UserEJBTable
由上述所列程序代码及图,读者应该可以了解使用者登入在 EJBztier 的运作情形,在 UserEJB 也看到一个不一样的用法,就是在 Entity Bean 上使用商业逻辑- matchPassword() 函数,在大部份的 EJB 书籍或文件都告诉我们商业逻辑应该用 Session Bean 去实作,资料则用 Entity Bean 来实作,但在这里密码比较的商业逻辑是非常简单的,只用了一行程序就解决了,所以也不需为了它另外再做一个 Session Bean ,
也许读者会问为什么不将此函数写在 SignOnEJB ?笔者认为密码比对不只在登入流程上会使用,在使用者基本资料编辑时也可能会用到,所以还是放在 UserEJB 比较适合: ) 我们能够知道使用者基本资料是存于 UserEJBTable ,我们要如何知道此资料表所存内容是什么?请参阅注 4 。
现在请重复第一阶段验证步骤将程序重新编辑及部署,可发现程序如我们所预期般执行!
图 19 第二阶段程序验证结果
第三阶段
大家还记得在 SignOnFilter 之 validateSignOn() 函数,验证成功会将
SIGNED_ON_USER 变量 ( 对应实际变量为 j_signon) 的值设为真值 (true) :
hreq.getSession().setAttribute(SIGNED_ON_USER, new Boolean(true));
当我们再次从首页进入使用者基本资料浏览页,会再度给 SignOnFilter 拦截,从 Session 取出 SIGNED_ON_USER 变量 ( 对应实际变量为 j_signon) ,经判断为真值 (true) , SignOnFilter 则会放行转导至使用者基本资料浏览画面 (customer.do) ,读者可参考下列程序代码加入侦察码,在约 125 列 doFilter() 函数:
boolean signedOn = false;
if (hreq.getSession().getAttribute(SIGNED_ON_USER) != null) {
signedOn
=((Boolean)hreq.getSession().getAttribute(SIGNED_ON_USER)).booleanValue();
// 加入侦察码
System.out.println("signedOn="+signedOn);
} else {
hreq.getSession().setAttribute(SIGNED_ON_USER, new Boolean(false));
}
// jump to the resource if signed on
// 若已登入过,则结束此 Filter 工作,进入 Filter chain ,以本例来说,它为
Filter chain 中最后一个 Filter ,所以就是不做任何事,让使用者进入他的目的画面
if (signedOn) {
// 加入侦察码
System.out.println(" 使用者已登入过! ");
chain.doFilter(request,response);
return;
}
参考前面叙述重新编译部署后执行,可得下图预期结果:
图 20 第三阶段程序验证结果
customer.do
到这里相信读者能对 Petstore 登入流程控管有更深入的了解,通过登入流程就到达了使用者基本数据浏览画面 (customer.do) , *.do 与前二篇介绍的 *.screen 不同, *.screen 代表的是一个画面,如 main.screen 代表首页,它可由多个 .jsp 所组成; *.do 代表的是一个动作, customer.do 代表对使用者基本资料相关动作,如新增、修改、删除,它会透过 EJB tier 与资料库互动,最后得到的结果也是要展现,运用 *.screen 的机制组成结果画面,所以我们可以这样想象 *.screen 是名词, *.do 是动词,以本例来说,只是读取资料,虽然运用到 *.do ,但并没有任何动作,为了能让读者了解整个架构,还是在此稍事说明。
请开 ?deploytool ,点选左边窗格 Files > Applications > PetstoreEAR > PetstoreWAR > MainServlet ,选择右边 Alias 页,可发现处理 *.do 即是 MainServlet
图 21 *.do 对应 MainServlet
点选 General 页可找到实际对应类别,源码在
Petstore_home/src/waf/src/controller/com/sun/j2ee/blueprints/waf/controller/web/MainServlet.java
,在约 79 列可找到初始函数:
public void init(ServletConfig config) throws ServletException {
// 读取预设语系,值为 ”en_US”
String defaultLocaleString = config.getInitParameter("default_locale");
defaultLocale = I18nUtil.getLocaleFromString(defaultLocaleString);
this.context = config.getServletContext();
String requestMappingsURL = null;
try {
// 读取 mapping.xml
requestMappingsURL =
context.getResource("/WEB-INF/mappings.xml").toString();
} catch (java.net.MalformedURLException ex) {
System.err.println("MainServlet: initializing ScreenFlowManager
malformed URL exception: " + ex);
}
// 将 mappings.xml 转成 HashMap 类别并存入 ServletContext
urlMappings = URLMappingsXmlDAO.loadRequestMappings(requestMappingsURL);
context.setAttribute(WebKeys.URL_MAPPINGS, urlMappings);
eventMappings = URLMappingsXmlDAO.loadEventMappings(requestMappingsURL);
context.setAttribute(WebKeys.EVENT_MAPPINGS, eventMappings);
//ScreenFlowManager 初始化并存入 ServletContext
getScreenFlowManager();
//RequestProcessor 初始化并存入 ServletContext
getRequestProcessor();
}
图 22 MainServlet 实际对应类别
MainServlet 在初始化时会读取预设语系及对应设定档 (mapping.xml) ,及初始化 ScreenFlowManager 、 RequestProcessor ,预设语系设定在 MainServlet 之 init. Parameter 页,值为 ”en_US”( 英语系 ) 。
图 23 MainServlet 之 init. Parameter 设定
对应设定文件位置在
Petstore_home/src/apps/petstore/src/docroot/WEB-INF/mappings.xml
,我们可在约 95 行找到 customer.do 的相关设定
<url-mapping url="customer.do" screen="customer.screen" >
<web-action-class>com.sun.j2ee.blueprints.petstore.controller.web.actions.CustomerHTMLAction</web-action-class>
</url-mapping>
这一段卷标 (tag) 有三个参数:
url: 标示对应动作
web-action-class: 实际执行类别
screen: 结果显示画面
*.do 的运作机制是 Servlet 接收到 request ,从 mappings.xml 找到对应的 url ,
RequestProcessor 将对应的 web-action-class 初始化并执行, ScreenFlowManager
将执行结果传回给 *.screen ,再运用本系列前两篇所介绍 *.screen 机制组成实际画面,响应给使用者,大致的流程是这样,实际上更复杂,因牵涉到 Web tier 与 EJB tier 间沟通,但以本例来说, RequestProcessor 对应 web-action-class(CustomerHTMLAction) 并没有做什么事,后续 RequestProcessor 接收到结果显示画面 (customer.screen) ,则进行转导 (forward) 动作。
mappings.xml 读取后转成 HashMap 类别存入 ServletContext 供所有进入
Petstore 系统的人使用,接着将 ScreenFlowManager 及 RequestProcessor 初始
化并存入 ServletContext 。