Thursday, November 03, 2005

Implement Bound properties with AspectJ

วันนี้อ่านเจอ post ของ damnhandy
ที่เขียนถึง JavaBean Aspect
โดยเขาตั้งโจทย์ว่า
เขาต้องการใช้ JGoodies เป็นตัวกลางในการ
bind hibernate persistent class เข้ากับ Swing component
ซึ่ง JGoodies Require ว่า class ที่ต้องการ bind
เข้ากับ Swing component ต้องเป็น Java Bean ที่ support
Bound properties (สามารถ add propertyChangeListener ได้)

ซึ่ง solution ที่เขาเขียนนั้นเขาใช้ JBoss AOP
วันนี้ผมก็เลยทดลองใช้ AspectJ implement ดูว่า
AspectJ สามารถทำ feature ประเภทนี้ได้ไหม

solution ของ damnhandy นั้น มี feature
พอสมควร มีการใช้ annotation เข้ามาช่วยด้วย
ผมจะตัด feature บางอันออกก่อน เพราะเราจะทำแค่
ทดลองชิม AspectJ ว่ามีรสประมาณไหนก่อน

โจทย์ที่จะทดลองทำ จะมีแค่
ในกรณีที่เรามี POJO class อยู่แล้ว
และต้องการ add feature bound properties เข้าไป
โดยใช้ AspectJ เราจะต้องทำได้อย่างไร

ในตัวอย่างของ AspectJ มีตัวอย่างเรื่องนี้อยู่แล้ว
ในหัวข้อ BoundPoint aspect
แต่เขาทำในลักษณะของ per class, per method
นั่นก็คือ ต้อง declare ทุก class, ทุก method ที่ต้องการ add bound properties
ผมก็เลยทดลอง declare ให้เป็น Generic มากขึ้นดู

สมมติว่าเรามี Class ที่ชื่อ Person
package domain;

public class Person {
private Long id;
private String name;
private String addresss;

public String getAddresss() {
return addresss;
}
public void setAddresss(String addresss) {
this.addresss = addresss;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}


เริ่มด้วย สร้าง interface ที่ชื่อ IValueObject ขึ้นมาก่อน
package domain;

import java.beans.PropertyChangeListener;

public interface IValueObject {

public void addPropertyChangeListener(
PropertyChangeListener listener);

public void addPropertyChangeListener(
String propertyName,
PropertyChangeListener listener) ;

public void removePropertyChangeListener(
PropertyChangeListener listener) ;

public void removePropertyChangeListener(
String propertyName,
PropertyChangeListener listener) ;

}


เตรียม Abstract Aspect ที่ implement method ในส่วนของ IValueObject
package domain;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import org.apache.commons.beanutils.PropertyUtils;

public abstract aspect ValueObjectChangeAspect {

public PropertyChangeSupport IValueObject.support =
new PropertyChangeSupport(this);

public void IValueObject.addPropertyChangeListener(
PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}

public void IValueObject.addPropertyChangeListener(
String propertyName,
PropertyChangeListener listener) {
support.addPropertyChangeListener(propertyName, listener);
}

public void IValueObject.removePropertyChangeListener(
PropertyChangeListener listener) {
support.removePropertyChangeListener(listener);
}

public void IValueObject.removePropertyChangeListener(
String propertyName,
PropertyChangeListener listener) {
support.removePropertyChangeListener(propertyName, listener);
}

abstract pointcut setter(IValueObject vo);

void around(IValueObject vo): setter(vo) {
String tmp = thisJoinPointStaticPart.getSignature()
.getName().substring("set".length());
String propName = tmp.substring(0, 1).toLowerCase()
+ tmp.substring(1);
try {
Object newValue = thisJoinPoint.getArgs()[0];
Object oldValue = PropertyUtils.getProperty(vo, propName);
proceed(vo);
vo.support.firePropertyChange(propName, oldValue, newValue);

} catch (Exception e) {
e.printStackTrace();
}
}

}

Aspect ValueObjectChangeAspect จะ delcare java.beans.PropertyChangeSupport instance
ที่จะไว้ใช้ช่วยทำ bound properties
โดยเราจะ declare pointcut ที่ชื่อ setter ไว้
ให้สังเกตุว่า pointcut นี้ยังเป็น abstract อยู่ นั่นก็คือว่ายังไม่มีการระบุให้ชัดว่า
จะเอาไปใช้กับ method อะไร,ที่ไหน
ในส่วนของ advice around จะ trig เมื่อ pointcut setter
ถูกเรียกใช้ ขั้นตอนการทำงาน ก็จะทำการหาค่า oldValue กับ newValue
(ใช้ common BeanUtils ของ apache เข้ามาช่วย)
เพื่อที่จะไว้ firePropertyChange
Note: โปรดระวัง code นี้ไม่สามารถใช้กับ production ได้
เพราะว่า ยังไม่ได้คิดว่า ถ้าเกิด exception แล้วจะทำอะไร


ถึงขั้นนี้ เราก็มี abstract aspect เตรียมไว้แล้ว ขั้นต่อไป ก็เป็นการ
ทำ Aspect PersonChangeAspect ที่เป็นการ implement IValueObject เข้ากับ Person Class
package domain;

public aspect PersonChangeAspect extends ValueObjectChangeAspect {

declare parents: Person implements IValueObject;

pointcut setter(IValueObject vo): call(void Person.set*(*)) && target(vo);

}

ข้างใน aspect ก็จะมีการ declare ว่าให้ Person ไป implement IValueObject เสีย
จากนั้นก็ declare pointcut setter ว่าให้ไปดักการทำงานของ
ทุก method ที่มีชื่อขึ้นต้นว่า set ใน class Person

สุดท้ายก็ทดสอบโปรแกรมดูดังนี้
package domain;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class TestRun implements PropertyChangeListener {

/**
* @param args
*/
public static void main(String[] args) {
// initialize object
Person p1 = new Person();
p1.setName("x");

IValueObject vo = (IValueObject) p1;
vo.addPropertyChangeListener(new TestRun());

p1.setName("y");
}

public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
System.out.println(evt.getOldValue());
System.out.println(evt.getNewValue());
}

}


Output ที่ได้ ก็เป็นดังนี้

name
x
y

Related link from Roti

No comments: