Getting to Know
- Introspection Basics
- The Significance of Introspection
- Design Patterns
- Explicitly Providing Bean Information
- The Introspector
- Introspection and Security
- API Support
It is imperative in the design of JavaBeans that all beans somehow expose information about their public properties, methods, and events. In doing so, beans enable outside parties to learn what features they have to offer and how they are used. Outside parties in this case include application builder tools and application programmers, among others. The importance of this public exposure of bean information is that a builder tool or programmer needs to be able to take a prepackaged bean and have a consistent and automated way to find out how the bean works.
The process of analyzing a bean to find out how it works is called introspection. Introspection is a fundamental part of the JavaBeans API because it is the means by which beans are easily integrated into new environments. In this chapter you learn a great deal about introspection and how it is implemented behind the scenes in JavaBeans. Although introspection has often involved relatively complex schemes in other component models, you learn in this chapter that JavaBeans provides an extremely elegant and simple solution to introspection. More important, the JavaBeans approach to introspection requires very little effort on the part of beans developers. JavaBeans even goes a step further than most component models by providing two different approaches to introspection, each one suiting the needs of developers in different ways.
This chapter covers the following topics related to JavaBeans introspection:
- Introspection basics
- Design patterns
- Providing bean information explicitly
- A composite approach to introspection
- Security issues
- API support for introspection
The JavaBeans API provides a great deal of support for introspection, which enables any interested party to find out information about the structure and functionality of a bean. The introspection services provided by the JavaBeans API are divided into two parts: low-level services and high-level services. The low-level API services are typically used by application builder tools, which make heavy use of bean internals to provide advanced development features. This level of access isn't appropriate for application developers, however, because it exposes bean internals that aren't meant to be accessible at the application level. For developers, the high-level API services are more appropriate.
The high-level services provide access to a limited portion of a bean's internals, which typically consist of a bean's public properties, methods, and events. The high-level introspection services rely heavily on the low-level services; the primary difference between the two is that the high-level services limit access to bean internals. The end result is two different levels of introspection that offer solutions tailored to the access requirements of the party attempting to introspect a bean.
You might be wondering exactly how the introspection facilities in the JavaBeans API enable an outside party to find out about the internal workings of a bean. In other component models such as ActiveX, component developers are required to explicitly define each aspect of a component that they want to be available to outside parties. This process can get very messy and even involve learning a component definition language. JavaBeans takes a very different approach by offering a means of automatically assessing bean information from the class makeup of the bean itself. In other words, with JavaBeans it is possible for bean developers to not put forth any extra effort toward supporting introspection. This is very nice because it pushes the issue of introspection onto the JavaBeans introspection facilities, instead of saddling bean developers with the responsibility. This not only helps developers get up and running with JavaBeans development faster, but it also makes beans a little leaner and easier to work with. If JavaBeans is the first component software technology you've encountered, then you'll just have to take my word for it as far as how much of a convenience it is not having to worry about explicitly supporting introspection at the code level.
Before you start thinking JavaBeans has taken away all the fun, understand that the automatic approach to introspection isn't the only way introspection is handled. For those cases in which bean developers want to get up close and personal with bean information, there is an option for explicitly defining the information you want to be known publicly. Even this approach is much easier to use in JavaBeans than the component information schemes required in other component models. So, if you happen to be a control freak and insist on having total control over how your beans are introspected, you'll be glad to know it doesn't take too much extra effort.
The first approach of JavaBeans automatically assessing information about a bean uses the reflection facilities in the core Java API. The reflection facilities are responsible for analyzing the low-level structure of a bean and returning information. The introspection facilities in the JavaBeans API take this information and apply design patterns to it to determine the specifics about the public properties, methods, and events supported by a bean. You learn much more about design patterns in the next section of this chapter, so don't worry about it if they don't sound familiar.
The explicit approach to defining bean information requires a little more work on the part of bean developers. The JavaBeans API provides support classes that are used by developers to explicitly define the public information for a bean. Keep in mind that this approach is purely optional and can even be used in combination with the automatic approach. In other words, developers are free to explicitly provide some bean information and allow the introspection facilities to automatically determine the rest. It's important to note that automatic introspection is the default approach taken by JavaBeans, and is superseded only if explicit information is available. This ability to leverage both introspection approaches provides lots of flexibility for bean developers.
If you're new to the world of component software, you might not immediately see the importance of introspection. Although simple in concept, the idea of being able to find out information about an executable piece of software is a pretty big deal, at least in terms of components. Consider all the different applications installed on your computer. If you wanted to find out how one of them worked internally, how would you go about finding out? Give up? First off, commercial applications are always shipped in some form of executable machine code that isn't too human friendly. The only option you really have is to break out a disassembler, which is a tool that takes executable machine code and resolves it into comparable assembly language code. If you happen to be an assembly language whiz, you might be able to figure out a few things about the application after a great deal of effort. Even then, you probably wouldn't be anywhere near understanding the specific design of the application as a whole.
One of the reasons why commercial applications are so hard to reverse engineer is that their developers don't want to give away their secrets. Even if that wasn't an issue, however, you still would need to contend with the fact that executable code is virtually incomprehensible by humans. Don't get me wrong, there certainly are people capable of understanding machine code to some extent, and you might be one of them. If you are one of these people, feel free to skip the rest of this paragraph, you've earned it! For the rest of us mere mortals, machine code is just a big series of meaningless numbers. The point of all this is that executable code generally isn't designed to provide any information about its internal makeup to the outside world; executable code is designed for machines to understand, not people. This has worked out well for commercial applications, where developers typically don't want their hard work understood for fear of it being copied and reused.
NOTE: This discussion touches on an interesting subject related to the inability of humans to understand executable machine code. In the world of custom software development, many deals are structured based on whether source code is delivered or just an executable application is delivered. Developers charge a great deal more for source code because they know that by delivering just an executable, they still have tight control over their development efforts and aren't at risk of someone taking their code and reusing it elsewhere. In other words, outside of component software, reusability is possible only when you have direct access to source code.
Now, consider the issue of component software. A major requirement of an executable software component is that it provide information about its internal makeup so outside parties can know how it works. The problem is that components are developed and distributed using roughly the same model as applications, making them just as hard to figure out in executable form. Enter introspection. Introspection provides a means of getting around the problem of not being able to find anything out about an executable component. This explains why introspection simply must be a standard part of any component model.
I mentioned that one of the approaches taken by the JavaBeans API in assessing the makeup of a bean is automatic, meaning that bean developers aren't required to add any additional support for introspection in their code. Although it's true that developers don't have to add additional code, how they structure the code for the bean itself is important because JavaBeans relies on the naming and type signatures of the methods defined in the code for a bean to determine what properties, methods, and events the bean supports for public use. In other words, the conventions used in the code for a bean is also used as a means for introspecting the bean.
This might sound pretty strange, but keep in mind that most properties are accessed through very consistent methods: accessor methods. The JavaBeans architects reasoned that it would be pretty convenient if accessor methods were used to automatically determine the public properties of a bean. This approach is hardly just a convenience, however. The automatic approach to JavaBeans introspection relies heavily on the naming conventions and type signatures of a bean's methods, including methods for registering event listeners.
At first, the whole idea of JavaBeans analyzing the methods for a bean and automatically determining the bean's public properties might sound a little mystical. You might imagine that the JavaBeans API is jumping through many hoops to pull off such behavior. In fact, the mechanism behind this automatic introspection approach is quite simple. The approach relies on design patterns, which are standardized naming conventions and type signatures that are used to define bean methods based on their function. For example, accessor methods are a specific type of design pattern that applies to properties. Likewise, there is also a design pattern for events.
The whole premise behind design patterns is that method names and signatures generally conform to a standard convention, sometimes by accident, but more often by virtue of class organization. Because most Java developers use similar naming conventions for methods, the JavaBeans architects reasoned that with a little nudging developers might be willing to adhere to a more rigid naming convention, especially if there's something to be gained by doing so. Indeed, not having to worry about explicitly adding introspection support to a bean is certainly worth the effort of sticking with a naming convention.
There are a variety of different design patterns for specifying everything from simple properties to event sources. All of these design patterns rely on some type of consistent naming convention for methods and their related arguments and types. Keep in mind that this approach to introspection is not only convenient from the perspective of JavaBeans, but it also has the intended side effect of encouraging bean developers to use a consistent set of naming conventions. Here are the three major types of design patterns, each of which are discussed in more detail in the next few sections:
- Property design patterns
- Event design patterns
- Method design patterns
Property design patterns are used to identify the publicly accessible properties of a bean. Not surprisingly, property design patterns are closely related to accessor methods. In fact, you didn't really know it at the time, but in the previous chapter you were adhering to a design pattern when you saw the sample accessor methods for various properties. It turns out that accessor methods are the means by which the JavaBeans automatic introspection facilities determine which properties a bean exposes. Basically, any time the JavaBeans introspector encounters a public getter or setter method, it assumes the property in question is a public property and exposes it to the outside world.
If you recall from the previous chapter, properties need not always have pairs of accessor methods. For example, if a property has only a getter method, JavaBeans assumes it is a read-only property. Likewise, if a property has only a setter method, JavaBeans assumes it is a write-only property. If both methods are defined, guess what? You got it, the property is a read-write property. As you can see, design patterns are amazingly simple, but quite effective.
The design patterns for properties vary a little based on the type of property. Here are the property types supporting different design patterns:
- Simple properties
- Boolean properties
- Indexed properties
Simple Properties Simple properties consist of all single-valued properties, which include all built-in Java data types as well as classes and interfaces. For example, int, long, float, Color, Font, and boolean are all considered simple properties. The design patterns for simple property accessor methods follow:
public <PropertyType> get<PropertyName>(); public void set<PropertyName>(<PropertyType> x);
So, for a property called numBullets that is of type long, the following accessor methods would safely conform to the introspection design patterns:
public long getNumBullets(); public void setNumBullets(long nb);
When the JavaBeans automatic introspector encounters these method definitions, it determines both the numBullets public property, as well as the getNumBullets() and setNumBullets() accessor methods for accessing the property. And this takes place without you having to do anything more than conform to the simple property design patterns when you design your methods. Boolean Properties Although they are technically simple properties, boolean types have an optional design pattern for the getter method that can be used to make it more clear that a property is of boolean type. This design pattern follows:
public boolean is<PropertyName>();
The only difference between this design pattern and the one for simple properties is that it uses the word is instead of get in the name. It is fairly common for Java developers to access boolean properties with an is method, which is why JavaBeans supports the use of this special case design pattern. The following is an example of a pair of accessor methods for a boolean property named amphibious that conforms to the boolean design pattern:
public boolean isAmphibious(); public void setAmphibious(boolean a);
You might be wondering what happens in the event that both a get method and an is method are defined for a property. Not to worry, JavaBeans handles this by always using the is method if it is available, and the get method if not. Indexed Properties If you recall from the previous chapter, an indexed property is an array of simple properties. Because indexed properties require slightly different accessor methods, it only makes sense that their design patterns differ a little from other properties. Here are the design patterns for indexed properties:
public <PropertyElement> get<PropertyName>(int i); public void set<PropertyName>(int i, <PropertyElement> x); public <PropertyElement> get<PropertyName>(); public void set<PropertyName>(<PropertyElement> x);
The first pair of design patterns defines accessor methods for getting and setting individual elements in an indexed property. The second pair of patterns defines accessor methods for getting and setting the entire property array as a whole. You saw an example of how these accessor methods are defined for an indexed property in the previous chapter, but for clarity's sake, here's another one:
public Color getPalette(int i); public void setPalette(int i, Color c); public Color getPalette(); public void setPalette(Color c);
In this example, the indexed property is called palette and is an array of Color objects. The reason for the lowercase p in palette is because it is standard Java naming convention that all variables and methods begin with a lowercase letter. The first pair of accessor methods enable you to get and set individual colors in the palette, and the second pair enables you to get and set the whole palette of colors.
When a bean's internal state changes, it often is necessary to notify an outside party of the change. To accomplish this, beans are capable of broadcasting event notifications that inform interested parties of some change in a bean. Events themselves can represent many different types of notifications, but some of the most popular ones are mouse clicks and drags. For example, when the user drags the mouse over a bean, you might want the bean to notify its parent so the appearance of the cursor can be changed. This notification is handled by the bean sending its parent an event. You learn a great deal more about events in Chapter 6, "Handling Bean Events." For now, it's only important that you understand how a bean knows what to send event notifications to.
Beans send notifications to event listeners that have registered themselves with a bean as wanting to receive event notifications. Because listeners must register themselves with a bean, it is important that beans provide appropriate methods to facilitate the registration process. The event registration methods come in pairs, with one method enabling listeners to be added and the other enabling beans to be removed. Because this method pair is sufficient for defining an event registration between a bean and an interested party, it's probably not going to come as a surprise to you that the JavaBeans introspector uses these methods to assess the events that a bean is capable of sending.
Event registration methods must conform to the following design patterns for the automatic introspection services to work properly:
public void add<EventListenerType>(<EventListenerType> x); public void remove<EventListenerType>(<EventListenerType> x);
In these design patterns, <EventListenerType> refers to the object type
of the event listener being registered with the bean. JavaBeans requires that this
object implement the EventListener interface and have its name end with
Listener. Therefore, if you want to add support for an event listener implementing the ActionListener interface, you would do so like this:
public void addActionListener(ActionListener al); public void removeActionListener(ActionListener al);
The ActionListener interface is used to receive action events such as mouse clicks. In this example, action listeners can easily be registered to receive actions by calling the addActionListener() method, or removed as a listener by calling the removeActionListener() method.
The event registration design patterns discussed thus far are designed for beans that support multicast events, meaning that there can be multiple registered event listeners. JavaBeans also supports the use of unicast event sources, which are beans that enable only one listener at a time to receive event notifications. In other words, unicast beans enable only one event listener to be registered at a particular time. If an attempt is made to register more than one listener with a unicast bean, the add<EventListenerType>() method is thrown a TooManyListenersException exception. The design pattern for this method follows:
public void add<EventListenerType>(<EventListenerType> x) Âthrows TooManyListeners;
Here is the same action listener example designed for a unicast event source:
public void addActionListener(ActionListener al) throws ÂTooManyListeners; public void removeActionListener(ActionListener al);
The last area to cover in regard to design patterns is related to public methods that aren't accessor methods. These methods are completely user-defined in that they have no pre-established purpose such as getting or setting a property. Because the naming and type signature for non-accessor public methods is totally up to the bean developer, there aren't any specific design patterns governing them. JavaBeans assumes all public methods are to be publicly exported for outside use. The only difference between these public methods and public accessor methods is that accessor methods notify the JavaBeans introspector of a bean property.
Even though the design pattern approach to introspection is very useful and removes a significant burden from bean developers, there is nothing forcing you to follow them. What happens if you don't like the naming conventions enforced by the design patterns? Better yet, what happens if you have devised your own naming convention that you feel is far superior to the one used by the automatic design patterns? In either case, you are responsible for explicitly providing information about the publicly accessible parts of your beans. This certainly is more work than allowing the automatic introspection facilities to study and assess your beans, but it gives you total control over the parts of your beans that are made available for public use.
This explicit approach to exposing bean information involves creating a bean information class that specifies various pieces of information about a bean, including lists of public properties, methods, and events. The bean information class must implement the BeanInfo interface, which provides methods for learning about the public information for a bean. You form the name of a bean's information class by adding BeanInfo to the end of the bean's class name. Therefore, if you have a bean named CoolWidget, its associated bean information class must be named CoolWidgetBeanInfo.
Bean developers who want to explicitly provide bean information have the option of providing partial information through the BeanInfo interface if they so desire. In other words, it is possible to use the BeanInfo interface to offer information about a bean's methods but not about its events. In this case, the JavaBeans introspector will use the explicit information when analyzing the bean for methods but will fall back on automatic introspection for the bean's events and properties.
I have mentioned the JavaBeans introspector several times throughout the
chapter, and it's time you met the introspector. The JavaBeans introspector is implemented as a class called Introspector. This class is used to obtain information about a bean using the introspection facilities about which you've learned. The introspector is responsible for determining whether a bean has an associated bean information class, and for using automatic introspection if not. More specifically, the introspector closely examines the inheritance tree of a bean looking for explicit information. If this information has not been
provided via an object implementing the BeanInfo interface, then the introspector uses reflection techniques combined with design patterns to assess the bean's public attributes and behavior automatically.
This two-tiered approach to introspecting beans is provided to enable the utmost flexibility for bean developers. It is expected that many complex commercial beans will explicitly provide public bean information, but that end users deriving their own beans will probably prefer automatic introspection. The two-tiered introspection approach taken by the JavaBeans introspector enables this mixed arrangement to successfully work without any problems.
Security, which is always a big issue in regard to Java, has some relevance to JavaBeans as well. As you are probably aware, Java makes a huge distinction between whether an application is trusted or untrusted. Trusted applications include stand-alone Java applications as well as applets that have been digitally signed. Untrusted applications, on the other hand, are Java applets that have not been digitally signed. What does all this have to do with JavaBeans introspection?
One of the ways security enters the picture with JavaBeans is through the introspection of beans. The JavaBeans architects decided that internal information about a bean should be protected from untrusted applications with the same zeal as other resources, such as a user's hard drive. The reason is that you don't want an untrusted application to modify parts of a bean that they have no business bothering. To what parts am I referring? Basically, untrusted applications are only allowed access to public methods, properties, and events. This shouldn't come as a big surprise because that's how beans appear to most outside parties anyway. But not all.
A trusted application, such as an application builder tool, needs to know more about a bean than what is provided in a bean's public information. For trusted applications, the security restrictions are lifted, giving the applications the freedom to dig deeper into a bean's structure. This ultimately gets back to the discussion from the beginning of this chapter regarding low- and high-level JavaBeans introspection APIs. The low-level APIs are made available to trusted applications, while the high-level APIs are made available to untrusted applications.
None of the neat introspection features you've learned about in this chapter would mean much without the underlying classes and interfaces that make it all happen. You finish off the chapter by taking a quick look at the classes and interfaces that make the introspection facilities provided by the JavaBeans API a reality. Here are the classes and interfaces that make up the introspection portion of the JavaBeans API:
Note: All of these classes are covered in much greater detail in Appendix B, "JavaBeans API Quick Reference."
The BeanDescriptor class provides global information about a bean, including the name of the bean and the bean's customizer. You learn about customizers in Chapter 8, "Customization: Bean Support for Application Builders."
The EventSetDescriptor class represents a set of events that a bean is capable of generating. The events defined in an EventSetDescriptor class are all deliverable as method calls on a single event listener interface.
The FeatureDescriptor class serves as a common base class for the EventSetDescriptor, MethodDescriptor, and PropertyDescriptor classes. It represents bean information that is common across these classes, such as the name of an event, method, or property.
The IndexedPropertyDescriptor class represents a publicly accessible indexed property. This class provides methods for accessing the type of an indexed property along with its accessor methods.
The IntrospectionException class is used to bring attention to an error that has occurred during introspection, such as an accessor method with an invalid type signature.
The Introspector class provides the overhead necessary to analyze a bean and determine its public properties, methods, and events. This class uses explicit bean information if it exists, and it relies on reflection and design patterns to automatically analyze a bean if not.
The MethodDescriptor class represents a publicly accessible method. This class provides methods for accessing information such as a method's parameters.
The ParameterDescriptor class represents the parameters to a method and is mainly provided as a means for bean developers to provide detailed parameter information beyond that obtained through the Java reflection services.
The PropertyDescriptor class represents a publicly accessed property. This class provides methods for accessing the type of a property along with its accessor methods and whether it is bound or constrained.
The SimpleBeanInfo class is a support class designed to make it easier for bean developers to provide explicit information about a bean. This class basically implements every method in the BeanInfo interface but returns a value that triggers the automatic introspection services. This enables developers to provide selective bean information by overriding specific methods, without having to implement every method in the BeanInfo interface.
The BeanInfo interface defines a set of methods that can be used to find out information explicitly provided by a bean, such as lists of properties, methods, and events. Developers who want to provide explicit bean information must implement the BeanInfo interface and provide suitable information. It's important to note that any information not explicitly provided via the BeanInfo interface is determined using the automatic introspection facilities of the JavaBeans API.
This chapter took you on a tour of a very important part of the JavaBeans API: introspection. You began the chapter by learning exactly what introspection is and why it is important to a software component technology such as JavaBeans. You learned that beans would be pretty useless without some form of introspection. From there, you moved into the specifics of the automatic introspection services provided by the JavaBeans API, which rely heavily on design patterns. You saw how simple, yet powerful design patterns are in the automatic assessment of a bean's public information.
You also learned in this chapter that JavaBeans gives bean developers the capability
to explicitly provide information about their beans. This is an alternative to the
automatic introspection services provided by JavaBeans. It is the job of the JavaBeans
introspector to balance the use of these two introspection approaches and determine
exactly how to analyze a bean and find out about its public properties, methods,
and events. From there, you learned about
security as it relates to JavaBeans and introspection. You then finished up the chapter by taking a quick look at the specific classes and interfaces in the JavaBeans API that make introspection possible.
Now that you have a solid grasp of introspection and how it fits into the JavaBeans
API, you're ready to move on to yet another API area: event