使用JSF 和facelets来创建复合控制器

阅读更多

译者注:

在[“Facelets 非常适合 JSF”(Richard Hightower,developerWorks,2006 年 2 月):开始第一个能真正 解决您的 JSF-and-JSP 烦恼的解决方案。  ] 一文中我们看到了如何使用Facelets来创建复合组件. 这是facelets的强大功能之一. 但是还是有点缺陷(如何创建复合控制). 下面我们就来看看,如何解决这个问题:

原文: http://andrewfacelets.blogspot.com/2006/06/creating-composite-controls-with-jsf.html

翻译: icess http://blog.matrix.org.cn/page/icess 

 JSF是一个强大的web框架,但是没有很好的工具用来支持开发复合控制(composite controls). 如果创建一个具有很多子control的control 或者组合了100个子组件的control,则要有很多工作要做,这没有一个简单的解决方案.JSF规范上只不过写道: 创建一个组件并且有她自己来呈现她.

而Facelets 是JSF的一个非常强大的扩展. 使用一个  user tag (a.k.a. source tag), 可以很容易的创建一个 composite control .问题是传递一个方法绑定到子组件不是  立即可以使用的( out-of-the-box).

例如:
Snippet from taglib.xml:


test
tags/testTag.xhtml



Usage in an XHTML file:



User Tag file:





上面的问题是:  facelets 的user tag handler总是为每一个source tag的属性 创建 ValueExpression 对象  ,这对于简单属性是很好的,但是在上面的情况下 ,应该创建 一个 MethodExpression

我想解决该问题,使用者不需要创建新的tag handler 或者 component handler 就可以使用. 我还想使用一个 re-usable complete solution. 在我潜入到facelets的代码中后发现我只需要扩展faceltets的api就可以了,  解决方案有两部分:


  1. Create a tag handler with component support
  2. Create a new value expression that returns method expressions

Creating a value expression that is a method expression


首先讨论上面的第二步是比较容易的. 思想是使用一个返回method expression值的 value expression函数. 这将把"#{myBean.doSomething}" 解释为一个方法调用 "getDoSomething"

下面的代码可能还不是十分完善,但是她工作的很好:

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

import javax.el.ELContext;
import javax.el.MethodExpression;
import javax.el.ValueExpression;

public class MethodValueExpression
extends ValueExpression
implements Externalizable
{
private ValueExpression orig;
private MethodExpression methodExpression;

public MethodValueExpression() {}

MethodValueExpression(ValueExpression orig, MethodExpression methodExpression)
{
this.orig = orig;
this.methodExpression = methodExpression;
}

@Override
public Class getExpectedType()
{
return orig.getExpectedType();
}

@Override
public Class getType(ELContext ctx)
{
return MethodExpression.class;
}

@Override
public Object getValue(ELContext ctx)
{
return methodExpression;
}

@Override
public boolean isReadOnly(ELContext ctx)
{
return orig.isReadOnly(ctx);
}

@Override
public void setValue(ELContext ctx, Object val) {}

@Override
public boolean equals(Object val)
{
return orig.equals(val);
}

@Override
public String getExpressionString()
{
return orig.getExpressionString();
}

@Override
public int hashCode()
{
return orig.hashCode();
}

@Override
public boolean isLiteralText()
{
return orig.isLiteralText();
}

/**
* @see java.io.Externalizable#readExternal(java.io.ObjectInput)
*/
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException
{
orig = (ValueExpression)in.readObject();
methodExpression = (MethodExpression)in.readObject();
}

/**
* @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
*/
public void writeExternal(ObjectOutput out)
throws IOException
{
out.writeObject(orig);
out.writeObject(methodExpression);
}
}



现在我们有一个包装了method expression的value expression. 我们需要在JSF环境中替换他们.当facelets 应用组件到组件树上时, 有一个  EL context 来解释数据. 如果我们知道绑定的方法签名( method signatures) 我们就可以创建  MethodExpression 对象.

我们从构造函数开始. 我们要一个attribute来保存我们的配置 . 不幸的是. 一个 attribute 只能指定一次( given once), 因此我将创建一个 custom format 来代替使用. Starting code:

public class CompositeControlHandler
extends TagHandler
{
private final TagAttribute methodBindings;

private ComponentHandler componentHandler;

/**
* @param config
*/
public CompositeControlHandler(TagConfig config)
{
super(config);
methodBindings = getAttribute("methodBindings");
}
// TODO...
}


现在我们有一个skeleton 用来声明 我们自定义tag的"methodBindings" attribute  ,来定义我们自定义tag中的那些属性应该用方法绑定来替代变量. 我使用下面的格式:

attribute-name=java-return-type first-java-parameter-type second-java-parameter-type;
second-attribute-name=java-return-type first-java-parameter-type second-java-parameter-type;


Example from above:

actionListener=void javax.faces.event.ActionEvent;


现在, 我们有一个方法来指定那些属性是 methods, 我们还需要做些工作. Steps:


  1. 解析attribute
  2. 对于每一个Attribute,看看她是否 "bound" 到当前的 variable mapper上
  3. 如果bound了,从配置信息中创建一个新的  method expression
  4. 使用method expression来隐藏初始变量


The resultant code is as follows:

public class CompositeControlHandler
extends TagHandler
{
private final static Pattern METHOD_PATTERN = Pattern.compile(
"(\\w+)\\s*=\\s*(.+?)\\s*;\\s*");

private final TagAttribute methodBindings;

/**
* @param config
*/
public CompositeControlHandler(TagConfig config)
{
super(config);
methodBindings = getAttribute("methodBindings");
}

/**
* @see com.sun.facelets.FaceletHandler#apply(com.sun.facelets.FaceletContext, javax.faces.component.UIComponent)
*/
public void apply(FaceletContext ctx, UIComponent parent)
throws IOException, FacesException, FaceletException, ELException
{
VariableMapper origVarMap = ctx.getVariableMapper();

try
{
VariableMapperWrapper variableMap = new VariableMapperWrapper(origVarMap);
ctx.setVariableMapper(variableMap);

if (methodBindings != null)
{
String value = (String)methodBindings.getValue(ctx);
Matcher match = METHOD_PATTERN.matcher(value);

while (match.find())
{
String var = match.group(1);
ValueExpression currentExpression = origVarMap.resolveVariable(var);
if (currentExpression != null)
{
try
{
FunctionMethodData methodData = new FunctionMethodData(
var, match.group(2).split("\\s+"));

MethodExpression mexpr = buildMethodExpression(ctx,
currentExpression.getExpressionString(), methodData);
variableMap.setVariable(var, new MethodValueExpression(
currentExpression, mexpr));
}
catch (Exception ex)
{
throw new FacesException(ex);
}
}
}
}

// TODO: will do this next
}
finally
{
ctx.setVariableMapper(origVarMap);
}
}

private MethodExpression buildMethodExpression(FaceletContext ctx, String expression,
FunctionMethodData methodData)
throws NoSuchMethodException, ClassNotFoundException
{
return ctx.getExpressionFactory().createMethodExpression(ctx, expression,
methodData.getReturnType(), methodData.getArguments());
}

private class FunctionMethodData
{
private String variable;
private Class returnType;
private Class[] arguments;

FunctionMethodData(String variable, String[] types)
throws ClassNotFoundException
{
this.variable = variable;
if ("null".equals(types[0]) || "void".equals(types[0]))
returnType = null;
else
returnType = ReflectionUtil.forName(types[0]);
arguments = new Class[types.length - 1];
for (int i = 0; i < arguments.length; i++)
arguments[i] = ReflectionUtil.forName(types[i + 1]);
}

public Class[] getArguments()
{
return this.arguments;
}

public void setArguments(Class[] arguments)
{
this.arguments = arguments;
}

public Class getReturnType()
{
return this.returnType;
}

public void setReturnType(Class returnType)
{
this.returnType = returnType;
}

public String getVariable()
{
return this.variable;
}

public void setVariable(String variable)
{
this.variable = variable;
}
}
}


现在,比较有意思了,为什么不使用一个 tag handler来代替她呢,但是一个component handler也是一样的. 下一个步骤将可以不需要任何XML配置文件就可以使用该 user tag了 . 目标是让用户指定作为我们user tag的组件的  component type and renderer type   (If none is given, the ComponentRef from facelets will be used).

代码并不是很难,下面是完整的代码:

public class CompositeControlHandler
extends TagHandler
{
private final static Pattern METHOD_PATTERN = Pattern.compile(
"(\\w+)\\s*=\\s*(.+?)\\s*;\\s*");

private final TagAttribute rendererType;
private final TagAttribute componentType;
private final TagAttribute methodBindings;

private ComponentHandler componentHandler;

/**
* @param config
*/
public CompositeControlHandler(TagConfig config)
{
super(config);
rendererType = getAttribute("rendererType");
componentType = getAttribute("componentType");
methodBindings = getAttribute("methodBindings");

componentHandler = new ComponentRefHandler(new ComponentConfig() {
/**
* @see com.sun.facelets.tag.TagConfig#getNextHandler()
*/
public FaceletHandler getNextHandler()
{
return CompositeControlHandler.this.nextHandler;
}

public Tag getTag()
{
return CompositeControlHandler.this.tag;
}

public String getTagId()
{
return CompositeControlHandler.this.tagId;
}

/**
* @see com.sun.facelets.tag.jsf.ComponentConfig#getComponentType()
*/
public String getComponentType()
{
return (componentType == null) ?
ComponentRef.COMPONENT_TYPE :
componentType.getValue();
}

/**
* @see com.sun.facelets.tag.jsf.ComponentConfig#getRendererType()
*/
public String getRendererType()
{
return (rendererType == null) ?
null : rendererType.getValue();
}
});
}

/**
* @see com.sun.facelets.FaceletHandler#apply(com.sun.facelets.FaceletContext, javax.faces.component.UIComponent)
*/
public void apply(FaceletContext ctx, UIComponent parent)
throws IOException, FacesException, FaceletException, ELException
{
VariableMapper origVarMap = ctx.getVariableMapper();

try
{
VariableMapperWrapper variableMap = new VariableMapperWrapper(origVarMap);
ctx.setVariableMapper(variableMap);

if (methodBindings != null)
{
String value = (String)methodBindings.getValue(ctx);
Matcher match = METHOD_PATTERN.matcher(value);

while (match.find())
{
String var = match.group(1);
ValueExpression currentExpression = origVarMap.resolveVariable(var);
if (currentExpression != null)
{
try
{
FunctionMethodData methodData = new FunctionMethodData(
var, match.group(2).split("\\s+"));

MethodExpression mexpr = buildMethodExpression(ctx,
currentExpression.getExpressionString(), methodData);
variableMap.setVariable(var, new MethodValueExpression(
currentExpression, mexpr));
}
catch (Exception ex)
{
throw new FacesException(ex);
}
}
}
}

componentHandler.apply(ctx, parent);
}
finally
{
ctx.setVariableMapper(origVarMap);
}
}

private MethodExpression buildMethodExpression(FaceletContext ctx, String expression,
FunctionMethodData methodData)
throws NoSuchMethodException, ClassNotFoundException
{
return ctx.getExpressionFactory().createMethodExpression(ctx, expression,
methodData.getReturnType(), methodData.getArguments());
}

private class FunctionMethodData
{
private String variable;
private Class returnType;
private Class[] arguments;

FunctionMethodData(String variable, String[] types)
throws ClassNotFoundException
{
this.variable = variable;
if ("null".equals(types[0]) || "void".equals(types[0]))
returnType = null;
else
returnType = ReflectionUtil.forName(types[0]);
arguments = new Class[types.length - 1];
for (int i = 0; i < arguments.length; i++)
arguments[i] = ReflectionUtil.forName(types[i + 1]);
}

public Class[] getArguments()
{
return this.arguments;
}

public void setArguments(Class[] arguments)
{
this.arguments = arguments;
}

public Class getReturnType()
{
return this.returnType;
}

public void setReturnType(Class returnType)
{
this.returnType = returnType;
}

public String getVariable()
{
return this.variable;
}

public void setVariable(String variable)
{
this.variable = variable;
}
}
}


现在我们要在一个 taglib.xml 中注册她, 然后就可以使用了:


compositeControl
mypackage.CompositeControlHandler


注册后让我们开始使用她吧. 使用该tag的XHTML file:


The user tag does look different, but not that much:

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:my="http://mynamespace">


id="#{id}"
methodBindings="action=java.lang.String;
actionListener=void javax.faces.event.ActionEvent;">

actionListener="#{actionListener}"
action="#{action}" />




That should be enough to get you going.
© Copyright 2006 - Andrew Robinson.
Please feel free to use in your applications under the LGPL license (http://www.gnu.org/licenses/lgpl.html).

你可能感兴趣的:(JSF,UI,SUN,XHTML,XML)