程式扎記: [ In Action ] Dynamic object orientation - Working with GroovyBeans

標籤

2014年4月5日 星期六

[ In Action ] Dynamic object orientation - Working with GroovyBeans

Preface:
The JavaBeans specification was introduced with Java 1.1 to define a lightweight and generic software component model for Java. The component model builds on naming conventions and APIs that allow Java classes to expose their properties to other classes and tools. This greatly enhanced the ability to define and use reusable components and opened up the possibility of developing component-aware tools.

The first tools were mainly visually oriented, such as visual builders that retrieved and manipulated properties of visual components. Over time, the JavaBeans concept has been widely used and extended to a range of use cases including server-side components (in Java Server Pages [JSP]), transactional behavior and persistence (Enterprise JavaBeans [EJB]), object-relational mapping (ORM) frameworks, and countless other frameworks and tools.

Groovy makes using JavaBeans (and hence most of these other JavaBean-related frameworkseasier with special language support. This support covers three aspects: special Groovy syntax for creating JavaBean classes; mechanisms for easily accessing beans, regardless of whether they were declared in Groovy or Java; and support for JavaBean event handling. This section will examine each part of this language-level support as well as cover the library support provided by the Expando class.

Declaring beans:
JavaBeans are normal classes that follow certain naming conventions. For example, to make a String property myProp available in a JavaBean, the bean’s class must have public methods declared as String getMyProp and void setMyProp (String value) . The JavaBean specification also strongly recommends that beans should be serializable so they can be persistent and provide a parameterless constructor to allow easy construction of objects from within tools. A typical Java implementation is as follows:
  1. // Java  
  2. public class MyBean implements java.io.Serializable {  
  3.   private String myprop;  
  4.   public String getMyprop(){  
  5.     return myprop;  
  6.   }  
  7.   public void setMyprop(String value){  
  8.     myprop = value;  
  9.   }  
  10. }  
The Groovy equivalent is:
  1. class MyBean implements Serializable {  
  2.   String myprop  
  3. }  
The most obvious difference is size. One line of Groovy replaces seven lines of Java. But it’s not only about less typing, it is also about self-documentation. In Groovy, it is easier to assess what fields are considered exposed properties: all fields that are declared with default visibility. The three related pieces of information—the field and the two accessor methods—are kept together in one declaration. Changing the type or the name of the property requires changing the code in only a single place.

Underneath the covers, Groovy provides public accessor methods similar to this Java code equivalent, but you don’t have to type them. Moreover, they are generated only if they don’t already exist in the class. This allows you to override the standard accessors with either customized logic or constrained visibility. Groovy also provides a private backing field (again similar to the Java equivalent code).

Note that the JavaBean specification cares only about the available accessor methods and doesn’t even require a backing field; but having one is an intuitive and simple way to implement the methods—so that is what Groovy does.
Note.
It is important that Groovy constructs the accessor methods and adds them to the bytecode. This ensures that when using a MyBean in the Java world, the GroovyMyBean class is recognized as a proper JavaBean.

Listing 7.18 shows the declaration options for properties with optional typing and assignment. The rules are equivalent to those for fields (see section 7.2.1).
- Listing 7.18 Declaring properties in GroovyBeans
  1. class MyBean implements Serializable {  
  2.     def untyped  
  3.     String typed  
  4.     def item1, item2  
  5.     def assigned = 'default value'  
  6. }  
  7. def bean = new MyBean()  
  8. assert 'default value' == bean.getAssigned()  
  9. bean.setUntyped('some value')  
  10. assert 'some value' == bean.getUntyped()  
  11. bean.setUntyped(10)  
  12. assert 10 == bean.getUntyped()  
  13. bean = new MyBean(typed:'another value')  
  14. assert 'another value' == bean.getTyped()  
Properties are sometimes called readable or writeable depending on whether the corresponding getter or setter method is available. Groovy properties are both readable and writeable, but you can always roll your own if you have special requirements. When the final keyword is used with a property declaration, the property will only be readable (no setter method is created and the backing field is final).

Working with beans:
The wide adoption of the JavaBeans concept in the world of Java has led to a common programming style where bean-style accessor methods are limited to simple access (costly operations are strictly avoided in these methods). These are the types of accessors generated for you by Groovy. If you have complex additional logic related to a property, you can always override the relevant getter or setter, but you are usually better off writing a separate business method for your advanced logic.

Accessor methods
Even for classes that do not fully comply with the JavaBeans standard, you can usually assume that such an accessor method can be called without a big performance penalty or other harmful side-effects. The characteristics of an accessor method are much like those of a direct field access (without breaking the uniform access principle).

Groovy supports this style at the language level according to the mapping of method calls shown in table 7.2.


This mapping works regardless of whether it’s applied to a Groovy or plain old Java object (POJO), and it works for beans as well as for all other classes. Listing 7.19 shows this in a combination of bean-style and derived properties.
- Listing 7.19 Calling accessors the Groovy way
  1. class MrBean {  
  2.     String firstname, lastname              // Groovy style properties  
  3.       
  4.     String getName(){                       // 1) Getter for derived property  
  5.         return "$firstname $lastname"  
  6.     }  
  7. }  
  8.   
  9. def bean = new MrBean(firstname: 'Rowan')   // Generic constructor  
  10. bean.lastname = 'Atkinson'                  // 2) Call setter  
  11. assert 'Rowan Atkinson' == bean.name        // 3) Call getter  
Note how much the Groovy-style property access in (1) and (2) looks like direct field access, whereas (3) makes clear that there is no field but only some derived value. From a caller’s point of view, the access is truly uniform. Because field access and the accessor method shortcut have an identical syntax, it takes rules to choose one or the other.
RULES.
When both a field and the corresponding accessor method are accessible to the caller, the property reference is resolved as an accessor method call. If only one is accessible, that option is chosen.

That looks straightforward, and it is in the majority of cases. However, there are some points to consider, as you will see next.

Before we leave the topic of properties, we have one more example to explore:

Field access with .@ 
listing 7.20. The listing illustrates how you can provide your own accessor methods and also how to bypass the accessor mechanism. You can get directly to the field using the .@ dot-at operator when the need arises.
- Listing 7.20 Advanced accessors with Groovy
  1. class DoublerBean {  
  2.     public value  
  3.       
  4.     void setValue(value){  
  5.         this.value = value  // 1) Inner field access  
  6.     }  
  7.       
  8.     def getValue(){  
  9.         value * 2           // 2) Inner field access  
  10.     }  
  11. }  
  12.   
  13. def bean = new DoublerBean(value: 100)  
  14. assert 200 == bean.value    // 3) Property access  
  15. assert 100 == bean.@value   // Outer field access  
  16. bean.value = 150  
  17. assert 300 == bean.value      
  18. assert 150 == bean.@value  
Let’s start with what’s familiar: bean.value at (3) calls getValue and thus returns the doubled value. But wait— getValue calculates the result at (2) as value*2 . If valuewas at this point interpreted as a bean shortcut for getValue , we would have an endless recursion.

A similar situation arises at (1), where the assignment this.value= would in bean terms be interpreted as this.setValue , which would also let us fall into endless looping. Therefore the following rules have been set up.
RULES.
Inside the lexical scope of a field, references to fieldname or this.fieldname are resolved as field access, not as property access. The same effect can be achieved from outside the scope using the reference.@fieldname syntax.

Bean-style event handling
Besides properties, JavaBeans can also be event sources that feed event listeners. An event listener is an object with a callback method that gets called to notify the listener that an event was fired. An event object that further qualifies the event is passed as a parameter to the callback method.

The JDK is full of different types of event listeners. A simple event listener is the ActionListener on a button, which calls an actionPerformed(ActionEvent) method whenever the button is clicked. A more complex example is the VetoableChangeListener that allows listeners to throw a PropertyVetoException inside theirvetoableChange(PropertyChangeEvent) method to roll back a change to a bean’s property. Other usages are multifold, and it’s impossible to provide an exhaustive list.

Groovy supports event listeners in a simple but powerful way. Suppose you need to create a Swing JButton with the label “Push me!” that prints the label to the console when it is clicked. A Java implementation can use an anonymous inner class in the following way:
  1. // Java  
  2. final JButton button = new JButton("Push me!");  
  3. button.addActionListener(new IActionListener(){  
  4.     public void actionPerformed(ActionEvent event){  
  5.         System.out.println(button.getText());  
  6.     }  
  7. });  
The developer needs to know about the respective listener and event types (or interfaces) as well as about the registration and callback methods. A Groovy programmer only has to attach a closure to the button as if it were a field named by the respective callback method:
  1. button = new JButton('Push me!')  
  2. button.actionPerformed = { event ->  
  3.     println button.text  
  4. }  
The event parameter is added only to show how we could get it when needed. In this example, it could have been omitted, because it is not used inside the closure.
Note.
Groovy uses bean introspection to determine whether a field setter refers to a callback method of a listener that is supported by the bean. If so, a ClosureListener is transparently added that calls the closure when notified. A ClosureListener is a proxy implementation of the required listener interface.

Event handling is conceived as a JavaBeans standard. However, you don’t need to somehow declare your object to be a bean before you can do any event handling. The dependency is the other way around: As soon as your object supports this style of event handling, it is called a bean.

Using bean methods for any object:
Groovy doesn’t distinguish between beans and other kinds of object. It solely relies on the accessibility of the respective getter and setter methods. Listing 7.21 shows how to use the getProperties method and thus the properties property to get a map of a bean’s properties. You can do so with any object you fancy.
- Listing 7.21 GDK methods for bean properties
  1. class GroovyClass {  
  2.     def       someProperty  
  3.     public    someField  
  4.     private   somePrivateField  
  5. }  
  6. def obj = new GroovyClass()  
  7. def store = []  
  8. obj.properties.each { property ->  
  9.     store += property.key  
  10.     store += property.value  
  11. }  
  12. assert store.contains('someProperty')  
  13. assert store.contains('someField')        == false  
  14. assert store.contains('somePrivateField') == false  
  15. assert store.contains('class')  
  16. assert store.contains('metaClass')  
  17. assert obj.properties.size() == 3  
In addition to the property that is explicitly declared, you also see class and metaClass references. These are artifacts of the Groovy class generation. This was a taste of what will be explained in more detail in section 9.1.

Fields, accessors, maps, and Expando:
In Groovy code, you will often find expressions such as object.name. Here is what happens when Groovy resolves this reference:
* If object refers to a mapobject.name refers to the value corresponding to the name key that is stored in the map.
* Otherwise, if name is a is a property of object , the property is referenced (with precedence of accessor methods over fields, as you saw in section 7.4.2).
* Every Groovy object has the opportunity to implement its own getProperty(name) and setProperty(name, value) methods. When it does, these implementations are used to control the property access. Maps, for example, use this mechanism to expose keys as properties.
* As shown in section 7.1.1field access can be intercepted by providing the object.get(name) method. This is a last resort as far as the Groovy runtime is concerned: It’s used only when there is no appropriate JavaBeans property available and when getProperty isn’t implemented.

It is worth noting that when name contains special characters that would not be valid for an identifier, it can be supplied in string delimiters: for example, object.'my-name'. You can also use a GStringdef name = 'my-name'; object."$name" . As you saw in section 7.1.1 and we will further explore in section 9.1.1, there is also a getAtimplementation on Object that delegates to the property access such that you can access a property via object[name].

The rationale behind the admittedly nontrivial reference resolution is to allow dynamic state and behavior for Groovy objects. Groovy comes with an example of how useful this feature is: Expando. An Expando can be thought of as an expandable alternative to a bean, albeit one that can be used only within Groovy and not directly in Java. It supports the Groovy style of property access with a few extensions. Listing 7.22 shows how an Expando object can be expanded with properties by assignment, analogous to maps. The difference comes with assigning closures to a property. Those are executed when accessing the property, optionally taking parameters. In the example, the boxer fights back by returning multiple times what he has taken before.

Supplement:
Feature Overview : GroovyBeans
GroovyBeans are JavaBeans but using a much simpler syntax...

Dynamic Groovy : Using invokeMethod and getProperty
Groovy supports the ability to intercept all method and property access via the invokeMethod and get/setProperty hooks...

This message was edited 40 times. Last update was at 22/01/2014 17:57:24

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!