J2EE: Changing Objects with Remote Interfaces
Articles —> J2EE: Changing Objects with Remote Interfaces
It was a bug whose solution should have been completely obvious to me. The scenario: a servlet accessed a local interface of a session bean to perform a simple process. To incorporate multi-client remote access, I moved the method into the remote interface of the session bean - and suddenly it broke: while changes were being persisted server side, the clients that were expected to reflect those changes remained the same.
Not long after it became apparent that the problems resided in how these functions were being used, and how local and remote interfaces differ. I was passing an object to the method, expecting a variables of that object to change. In a trivial J2SE environment there wouldn't be a problem. Take the following class, which acts as a wrapper for a string and serves as a very simple example
import java.io.*; /** * Simple demonstration class to show variable string changes. * @author Greg Cope * */ public class StringWrapper implements Serializable{ static final long serialVersionUID = 10000L; private String value; /** * Creates a new StringWrapper * @param s */ public StringWrapper(String s){ value = s; } /** * Gets the value * @return */ public String getValue() { return value; } /** * Sets the value. * @param value */ public void setValue(String value) { this.value = value; } /** * Application entry point. * @param args */ public static void main(String[] args){ StringWrapper sw = new StringWrapper("Initialize"); System.out.println(sw.getValue()); changer(sw); System.out.println(sw.getValue()); } /** * Demo to alter the value of the parameter StringWrapper * @param sw */ public static void changer(StringWrapper sw){ sw.setValue("Value Changed"); } }
The above example is trivial. It creates a StringWrapper, changes the value, and prints out that value before and after the changes, printing out:
Initialize Value Changed
In a typical J2SE environment, passing this object to a method and changing its variables in that method will persist when that method exits, as the above code demonstrates. However, the case is a bit different in a client/server environment.
Imaging the following scenario where, instead of a local method changing the StringWrapper, a remote method performs those changes:
@Local public interface TestingLocal { /** * Tests the ability of this function to change the variables in the parameter object. * @param sw */ public void testingPasserLocal(StringWrapper sw); } --------------------------- @Remote public interface TestingRemote { /** * Tests the ability of this function to change the variables in the parameter object. * @param sw */ public void testingPasserRemote(StringWrapper sw); } --------------------------------- /** * Session Bean implementation class Testing */ @Stateless public class Testing implements TestingRemote, TestingLocal { /** * Default constructor. */ public Testing() { } /** * Tests the ability of this function to change the variables in the parameter object. * @param sw */ public void testingPasserRemote(StringWrapper sw){ sw.setValue("Test Change"); } /** * Tests the ability of this function to change the variables in the parameter object. * @param sw */ public void testingPasserLocal(StringWrapper sw){ sw.setValue("Test Change"); } } ----------------------- /** * Client servlet do get method. * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try{ PrintWriter out = response.getWriter(); out.println("Local"); InitialContext ctx = new InitialContext(); TestingLocal local = (TestingLocal) ctx.lookup("Testing/local"); StringWrapper wrapper = new StringWrapper("set"); local.testingPasserLocal(wrapper); out.println(wrapper.getValue()); out.println("Remote"); TestingRemote remote = (TestingRemote) ctx.lookup("Testing/remote"); wrapper = new StringWrapper("set"); remote.testingPasserRemote(wrapper); out.println(wrapper.getValue()); }catch(Exception e){ e.printStackTrace(); } }
Here the context is quite different than the above J2SE example. The client servlet prints out the following
Local: Initialize Test Change Remote: Initialize Initialize
Interesting. The parameters are passed to the functions, and similar to the J2SE demo above the value of the parameter should change. For the local interface it behaves as expected, but what is happening in the case of the remote interface?
One of the fundamental differences between local and remote interfaces is being demonstrated here. In the remote interface, objects are serialized during communication - in short the server now has its own copy of the parameter objects. Changing this copy changes the server object, but this change is not reflected on the client side unless that object is subsequently passed back to the client.
How can this issue be resolved? Values that need to change in the client should be passed back to the client for those changes to take effect. This can be accomplished in a few ways. First, the object can simply be returned to the client with the changes made on the server. Alternatively, to avoid passing unnecessary portions of the object to the server, only the changed portions can be passes and returned. Either way, the client becomes somewhat responsible for making changes, and it can get ugly if multiple clients are used and each must implement the same code. This is a good example of the benefits of using a Business Delegate, where this logic is stripped out of the client and placed in the delegate, who then is responsible for accessing the server and performing the necessary changes client side.
It all started as a simple bug, and extended into a good learning experience. Knee deep in code, sometimes basic principles can be forgotten, which is good reason to frequently take steps back and think about the use cases, problems, designs, and principles before moving forward at full force.
There are no comments on this article.