| JavaBeansConsideredEvil |
UserPreferences |
| JicarillaWiki | FrontPage | Overview | Articles | GetStarted | Sourceforge Project Page | RecentChanges |
This wiki is in 'slumber' mode, just like its associated sourceforge project. Edits are disabled, the content is potentially stale and is not maintained. That said, it contains some really useful stuff still. Enjoy!
This work is licensed under a
Creative Commons License.
If you're reading this, you've likely spent the last few years of your programming life following the ever-present JavaBean paradigm. That's just handy dandy for type-safe configuration, but it wreaks havoc on your application security.
Why is that? The answer is simple: it opens up the possibility of components being in an unsafe state. Which is bad.
I assume you are at least passingly familiar with the
JavaBeans pattern,
basic component-oriented design and the
"design by contract" principle. If not, read the
Design Patterns book and
some bits on the avalon website.
We say that a component is in an unsafe state when it is not capable of fulfilling the contract of its work interface. Here is a trivial example of a component class that will always result in instances that are in an unsafe state:
// Example 1 (fragments)
public interface MyContract
{
public void doSomeStuff();
}
public class MyComponent implements MyContract
{
public void doSomeStuff()
{
throw new UnsupportedOperationException();
}
}
More realistically, a component can be in an unsafe state when it has been constructed, but not yet initialized or configured. For example:
// Example 2 (fragments)
public class MyComponent implements MyContract
{
private MyHelper helper;
public void setHelper( MyHelper h )
{
Assert.assertNotNull( "The helper may not be null!", h );
helper = h;
}
private void doSomeStuff()
{
helper.helpDoSomeStuff();
}
}
// elsewhere...
MyContract comp = new MyComponent(); // 1
try { // 2
comp.doSomeStuff(); // results in NullPointerException // 3
} catch( Exception e ) {} // 4
comp.setHelper( new MyHelperImpl() ); // 5
comp.doSomeStuff(); // 6
In this example, the component instance 'comp' is in an invalid state on the lines marked 1 through 4. Line 5 corrects this, and the fact that line 6 does not cause an exception proves that the component is, at that point, no longer in an invalid state.
Clearly, we do not want to use any component instance until it is in a safe state. We should look for a mechanism that ensures that client components will not be able to call any method on any component in an unsafe state.
Fortunately, a powerful and flexible safety mechanism is built right into the java language: the constructor. The suggestion I want to make in this essay is simple: use constructors to avoid unsafe state. That may sound trivial, and it is. We are specifying a contract surrounding the constructor:
"On successfully leaving a constructor, a component may not be in an unsafe state".
But the components in the example above are in blatant violation of that contract. Fortunately, this is trivial to fix:
// Example 3 (fragments)
public class MySafeComponent implements MyContract
{
private MyHelper helper;
public MySafeComponent( MyHelper h )
{
Assert.assertNotNull( "The helper may not be null!", h );
helper = h;
}
private void doSomeStuff()
{
helper.helpDoSomeStuff();
}
}
In this example, we've only had to change one line of code to make this work. Most realistic components need more than just a helper before they can be considered safe, but the changes to existing beans are quite often just as trivial. Usually the use of the constructor results in less typing!
Note that the assertion statement is important: if it is omitted, the component could be unsafe after returning from the constructor:
// Example 4 (fragments) MyContract comp = new MySafeComponent( null ); // 1 comp.doSomeStuff(); // results in NullPointerException // 2
With consistent application of this simple contract applied to all your constructors, you are totally sure that all references to all components in your system are always to components in a safe state. This is a re-assuring fact. And all this from following a simple coding pattern!
Many JavaBeans do not follow this pattern. Most will use an empty constructor, then use "setters" (as in example 2) to bring a component into a safe state. This is a bad idea, mainly because your average guaranteed-to-be-safe contract is far less simple:
"For each new instance of this component, you must call each of the setHelper(), setOtherHelper(), setConfig(), ..., setXXX() methods exactly once to bring it into a safe state."
furthermore, these contracts vary on a per-class basis. Some classes might require all setXXX() methods to be called, others may be in a safe state even if some of its setXXX() methods have never been called. A user of such a JavaBean must figure out these contractual specifics, often via a little trial and error.
Chances are, that you, will be in strong disagreement with what I just wrote. After all, there's various solutions out there which work just as well and avoid all thes hassles. Right? Well, let us take a look...
This idea is pretty simple as well: let something else (not the component author) make sure that all references to all components are always to components in a safe state. You get a reference to a component instance only by asking the factory/container for it, and the factory/container will guarantee that the instance is not in an unsafe state. That usually looks like somewhat this:
// Example 5 (fragments)
public interface Factory
{
public Object getInstance();
}
public class MyComponentFactory implements Factory
{
public Object getInstance()
{
MyComponent comp = new MyComponent();
comp.setHelper( new MyHelperImpl() );
return comp;
}
}
public class MyComponent implements MyContract
{
// package access
MyComponent() { /* ... */ }
/* ... */
}
// elsewhere...
ComponentFactory factory = new MyComponentFactory();
MyContract comp = (MyContract)factory.getInstance();
The contract associated with the factory is this:
"The return value of getInstance() will be a component that is never in
an unsafe state"
Variations on this theme include static factories and factory methods. All of them require another work interface (Factory) and an implementation of that interface (inside the component itself in the case of the factory method), including possibly association of that implementation with the component implementation, and travelling of that association by the client component. (In other words, the client needs to find a reference to the factory and know how to deal with it).
That's considerably more typing. Also note that, in most realistic examples, you need a typecast. While this is usually not a big deal, it just opens a tiny little window for bugs.
I am not saying that the Factory pattern is a bad pattern. But using it merely to avoid having to pass in state through the constructor is not a very good idea, especially not when the use of factories is optional.
You can take the factory approach further and delegate the instantation and setup of a component to a sort of "generic factory" that also happens to do lots of other things: a container. A sweet example of such
a container is the
Spring framework. Most uses of the spring framework revolve around the component user creating or modifying an xml file. This xml file is read in by the framework at runtime and component instances are created based on the xml file. Here's how you could use Spring with example 2:
// Example 5 (fragments)
<?xml version="1.0"?><!-- config.xml -->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="helper" class="MyHelperImpl"/>
<bean id="my" class="MyComponent">
<property name="helper"><ref bean="helper"/></property>
</bean>
</beans><!-- end of config.xml -->
// somewhere else...
InputStream conf = getClass().getResourceAsStream("config.xml");
factory = new XmlBeanFactory( conf );
factory.preInstantiateSingletons();
MyContract comp = (MyContract)factory.getBean( "my",
MyContract.class );
comp.doSomeStuff(); // 5
Unfortunately, this does not solve anything; it just moves the problem into the xml file. Consider
// Example 6 (fragment)
<beans>
<bean id="helper" class="MyHelperImpl"/>
<bean id="my" class="MyComponent">
<!-- forgetting the setHelper() here -->
</bean>
</beans>
it suffers from the exact same problem we saw in example 2: there will be a NullPointerException on line number 5 if you use the fragment from example 6.
Note that this is not a problem with or a bug in the Spring framework. How could the framework possibly know that it is supposed to call the setHelper() method? There is simply no way for the framework to satisfy the desired contract
"The return value of getBean() will be a component that is never in an unsafe state"
since it doesn't know about the specific contract surrounding the MyComponent bean
"For each new instance of this component, you must call the setHelper() method exactly once to bring it into a safe state."
Of course, there's 'workarounds', and many have been proposed. One that seems to be popular is the use of attributes:
// Example 7 (fragment)
/** @@Required */
setHelper( Helper h } { /* ... */ }
where we expect the container to find and interpret the '@@Required' attribute and have it make sure this method is called before agreeing to return an instance. Again, this just moves the problem: now a programmer might forget to add the attribute.
It is just not going to work.
PicoContainer is a container that is in many ways a lot like spring (only its a lot smaller and has a lot less features). One of the cool things about Pico is its ability to call a constructor, with all the proper arguments, for you. Like this:
// Example 8 (fragments)
MutablePicoContainer pico = new DefaultPicoContainer();
pico.registerComponentImplementation( MyHelperImpl.class );
pico.registerComponentImplementation( MySafeComponent.class );
MyContract comp = (MyContract)pico.getComponentInstance(
MyContract.class );
We started out by defining the concept of "unsafe state", which is a state in which a component does not do its work properly. We briefly outlined what is so bad about allowing components to be in an unsafe state. Then we showed a simple way to avoid that by using the constructor.
Charging on, we visited some attempts to fix the issue in other ways and showed why they're inferior. Finally, I gave a very brief overview of how Picocontainer supports the use of the constructor as I outlined it.
Comment (2003-11-13) by Colin Sampaleanu: Note that Spring has actually been able to 'autowire' components in a similar fashion to Pico, for a while now. [rest of comment deleted by LSD]
Comment (2003-11-14) by Leo Simons: Let us not get into a feature comparison between Spring and Pico here, please. I've seen enough of those on TSS. The point is not about which container to use (after all, it ain't rocket science to modify one to make it do what you want it to), but about how you should write your components.
Comment (2003-11-14) by Colin Sampaleanu: Sorry Leo, I do realize this is not a forum. The only reason I made that comment about Spring (and I was not trying to compare to Pico, so should not have even mentioned Pico), is that I think anybody reading the article above can reasonably assume this is the _only_ way Spring wires classes together. (I also made the second point about the possibility of using an initializing interface to ensure proper startup state, which I think is relevant, but maybe not the right place to mention that (on this wiki page)).
Comment (2003-11-17) by Leo Simons: No worries, dude! It is indeed relevant, and I will see about working it into the main text :D
Comment (2003-11-22) by Juergen Hoeller: I'd like to note that Spring not only has autowire but also configurable dependency checks, e.g. check the population of all non-simple bean properties. This can guarantee a similar safeness of state as using a constructor - if created in the factory, of course. Furthermore, as of the upcoming release 1.0 M3, Spring can also invoke constructors just like Pico now, via autowiring or explicit specification of constructor arguments.
Comment (2003-11-24) by Leo Simons: Oh, come on, guys! Of course there is a framework X which has feature Y and will furthermore have feature Z from framework B in the next release. That is quite irrelevant. The use of configurable dependency checks does not guarantee the same kind of safety. You are moving the problem to the configuration of the dependency checking, and introducing a requirement to use a factory (and a rather elaborate factory at that). It is more complex, more prone to errors, and requires much more magic to be put in place in order to work.
Comment (2004-01-21) by Bo: There are a great many problems with this article. First, I think your notion of constructors is wrong. The reason constructors exist isn't to make sure an object is in a 'safe' state but to make sure it's in a well-defined state. To understand the reasoning here, you have to go back a ways but once upon a time you used to construct objects that held pointers which literally contained random garbage data. Constructors were needed to let them zero out these pointers.
Constructors aren't intended as a way to **configure** objects but as a simple mechanism to give objects a chance to initialize themselves. This is why the whole Picocontainer thing is so wrong. You're trying to do both and it's very doubtful it'll scale to any sort of real complexity. (I shudder to think what will happen when you try to subclass these complex components that have these huge constructors. And the simple truth is that a constructor is simply not expressive enough to completely define a component's dependencies. You couldn't even configure most EJBs if you just used constructors and interfaces. And let's not even get into runtime reconfiguration where you have to be moving databases around without restarting the application...)
Second, I think your notion of an 'unsafe state' is backwards. The simple truth is this: if an object A has a reference to an object B then it got that reference either 1) by constructing B or 2) by receiving a reference to B. The question isn't whether B can be in a safe state, it's whether A should know how to construct B. If A shouldn't know how to constructor B then it needs to be passed B. This is pretty common sense OO design. In fact, I highly doubt 'unsafe states' are a real problem in java development. I know I've never needed to write code like 'if (a.isInitialized())' and I've rarely seen such code long before 'dependency injection' was branded. BTW, if you have public setters that receive null you should throw an IllegalArgumentException not an assertion exception. ;)
Comment (2004-01-21) by Leo Simons: Thanks for your comments, Bo! I always enjoy reading strong opinions...you had me thinking for a while :D
As to "what constructors are for"...java has no pointers, yet it has constructors. These weren't invented just to make C++ programmers happy. After all, any instance is always in a "well-defined" state in the sense you refer to: members are initialized to null, primitives to 0, etc. But why would there be language support for constructors, and providing arguments in a constructor, if those mechanisms are not meant to be used?
As to "constructors are not expressive enough"...that's a very powerful argument. It seems a lot like using constructors wouldn't, especially not with complex things like (spit) EJB....well, you can introduce value objects and a service locator in the constructor, for example. Something like this:
// Example 9 (fragments)
public class MyComponent implements MyContract
{
public MyComponent( Configuration config, ServiceLocator locator ) { /* ... */ }
}
...and there's half a dozen other options to explore. It may turn out two years from now that it was all a bad idea to use them. But I'm going to think otherwise until someone shows me an example of a big application that sucked because people were using constructor dependency injection. And I've seen plenty of big applications that sucked because of overuse of XML, the use of EJBs, etc etc. Besides that, I've tried some of the options like this, and it can work, but I've found that in what I consider a well-defined application, there's really no need for that. If a component does one thing and does it well, it will have only a few dependencies almost by definition.
As to "runtime reconfiguration"...okay, there are some proven needs for that. But if you can't isolate 98% of your application from that, IMHO you're doing something wrong. Just use some kind of hotswapping technique, or create new component instances to replace the old ones for new requests, or ...any of a number of other things.
As to "I hightly doubt 'unsafe states' are a real problem"...I guess that depends on how you write your software and whom you work with. If everyone who works on a project is disciplined in setting up safe state, then unsafe states are obviously not a problem. With people unit testing things, it becomes less of a problem everyday. I agree that 'if (a.isInitialized())' is probably not something you'd come across a lot. But have you never found a bug where a setXXX was not called or with the wrong arguments, or where a config file was missing a property? Maybe you work with better programmers than I do :D
Closing up with the IllegalArgumentException thing...well, I just disagree (throwing Errors is more efficient and in a properly tested application these kind of things should never occur), but at least the decision is now isolated into an Assert class. Throwing IllegalArgumentException instead of AssertionError in my applications involves changing a single line of code :D