Type Safety when using Butterfly Container (or Spring XML)
By Jakob Jenkov
It is often claimed that dynamic reflection based dependency injection (DI), like
Spring's XML-configurations or Butterfly Container Script, are not type safe,
and therefore bad, error prone etc. Too often these claims are not backed up
by any concrete cases where people have suffered from this, or even examples
of common errors resulting from the lack of compile time checking in
Spring's XML configurations or Butterfly Container Script. This text will
dive into this discussion. Hopefully, at the end of this text you will be
able to decide for yourself if the relaxed typed checking of Butterfly Container
(or Spring for that matter) is a problem in your concrete case.
Static Types vs. Type Safety
Java is a statically typed language meaning that types (primtives and classes)
cannot change at runtime. Because they cannot change at runtime the compiler
can do a lot of static checking of types. For instance, the compiler utilizes
knowledge of the static types to check if a parameter passed to a method
is of the required type.
Static types primarily help developers with:
- Correctness - Limits error possibilities.
- Documentation - Shows other developers what types a given piece of code expects.
- Tool Assists - Features like code completion, refactoring etc. can be more powerful with static types.
When it is said that Springs XML format and Butterfly Containers script language
are not type safe, what is typically meant is that the advantage of static types is lost,
because the Java compiler cannot check the Spring XML files, nor the Butterfly Container Scripts.
Personally I think it is an overstatement to claim that Spring and Butterfly sacrifices
type safety. It is not like you can all of a sudden let your Java class members, method
parameters and variables be untyped. You still have to specify type. These types
are still helpful for documentation purposes. Your IDE can also still help you with
code completion, refactorings, JavaDoc etc. It is true that the Java compiler cannot
check the XML and script files at compile time. It may also be true that your IDE
cannot always refactor into the XML or script files (IntelliJ IDEA can to some extend).
This makes it easier to make mistakes in the wiring of components, true. But both
Spring and Butterfly Container will check the wirings eagerly at startup, minimizing
the problem. The rest of the text will examine this closer.
Assembly vs. Execution Type Safety
Dependency injection is primarily used when assembling components,
and usually only the major components of an application. Once wired together
these components starts executing. When executing the components call methods
on each other passing in parameter values and receiving return values. The vast
majority of this execution code is injection free meaning type safety is kept
in that part of the application. In my experience the execution parts of an
application are usually a lot larger than the assembly parts, meaning the
majority of most applications are perfectly type safe even when using DI
for assembly. I'll even argue that you can unit test your way out of the lack
of assemply type safety. I'll address that later in this text.
Here is an example of assembly and execution parts of an application:
You may use DI to wire a DAO to
a business object (BO) and suffer a bit on assembly type safety.
But, once the BO starts calling the DAO at runtime no DI is involved.
Below is sketched an example of this:
BO.setDao(dao) //done using DI
Person person = this.dao.readPerson(personId);
//check if user is valid
Person person = null;
ResultSet result = ...
person = new Person();
In this example DI is used to inject a DAO instance into the BO instance. This is done
at the beginning, by the method call BO.setDao(dao). But, notice how no DI is used inside
the BO.checkPersonIsValid() or the DAO.readPerson() methods.
It is plain, type safe Java.
You may argue that you could use the DI container to create
the Person object and that this would break type saftey. However, I dont' see the benefit
of doing so. The Person object is a simple data object (DO). I most likely never have to
change implementation of the Person class. In addition most DO's are configured from
data read in some system (database, legacy system etc.), so configuration of DO's is
usually the responsibility of the DAO's, not the DI container.
Compile Time vs. Runtime Type Checking
Much of the cause for debate is the fact that script files (or Spring XML files)
are not type checked at compile time, meaning that it is possible to specify
an injection of a type B into A, when A really depends on C, not B.
In Java this will result in a class cast exception at runtime (or some similar
type error exception).
Type errors like these are a problem if they do not occur until the application
has been running for hours. If they occur that late it can be hard to track down
just exactly what caused the type error, and hard to bring the application into
that state again to reproduce the error. Therefore, like with any other type of
error, the earlier in the development process type errors can be caught, the better.
Application Startup Time Type Checking
With Butterfly Container (and Spring) the configuration
of the container is typically read at application startup time. At this time the
types are checked to verify that the specified injections are valid. This means
that the application will fail early if an injection is invalid. So, basically
the type checking has been deferred a bit, from compile time to application
startup time. While this is theoretically a cause of annoyance to have to wait
until startup to see if an injection is invalid or not, in my experience these
type errors happen so rarely in practice that it isn't a big issue.
Build Time Type Checking
If you write unit tests to assure that your dependency injection container
configuration is correct (and you do, right?), you are able to catch type
errors already at build time.
Development Time Type Checking
To me (and many other developers) running the applications unit tests
has become an integrated part of the development process. I write a bit
of code, add a unit test for it (or write the test first, then code), and
then I run the unit tests to check if the code still behaves as expected.
If some of your unit tests checks the dependency injection containers configuration,
type errors will be caught already here, in the development phase.
The DI configuration type safety debate mostly revolves around the fact that script
and XML configurations do not provide compile time type checking. As I said earlier,
the fact that you choose to use Spring XML or Butterfly Container Script to wire
up your components, doesn't all of a sudden ruin tool support. Nor does it make
your classes any harder to read. All types are still statically defined and referenced
in your code.
DI is mostly used for component assembly. In my experience the assembly part of
an application is usually only a fraction of the total application. The execution part is much larger.
Therefore, type safety is usually only an issue in a fraction of the application.
Even if dependency injection containers like Butterfly Container and Spring may not
provide compile time type checking, it does not mean that they do not provide
type checking at all. Most often they will provide at least application startup
time type checking. Provided you write unit tests to check the containers configuration
you can pull the type checking forward to build time, and even development time.
The type checking times are summarized below.
Unit Test Time - Development
Unit Test Time - Build
Application Startup Time
Back to Top