DekGenius.com
Previous Section  < Day Day Up >  Next Section

4.4 Reflection

The most accessible way to build a service that depends on model data is runtime reflection. I don't know why Java developers never have embraced runtime reflection the way that other languages have. It tends to be used by tool developers and framework providers but not general application programmers. You don't have to use reflection everywhere, nor should you try. Instead, apply a little reflection where it can have a tremendous impact. Here are some things to keep in mind:


General needs versus specific needs

When you need to access an object's features in a general way, use reflection. For example, if you're moving the color field from an object to a buffer for transport, and you don't ever use the field as a color, consider reflection for the task, in order to reduce coupling. If you're reading the color and setting other objects to the same color, direct property accesses might be best.


Delaying decisions

If you don't know the name of a method or class until runtime, consider reflection. If you already know the name, there's no reason to delay the decision—a simple method call is a better choice. For example, through configuration, you can frequently decouple code and delay decisions until runtime. Reflection gives you a tool to help this happen.

Bear in mind that although the performance of reflection has improved in recent years, postponing binding decisions until runtime has a performance cost. Jumping to a specifically named method or property is much faster than going through the reflection API by a factor of two or more. Reflection offers great power, but be judicious.

4.4.1 The Reflection API

The Java reflection API lets you access all of the elements that make up a class at runtime without requiring source code or any other advanced knowledge of the classes. Using reflection, you can:


Access a class's definition

When you declare a class, you specify a class name and a set of modifiers (like synchronized, static, and public).


Get all field definitions in a class

You can get the names, types, and modifiers for all of the fields in a class.


Get all of the method definitions in a class

You can get the names, return types, parameters and types, and modifiers for all of the methods in a class.


Get the parent class

Of course, since you can get a superclass, you can get all of the indirect methods and fields as well.


Access an instance's fields

You can read or write directly to the fields or utilize any getters and setters the instance might expose.


Call an instance's methods

Using reflection, you can also call methods on an instance.

In short, you can learn anything that you need to know about a class and directly manipulate an instance of that class. If you want to build a service that's independent of the structure of an object, that's a powerful combination of tools. You can inspect any method or field. You can load a class, call a method, or get the data from a field without knowing anything about the class in advance.

Further, reflection works well with a passive model because through reflection, the model already has all of the information that a potential service might need. That service can accept the whole model (or one class from the model) as input and use reflection to extract the data to do something useful, such as serialize the data to XML (like Castor), save it to a database (like Hibernate), or even wire together behaviors and properties (like Spring).

When you use the reflection framework, you must import the reflection libraries:

import java.lang.reflect.*;

The java.lang.reflection package contains everything you need, including four major classes: Class, Constructor, Field, and Method. These classes let you deal with each major element of a Java class. Java's runtime architecture makes it possible.

If you're like most Java developers, you probably deal much more with an instance of an object rather than the class object, but the class object is an integral part of Java's runtime architecture. It contains the basic DNA for a class. It's used to create classes and serves as an attachment point for static methods and members. It keeps track of its parent, to manage inheritance. You've probably used classes in these ways, but it's also the tool that enables reflection. Figure 4-5 shows a class instance at runtime. Each object has an associated class, which is the DNA for a class that determines its type. The class is the central entry point for the Java reflection API. Using it, you can access the fields, methods, and constructors for any class. You can also invoke methods and access the data for an individual instance of the class. The rectangles in grey represent the Java reflection framework.

Figure 4-5. All Java objects have an associated class
figs/bflJ_0405.gif


4.4.2 Accessing a Class

The class is the entry point for reflection. Once you have the class, you can call specific methods to get the associated methods, constructors, and fields. Using the class, you can get a single method or field by name, or get an array of all of the supported fields or methods.

You can get the class in several ways. Sometimes, you don't have an instance. For example, if you're working on a factory, you might have access to only the name of a class. In that case, load the class like so:

Class c = Class.forName(aString);

Other times, you might have nothing more than an object. In that case, you'd get the class from the instance, like this:

Class cls = obj.getClass( );

It's actually not quite that simple. But for now, you just need to understand that Java supports more than one class loader (though the architecture for doing so changed for Version 1.2 and beyond). Chapter 6 fills in the details about class loading.

4.4.3 Accessing Fields

You're probably ready to see a method that's a little more concrete. Let's use reflection to build a transparent service. Assume that you need a service to emit XML for a given object. Further, you want the model to remain transparent to the service, so you won't need to change any model code to support new objects. Given a target object, you're going to attack the problem using reflection:

  1. Get the class for the target object.

  2. Get the declared fields from the class.

  3. Get the value for each field.

  4. If the object is primitive, emit the appropriate XML.

I'll show you the code all together, and then we'll go through it in pieces. Here's the entire method to process an object:

  public static void doObject(Object obj) throws Exception {
[1]      Class cls = obj.getClass( );
    emitXMLHeader(cls);
[2]      Field[] fields = cls.getDeclaredFields( );
    for (int i=0; i < fields.length; i++) {
      Field field = fields[i];
[3]        field.setAccessible(true);
[4]        Object subObj = field.get(obj);
[5]        if (!Modifier.isStatic(field.getModifiers( ))) {
        if ((field.getType( ).isPrimitive( )) ||
           ((field.getType( ).getName( ) == "java.lang.String"))) {
[6]            emitXML(field.getName( ), subObj);
        } else {
[7]            doObject(subObj);
        }
      } 
    }
    emitXMLFooter(cls);
  }

That's it. Let's see how the individual pieces work.

[1] First, you've got to get the class object, given an instance. You need the class to emit the XML tags that bracket an entire object. You'll also get individual fields from the class. Since you're getting the class from a known instance, you use this method.
[2] Next, the class must declare the fields. The Class object lets you use several methods to access its fields. At times, you'll want to get a specific field, given the name of a property. You can do so with getField(String name), a method on class. Sometimes, you want to get only the declared fields for a class. Do this with getDeclaredFields( ), which returns an array of fields. Other times, you also want to get inherited fields. You can do this with getFields( ), which returns an array of all fields declared in a class and its parents. In this case, you'll get the declared fields, get back an array, and iterate over the array.
[3]Often, your service will need to access fields with a narrow scope, such as private fields. You wouldn't want to always package such a service with your model, so the Field class lets you step outside of the scoping rules for convenience. In this example, you'll want to make sure that the field is accessible, even if it's a private field. To do so, simply call setAccessible(true) on the field.
[4]Access the field's value by calling the get( ) method on your field object, to get the value for the individual field.
[5]Look at the modifiers to see whether the field is primitive or static. If it's primitive, emit the XML. If it's static, you'll want to skip it, because it's attached to the class instead of your object. The reflection API encodes all of the modifiers within an integer, so they'll take up less space. In order to read them, use a helper class called Modifier to check if a modifier applies to your class. You can access any modifier on a class, field, method, or constructor in this way.
[6]If it's primitive, emit the appropriate XML and complete the XML for the class.
[7]If it's not a primitive, call the method doObject again, this time with the field value.

The bulk of the work is done within the doObject method, as it should be. The code to emit the XML is surprisingly simple. Here are the methods to emit XML for the overall class, and for a field:

public static void emitXML(String name, Object value) {
  System.out.println("<" + name + ">");
  System.out.println(value.toString( ));
  System.out.println("</" + name + ">");
}

public static void emitXMLHeader(Class cls) {
  System.out.println("<"+cls.getName( )+">");
}

public static void emitXMLFooter(Class cls) {
  System.out.println("</"+cls.getName( )+">");
}

The nuts and bolts are all there for an XML emitter. You've probably noticed that I cheated and handled only the simplest case. In fact, you'll need to handle at least four types of fields for a general-purpose emitter:


Primitives

With reflection, you deal with everything as an object. Since primitives are not objects, reflection wraps them in type wrappers. For example, to wrap the int 37 in a wrapper, you'd say Integer intWrapper = new Integer(37). To get the value from a wrapper, call a method on the Integer class, like intWrapper.intValue( ).


Objects

If a field value is not an array or primitive, it's an object. You can deal with other classes recursively. Get the class from the object and iterate through its fields.


Arrays

Reflection uses a special class to wrap arrays called Array. This class lets you access the type of the array and also provides access to each individual element of the instance.


Special classes

Generally, you're going to want to treat some classes differently than others. For example, you may want special treatment for strings or collections.

We've only handled the first two types of fields, plus strings, but you can see how reflection works. You've supported a surprising number of classes without needing to alter model code at all. I must note that the emitter we've constructed here, though generic and useful, is not a full implementation. For a truly generalized emitter, our class would have to be able to handle circular references between classes, optional omission of referenced classes, logically transient fields, and some kind of optional name-substitution mapping pattern. Regardless, the point is no less salient: reflection can provide an enormous amount of power without any tight coupling.

You've seen how many transparent services use reflection: they simply access a list of properties, recursively if needed, and do the appropriate service. The types of services are unlimited:

  • Hibernate, a persistence framework discussed in Chapter 7, looks at the value of your model before and after you change it, and then generates SQL based on your mappings to save the changes to a database.

  • Spring, a lightweight container discussed in Chapter 8, populates fields in your objects based on a configuration file to wire your target objects to services.

  • XML emitters like Castor scan an object's fields recursively to emit XML.

  • Distributed messaging services can use reflection to scan an object's fields so that they can store compound objects without depending on a memory address.

So far, I've only told you how to deal with data. Fortunately, the reflection API also makes it easy to deal with behavior.

4.4.4 Accessing Methods and Constructors

You can use reflection to examine and execute methods. You can access methods through java.lang.reflection.Method and constructors through java.lang.reflection.Constructor. I'll describe the way that methods work; you'll find constructors work the same way.

As with fields, you can use Class to access methods in two ways: getMethods( ) returns an array with all supported methods, and getDeclaredMethods( ) returns only the declared methods for a class. You t hen can access the parameter types, modifiers, and return value from the Method class.

Here's an example that prints all of the declared methods in a class. It also prints out the types of each parameter, and the return value:

  public static void printMethods(Object obj) throws Exception {
    Class cls = obj.getClass( );
[1]      Method[] methods = cls.getDeclaredMethods( );
    for (int i=0; i < methods.length; i++) {
      Method method = methods[i];
[2]        System.out.println("Method name:" + method.getName( ));
[3]        Class parmTypes[] =  method.getParameterTypes( );
      for (int j = 0; j < parmTypes.length; j++) {
        System.out.print("  Parameter " + (j+1) + " type:");
        System.out.println(parmTypes[j]);
      }
      System.out.println("  Returns: "+method.getReturnType( )+"\n");
    }
    
  }

Here's what the annotations indicate:

[1] As with the field example, you'll use the class object to return all of the declared methods for the class.
[2] Once you have a method, you have access to its name and type.
[3] You can also access each of the parameters. This example simply iterates through them to print their types.

As you can see, inspecting methods works a whole lot like inspecting fields. All that remains is to invoke a method.

4.4.4.1 Invoking a method

Often, you'll want to invoke a method or constructor without knowing all of the details until runtime, such as configuring an object from a file. To do so, you'll need several things:


The name of the method

Remember, that's not enough to identify a method in Java.


The types of parameters

You'll also need an array with the parameter types, because two methods with different signatures can share the same name.


The parameter values

You'll need to build an array of parameters. If a parameter is an object, you'll place it in the array directly.

If a parameter is a primitive or array, you'll need to wrap it first. For example, call new Integer(59) to wrap a primitive integer. To wrap an array, you wrap it in an instance of Array. For example, to wrap an array of five Integers, a single parameter would look like wrappedArray below:

int a[]={1,2,3,4,5);
Object wrappedArray = Array.newInstance(Integer.TYPE, a);


The return type

The invocation returns an object or nothing at all. You'll need to cast it to the appropriate type.

Here's the code to invoke a method called sum on class Adder that takes two int parameters and returns an Integer:

// target object is called "target"
Class c = Class.forName("Adder");
Class parameterTypes[] = new Class[2];
parameterTypes[0] = Integer.TYPE;
parameterTypes[1] = Integer.TYPE;
Method m = c.getMethod("sum", parameterTypes);
Object parms[] = new Object[2];
parms[0] = new Integer(1);
parms[1] = new Integer(1);
Integer returnValue = (Integer)m.invoke(target, parms);

That's really the bulk of working with reflection. Compared to a simple method invocation or a simple field access, it does not look simple. When you consider the overall impact, though, the effort makes a huge difference.

    Previous Section  < Day Day Up >  Next Section