译者注:
在[“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:
<tag>
<tag-name>test</tag-name>
<source>tags/testTag.xhtml</source>
</tag>
Usage in an XHTML file:
<my:test actionListener="#{myBean.doSomething}" />
User Tag file:
<ui:composition>
<h:commandButton value="Click Me" actionListener="#{actionListener}" />
</ui:composition>
上面的问题是: facelets 的user tag handler总是为每一个source tag的属性 创建 ValueExpression 对象 ,这对于简单属性是很好的,但是在上面的情况下 ,应该创建 一个 MethodExpression
我想解决该问题,使用者不需要创建新的tag handler 或者 component handler 就可以使用. 我还想使用一个 re-usable complete solution. 在我潜入到facelets的代码中后发现我只需要扩展faceltets的api就可以了, 解决方案有两部分:
- Create a tag handler with component support
- 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:
- 解析attribute
- 对于每一个Attribute,看看她是否 "bound" 到当前的 variable mapper上
- 如果bound了,从配置信息中创建一个新的 method expression
- 使用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 中注册她, 然后就可以使用了:
<tag>
<tag-name>compositeControl</tag-name>
<handler-class>mypackage.CompositeControlHandler</handler-class>
</tag>
注册后让我们开始使用她吧. 使用该tag的XHTML file:
<my:test actionListener="#{myBean.doSomething}" />
The user tag does look different, but not that much:
<!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: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">
<body>
<ui:composition>
<my:compositeControl
id="#{id}"
methodBindings="action=java.lang.String;
actionListener=void javax.faces.event.ActionEvent;">
<ui:debug />
<h:commandButton value="Click me"
actionListener="#{actionListener}"
action="#{action}" />
</my:compositeControl>
</ui:composition>
</body>
</html>
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).
分享到:
相关推荐
2. **视图(View)**:JSF使用Facelets作为默认的视图表示技术,这是一种基于XML的模板语言,允许开发者创建和组合组件,形成用户界面。 3. **控制器(Controller)**:JSF通过生命周期管理请求,自动处理用户的...
3. **视图层**:JSF使用Facelets作为默认的视图表示技术,这是一种XML-based模板语言,用于创建和组合JSF组件。Facelets允许动态生成和管理视图,提供了模板继承和复合组件等功能。 4. **控制器**:JSF的控制器主要...
JSF通过这些标签和注解,提供了完整的MVC(模型-视图-控制器)架构支持,简化了Web应用的开发。开发者可以通过组合使用各种标签和注解,创建复杂的应用逻辑,处理用户输入,进行数据验证,以及实现动态的Ajax行为。...
7. **Facelets的进一步改进**:如模板、复合组件和命名空间的增强,使得视图开发更加灵活。 这些API文档包含了JSF各个版本的详细接口、类和方法说明,对于开发者来说,无论是学习基础概念还是解决实际问题,都是不...
JavaServer Faces (JSF) 是一个用于构建Web应用程序的MVC(模型-视图-控制器)框架,由Java Community Process(JCP)开发并维护。JSF 2.0是该框架的一个重要版本,引入了许多改进和新特性,使得开发更高效、更易于...
- **模型-视图-控制器**:JSF遵循MVC模式,组件是视图,Managed Beans是模型,而JSF生命周期处理控制器职责。 8. **FacesServlet** - **关键组件**:它是JSF的入口点,负责处理HTTP请求并调用JSF生命周期。 9. *...
- 本章将讲解JSF的核心概念,如组件树、事件处理机制、转换器和验证器等。 - 学习JSF可以帮助开发者快速构建功能丰富的Web应用,并且能够很好地与其他Java EE技术集成。 ##### 4. **Facelets** - **Facelets** 是...
- **Spring Security Facelets标签库**:为安全控制提供了更丰富的标签支持。 - **Spring JavaScript更新**:增强了JavaScript的支持能力。 - **JSP Portlet支持**:提供了对JSP Portlet的更好支持。 #### 定义流程...