`
withyou
  • 浏览: 464616 次
社区版块
存档分类
最新评论

使用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:

<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就可以了,  解决方案有两部分:


  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 中注册她, 然后就可以使用了:

<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).

分享到:
评论

相关推荐

    从零开始,跟我学JSF,起步 JSF

    2. **视图(View)**:JSF使用Facelets作为默认的视图表示技术,这是一种基于XML的模板语言,允许开发者创建和组合组件,形成用户界面。 3. **控制器(Controller)**:JSF通过生命周期管理请求,自动处理用户的...

    JSF书,源代码,PPT

    3. **视图层**:JSF使用Facelets作为默认的视图表示技术,这是一种XML-based模板语言,用于创建和组合JSF组件。Facelets允许动态生成和管理视图,提供了模板继承和复合组件等功能。 4. **控制器**:JSF的控制器主要...

    JSF标签暨注解规范

    JSF通过这些标签和注解,提供了完整的MVC(模型-视图-控制器)架构支持,简化了Web应用的开发。开发者可以通过组合使用各种标签和注解,创建复杂的应用逻辑,处理用户输入,进行数据验证,以及实现动态的Ajax行为。...

    JSF1.1,1.2,2.0API大集合

    7. **Facelets的进一步改进**:如模板、复合组件和命名空间的增强,使得视图开发更加灵活。 这些API文档包含了JSF各个版本的详细接口、类和方法说明,对于开发者来说,无论是学习基础概念还是解决实际问题,都是不...

    JSF2-Getting-Started.zip_Getting Started

    JavaServer Faces (JSF) 是一个用于构建Web应用程序的MVC(模型-视图-控制器)框架,由Java Community Process(JCP)开发并维护。JSF 2.0是该框架的一个重要版本,引入了许多改进和新特性,使得开发更高效、更易于...

    jsf入门

    - **模型-视图-控制器**:JSF遵循MVC模式,组件是视图,Managed Beans是模型,而JSF生命周期处理控制器职责。 8. **FacesServlet** - **关键组件**:它是JSF的入口点,负责处理HTTP请求并调用JSF生命周期。 9. *...

    Java EE 7 Recipes

    - 本章将讲解JSF的核心概念,如组件树、事件处理机制、转换器和验证器等。 - 学习JSF可以帮助开发者快速构建功能丰富的Web应用,并且能够很好地与其他Java EE技术集成。 ##### 4. **Facelets** - **Facelets** 是...

    spring webflow reference,

    - **Spring Security Facelets标签库**:为安全控制提供了更丰富的标签支持。 - **Spring JavaScript更新**:增强了JavaScript的支持能力。 - **JSP Portlet支持**:提供了对JSP Portlet的更好支持。 #### 定义流程...

Global site tag (gtag.js) - Google Analytics