3

I would like to modularize my JSF 2.1 / Java EE application. I have a page that consists of several modules. Each module should use a separate JSF backing bean. Some of these modules need to show and change some data from/on the same entity.

I've already tried some approaches but I am not satisfied so far. I ask myself what the best way to do this would be?

All modules use the same entity and they (probably) need to notify the other backing beans if some data has changed.

Comments to some approaches that I've already tried:

  • Passing the entity to my component (XHTML) via interface will not pass it to the backing bean as well
  • Loading the entity in the bean's postContruct method by reading an id from the request paramters is commonly discouraged in favour of using the "viewParam" approach
  • Using "viewParam" is IMHO not as good as having the entity after bean creation (in my postConstruct). I'm not sure when the bean's setter is invoked.
  • Why not just save the entity object in the session and use it across all beans. – Adarsh Dec 09 '13 at 10:07
  • I don't want to bloat the session. I'm looking for a solution that better fits into the JSF framework. Storing something in the session sounds like a workaround. – Christopher Cudennec Dec 09 '13 at 12:05
  • Definitely you need a `@SessionScoped`/`@ApplicationScoped` managed bean. About the beans being notificated, you need to be more specific. – Aritz Dec 09 '13 at 13:56
  • Why? RequestScope and ViewScope seem more than enough for my needs. I want to achieve that all beans work with the same entity instance although one bean might change some parts of it in an action. – Christopher Cudennec Dec 09 '13 at 13:58

2 Answers2

0

Despite it being "commonly discouraged" (by whom?), I'm using something one of the following techniques in my JSF-2.2 backing beans (depending on whether I need personId for something else):

@ViewScoped
public class BeanConverter {
    @Inject @Param(name = "personId")
    private ParamValue<Person> curPerson;
}

@ViewScoped
public class BeanConstruct {
    @PersistenceContext
    private EntityManager em;

    @Inject @Param
    private ParamValue<Long> personId;

    private Person curPerson;

    @PostConstruct
    public void init() {
        curPerson = em.find(Person.class, personId.getValue());
    }
}

This uses the excellent CDI support of Omnifaces. I then use merge() to update the entity, but in my case only one bean gets to save changes to the entity, so YMMV. When beans need to communicate updates or entity creations among themselves, I usually go for javax.enterprise.Events where the event gets the entity as a constructor argument:

public class BeanSending {
    @Inject
    private Event<PersonCreated> personCreatedEvent;

    public void constructPerson() {
        Person person = makePerson();
        personCreatedEvent.fire(new PersonCreated(person));
    }
}

public class BeanUpdater {
    public void updatePerson(@Observes PersonCreated evt) {
        doStuffWithPerson(evt.getPerson());
    }
}
mabi
  • 5,279
  • 2
  • 43
  • 78
  • I used "discouraged" for reading the request parameters directly from the FacesContext. It does not refer to your solution injecting the property with @Param. – Christopher Cudennec Dec 09 '13 at 12:03
  • @ChristopherCudennec yeah, but `@Param` is just (some very candid) sugar on top. The principle is that all beans agree on a set of parameter names so they can get the same entity to work on. – mabi Dec 09 '13 at 12:15
  • Nice. I like that the value is injected into the bean. On the other hand the bean knows about the name of the parameter. – Christopher Cudennec Dec 09 '13 at 13:43
  • @ChristopherCudennec yes, that's a downside. But the mapping from name to value has happen *somewhere*. If you want to stay really DRY, you can probably collect all those parameter name strings in a `enum`. – mabi Dec 09 '13 at 14:07
0

I think what you need is the CDI - Context & Dependency Injection. Just chop up your page into a number of smaller CDI beans and inject them into each other as you need.

Mr.J4mes
  • 9,168
  • 9
  • 48
  • 90
  • Managing the page is not the part that I'm interested in with my question. All my backing beans are already CDI beans that can be injected. I'm interested in sharing the entity. Let's say the entity is a customer that I get from my database. – Christopher Cudennec Dec 09 '13 at 13:03
  • @ChristopherCudennec: If you're already using CDI beans, just inject the customer in 1 of the bean X and then inject that bean X into other beans to share the same customer. – Mr.J4mes Dec 09 '13 at 13:44
  • I've already tried that. I'd prefer the beans to be autonomous, i.e. they should not know each other. – Christopher Cudennec Dec 09 '13 at 13:47
  • @ChristopherCudennec: It seems you're trying to break your app into as many small independent beans as possible and then mix the beans in different combinations for different purposes/pages. Still, injection is meant to help beans share their resources in a self-documenting manner. If you don't want them to know each other, you must use a middle-man such as `HttpSession`'s attribute to store the common customer. It's gonna be ugly and messy :) – Mr.J4mes Dec 09 '13 at 16:03