JSF基于事件的沟通:过时的方法

用JSF编写的Web应用程序由相互交互的bean组成。 在开发Web应用程序时,bean之间的通信是主要的设计模式之一。 有时,一个bean需要向其他bean发送事件,以通知它们某些更改或其他任何更改。 我们通常可以将托管bean或Spring bean注入另一个bean的属性中,以便另一个bean可以直接通知注入的bean。 注入是好的,但是它并不是出于交流目的而引入的。 它与动态的松散耦合系统相距甚远,在该系统中,每个bean都不了解其他bean。 在松耦合系统中,我们需要一种基于事件的良好通信机制。 这篇文章将涵盖两种设计模式:观察者/事件监听器和中介者模式。 这些模式如今已在许多Web应用程序中广泛使用,但是它们具有缺点。 该系统并不是真正与它们松散耦合。 有很多更好的现代方法。 因此,我在帖子名称中写了“ Old-school approach”。 新学校的方法将在下一篇文章中公开。

观察员/事件听众
 

我们将从观察者(也称为事件监听器)模式开始。 一个称为主题或可观察对象的对象会维护其依赖项的列表(称为观察者),并自动将状态变化通知他们。 在Java中,有一些类java.util.Observer和java.util.Observable可以帮助实现此模式。 通过此模式进行的基于事件的通信的其他相关构造是类java.util.EventObject和接口java.util.EventListener。 让我们开始编码。 假设我们有一个I18N Web应用程序,并且用户可以在用户设置中的某处选择一种语言(语言环境)。 假设我们有一个名为UserSettingsForm的bean,它负责用户设置。 某些会话作用域的Bean可以保留I18N文本/消息,因此,当用户更改当前语言时,需要以最后选择的语言重置以前的文本/消息。 首先,我们需要一个LocaleChangeEvent。
public class LocaleChangeEvent extends EventObject {
    
    Locale locale;

    public LocaleChangeEvent(Object source, Locale locale) {
        super(source);
        this.locale = locale;
    }

    public Locale getLocale() {
        return locale;
    }
}

其次,我们需要一个接口LocaleChangeListener。

public interface LocaleChangeListener extends EventListener {
    
    void processLocaleChange(LocaleChangeEvent event);
}
我们的UserSettingsForm现在可以通过注册字符串并通知它们来管理LocaleChangeListener类型的实例。
@ManagedBean
@SessionScoped
public class UserSettingsForm implements Serializable {

    private Locale selectedLocale;
    private List<SelectItem> locales;
    private List<LocaleChangeListener> localeChangeListeners = new ArrayList<LocaleChangeListener>();

    public void addLocaleChangeListener(LocaleChangeListener listener) {
        localeChangeListeners.add(listener);
    }

    public void localChangeListener(ValueChangeEvent e) {
        ...
        // notify listeners
        LocaleChangeEvent lce = new LocaleChangeEvent(this, this.selectedLocale);
        for (LocaleChangeListener lcl : localeChangeListeners) {
            lcl.processLocaleChange(lce);
        }
    }
    ...
}
方法localChangeListener()是JSF ValueChangeListener,可以在例如h:selectOneMenu中应用。 每个实现LocaleChangeListener的bean都应该由UserSettingsForm注册,以便通过语言环境更改得到通知。
@ManagedBean
@SessionScoped
public MyBean implements LocaleChangeListener, Serializable {

    // UserSettingsForm can be injected e.g. via @ManagedProperty annotation or via Spring facility
    private UserSettingsForm userSettingsForm;

    @PostConstruct
    public void initialize() {
        userSettingsForm.addLocaleChangeListener(this);
    }

    public void processLocaleChange(LocaleChangeEvent event) {
        // reset something related to I18N data
        ...
    }
}

就观察者模式而言,UserSettingsForm是可观察的,而LocaleChangeListener的实例(如MyBean)则是观察者。 讨论的模式带有一些您需要注意的重要问题。 豆紧密耦合。 有很多手动工作来重新注册bean。 Bean必须实现定义的接口。 如果您有100个语义不同的更改通知了bean,则它必须实现100个接口。 无法通知已注册的侦听器的子集–即使不需要通知所有侦听器,也总是会通知他们。 最后但并非最不重要的– 内存管理问题 。 马丁·福勒(Martin Fowler)写道: “假设我们有一些观察某些域对象的屏幕。 关闭屏幕后,我们希望将其删除,但是域对象实际上通过观察者关系携带了对屏幕的引用。 在内存管理的环境中,寿命长的域对象可能会占据很多僵尸屏幕,从而导致大量内存泄漏。”

调解员
 

与“观察者/事件侦听器”模式相比,“中介者”模式改善了基于事件的通信。 使用中介者模式,对象之间的通信将与中介者对象一起封装。 对象不再彼此直接通信,而是通过调解器进行通信。 这减少了通信对象之间的依赖性。 我们将看到它如何用于JSF-Spring Bean(在上面的示例中是标准托管Bean)。 我们将实现一个Mediator类来管理作用域bean之间的通信。 重要的是要理解一个bean只能通知范围更广的另一个bean。 视图作用域的bean可以通知视图作用域的会话,会话作用域和应用程序作用域的bean,但不能请求作用域较小的作用域的bean。 请遵循此规则以避免麻烦。 这是作用域Bean的一种特性–您可能还记得,可以始终将作用域更广的bean注入到作用域更窄的bean中,反之亦然。 为了开始使用Mediator,我们将引入两个接口MediatorEvent,MediatorListener和中心类Mediator。
public interface MediatorEvent {
    ...
}

public interface MediatorListener {

    public void listenToEvent(MediatorEvent event);
}

public class Mediator implements Serializable {

    private Collection<MediatorListener> collaborators = new HashSet<MediatorListener>();

    public static Mediator getCurrentInstance() {
        // access Mediator bean by JSF-Spring facility
        return ContextLoader.getCurrentWebApplicationContext().getBean("mediator");
    }

    public void fireEvent(MediatorEvent event) {
        for (MediatorListener mediatorListener : collaborators) {
            mediatorListener.listenToEvent(event);
        }
    }

    public void addCollaborator(MediatorListener collaborator) {
        collaborators.add(collaborator);
    }

    public void removeCollaborator(MediatorListener collaborator) {
        collaborators.remove(collaborator);
    }
}
介体是一个有作用域的bean,可以注册并通知协作者。 协作者通过调解员进行注册。 在Spring中,bean可以实现接口InitializingBean,以便在bean实例化之后自动调用afterPropertiesSet()方法。 这类似于@PostConstruct。 afterPropertiesSet()是此类bean通过介体注册的正确位置。 Bean还应该实现MediatorListener以便被通知(请参见listenToEvent())。
public MyBean implements MediatorListener, InitializingBean, Serializable {

    public void afterPropertiesSet() throws Exception {
        ...
        Mediator.getCurrentInstance().addCollaborator(this);
    }

    @Override
    public void listenToEvent(MediatorEvent event) {
        if (event instanceof LocaleChangeEvent) {
            // do something
        }
    }
}
我们将在UserSettingsForm和区域设置更改中使用相同的方案。 由Mediator注册的Bean将通过fireEvent()进行通知。
public class LocaleChangeEvent implements MediatorEvent {
    ...
}

public class UserSettingsForm implements Serializable {

    private Locale selectedLocale;
    private List<SelectItem> locales;

    public void localChangeListener(ValueChangeEvent e) {
        ...
        // notify listeners
        Mediator.getCurrentInstance().fireEvent(new LocaleChangeEvent(this, this.selectedLocale));
    }
    ...
}
调解器模式提供了豆之间更好的耦合,但是它们仍与调解器耦合。 进一步的缺点:仍然需要手动注册bean –请参见附加代码Mediator.getCurrentInstance()。addCollaborator(this)。 每个bean仍应至少实现一个MediatorListener,这会带来另一个约束– listenToEvent()。 每个bean都应实现此接口方法! JSF中介体模式的最大缺点可能是它是有作用域的bean。 视图作用域调解器只能与视图作用域的bean一起顺利使用。 当视图作用域调解器被销毁时,注册的视图作用域Bean将自动删除。 其他情况可能会导致内存泄漏或几个问题。 例如,应该通过调用removeCollaborator()手动删除由视图作用域介体注册的请求作用域Bean(很容易忘记)。 会话作用域的Bean应该由会话作用域的介体注册,否则销毁视图作用域的介体后,它们将不会得到通知。 等等
实际上,中介器模式仅比常规的“观察者/事件侦听器”概念好一步。 有更灵活的方法,其中“任何方法”都可以捕获引发的事件,而不仅可以修复指定的问题,例如listenToEvent()。 在下一篇文章中,我们将看到简单而简单的方法,如何仅通过一种方法和其他建议来捕获乘法事件。

参考: JSF中基于事件的通信。 我们的JCG合作伙伴 Oleg Varaksin在“ 软件开发思想”博客上的过时做法


翻译自: https://www.javacodegeeks.com/2012/07/jsf-event-based-communication-old.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于Spring Boot整合JSF,你可以按照以下步骤进行操作: 1. 添加JSF依赖:在pom.xml文件中,添加JSF的依赖项,如下所示: ```xml <dependency> <groupId>javax.faces</groupId> <artifactId>javax.faces-api</artifactId> <version>2.3</version> </dependency> ``` 2. 配置JSF Servlet:在Spring Boot的配置类中,添加以下配置,以将JSF Servlet映射到`/javax.faces`路径下: ```java import javax.faces.webapp.FacesServlet; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JsfConfig { @Bean public ServletRegistrationBean<FacesServlet> facesServletRegistration() { ServletRegistrationBean<FacesServlet> registration = new ServletRegistrationBean<>(new FacesServlet(), "*.jsf"); registration.setName("FacesServlet"); return registration; } } ``` 3. 配置JSF View Resolver:在Spring Boot的配置类中,添加以下配置,以将JSF视图解析为`.xhtml`文件: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration public class JsfConfig { @Bean public ViewResolver internalResourceViewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setViewClass(JsfView.class); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".xhtml"); return resolver; } } ``` 4. 创建JSF视图:在`/WEB-INF/views/`目录下创建JSF视图文件,例如`hello.xhtml`,并在其中定义JSF组件。 现在,你已经成功地将Spring Boot与JSF整合起来了。你可以在JSF视图中使用JSF的标签和组件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值