【JSF专家Dennis Byrne】JSF反模式与陷井(三)完 博客分类: 专家文章翻译 JSF设计模式编程EXT网络应用
终于到本文的最后一节了,本节内容主要讲JSF也是面对接口编程的,作者举了个简单的例子。然后在JSF安全问题上,使用了类似于ASP.NET的viewstate概念的东东。最后谈到了头痛的Portlet程序开发问题,作者指出Portlet问题远不止这些,而且问题都出在Portlet本身,非常同情Portlet开发人员。
基于接口编程
JSF鼓励“包含与扩展”的设计原则。因此JSF组件模型广泛基于接口。下面的这个例子中,“ImplementationDependentManagedBean.java”却没有使用接口。好吧,我需要对它做些小小的修正。
import org.apache.myfaces.component.html.ext.HtmlInputHidden; import org.apache.myfaces.component.html.ext.HtmlInputText; import org.apache.myfaces.component.html.ext.HtmlOutputText; public class ImplementationDependentManagedBean { private HtmlInputText input ; private HtmlInputHidden hidden ; private HtmlOutputText output ; /* 省略getters和setters方法 */ public boolean recordTotal(ActionEvent event) { long total = ((Long)input.getValue()).longValue(); total += ((Long)hidden.getValue()).longValue(); total += ((Long)output.getValue()).longValue(); return new JmsUtil().broadcastTotal(total); } }
这个bean绑定了三个组件作为其属性,业务逻辑部分将这三个组件封装到一个action listener里去了。(action listener指的就是这里的recordTotal方法)。这三个类(这里指的是HtmlInputText,HtmlInputHidden,HtmlOutputText)都是MyFaces包自带的。这段代码中需要注意的是这个类的业务逻辑部分recordTotal方法只会调用每个组件的getValue方法。而通过JSF的API,你又会发现这些组件的getValue方法又承继于它们的父类。因此我决定要重构一下代码,将这几个导入的包给换掉:
import javax.faces.component.ValueHolder; public class RefactoredManagedBean { private ValueHolder input ; private ValueHolder hidden ; private ValueHolder output ; /* getters & setters ommitted */ public boolean recordTotal(ActionEvent event) { long total = 0; for(ValueHolder valued : new ValueHolder[] {input, hidden, output}) total += ((Long)valued.getValue()).longValue(); return new JmsUtil().broadcastTotal(total); } }
现在我要说一下,ValueHolder是HtmlInputText, HtmlInputHidden 和 HtmlOutputText父类实现的一个接口。通过这次重构,我们只要一个ValueHolder接口就可完成原本需要三个类(上面提到的HtmlInputText, HtmlInputHidden和 HtmlOutputText)才能完成的工作,代码也干净整洁。你会发现使用Polymorphism(多态)对于基于接口编程的业务逻辑代码整理是相当优雅的。(注:不论是JDK本身还是,目前主流的开源框架,接口,多态运用的非常广泛)
请记住,JSF的优点之一就是让你*自在的在POJO里编写的你的业务逻辑,开发人员并不要实现任何接口或继承任何类!这一点并没有意味着我们失掉了面向对象的编程原则。当你有机会在基于标准的接口上编程的话,大胆去做吧。
最后,你有没有注意到recordTotal这个action listener 。JSF规范规定Action的listener方法返回值要为void,但咱们这里却返回一个boolean。实际上MyFaces相对规范来说,宽松了点,但允许你这么做,但实际上还是会忽略返回值的。但尽量还是避免这样,因为根据JSF的实现参考来说,有返回值是应该抛异常的,也许别的JSF实现可能与MyFaces不同了吧。
View State(视图状态)加密
一个普遍的错误观念就是许多开发人员觉得View State加密根本不需要SSL。很显然嘛,SSL和View State加密根本没有共同点——它们在各自不同的网络协议中解决属于各自的问题,互不相干。
好,我们来再来举个例子。假设Manfred是个银行,Sean是个在线客户,而Mike是媒介。在使用普通的HTTP的情况下,Mike可以截取Sean发送给Mannfred的请求,偷偷记下Sean的密码,然后再转发给Manfred。这一系列动作对于Manfred或Sean任何一方来说,都是神不知,鬼不觉的。
如果在网络传输层上Manfred和Sean使用了SSL的话,就可以有效阻止Mike窃取他们之间的信息。
SSL提供的是点到点的安全保障,这样从Sean发送到Manfred的请求就不会被Mike截取了。但如果应用程序在客户端保存状态的话,SSL就法保证一定是安全的了。当使用客户端状态保存时,JSF会创建一个数据结构,它是一个控件树的序列化Base64编码,用来表示绑定组件的响应状态值,这个数据结构就是view state,并且此时view state是被序列化和加密过的,然后隐藏在的一个HTML hidden字段中。 当HTML表单提交时,它会将view state的值作为一个HTTP参数传到服务器后台。JSF利用该参数的值,进行反序列化,解密等逆向操作来还原先前的视图是的状态值。这对所有的JSF实现来说都是一个很大安全性挑战,因为Sean是可以*的改变view state值的(因为它只是一个隐藏的hidden字段)。 我强烈推荐在你的产品中使用view state加密,因为这样可以防止web客户端被恶意篡改view state的值。我也建议在开发和功能测试阶段关闭view state加密(因为Base64编码后的数据量很大,反序列化或解密操作估计有性能损耗)。要想使用view state加密,去好好看看你使用的JSF实现文档吧。
Portlet问题
我有点同情Portlet开发人员。确实,他们总是在邮件列表或论坛上不断的提出一个接一个问题,而这些问题本身来自Portlet,与他们无关。如果一定要说有这么一批人不得不对标准低头,*屈服的话,那非Portlet开发人员莫属了。(同情中………………)
有些JSF API在基于Portlet的应用程序中表现的行为是不同的。如果你的Portlet应用程序运行在一个普通的Servlet容器里,那么有一些假设需要去避免。现在假设下面的这段代码在Servlet容器里运行的好好的:
FacesContext ctx = FacesContext.getCurrentInstance(); ExternalContext externalContext = ctx.getExternalContext(); ServletRequest request = (ServletRequest) externalContext.getRequest(); String id = request.getParameter("id");
如果现在你想运行Portlet应用程序,就必须取消显式的对javax.servlet.ServletRequest or javax.servlet.ServletResponse进行转换。在Portlet应用程序中,ExternalContext.getRequest 返回的是一个javax.portlet.PortletRequest,结果可想而知,当然会抛出ClassCastException异常。我这里有一个通用的好办法,也可以获取request中的属性值,请看:
FacesContext ctx = FacesContext.getCurrentInstance(); ExternalContext externalContext = ctx.getExternalContext(); externalContext.getRequestParameterMap().get("id");
(全文完)