20.4. 其他与IoVC相关的Annotation

除了上面提到的@Bind与@Action外,OperaMasks还提供了以下annotation(仅简单列出,详细参数请参考API Javadoc):

20.4.1. 定义与初始化

20.4.1.1. @ManagedBean

作用范围:Type

定义一个LiteBean。目前为向下兼容,OperaMasks仍使用@ManagedBean annotation来注解LiteBean。OperaMasks支持将Spring Bean或EJB 3对象(需要Apusic Application Server支持)直接声明为LiteBean。例如:

@Aspect 
@ManagedBean(scope=ManagedBeanScope.APPLICATION) 
public class HelloAspect{
}

20.4.1.2. @Accessible

作用范围:Field,Method,Annotation

使得域或方法可以被EL表达式访问(忽略其自身定义的可见性)。若注解了一个Annotation,则被注解的Annotation也具有于@Accessible同样作用。显式使用@Accessible(false)可以取消被注解的Annotation所带来的隐含可见性。

20.4.1.3. @SaveState

作用范围:Field

声明一个域的状态需要被保存。用于需要使域的状态比其所在的Bean的生命周期要长的场景。例如下面的count如果对它赋值了,那么赋的新值在下次请求中仍然保留:

@ManagedBean(scope=ManagedBeanScope.REQUEST) 
public class ClickBean { 
    @SaveState 
    private int count = 0; 
    ...
}

20.4.1.4. @ManagedProperty

作用范围:Field,Method

定义并初始化一个LiteBean属性。(例如,另一个LiteBean实例)。

/* 使用@ManagedProperty来定义并初始化一个LiteBean属性。利用此方法可以 
 * 方便地声明一个属性而不必定义get和set方法,属性的初始值可以来自于EL表达式。 
 */ 
    @ManagedProperty(value="#{ColorBean}") 
    private ColorBean color; 

    /* 注意,color属性是只读的,这是由于只定义了get方法而没有定义set方法,但 
     * 初始值仍会被注入。
     */
    public ColorBean getColor() { 
        return color; 
    }
}

20.4.1.5. @Inject

作用范围:Field,Method

注入依赖对象。此注释必须应用到一个类成员变量或对象属性的get或set方法, 但以上三个地方不能同时使用。

可通过@Inject注入的系统资源对象包括:

  • java.util.logging.Logger

  • javax.faces.context.FacesContext

  • javax.faces.application.Application

  • javax.faces.application.NavigationHandler

  • javax.el.ExpressionFactory

  • org.operamasks.faces.event.EventBroadcaster

例如:

@Inject
private  Logger log; 
@Inject
private  EventBroadcaster event; 
//User是一个JPA持久化对象 
@Inject 
@Accessible 
private User user;

20.4.1.6. @Outject

作用范围:Field,Method

注出依赖对象。此注释必须应用到一个类成员变量或对象属性的get或set方法, 但以上三个地方不能同时使用。其value属性具有以下两种形式

  • 与上下文相关的对象名称,在注出时对象值将被赋予到由scope指定的上下文中。scope属性默认为ManagedBeanScope.REQUEST。

  • EL表达式,在注出时将对象值赋予到由该表达式所指定的目标中。

当@Outject和@Inject联用时可以省略value属性, 此时将使用Inject中指定的value。 如果未和Inject联用也未指定value,则使用被注释属性的名称作为注出对象名称。例如:

@Outject 
@Bind(value="response") 
private String response;

20.4.1.7. @Factory

作用范围:Method

指定一个工厂方法,用于为LiteBean创建实例。例如:

@Factory(name="user", scope=ManagedBeanScope.SESSION) 
public User createUser() {
    return new User(); 
}

20.4.1.8. @Init

作用范围:Method

声明一个方法,在LiteBean被注入前调用此方法。例如:

@DataModel(id="table") 
private List<Quote> tableData; 
@Init 
private void initTableData() { 
    this.tableData = Arrays.asList(model.fetch()); 
}

20.4.1.9. @ComponentAttributes

将模型对象属性批量绑定到视图构件上。例如:

@ComponentAttributes(id="txt") 
Map<String, Object> txt = new HashMap<String, Object>(); 
... 
@BeforeRender 
public void beforeRender(boolean isPostback) { 
    if (!isPostback) { 
        txt.put("value", "12345"); 
        txt.put("width", 100); 
        txt.put("readonly", true); 
    } 
}

此注解特别适用于当构件属性是由配置文件或其他外部方法批量读取,并返回一个Map的场景。使用时有以下限制:

  1. 用户程序需要自行负责属性映射对象的创建和初始化;

  2. 属性映射对象中出现的属性取值具有最高优先级,通过其他形式绑定或通过构件类API所作的修改都会被属性映射对象中的取值覆盖。换而言之,OperaMasks框架只会将属性映射对象中的取值更新到构件,而不会将构件的属性值更新到属性映射对象中。当属性映射对象中出现某属性时,对此属性的任何修改都需要通过属性映射对象进行;

  3. 若在程序中需要恢复其他绑定形式的控制,需要先把相关属性从映射对象中移除。

例如:

@ManagedBean(name="test1Bean", scope=ManagedBeanScope.SESSION) 
public class Test1Bean implements Serializable { 
    @ComponentAttributes(id="txt") //绑定页面中id为txt的w:textField 
    Map<String, Object> txt = new HashMap<String, Object>();
    @Bind(id="txt", attribute="width") 
    int width = 50; //由于构造方法中添加了映射属性width,此设置无效 
    public Test1Bean() { 
        txt.put("width", 100); //初始宽度 
    } 
    @Action //绑定页面中id为btn1的w:button 
    @Label("修改宽度") 
    public void btn1() { 
        txt.put("value", "12345"); 
        txt.remove("width"); //移除映射属性 
        width = 50; //此句生效 
    } 
    @Action //绑定页面中id为btn2的w:button 
    @Label("修改宽度(无效)") 
    public void btn2() { 
        txt.put("value", "223344"); 
        txt.put("width", 100); //此句生效 
        width = 50; //此句无效 
    }
}

20.4.2. 事件

20.4.2.1. @ActionListener

作用范围:Method

声明一个动作侦听方法。此方法将和视图中的一个动作构件进行绑定,当动作构件被激活时调用此动作侦听方法。与@Action注解不同的是,同一个动作构件可以绑定多个动作侦听方法,但他们的调用顺序是未定义的。此外,与@Action一样,如果需要把绑定构件的immediate属性设为true,必须使用注解上的属性来设置。

20.4.2.2. @ActionListeners

作用范围:Method

将一个动作侦听方法同时和多个视图构件绑定。

20.4.2.3. @BeforePhase

作用范围:Method

声明一个生命周期侦听方法,当所绑定的视图任何一个JSF生命周期阶段即将开始时调用此方法。其注解的方法必须符合以下签名:

@BeforePhase 
void phaseListener(javax.faces.event.PhaseEvent event);

注意由于目前IoVC绑定动作发生在生命周期第一阶段Restore View中,因此使用此注解的侦听方法无法侦听到Restore View阶段的生命周期事件。

20.4.2.4. @AfterPhase

作用范围:Method

声明一个生命周期侦听方法,当所绑定的视图的任何一个JSF生命周期阶段完成时调用此方法。其注解的方法必须符合以下签名:

@AfterPhase 
void phaseListener(javax.faces.event.PhaseEvent event);

注意由于目前IoVC绑定动作发生在生命周期第一阶段Restore View中,因此使用此注解的侦听方法无法侦听到Restore View阶段的生命周期事件。

20.4.2.5. @BeforeRender

作用范围:Method

声明一个生命周期侦听方法,当所绑定的视图渲染阶段即将开始时调用此方法。其注解的方法必须符合以下签名:

@BeforeRender 
void beforeRender(boolean isPostback);

特别地,对于首次请求,这个注解的方法是完成IoVC绑定后,渲染视图之前唯一一个可以加入用户代码的位置(也可以使用作用相同的PhaseListener或者@BeforePhase注解)。在@BeforeRender注解的方法中一般可以包含以下逻辑:

  • 在渲染之前对构件树进行最后调整,例如在首次请求时创建和配置构件

  • 在首次请求时处理请求的GET方式参数;

20.4.2.6. @AfterRender

作用范围:Method

声明一个生命周期侦听方法,当所绑定的视图渲染阶段完成时调用此方法。其注解的方法必须符合以下签名:

@AfterRender 
void beforeRender(boolean isPostback);

20.4.3. 数据检验

20.4.3.1. @Required

作用范围:Field,Method

声明一个域或Bean属性必须具有输入值。例如:

@Bind 
@Required 
private double first = 22.0;

注意,在默认检验器中,除了@Required之外的其他检验器通常会把空值视为合法输入,包括字符串长度检验等。

20.4.3.2. @ValidateDoubleRange

作用范围:Field,Method

检验一个浮点数取值是否在指定范围中。

20.4.3.3. @ValidateLongRange

作用范围:Field,Method

检验一个整型数取值是否在指定范围中。

20.4.3.4. @ValidateLength

作用范围:Field,Method

检验一个字符串的长度是否在指定范围中。

20.4.3.5. @ValidateRegexp

作用范围:Field,Method

检验一个字符串是否可以匹配指定的正则表达式。

20.4.3.6. @Validate

作用范围:Method

声明一个检验方法。使用检验方法能够简化数据转换逻辑的实现,不需要编写单独的检验器,也不需要在faces-config.xml中进行配置。转换方法既可以直接在模型对象所在的托管bean中实现(应用于耦合度较高的场合),也可以编写和模型对象分离的转换器(使用@DefineValidater注解,应用于松散耦合的场合)。

@Bind 
private String email;
 @Validate(message="请输入正确的email格式") 
private boolean 1validateEmail(String email){ 
    return email.contains("@"); 
}
1

当方法名为“validate”+<ID>时,根据“约定优于配置”规则绑定ID所指定的构件。

被@Validate注解的方法可以采用以下签名:

  • 标准写法:

    public void validate(FacesContext context, UIComponent component, Object value);

    这是javax.faces.validator.Validator接口所定义的标准方法,采用这种写法可以得到当前正在校验的UI构件,以获得更多的控制。由于方法没有返回值,用户需要使用构件的setValid(boolean)方法标记构件是否合法,并自行使用context.addMessage()方法添加出错信息。

  • 简略写法

    public boolean validate(Object value);

    对输入值进行校验,如果成功则返回true,否则返回false。当采用这种写法时最好设置@Validate注解的message属性,用于提供出错信息。

  • 返回不同出错信息的简略写法

    public String validate(Object value);

    当需要根据不同的校验结果显示不同的出错信息时可以采用这种写法,当方法返回null时表示校验成功,否则将返回值作为出错信息。注意在返回的字符串中可以包含EL表达式,因此可以很容易地实现国际化而不是硬编码的固定字符串。

  • 抛出ValidatorException的简略写法

    public void validate(Object value) throws ValidatorException;

    同第三种方式类似,只不过将出错信息包含在ValidatorException中抛出。

注意在以上所有格式中,最后一个参数value不必是Object类型,可以是实际的数据类型,例如上面例子中的String。

20.4.3.7. @Validator

作用范围:Annotation_Type,Method,Field

当作用在另一个Annotation上时,它将此Annotation声明为检验注解,被此注解所注解的Bean属性或Field将使用指定检验器类进行检验。

@Target({ElementType.METHOD, ElementType.FIELD}) 
@Retention(RetentionPolicy.RUNTIME) 
@Validator(PasswordValidator.class) // 这里使annotation和检验器类发生关联 
public @interface ValidatePassword { 
    int minimum() default 0; 
    int maximum() default Integer.MAX_VALUE; 
}

校验器类定义:

/** * 采用MVB编写校验器将变得非常简单,校验器不需要继承自javax.faces.validator.Validator, 
 * 也不需要在faces-config.xml中进行配置。你只需关注于实际的校验逻辑,OperaMasks会帮你 
 * 把其余的事情做好。 
 */ 
public class PasswordValidator { 
    private int maximum = 0; 
    private boolean maximumSet = false; 
    public int getMaximum() { 
        return this.maximum; 
    } 
    public void setMaximum(int maximum) { 
        this.maximum = maximum; 
        this.maximumSet = true; 
    } 
    private int minimum = 0; 
    private boolean minimumSet = false; 
    public int getMinimum() { 
        return this.minimum; 
    } 
    public void setMinimum(int minimum) { 
        this.minimum = minimum; 
        this.minimumSet = true; 
    } 
    @LocalString 
    private Map<String,String> messages; 
    @Validate 
    public String validate(String value) { 
        if (value != null) { 
            if (this.maximumSet && value.length() > this.maximum) { 
                return "#{this.messages.passwordTooLong(this.maximum)}"; 
            } 
            if (this.minimumSet && value.length() < this.minimum) { 
                return "#{this.messages.passwordTooShort(this.minimum)}"; 
            } 
        } 
        return null; 
    } 
}

使用场景:

@Bind 
@Required 
@ValidatePassword(minimum=3, maximum=10) // 使用定制校验器 
public String getPassword() { 
    return password; 
}

当作用在Field或对象属性上时,被注解的Field或属性在访问时将使用指定的校验器类进行校验。

20.4.3.8. @Validators

作用范围:Field,Method

允许在同一个Field或对象属性上注解多个@Validator。

20.4.3.9. @DefineValidator

声明一个校验器类,此类的用法等同于标准JSF中javax.faces.validator.Validator接口的实现类。但不需要实现Validator接口,也不需要在faces-config.xml中进行配置。OperaMasks引擎将使用类中的validate()方法,或使用@Validate注解的方法进行校验。

20.4.4. 数据转换

20.4.4.1. @ConvertDateTime

作用范围:Field,Method

作用于一个Date类型的域或setter方法,声明将输入值转换成该域实际值时所使用的转换规则。

20.4.4.2. @ConvertNumber

作用范围:Field,Method

作用于一个数值类型的域或setter方法,声明将输入值转换成该域实际值时所使用的转换规则。

20.4.4.3. @Pattern

作用范围:Field,Method

声明一个简单的将实际值转换为输出字符串的转换规则。例如:

@Bind 
@Pattern("0.00%") 
public double getPctChange() { 
    return change / price; 
}

20.4.4.4. @Convert

作用范围:Method

声明一个转换方法,用于将输入值转换成实际值,类似于标准JSF中javax.faces.convert.Converter接口的getAsObject方法。使用转换方法能够简化数据转换逻辑的实现,不需要编写单独的转换器,也不需要在faces-config.xml中进行配置。转换方法既可以直接在模型对象所在的托管bean中实现(应用于耦合度较高的场合),也可以编写和模型对象分离的转换器(应用于松散耦合的场合).

20.4.4.5. @Format

作用范围:Method

声明一个格式化方法,用于将实际值转换成输出值,类似于标准JSF中javax.faces.convert.Converter接口的getAsString方法。使用格式化方法能够简化数据转换逻辑的实现,不需要编写单独的转换器,也不需要在faces-config.xml中进行配置。格式化方法既可以直接在模型对象所在的托管bean中实现(应用于耦合度较高的场合),也可以编写和模型对象分离的转换器(应用于松散耦合的场合)。

20.4.4.6. @DefineConverter

作用范围:Type

声明一个转换器类,此类的用法等同于标准JSF中javax.faces.convert.Converter接口的实现类。但不需要实现Converter接口,也不需要在faces-config.xml中进行配置。OperaMasks引擎将使用类中的convert()方法,或使用@Convert与@Format所注解的方法进行转换。

20.4.4.7. @Convertor

作用范围:Annotation_Type,Method,Field

当作用在另一个注解上时,它将此注解声明为转换器注解,被此注解所注解的Bean属性或Field将使用指定转换器类进行转换。以下是一个简单的例子,@ConvertePlus转换器将构件中输入的数字加上一个指定值作为绑定属性的取值。

@Target({ElementType.FIELD, ElementType.METHOD}) 
@Retention(RetentionPolicy.RUNTIME) 
@Converter(PlusConverter.class) 
public @interface ConvertePlus { 
    int delta() default 1; 
}

转换器类定义

public class PlusConverter { 
    private int delta; 
    @Format public String format(int value) { 
        return String.valueOf(value - delta); 
    } 
    @Convert 
    public int convert(String value) { 
        try { 
            return Integer.valueOf(value) + delta; 
        } catch (NumberFormatException ex) { 
            throw new ConverterException("Convert Error"); 
        } 
    } 
    public int getDelta() { 
        return delta; 
    } 
    public void setDelta(int delta) { 
        this.delta = delta; 
    } 
}

应用场景:

@ManagedBean(name="test1Bean", scope=ManagedBeanScope.SESSION) 
public class Test1Bean implements Serializable { 
    @Bind //绑定id为txt的w:numberField构件 
    @ConvertePlus(delta = 2) 
    int txt; 
    @Bind //绑定id为result的h:outputText构件 
    String result; 
    
    @Action //绑定id为btn1的w:button构件 
    public void btn1() { 
        result = String.valueOf(txt); 
    } 
}

20.4.5. 模型数据

20.4.5.1. @DataModel

作用范围:Field,Method

声明域或方法的值将作为UIData类型构件的数据模型。

注意:@DataModel绑定的模型数据可以是属性也可以是方法,但属性类型或方法的返回值必须是数组或List。例如要为页面中id="grid"的w:dataGrid绑定模型数据,代码如下:

@DataModel
private List<MyObject> myObjects; 

也可以绑定一个方法,例如:

@DataModel
public MyObject[] getDridValues(){
    ......
}

20.4.5.2. @SelectItems

作用范围:Field,Method

当域或bean属性getter/setter被绑定到一个选择构件时,使用此标签设置构件初始状态。它可以为w:combo、w:radioGroup、h:selectOneListbox、h:selectManyListbox、h:selectOneRadio、h:selectBooleanCheckbox、w:checkBoxGroup、h:selectManyCheckbox等构件提供列表元素。但对w:checkBoxGroup和h:selectManyCheckbox推荐使用下面的第三种方法。

假设有这样的Sex类:

public class Sex {
    private String label;
    private String value;

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public Sex(String label, String value) {
        super();
        this.label = label;
        this.value = value;
    }
}

假设页面代码为:

<w:combo id="mycombo" />

我们可以这样绑定它的列表项:

//方法1
@Bind
@SelectItems(source = "#{this.sexs}", mapValue = "#{item.value}", mapLabel = "#{item.label}")
private String mycombo;

public Sex[] sexs = new Sex[] { 
    new Sex("男", "M"), 
    new Sex("女", "F") 
};

也可以这样绑定:

//方法2
@Bind
@SelectItems(source = "#{this.sexs2}")
private String mycombo;

public SelectItem[] sexs2=new SelectItem[]{
    new SelectItem("M", "男"),
    new SelectItem("F", "女")
};

也可以将页面代码改为:

<w:combo id="mycombo">
    <f:selectItems id="sexs"/>
</w:combo>

后台代码改为:

//方法3
@Bind
@SelectItems
private SelectItem[] sexs = new SelectItem[] {
    new SelectItem("M", "男"),
    new SelectItem("F", "女"),
};

@Bind
private String mycombo;

3种效果一样,运行后的页面中mycombo这个下拉列表框有两个下拉选项“男”和“女”,其后台得到的mycombo值为“M”或“F”。

20.4.5.3. @ListEntries

作用范围:Field,Method

初始化一个List域或bean属性,初始值可使用EL表达式。例如:

@ListEntries({"Sun", "Mon", "Tue", "Wen", "Thu", "Fri", "Sat"}) 
private String[] dates;

20.4.5.4. @MapEntries

作用范围:Field,Method

初始化一个Map类型的域或属性,初始值可使用EL表达式。例如:

@MapEntries({ @MapEntry(key="red", value="#ff0000"), 
              @MapEntry(key="green", value="#00ff00"), 
              @MapEntry(key="blue", value="#0000ff") }) 
private Map<String,String> colorMap;

20.4.6. 页面展示

20.4.6.1. @LocalString

作用范围:Field,Method,Type

注入本地化字符串。本地化字符串所在的资源文件必须与当前类位于同一个包下, 并且资源包的基本名必须是LocalStrings。也可以通过@LocalString注解的basename属性显式指定一个资源包。通过@LocalString注入的变量都可以在EL表达式中被访问到,除非增加@Accessible(false)注解。

如果变量类型是java.lang.String,则以变量名为关键字从本地字符串资源文件中查找字符串并注入变量,也可以在LocalString注解中指定字符串关键字。

如果变量类型为java.util.Map,则注入一个包含所有以当前类名为前缀的本地字符串的只读Map,可以用关键字从此Map中查找字符串。

如果变量类型为java.util.ResourceBundle,则注入一个包含所有本地字符串的资源包。例如:

@LocalString
private  Map<String,String> messages; 
@LocalString(basename="demo.LocalStrings") 
private ResourceBundle bundle;

20.4.6.2. @Label

作用范围:Field,Method

设置绑定构件的展现名称(例如button标签的value值)。例如:

@Action 
@Label("Click") 
public void click() { 
    this.count++; 
}

20.4.6.3. @Description

作用范围:Field,Method

设置绑定构件的描述标签(tooltip)。例如:

@Action 
@Description("Click to show the result") 
public void btn1() { 
    result = String.valueOf(txt); 
}