access existing instance stateful inside stateless

2020-01-31 11:58发布

问题:

Is it possible to access a stateful session bean inside a stateless bean?

My problem is that I have a session bean called User and I want to access user info inside a stateless bean...

I am trying like this:

Ejb Side:

@Stateless
public class OfferManagerBean implements OfferManagerLocal, OfferManager
{
    @Resource 
    private SessionContext context;
    @EJB
    private ro.project.ejb.interfaces.User user;
    public String getUsername()
    {
        user = (ro.project.ejb.interfaces.User) context.lookup("java:global/project/projectEJB/User!ro.project.ejb.interfaces.User");
        return user.getUsername();
}

Client side

 User user = (User) ctx.lookup("java:global/project/projectEJB/User!ro.project.ejb.interfaces.User");
 user.setUsername("Alex");

 OfferManager offerManager = (OfferManager) ctx.lookup("java:global/project/projectEJB/OfferManagerBean!ro.project.ejb.interfaces.OfferManager");
 assertEquals(offerManager.getUsername(), "Alex");

The result of this test case is java.lang.AssertionError: expected:<null> but was:<Alex>

it fails.. It seems that how I am requesting the stateful bean is returning me a new instance...

  1. I know why this is not working. Because my test fails :P. I get a new instance..
  2. I want to check certain permissions of the logged in user in EJB because I don't want to count on the client side because I might do a mistake there or I will tell other developers to make a GUI for my project..
  3. I don't want to use Java EE Security beucause I don't know how to make the login in a RCP Application
  4. My main question is: How do I access a session bean (the same one the client own) inside an EJB.. is it possible? And how?

I am asking almost the same thing this guy is asking: Concept for reusable login session in rmi ejb calls

I want to do that but not with JAAS...

Thank you in advance

回答1:

You should never inject a @Stateful bean (SFSB) in a @Stateless bean (SLSB). A SFSB lives as long as its client lives (the client is the instance where the SFSB is been injected, which is in this case the SLSB itself). SLSB's are however intented to be stateless and most containers have them in a pool. So whenever the SLSB goes back to the pool after use, it will be reused entirely elsewhere, but it holds the same SFSB instance as it was when the SLSB was been created for the first time! This may lead to undesireable results.

Also, everytime when you get the SFSB from JNDI, you will get a brand new instance which is unlike SLSBs not shared elsewhere. The SFSB's client is then the current client class instance where you've got the SFSB from JNDI. You're supposed to keep hold of this instance yourself and reuse the very same instance until you're finished with performing the transaction on it. One of the ways is storing it in the HTTP session yourself or in a session scoped managed bean of the MVC framework you're using.

The functional requirement is not entirely clear to me, so it's hard to give a suitable answer how to solve your particular problem, but I have the impression that you actually need to store the user in the HTTP session, not in a SFSB. The most common beginner's mistake as to session beans is namely that they incorrectly interpret the "session" in EJB context as being the HTTP session.

See also this related answer on a question of the same kind for a more in-depth explanation: JSF request scoped bean keeps recreating new Stateful session beans on every request? According to your question history you're familiar with JSF, so this answer should be easy understandable.



回答2:

In general it is possible to access certain existing stateful session bean inside stateless bean. It can be for example given as argument to the business method of stateless session bean.

But what you are trying cannot work. Reason is that both, dependendy injection (@EJB ) and lookup (ctx.lookup...) are guaranteed to call newInstance and as consequence you will have new instance.

This is explained in specification with following words:

A session bean instance’s life starts when a client obtains a reference to a stateful session bean instance through dependency injection or JNDI lookup, or when the client invokes a create method on the session bean’s home interface. This causes the container to invoke newInstance on the session bean class to create a new session bean instance.



回答3:

In case the others weren't clear enough already: You're doing it wrong! ;)

I agree that it might be confusing, but the only session in EJB session beans is kept in the proxy bean that you obtain from the InititalContext.

Different beans that you obtain from this context do not share any kind of common session. In EJB, beans are not stored IN an EJB session but they ARE this session.

In other words, the InitialContext (ctx in your code) is NOT the EJB equivalence for the HttpSession.

Even worse perhaps is that in your code the User IS an EJB bean. This is WRONG.

A user is a noun in your application. These are represented by JPA entities or simple 'normal' java beans. EJBs are for implementing the verbs in your application: Services, DAOs, Repositories, etc.

The state in stateful session beans is supposed to hold on to model data during a business process ( for caching, locking, reservations, etc purposes). In no situation should this state BE the model data.

My advice: let your current 'design' go. Don't try to fix it, don't try to justify it. Let it go, delete your code, don't look back. Read some good books about EJB and then start over.

Good luck!



回答4:

I am not validating whether you are doing right/wrong in the usage of SFSB & SLSB. But below is the answer to your problem

Option-1 : From your servlet, perform JNDI lookup for SFSB. This should be one time. Store the returned reference of SFSB in your HttpSession. When you call the SLSB method which needs the SFSB instance for storing the user details, pass the above SFSB reference object as parameter to the SLSB method. Then access it from SLSB method and store user data.

Issue with Option-1 : You are tying couple your persistence mechanism (SFSB) to your UI code (since you store it in HttpSession and pass it around). Tomorrow, if you want to change to another persistence mechanism like a cache, you will need to do a lot of rework. Again, if you want to expose your SLSB method as a WebService, you won't be able to do that as you cannot xml-ize the SFSB object reference. In short, a bad option.

Option-2 : Have a static hashmap in some class in the business tier. Assume you have some unique transaction attributes for every transaction, something like an ID. When you start transaction, from your Business Tier, create the SFSB, store its reference in the static hashmap with ID as key. When you call the SLSB service method, pass this ID along (I assume each transaction can have a unique ID). From SLSB method, use the ID to lookup SFSB reference stored in the static hashmap. Use it for your storage. In this option, your UI & SFSB is not coupled. Tomorrow, if you want to chnage your persistence mechanism, you can do with out impacting clients as your changes will be limited within BC tier.