没有比人更高的山

一个隐藏了将近2年的Bug1

最近在维护华中科技大学电信系的电子档案库,发现了一个隐藏了将近2年的Bug,记录如下:

需求:一个科研项目有一定的工作量,而每个项目有很多参与人,需要为每个参与人设置其工作量分配额度。当用户添加了一个项目时,需要同时添加其参与人:

实现:当用户录入了科研项目信息之后,点击“添加参与人”的按钮,转向到选择参与人的页面,可以勾选任意数目的参与人,点击“保存”按钮后返回科研项目信息页面。

在执行保存选择的参与人信息时,检查所选择的人员是否已经在科研项目的参与人中存在的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
for (Iterator<string> iterator = selectedMemberList.iterator(); iterator.hasNext();) {
    ParticipatorBean participatorBean = new ParticipatorBean();
    long teacherId = Long.parseLong(iterator.next());
    Teacherbasicinfo teacher = ServiceProvider.getTeacherInfoService().findById(teacherId);
    Long id = teacher.getTeacherId();
    participatorBean.setTeacherId(id);
    participatorBean.setTeacherName(teacher.getTeacherName());
    if (participatorsMap.get(id) == null) {
        participatorsMap.put(String.valueOf(id), String.valueOf(id));
        participators.add(participatorBean);
    }
}
</string>

其中,selectedMemberList是选中的用户列表,ParticipatorBean是用来传递给前一个页面的参与人的Java类,Teacherbasicinfo是领域模型,代表教师基本信息,老师编号teacherId为Long型。

participatorsMap是我从来去除重复的一个HashMap,也就是说我每遍历一个Teacherbasicinfo,就将其teacherId转换成字符串型添加到这个Map中,在每次添加之前我先检查这个Map是否已经存在了(且不论这种方法的好与坏,这是我们几年前的代码了)

这段代码无编译异常、无运行异常,但是却有逻辑异常。有用户反映。多次选择的时候会有重复的项出现。

经过多番调试,最后发现第八行代码有误,应该将

1
if (participatorsMap.get(id) == null) {

改成为:

1
if (participatorsMap.get(String.valueOf(id)) == null) {

恍然大悟,虽然我使用的是范型,但是Map的get方法仍然接受的是Object类型的参数,不对其参数进行检查,其put方法的签名为V put(K key, V value),则会对key和value的类型都进行检查。

这是我的错呢,还是Java的错,为啥不把get方法的参数用范型类型检查一下呢?可能是sun的工程师有别的考虑吧。

VN:F [1.7.5_995]
Rating: 10.0/10 (1 vote cast)
VN:F [1.7.5_995]
Rating: -1 (from 1 vote)

JSF中CommandButton与CommandLink传值0

f:param标签能够将一个参数添加到组件。需要注意的是f:param标签的不同表现依赖于它所关联的组件类型

【1】如果为 h:outputText添加f:param标签,那么JSF实现将使用参数来填充占位符,例如{0}、{1}等。

【2】如果添加f:param标签到h:commandLink,JSF实现会将参数值作为请求参数传递到服务器,如:

1
2
3
<h :commandLink actionListener="#{userListBean.checkUser}" value="审核通过">
    <f :param name="userId" value="#{user.userId}" />
</h>

在服务器端可以使用如下方法来获取传递到服务器端的值:

1
2
3
4
private void checkUser(ActionEvent actionEvent){
    String uid = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("userId");
    // other code
}

但是f:param的传值方式对于h:commandButton是没有作用的,(如果你是用搜索引擎搜到这篇文章的话,相信你肯定是遇到了这个问题),详情可以参考http://www.javaeye.com/topic/93388

如果是h:commandButton,那么可以使用f:attribute来进行传值,示例如下:

1
2
3
<h :commandButton actionListener="#{userListBean.resetPassword}" value="审核通过">
    <f :attribute name="userId" value="#{user.userId}"/>
</h>

在服务器端可以使用如下方法来获取传递到服务器端的值:

1
2
3
4
private void resetPassword(ActionEvent actionEvent){
    long userIdString = (Long) actionEvent.getComponent().getAttributes().get("userId");
    //other code ……
}
VN:F [1.7.5_995]
Rating: 0.0/10 (0 votes cast)
VN:F [1.7.5_995]
Rating: 0 (from 0 votes)

Servlet、JSP中获取Web应用路径(context path)的方法0

在Servlet中可以直接使用request.getContextPath()获取当前web应用的路径(context path)

在JSP 2.0页面中可以使用el表达式${pageContext.request.contextPath}

如果是JSF页面,因为JSF默认使用#作为el表达式的起始符,所以应该写成#{pageContext.request.contextPath}

因为JSP2.0之前的版本不支持文本模板中的el表达式,故可以使用嵌入java代码来实现,和Servlet中一样

如果在JSP2.0之前的版本中使用了JSTL标签(el表达式的概念是JSTL 1.0推出来的),所以仍然可以配合JSTL的标签使用el表达式输出当前Web应用路径(context path)

VN:F [1.7.5_995]
Rating: 0.0/10 (0 votes cast)
VN:F [1.7.5_995]
Rating: 0 (from 0 votes)

JSF异常消息机制及应用0

JSF有两种异常消息:

[1] JSF提供的标准异常信息,如标准的验证器和转换器生成的异常信息等

[2] 自定义的异常信息

与消息相关的类是javax.faces.application.FacesMessage,他封装了单一的、本地化的、人类可以理解的消息,除了消息字符串本身外,FacesMessage还有三个属性:severity(严重性),summary(摘要),detail(详细信息)

serverity被定义成FacesMessage的内部类,有四种类型:Info、Warn、Error、Fatal
FacesContext负责维护FacesMessage的两个逻辑集合,一个和组件相关的消息集合、一个不与组件相关的消息集合,定义了如下的和消息相关的方法
public Iterator<FacesMessage> getMessages();
public Iterator<FacesMessage> getMessages(String clientId);
public Iterator<String> getClientIdsWithMessages();//返回有消息绑定的组件id
public void addMessage(String clientId, javax.faces.application.FacesMessage message);//向FacesContext中加入一条FacesMessage
public FacesMessage.Severity getMaximumSeverity();//返回最严重的问题的严重性

FacesMessage的部分源代码如下:


private FacesMessage.Severity _severity;
private String _summary;
private String _detail;

public FacesMessage()
{
    _severity = SEVERITY_INFO;
}

public FacesMessage(String summary)
{
    _summary = summary;
    _severity = SEVERITY_INFO;
}

public FacesMessage(String summary, String detail)
{
    _summary = summary;
    _detail = detail;
    _severity = SEVERITY_INFO;
}

public FacesMessage(FacesMessage.Severity severity, String summary, String detail)
{
    if(severity == null) throw new NullPointerException("severity");
    _severity = severity;
    _summary = summary;
    _detail = detail;
}

和消息相关的两个标签是<h:message>与<h:messages>:
<h:message> 有一个必设属性for,标示消息的绑定组件id;showDetail属性默认为false,showSummary属性默认为true
<h:messages>渲染所有的排队消息,在globalOnly设为true时只渲染哪些没有组件标识符的消息。

我一般会在项目中新建一个MessageUtil类,处理消息,其代码如下:

public class MessageUtil {

    public static void addErrorMessage(FacesContext context, String clientId, String message){
        context.addMessage(clientId, new FacesMessage(message));
    }

    public static void addErrorMessage(FacesContext context, String message) {
        addErrorMessage(context, null, message);
    }

    /**
    * Add a global message, the detail is null and the servity is info by default
    * @param message message summary
    */
    public static void addErrorMessage(String message){
        addErrorMessage(FacesContext.getCurrentInstance(), message);
    }

    /**
    * Binding messages to a ui component
    * @param clientId The ui component id
    * @param message message summary
    */
    public static void addErrorMessage(String clientId, String message){
        addErrorMessage(FacesContext.getCurrentInstance(), clientId, message);
    }
}

如 我们在登录时需要向用户提示“用户名或者密码错误”,可以调用public static void addErrorMessage(String message),然后在页面上使用<h:messages globalOnly=”true”/>来显示这个信息。

VN:F [1.7.5_995]
Rating: 0.0/10 (0 votes cast)
VN:F [1.7.5_995]
Rating: 0 (from 0 votes)