Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 25 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
25
Dung lượng
429,46 KB
Nội dung
Building Java™ Enterprise Applications Volume I: Architecture 157 public float getBalance(int accountId) throws RemoteException { return getAccount(accountId).getBalance( ); } private Account getAccount(int id) { try { // Get an InitialContext Context context = new InitialContext( ); // Look up the Account bean AccountHome accountHome = (AccountHome) context.lookup("java:comp/env/ejb/AccountHome"); Account account = accountHome.findByPrimaryKey(new Integer(id)); return account; } catch (Exception e) { // Any problems - just return null return null; } } } This is all basic EJB material, and shouldn't cause you any problems. You'll notice that this class also uses a new finder method on the Account bean: public Collection findByUserId(Integer userId) throws FinderException, RemoteException; The accompanying query element in the Account bean's entry in the ejb-jar.xml descriptor would look like this: <query> <query-method> <method-name>findByUserId</method-name> <method-params> <method-param>java.lang.Integer</method-param> </method-params> </query-method> <ejb-ql> <![CDATA[WHERE userLocal.id = ?1]]> </ejb-ql> </query> To deploy the AccountManager bean, you would use this (additional) XML entry in your ejb- jar.xml deployment descriptor: <session> <description> This AccountManager bean allows administration of Forethought accounts. </description> <ejb-name>AccountManagerBean</ejb-name> <home>com.forethought.ejb.account.AccountManagerHome</home> <remote>com.forethought.ejb.account.AccountManager</remote> <ejb-class>com.forethought.ejb.account.AccountManagerBean</ejb-class> <session-type>Stateful</session-type> <transaction-type>Container</transaction-type> Building Java™ Enterprise Applications Volume I: Architecture 158 <ejb-ref> <ejb-ref-name>ejb/AccountHome</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.forethought.ejb.account.AccountHome</home> <remote>com.forethought.ejb.account.Account</remote> <ejb-link>AccountBean</ejb-link> </ejb-ref> </session> Additions to your application server's vendor-specific descriptors should be equally simple. With this bean in stateful form ready for use, it's time to see how it can be turned into a better- performing stateless session bean. 8.3.2 Going Stateless To move this bean into stateless territory, you first need to change the home interface's create( ) signature. Since stateless beans can't maintain any information between method calls, passing in a username (or any other data) to the create( ) method is useless. Make the following change: public AccountManager create( ) throws CreateException, RemoteException; Once this change has been made, you need to determine which methods advertised by the bean require a username for operation. In other words, browse through your bean's implementation class and note any method that uses the username or user method variable. Once you've determined the methods in this category, you will need to change the signature for those methods in the remote interface: public interface AccountManager extends EJBObject { public AccountInfo add(String username, String type, float balance) throws RemoteException, UnknownAccountTypeException; public AccountInfo get(int accountId) throws RemoteException; public List getAll(String username) throws RemoteException; public AccountInfo deposit(AccountInfo accountInfo, float amount) throws RemoteException; public AccountInfo withdraw(AccountInfo accountInfo, float amount) throws RemoteException; public float getBalance(int accountId) throws RemoteException; public boolean delete(int accountId) throws RemoteException; } In this case, only two methods require this information, so it's not terribly inconvenient. However, in many cases conversion from stateful to stateless requires a parameter to be added to ten, twenty, or more methods. Even though this example is somewhat trivial, I want to continue the discussion assuming that it is a major issue to have to keep the username around for these multiple method calls. Before getting to the solution, though, you'll need to update Building Java™ Enterprise Applications Volume I: Architecture 159 your bean implementation class to operate without maintaining state. First, add a utility method to the end of the class: private User getUser(String username) throws RemoteException { try { // Get an InitialContext Context context = new InitialContext( ); // Look up the Account bean UserHome userHome = (UserHome) context.lookup("java:comp/env/ejb/UserHome"); User user = userHome.findByUserDn(LDAPManager.getUserDN(username)); return user; } catch (NamingException e) { throw new RemoteException("Could not load underlying User bean."); } catch (FinderException e) { throw new RemoteException("Could not locate specified user."); } } Then remove the username and user member variables, and modify three methods (those affected by the change to stateless): public void ejbCreate( ) throws CreateException { // Nothing to be done for stateless beans } public AccountInfo add(String username, String type, float balance) throws UnknownAccountTypeException { try { // Get an InitialContext Context context = new InitialContext( ); // Get the correct user User user = getUser(username); // Look up the Account bean AccountHome accountHome = (AccountHome) context.lookup("java:comp/env/ejb/AccountHome"); Account account = accountHome.create(type, balance, user); return account.getInfo( ); } catch (RemoteException e) { return null; } catch (CreateException e) { return null; } catch (NamingException e) { return null; } } public List getAll(String username) { List accounts = new LinkedList( ); try { User user = getUser(username); Integer userId = user.getId( ); // Get an InitialContext Context context = new InitialContext( ); Building Java™ Enterprise Applications Volume I: Architecture 160 // Look up the Account bean AccountHome accountHome = (AccountHome) context.lookup("java:comp/env/ejb/AccountHome"); Collection userAccounts = accountHome.findByUserId(userId); for (Iterator i = userAccounts.iterator(); i.hasNext( ); ) { Account account = (Account)i.next( ); accounts.add(account.getInfo( )); } } catch (Exception e) { // Let fall through to the return statement } return accounts; } Finally, don't forget to change your deployment descriptor: <session> <description> This AccountManager bean allows administration of Forethought accounts. </description> <ejb-name>AccountManagerBean</ejb-name> <home>com.forethought.ejb.account.AccountManagerHome</home> <remote>com.forethought.ejb.account.AccountManager</remote> <ejb-class>com.forethought.ejb.account.AccountManagerBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> <ejb-ref> <ejb-ref-name>ejb/AccountHome</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.forethought.ejb.account.AccountHome</home> <remote>com.forethought.ejb.account.Account</remote> <ejb-link>AccountBean</ejb-link> </ejb-ref> <ejb-ref> <ejb-ref-name>ejb/UserHome</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.forethought.ejb.user.UserHome</home> <remote>com.forethought.ejb.user.User</remote> <ejb-link>UserBean</ejb-link> </ejb-ref> </session> All things considered, these changes are relatively simple to make, and have the net effect of making your bean faster, more efficient, and only marginally harder to use. However, as I mentioned, there are times when the changes to the bean's remote interface are more difficult. Passing in a username or any other piece of data ten, twenty, or more times to a bean's methods can result in pain for the developer, and less-clear code. In these cases, a simple helper class on the client can make a stateless session bean behave just as a stateful one did. Example 8-10 shows this principle in action, detailing the AccountManagerHelper utility class. Building Java™ Enterprise Applications Volume I: Architecture 161 Example 8-10. An AccountManager Helper Class package com.forethought.client; import java.rmi.RemoteException; import java.util.List; import javax.ejb.CreateException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.rmi.PortableRemoteObject; // Account bean import com.forethought.ejb.account.AccountInfo; import com.forethought.ejb.account.AccountManager; import com.forethought.ejb.account.AccountManagerHome; // AccountType bean import com.forethought.ejb.accountType.UnknownAccountTypeException; public class AccountManagerHelper { /** The username for this account's user */ private String username; /** The <code>AccountManager</code> bean instance */ private AccountManager manager; public AccountManagerHelper(String username) throws CreateException, NamingException, RemoteException { this.username = username; Context context = new InitialContext( ); // Get the stateless bean instance Object ref = context.lookup("forethought.AccountManagerHome"); AccountManagerHome accountManagerHome = (AccountManagerHome) PortableRemoteObject.narrow(ref, AccountManagerHome.class); this.manager = accountManagerHome.create( ); } public AccountInfo add(String type, float balance) throws RemoteException, UnknownAccountTypeException { return manager.add(username, type, balance); } public AccountInfo get(int accountId) throws RemoteException { return manager.get(accountId); } public List getAll( ) throws RemoteException { return manager.getAll(username); } public AccountInfo deposit(AccountInfo accountInfo, float amount) throws RemoteException { return manager.deposit(accountInfo, amount); } Building Java™ Enterprise Applications Volume I: Architecture 162 public AccountInfo withdraw(AccountInfo accountInfo, float amount) throws RemoteException { return manager.withdraw(accountInfo, amount); } public boolean delete(int accountId) throws RemoteException { return manager.delete(accountId); } public float getBalance(int accountId) throws RemoteException { return manager.getBalance(accountId); } } Looking at the methods available on this helper class, you should realize pretty quickly that it mirrors the remote interface of the AccountManager session bean; however, it looks like the stateful bean version, rather than the new stateless version. The constructor for the class then takes the place of the old stateful bean's create( ) method from the home interface. This class then maintains a bean instance, the username for the manager, and delegates to the session bean. All of the same exceptions are passed through to the client, so the interface is very similar; the only difference is that context lookups are handled within the helper class. This makes the client code even simpler, as this code fragment shows: // Look up the AccountManager bean System.out.println("Looking up the AccountManager bean."); AccountManagerHelper accountHelper = new AccountManagerHelper("gqg10012"); // Create an account AccountInfo everydayAccount = accountHelper.add("Everyday", 5000); if (everydayAccount == null) { System.out.println("Failed to add account.\n"); return; } System.out.println("Added account.\n"); // Get all accounts List accounts = accountHelper.getAll( ); for (Iterator i = accounts.iterator(); i.hasNext( ); ) { AccountInfo accountInfo = (AccountInfo)i.next( ); System.out.println("Account ID: " + accountInfo.getId( )); System.out.println("Account Type: " + accountInfo.getType( )); System.out.println("Account Balance: " + accountInfo.getBalance( ) + "\n"); } // Deposit accountHelper.deposit(everydayAccount, 2700); System.out.println("New balance in everyday account: " + accountHelper.getBalance(everydayAccount.getId( )) + "\n"); // Withdraw accountHelper.withdraw(everydayAccount, 500); System.out.println("New balance in everyday account: " + accountHelper.getBalance(everydayAccount.getId( )) + "\n"); Building Java™ Enterprise Applications Volume I: Architecture 163 // Delete account accountHelper.delete(everydayAccount.getId( )); System.out.println("Deleted everyday account."); You may find that helper classes like this can simplify your own client code, even if you don't need to provide stateful session bean masquerading, where a stateless bean is made to look like a stateful one. In any case, this approach provides the best of both session bean types: the performance of a stateless bean with the interface of a stateful one. This technique will allow you to convert all of your application's stateful session beans into stateless ones, which will yield some dramatic performance improvements. 8.4 What's Next? You now have the tools to build the back-end of almost any enterprise application you may come across, and apply your knowledge to most of the problems you will encounter in the enterprise Java space. In the next chapter, though, I want to move beyond the basics into the less-used realm of the Java Message Service (and specifically, message-driven beans). Although it is still somewhat unusual to see these kinds of beans in action, you will find that JMS offers several attractive features. I'll detail these and how they can help in asynchronous tasks in the next chapter, which focuses specifically on messaging in enterprise applications. Building Java™ Enterprise Applications Volume I: Architecture 164 Chapter 9. Messaging and Packaging Up until now, everything detailed in the Forethought application has been based on synchronous processing. This simply means that an event is triggered by some client, then responded to by an application component, and finally an answer is returned to that client. For example, when a Java class requests that a new user be created, the UserManager accesses the User bean, that bean interacts with the database, and an acknowledgment is triggered back up the calling stack. The extensive coverage of this type of interaction is justified, as you will be dealing with synchronous processing far more often than not. However, there are times when you want more of a listener paradigm. In this case, an application component waits for certain types of events and responds only when those events occur. That component is called a listener, because it listens for application events. When it is activated, it takes some sort of action, often interacting with various other components in the application. It typically does not send any acknowledgment when its actions are done, making it asynchronous in operation. I'll detail this sort of behavior in this chapter, focusing on the scheduling component of the Forethought application. Meetings will be added to the Forethought queue and reported to a scheduling client, which simply spits these meetings back out to waiting recipients. Additionally, this chapter will wrap up some loose ends by detailing the final packaging of the enterprise beans detailed in this and previous chapters. This will fill in the blanks on assembly descriptors, method permissions, and other deployment descriptor options previously left uncovered. At the end of this chapter, you'll have a complete, working application foundation, ready for use. 9.1 Messaging on the Server To begin the discussion on messaging, I want to focus on the scheduling component of the Forethought application. Specifically, I want to look at messaging components on the server. By "the server," I simply mean the back-end of the application, as distinguished from any set of application clients. This may or may not be a separate physical machine, but in either case, it is distinct from application clients such as desktop programs or other application interface tools. Once you understand how this messaging operates within the application, you will be ready for the next section, where clients are discussed and built. 9.1.1 Premise First, let's revisit the scheduler facility for the Forethought application. The application should be able to store events that are important to the company. As you recall from Chapter 3, the EVENTS table is set up for just such a purpose. Then, users in the Forethought application are associated with these events and become attendees (not surprisingly, stored in the ATTENDEES table). This is all fairly basic material. Scheduling comes into play when you realize that individual employees will probably run some type of calendar or scheduling software on their computers. For the sake of this discussion, assume that this software is customizable, and that you can add features to it. That is important, as it allows the messaging and scheduling components in the application to be hooked into their desktop software. Given that assumption, the task becomes clear. Building Java™ Enterprise Applications Volume I: Architecture 165 Each time a new event is added to the data store, a message should be fired off. This message should indicate that a new event has been created, and also include the attendees for that event. Since you should already have an Event entity bean (from Appendix E), it is fairly easy to extrapolate the need for a session bean to handle the addition of data to that bean, as discussed in the last chapter. I'll call this session bean Scheduler. While it could have been called EventManager, I've used a different name to indicate that it is not a simple administrative component, as the other manager beans were. This component will handle creation of events, and then send off a Java Message Service (JMS) message indicating this creation. The purpose of this message is simple: it allows any application client subscribed to the same JMS topic to which these messages are sent to consume the new message. The client can examine the new event, and if the event has a certain individual as an attendee, it can sound an alarm, send email, or otherwise notify the relevant attendee. I'll delve into specific examples of these actions in Section 9.2. However, understand that once your component makes these messages available through JMS, the possibilities for client interaction become nearly limitless. 9.1.2 The EventManager Bean Actually putting these principles into practice is not nearly as complex as you might expect. First, you should already have the Event bean coded from Appendix E. You'll then need to code up a manager session bean to allow access to this entity. I've kept this bean extremely simple, as it's not the focus of this discussion. Example 9-1 shows the remote interface for this new manager. Example 9-1. The EventManager Remote Interface package com.forethought.ejb.event; import java.rmi.RemoteException; import java.util.Collection; import java.util.Date; import javax.ejb.EJBObject; public interface EventManager extends EJBObject { public EventInfo addEvent(String description, Date dateTime, Collection attendees) throws RemoteException; public boolean removeEvent(EventInfo eventInfo) throws RemoteException; } Example 9-2 is the home interface for the new manager. Building Java™ Enterprise Applications Volume I: Architecture 166 Example 9-2. The EventManager Home Interface package com.forethought.ejb.event; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; public interface EventManagerHome extends EJBHome { public EventManager create( ) throws CreateException, RemoteException; } Example 9-3 is the implementation class for this bean, and introduces some basic JMS concepts. Example 9-3. The EventManager Bean package com.forethought.ejb.event; import java.rmi.RemoteException; import java.util.Collection; import java.util.Date; import javax.ejb.CreateException; import javax.ejb.EJBHome; import javax.jms.JMSException; import javax.jms.ObjectMessage; import javax.jms.Topic; import javax.jms.TopicConnection; import javax.jms.TopicConnectionFactory; import javax.jms.TopicPublisher; import javax.jms.TopicSession; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import com.forethought.ejb.util.SessionAdapter; public class EventManagerBean extends SessionAdapter { /** <p> Required method for allowing bean lookups. </p> */ public void ejbCreate( ) throws CreateException { // No action required for stateless session beans } public EventInfo addEvent(String description, Date dateTime, Collection attendees) throws RemoteException { try { // Get an InitialContext Context context = new InitialContext( ); // Add event to database EventHome eventHome = (EventHome) context.lookup("java:comp/env/ejb/EventHome"); Event event = eventHome.create(description, dateTime, attendees); EventInfo eventInfo = event.getInfo( ); [...]... import import java. util.Iterator; java. util.List; javax.ejb.EJBException; javax.ejb.MessageDrivenBean; javax.ejb.MessageDrivenContext; javax.jms.JMSException; javax.jms.Message; javax.jms.MessageListener; javax.jms.ObjectMessage; javax.jms.Session; javax.jms.Topic; javax.jms.TopicConnection; javax.jms.TopicConnectionFactory; javax.jms.TopicSession; javax.jms.TopicPublisher; javax.naming.Context; javax.naming.InitialContext;... import import import import import java. util.Date; javax.jms.JMSException; javax.jms.Message; javax.jms.MessageListener; javax.jms.ObjectMessage; javax.jms.Session; javax.jms.TextMessage; javax.jms.Topic; javax.jms.TopicConnection; javax.jms.TopicConnectionFactory; javax.jms.TopicSession; javax.jms.TopicSubscriber; javax.naming.Context; javax.naming.InitialContext; javax.naming.NamingException; // Event... take on enterprise applications 178 Building Java Enterprise Applications Volume I: Architecture Chapter 10 Beyond Architecture You now have a solid application backbone in place You may be expecting another five or ten chapters detailing how to write a GUI or HTML interface, servlets and JavaServer Pages for application logic, a web services interface, or any number of other layers However, the Building. .. PortableRemoteObject.narrow(ref, EventManagerHome.class); EventManager eventManager = eventManagerHome.create( ); // Create an event java. text.DateFormat formatter = java. text.DateFormat.getDateInstance( List attendees = new LinkedList( ); ); 173 Building Java Enterprise Applications Volume I: Architecture attendees.add(userManager.get("shirlbg")); attendees.add(userManager.get("gqg10012")); EventInfo eventInfo... InitialContext( ); TopicConnectionFactory factory = (TopicConnectionFactory)context.lookup( "java: comp/env/jms/TopicFactory"); Topic topic = (Topic)context.lookup( "java: comp/env/jms/EmployeeTopic"); // Connect to topic TopicConnection connection = factory.createTopicConnection( ); 170 Building Java Enterprise Applications Volume I: Architecture // Send off notification of this event TopicSession session = connection.createTopicSession(false,... deploy these new components, including the message-driven bean Once those resources are in place, it's time to look at writing a standalone Java client that takes these messages and does something with them 171 Building Java Enterprise Applications Volume I: Architecture 9.2 Messaging on the Client At this point, handling messaging from the client perspective is a piece of cake It's simply a matter... design strategies to speak of yet Future volumes will cover linking the web application tier and web services tier with beans, and coordinating method permissions in that respect At this point, though, you can define as few or as many logical roles as you like to get familiar with this portion of the XML descriptor 176 Building Java Enterprise Applications Volume I: Architecture 9.3.3 Container Transactions... AccountManager) might use another manager (such as UserManager) The two options are to use the same transaction throughout (a value of Required), or to require a new transaction for 177 Building Java Enterprise Applications Volume I: Architecture the scope of each manager (a value of RequiresNew) I prefer to have each manager executing within its own transaction This keeps data consistency, but also allows.. .Building Java Enterprise Applications Volume I: Architecture // Get topic factory TopicConnectionFactory factory = (TopicConnectionFactory)context.lookup( "java: comp/env/jms/TopicFactory"); Topic topic = (Topic)context.lookup( "java: comp/env/jms/SchedulerTopic"); // Connect to topic TopicConnection connection = factory.createTopicConnection(... it's well worth repeating I'll briefly run over the salient points in relation to application architecture here, for use in your own programming projects You can also see these in effect in the various code samples throughout the book, as well as in Appendix E 179 Building Java Enterprise Applications Volume I: Architecture 10.1.1 Data Modeling Versus Entity Modeling The first consideration in design . event java. text.DateFormat formatter = java. text.DateFormat.getDateInstance( ); List attendees = new LinkedList( ); Building Java Enterprise Applications Volume I: Architecture 174 attendees.add(userManager.get("shirlbg"));. the next chapter, which focuses specifically on messaging in enterprise applications. Building Java Enterprise Applications Volume I: Architecture 164 Chapter 9. Messaging and Packaging Up. look at writing a standalone Java client that takes these messages and does something with them. Building Java Enterprise Applications Volume I: Architecture 172 9.2 Messaging on the Client