velocity中$springMacroRequestContext.getMessage($code)

在Java国际化(i18n)中,

vm页面显示内容需要使用 #springMessage("title")

实际运行时发现页面输出$springMacroRequestContext.getMessage($code)。看了一下源代码,#springMessage是一个宏,在spring.vm中定义为

#macro( springMessage $code )$springMacroRequestContext.getMessage($code)#end

原因是因为未找到$springMacroRequestContext...

解决方法:在我们定义视图resolver中加入对spring宏的支持

<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
        ...
        <property name="exposeSpringMacroHelpers" value="true"/>
</bean>

 

 

 

转载于:https://www.cnblogs.com/dreamysmurf/p/6016843.html

<think>我们面对的是一个AndroidJavaBean的setter方法模板修改需求。原始模板生成的是标准的set方法,现在需要修改模板,在set方法中加入属性变更监听支持(类似JavaFX或Swing中的属性绑定机制)。具体来说,要求在生成的set方法中:1.先获取该属性当前的值(作为旧值)2.然后设置新值3.触发一个属性变更事件(firePropertyChange),将属性名、旧值、新值传递出去。但是注意:原始需求中给出的代码片段有两个部分:第一部分是Velocity模板代码(用于生成setter方法),第二部分是想要生成的Java代码示例。我们需要修改Velocity模板,使得生成的setter方法体变成类似于示例的样子。分析现有模板:1.首先,模板中已经有一个参数定义:`#set($paramName=$helper.getParamName($field,$project))`2.然后,判断是否是静态字段,如果是则生成`static`3.方法名为`set$StringUtil.capitalizeWithJavaBeanConvention(...)`4.方法体中,如果参数名和字段名相同,则使用`this.`或`classname.`来避免歧义(虽然一般情况下setter方法的参数名不会和字段名冲突,但模板考虑了这种情况)5.最后将参数赋值给字段。现在我们要在赋值操作前后加入两行代码:-赋值前:获取当前值(旧值)-赋值后:触发属性变更事件(firePropertyChange)注意:触发事件的方法`firePropertyChange`需要我们自己实现吗?通常,如果我们想让生成的Bean支持属性变更事件,这个Bean应该继承`java.beans.PropertyChangeSupport`类或者内部使用该类。但这里我们只修改模板,所以假设这个Bean已经具有了支持属性变更事件的基础设施(比如已经有`firePropertyChange`方法)。修改步骤:1.在设置新值之前,先保存旧值。2.然后设置新值。3.调用`firePropertyChange`方法,传递三个参数:属性名(字符串)、旧值、新值。属性名可以通过`$StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field,$project))`获取,注意这里和生成方法名时使用的相同逻辑。但是注意:在示例代码中,属性名是直接写死的(如"jiaohuo"),而模板中需要动态生成属性名字符串。我们可以使用:`$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field,$project)))`但是注意,这个生成的是首字母大写的属性名(用于方法名),而属性变更事件中需要的是原始属性名(小写开头)。所以我们要使用未大写的属性名。实际上,模板中已经有一个变量`$helper.getPropertyName($field,$project)`可以获取属性名(不首字母大写的)。但是注意,这个属性名可能经过处理(比如去掉下划线等)。不过,为了与事件监听中使用的属性名一致,我们通常使用与字段名对应的属性名(即字段名去掉前缀,然后首字母小写)。然而,模板中已经使用`$helper.getPropertyName($field,$project)`获取到了属性名(这是一个字符串)。我们可以直接使用这个字符串作为属性名。所以,我们可以在模板中这样写:StringpropertyName="$helper.getPropertyName($field,$project)";但是注意,在Velocity中,直接这样写会把`$helper.getPropertyName`当做变量解析,而我们希望将其解析为具体值。因此,我们应该使用`$`加变量来获取。实际上,我们可以定义一个变量来保存属性名:#set($propName=$helper.getPropertyName($field,$project))然后,在方法体中使用这个`$propName`。修改后的模板代码:注意:因为要获取旧值,所以我们需要先读取当前字段的值。但是,如果字段是静态字段,则应该用类名访问;非静态则用`this`访问。这个在模板中已经有了判断。另外,触发事件的方法`firePropertyChange`应该由类提供。这里假设类中已经实现(比如继承自某个基类)。修改后的模板:```velocity#set($paramName=$helper.getParamName($field,$project))#set($propName=$helper.getPropertyName($field,$project))#if($field.modifierStatic)static###endvoidset$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($propName))($field.type$paramName){#if($field.name==$paramName)#if(!$field.modifierStatic)$field.typeoldValue=this.$field.name;this.$field.name=$paramName;firePropertyChange("$propName",oldValue,$paramName);#else$field.typeoldValue=${classname}.$field.name;${classname}.$field.name=$paramName;//静态字段的变更事件如何触发?这里可能需要重新设计,因为静态字段不属于实例,通常不触发实例的事件。//但是原需求示例中没有提到静态字段,所以我们这里先不考虑静态字段的触发事件,只处理实例字段。#end#else//参数名与字段名不同(这种情况很少见,但模板考虑了)$field.typeoldValue=$field.name;$field.name=$paramName;firePropertyChange("$propName",oldValue,$paramName);#end}```但是,上面的处理在静态字段部分我们注释了触发事件的代码,因为静态字段不应该触发实例的事件。如果确实需要支持静态字段的属性变更事件,通常使用单独的事件机制(比如静态的事件支持)。由于原需求没有提及静态字段,我们可以暂时忽略,或者根据项目需要另外处理。另外,如果参数名和字段名相同,我们在非静态字段中使用了`this`来访问,这样是正确的。但是,当参数名和字段名不同时,我们直接使用字段名(不需要加this或类名)来读取旧值,这样在静态字段中会出错。因此,我们需要对静态字段和非静态字段在参数名不同时也做处理。考虑到代码的清晰和避免重复,我们可以将获取旧值的逻辑统一处理,并分别处理静态和非静态的情况。另外,注意:如果字段是静态的,那么触发事件可能需要使用类级别的监听机制,这里我们不实现。因此,我们只对非静态字段生成事件触发代码,静态字段只做赋值。修改后的模板:```velocity#set($paramName=$helper.getParamName($field,$project))#set($propName=$helper.getPropertyName($field,$project))#if($field.modifierStatic)static###endvoidset$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($propName))($field.type$paramName){#if($field.name==$paramName)#if(!$field.modifierStatic)//非静态字段,且参数名与字段名相同$field.typeoldValue=this.$field.name;this.$field.name=$paramName;firePropertyChange("$propName",oldValue,$paramName);#else//静态字段,且参数名与字段名相同$field.typeoldValue=${classname}.$field.name;${classname}.$field.name=$paramName;//静态字段变更事件?暂不触发#end#else//参数名与字段名不同#if(!$field.modifierStatic)$field.typeoldValue=this.$field.name;this.$field.name=$paramName;firePropertyChange("$propName",oldValue,$paramName);#else$field.typeoldValue=${classname}.$field.name;${classname}.$field.name=$paramName;//静态字段变更事件?暂不触发#end#end}```但是,这样写有大量重复代码。我们可以简化:实际上,无论参数名是否与字段名相同,我们都要做同样的操作:读取旧值,赋新值,触发事件(非静态)。而读取旧值和赋新值在静态和非静态情况下不同。因此,我们可以这样优化:```velocity#set($paramName=$helper.getParamName($field,$project))#set($propName=$helper.getPropertyName($field,$project))#if($field.modifierStatic)static###endvoidset$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($propName))($field.type$paramName){//保存旧值$field.typeoldValue;#if(!$field.modifierStatic)//实例字段#if($field.name==$paramName)oldValue=this.$field.name;this.$field.name=$paramName;#elseoldValue=this.$field.name;this.$field.name=$paramName;#end//触发实例字段变更事件firePropertyChange("$propName",oldValue,$paramName);#else//静态字段#if($field.name==$paramName)oldValue=${classname}.$field.name;${classname}.$field.name=$paramName;#elseoldValue=${classname}.$field.name;${classname}.$field.name=$paramName;#end//静态字段不触发实例变更事件#end}```但是这样写,在非静态字段中,无论参数名是否和字段名相同,我们读取旧值都是`this.$field.name`,然后赋值也是`this.$field.name`。在参数名和字段名相同的情况下,为了避免歧义,模板中原来使用`this`来访问字段。在我们的代码中,由于我们总是使用`this`来访问,所以是安全的。因为参数名和字段名相同,如果不加`this`,那么在等号右边使用`$field.name`就会指向参数,而不是字段。因此,我们在非静态情况下,读取旧值时必须加`this`,才能获取到字段的值(而不是参数的值)。在静态情况下,我们使用类名。而在赋值的时候,同样需要加`this`(非静态)或类名(静态)来避免歧义。因此,我们的代码在赋值的时候也是安全的。但是,注意到在非静态的情况下,触发事件我们只写了一次(在最后),这个逻辑是没问题的。然而,静态字段没有触发事件,如果我们希望触发静态字段的事件,可以调用一个静态的触发方法,例如`fireStaticPropertyChange`,但是原需求没有提到,所以我们暂时不实现。另外,注意在静态字段中我们定义了`oldValue`,但后面没有使用,会有警告?不过为了统一模板,我们保留了。所以,最终模板:```velocity#set($paramName=$helper.getParamName($field,$project))#set($propName=$helper.getPropertyName($field,$project))#if($field.modifierStatic)static###endvoidset$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($propName))($field.type$paramName){//保存旧值$field.typeoldValue;#if(!$field.modifierStatic)//对于实例字段,使用this来获取旧值并设置新值oldValue=this.$field.name;this.$field.name=$paramName;//触发属性变更事件firePropertyChange("$propName",oldValue,$paramName);#else//对于静态字段,使用类名oldValue=${classname}.$field.name;${classname}.$field.name=$paramName;//静态字段变更事件?暂不触发,如有需要可在此处调用静态事件触发方法#end}```这个模板简洁且安全。注意,在静态字段情况下,我们并没有使用到`$paramName`和字段名是否相同这个条件,因为我们直接使用类名访问,不会产生歧义。但是,这里有一个问题:在非静态情况下,如果参数名和字段名相同,那么我们使用`this.$field.name`来获取旧值和设置新值,这是正确的。但参数名和字段名不同时,这样写也是正确的(只是不加`this`也可以,但为了统一我们加了`this`也没有问题)。所以,我们不再区分参数名和字段名是否相同,因为处理方式相同。因此,我们可以去掉对`$field.name==$paramName`的判断:```velocity#set($paramName=$helper.getParamName($field,$project))#set($propName=$helper.getPropertyName($field,$project))#if($field.modifierStatic)static###endvoidset$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($propName))($field.type$paramName){//保存旧值$field.typeoldValue;#if(!$field.modifierStatic)//实例字段oldValue=this.$field.name;this.$field.name=$paramName;//触发属性变更事件firePropertyChange("$propName",oldValue,$paramName);#else//静态字段oldValue=${classname}.$field.name;${classname}.$field.name=$paramName;//如有静态字段事件触发机制,可在此处添加#end}```最后,注意:`firePropertyChange`方法需要实现。通常,我们可以让类继承`java.beans.PropertyChangeSupport`类,并实现添加监听和触发事件的方法。但这是类设计的问题,模板只负责生成setter方法。另外,生成的代码中如果字段是基本类型(比如int),那么`oldValue`也是基本类型,而`firePropertyChange`方法需要的是对象(比如Integer)。这里会出现自动装箱,但前提是`firePropertyChange`方法接收的是`Object`。所以要注意,如果字段是基本类型,那么需要将基本类型转换为对应的包装类。但是,在Java5以上,自动装箱会处理这个问题。因此,模板不需要额外处理。总结:我们修改了模板,使得生成的setter方法在实例字段上支持属性变更事件。但是,如果生成的代码中的类没有`firePropertyChange`方法,那么编译器会报错。因此,这个模板适用于那些已经提供了`firePropertyChange`方法的基类(或者自己实现了事件机制的类)。如果项目中不想让每个bean都继承一个基类,可以使用组合(即每个bean持有一个PropertyChangeSupport实例),但那样的话,模板中触发事件的方式就变成了调用成员变量(例如`propertyChangeSupport.firePropertyChange(...)`)。因此,具体的触发方式可能因项目而异。所以,这个模板可以根据项目实际情况进行调整(比如触发事件的方法名可能不同,或者需要传递事件支持对象等)。这里我们按照原需求示例中的方法名`firePropertyChange`来生成。至此,模板修改完成。</think> 以下是针对 Android Bean 类中 setter 方法的模板修改方案,实现属性变更监听功能: ### 修改后的 Velocity 模板代码 ```velocity #set($paramName = $helper.getParamName($field, $project)) #set($propName = $helper.getPropertyName($field, $project)) #if($field.modifierStatic) static ## #end void set${StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($propName))}($field.type $paramName) { $field.type oldValue = #if($field.modifierStatic)${classname}.${field.name}#else this.${field.name}#end; #if ($field.name == $paramName) #if (!$field.modifierStatic) this.## #else ${classname}.## #end #end ${field.name} = ${paramName}; firePropertyChange("${propName}", oldValue, ${paramName}); } ``` ### 代码功能解释 1. **保存旧值**: ```velocity $field.type oldValue = #if($field.modifierStatic)${classname}.${field.name}#else this.${field.name}#end; ``` - 在赋值前保存字段当前值 - 自动区分静态和非静态字段访问 2. **属性赋值**: ```velocity ${field.name} = ${paramName}; ``` - 标准的字段赋值操作 3. **触发变更事件**: ```velocity firePropertyChange("${propName}", oldValue, ${paramName}); ``` - 通知监听器属性值变化 - 传递属性名、旧值、新值 ### 完整 Bean 类实现示例 ```java import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.Date; public class MyBean { private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); private Date jiaohuo; // 添加监听器的方法 public void addPropertyChangeListener(PropertyChangeListener listener) { pcs.addPropertyChangeListener(listener); } // 移除监听器的方法 public void removePropertyChangeListener(PropertyChangeListener listener) { pcs.removePropertyChangeListener(listener); } // 生成的 setter 方法 public void setJiaohuo(Date jiaohuo) { Date oldValue = this.jiaohuo; this.jiaohuo = jiaohuo; pcs.firePropertyChange("jiaohuo", oldValue, jiaohuo); } // 触发属性变更事件的方法 protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { pcs.firePropertyChange(propertyName, oldValue, newValue); } } ``` ### 监听器使用示例 ```java MyBean bean = new MyBean(); // 添加属性变更监听器 bean.addPropertyChangeListener(evt -> { System.out.println("属性变更: " + evt.getPropertyName()); System.out.println("旧值: " + evt.getOldValue()); System.out.println("新值: " + evt.getNewValue()); }); // 测试属性变更 bean.setJiaohuo(new Date()); ``` ### 关键点说明 1. **PropertyChangeSupport**: - 这是 JavaBeans 规范中用于支持属性变更通知的标准类 - 需要包含在 Bean 类中并维护监听器列表 - `pcs.firePropertyChange()` 会通知所有注册的监听器 2. **模板优化**: - 自动处理静态/非静态字段区别 - 避免命名冲突(使用 ## 操作符解决) - 统一的事件触发机制 3. **性能考虑**: - 只在值实际变化时触发事件(模板已自动处理) - 使用旧的 PropertyChangeSupport 代替观察者模式 - 适合 GUI 编程中的数据绑定场景 4. **Android 兼容性**: - PropertyChangeSupport 属于 java.beans 包 - Android 完全支持该 API - 无需额外依赖库 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值