升级至JSF 2.3后带属性/注入的自定义转换器失效问题
这个问题的核心在于JSF 2.3中@FacesConverter(managed=true)的转换器是由CDI托管的,而JSF仅会对非托管转换器(即JSF自身实例化的)调用setter方法来设置标签属性。当你启用managed=true时,转换器的生命周期交给了CDI,JSF不会再通过反射调用它的setter,导致标签中的attr属性无法被赋值;而关闭managed=true后,JSF接管实例化,setter能被调用,但CDI的依赖注入(比如BeanManager)就失效了。
下面提供两种可行的解决方案:
方案一:从UIComponent中直接获取标签属性(推荐)
保持@FacesConverter(managed=true)的配置,在转换器的转换方法中,从传入的UIComponent对象的属性集合里获取标签上的attr值,不需要依赖setter方法。修改后的转换器代码如下:
@Dependent @FacesConverter(value = "cdijsf.ConverterWithAttr", managed = true) public class ConverterWithAttr implements Converter<String> { @Inject private BeanManager beanManager; @PostConstruct private void init() { // BeanManager此时会被正常注入 } @Override public String getAsObject(FacesContext context, UIComponent component, String value) { // 从组件属性中获取标签里的attr值 String attr = (String) component.getAttributes().get("attr"); // 在这里使用attr做逻辑处理 return value; } @Override public String getAsString(FacesContext context, UIComponent component, String value) { // 同样可以在这里获取attr值 String attr = (String) component.getAttributes().get("attr"); return value; } }
这种方式的优势是完全适配CDI托管的转换器生命周期,同时能动态获取标签上的属性值,不需要额外的配置修改。
方案二:将转换器改为CDI命名Bean,通过标签绑定传递属性
如果你更倾向于保留setter方法的方式,可以将转换器定义为CDI命名Bean,不再使用@FacesConverter注解,然后在页面或标签库中通过绑定的方式使用它:
1. 修改转换器代码
移除@FacesConverter,添加@Named注解,保持CDI的依赖注入和setter方法:
@Dependent @Named("converterWithAttr") public class ConverterWithAttr implements Converter<String> { @Inject private BeanManager beanManager; private String attr; @PostConstruct private void init() { // BeanManager正常注入 } @Override public String getAsObject(FacesContext context, UIComponent component, String value) { return value; } @Override public String getAsString(FacesContext context, UIComponent component, String value) { return value; } public String getAttr() { return attr; } public void setAttr(String attr) { this.attr = attr; } }
2. 修改标签库定义
将<converter>标签替换为<component>,通过绑定方式引用CDI bean,并传递属性:
<facelet-taglib version="2.3" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_3.xsd"> <namespace>http://cdijsf.transdata.net/jsf</namespace> <tag> <tag-name>converterWithAttr</tag-name> <component> <component-type>javax.faces.Converter</component-type> <binding>#{converterWithAttr}</binding> </component> <attribute> <name>attr</name> <type>java.lang.String</type> <targets>javax.faces.Converter</targets> </attribute> </tag> </facelet-taglib>
这种方式通过CDI bean绑定来实现属性传递,setter方法会被正常调用,同时依赖注入也能正常工作。
为什么会出现这个问题?
在JSF 2.2及更早版本中,转换器默认由JSF自身实例化,会自动调用setter来设置标签属性;而JSF 2.3引入了managed=true选项,允许转换器由CDI托管,此时JSF会委托CDI创建转换器实例,不再负责属性的设置——因为CDI的注入模型和JSF的属性设置模型是分离的,所以导致了setter不被调用的情况。
内容的提问来源于stack exchange,提问作者Heiko Tappe

