简体   繁体   中英

Java | JavaBean Monitoring

While working of something, I needed to check if the value(s) in a JavaBean (a form bean in my case) has changed.

Generally in web form submission we encounter similar situation in update/edit flow. We generally store the form bean is session before update operation and compare the values in the newly submitted form bean. This is simple but clutters the code and requires code change when properties are added/modified.

Problem Statement:-

  1. I have a class with properties , primitive types, arrays, other class types....

  2. I have an object of this class. This object goes through a method which can potentially change the properties of this object.

  3. How I can transparently know:-

a) If the object properties has changed.

b) What are the properties that changed.

c) Add listener to the property change event.

The JDK has a java.beans.PropertyChangeSupport that solves this. This works fine but I think it can be done in a better way.

I want to add "Monitoring" functionality to my POJO declarative using annotations / reflections. For Example :-

@Monitored
class UserFormBean{

  private String name;
  private UserAccount account;
}

This class should have method similar to these injected:-

public boolean hasChanged();

// null if not changed, old value if changed.
public Object hasChanged(String propName);

Question:-

1) Is there an exiting API similar to what I suggest?.

2) Is it good idea to write such API ? Feasibility ?

1) Is there an exiting API similar to what I suggest?.

Not that I know of.

2) Is it good idea to write such API ? Feasibility ?

AOP would help you do this, but you wouldn't be able to call hasChanged() in code as the method won't exist until runtime.

You could have a method like this

boolean changed = Monitoring.hasChanged(myPojo);

This is how I tried to solve this:-

The aspect:-

/**
 * @author Kumar Sambhav Jain
 * 
 */
public aspect SetterMonitoringAspect {

/**
 * All classes annotated with @Monitored will implement the MonitoredBean
 * interface.
 */
declare parents : (@com.samsoft.bean.monitor.annotation.Monitored *) implements com.samsoft.bean.monitor.MonitoredBean;

/*
 * ******************************************** INTER TYPE DECLARATION START
 */

/**
 * Injected PropertyChangeSupport
 * 
 * @return
 */
private Map<String, Object> MonitoredBean.cache = null;

/**
 * 
 * @return true if start monitored was invoked on the bean.
 */
public boolean MonitoredBean.isMonitorinActive() {
    return cache != null;
}

/**
 * Stop Monitoring the bean.
 */
public void MonitoredBean.stopMonitoring() {
    cache = null;
}

/**
 * Start monitoring the bean for changes. Also Resets the
 * PropertyChangeSupport property.
 * 
 * @throws IllegalAccessException
 * @throws IllegalArgumentException
 */
public void MonitoredBean.startMonitor() {
    cache = new HashMap<String, Object>();
}

/**
 * 
 * @return null if monitoring is not enabled. true if the bean
 *         property/properties changed after startMonitor() was invoked on
 *         it.
 */
public Boolean MonitoredBean.hasChanged() {
    if (cache == null) {
        return null;
    } else {
        return cache.size() > 0;
    }
}

/**
 * Check if a particular property has changed.
 * 
 * @param propertyName
 *            Exact case sensitive property name.
 * @return null if the property was not changed. Old value if the property
 *         was changed.
 */
public Object MonitoredBean.hasChanged(String propertyName) {
    return cache.get(propertyName);
}

/*
 * ********************************************* INTER TYPE DECLARATION END
 */

/**
 * Point cut for setter methods of Java bean implementing.
 * 
 * {@link MonitoredBean}
 */
pointcut monitoredBeanInterfaceSetters(
        com.samsoft.bean.monitor.MonitoredBean monitoredBean) : target(monitoredBean) && within(@com.samsoft.bean.monitor.annotation.Monitored *) && execution(public void set*(..));

/**
 * Before advice on a setter of a monitored bean.
 * 
 * @param monitoredBean
 * @param joinPoint
 */
@Before(argNames = "monitoredBean", value = "monitoredBeanInterfaceSetters(monitoredBean)")
public void beforeSetterAdvice(MonitoredBean monitoredBean,
        JoinPoint joinPoint) {
    if (monitoredBean.isMonitorinActive()) {
        try {
            String fieldName = joinPoint.getStaticPart().getSignature()
                    .getName().substring(3).toLowerCase();
            Object newValue = joinPoint.getArgs()[0];

            Field declaredField = monitoredBean.getClass()
                    .getDeclaredField(fieldName);
            declaredField.setAccessible(true);
            Object oldValue = declaredField.get(monitoredBean);

            if (oldValue == null && newValue == null) {
                return;
            } else if ((oldValue == null && newValue != null)
                    || (oldValue != null && newValue == null))  {
                monitoredBean.cache.put(fieldName, oldValue);
            } else if (oldValue != null && newValue != null) {
                if (!oldValue.equals(newValue)) {
                    monitoredBean.cache.put(fieldName, oldValue);
                } else {
                    monitoredBean.cache.remove(fieldName);
                }
            }
        } catch (Exception e) {
            monitoredBean.cache = null;
            e.printStackTrace();
        }
    }
}
}

Sample JavaBean that needs monitoring:-

@Monitored  
public class UserDetails{

private String name;
private String email;
private short age;

    // getter setter ommitted
}

JUnit Tests:-

@Test
public void test() {

    UserDetails userDetails = new UserDetails();
    userDetails.setAge((short) 23);
    userDetails.setEmail("kumar.sambhav.jain@gmail.com");
    Assert.assertNotNull(userDetails);
    Assert.assertNotNull(userDetails.getEmail());
    // Start monitoring changes
    userDetails.startMonitor();

    userDetails.setAge((short) 23);
    Assert.assertFalse((userDetails.hasChanged()));  // 23 to 23 -> not changed
    userDetails.setEmail("kjai10@gmail.com");
    Assert.assertTrue(userDetails.hasChanged());
    Assert.assertNotNull(userDetails.hasChanged("email"));
    Assert.assertTrue(userDetails.hasChanged("email").equals(
            "kumar.sambhav.jain@gmail.com"));


    SampleBean sb = new SampleBean();
    sb.startMonitor();
    System.out.println(sb.hasChanged());;

}

The source code can be found here .

The client project of this API must enable compile time weaving using AspectJ.

For Maven users this can be achieved using plugin as:-

    <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>aspectj-maven-plugin</artifactId>
            <version>1.6</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>test-compile</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <encoding>UTF-8</encoding>
                <verbose>false</verbose>
                <outxml>true</outxml>
                <showWeaveInfo>false</showWeaveInfo>
                <XaddSerialVersionUID>true</XaddSerialVersionUID>
                <source>1.7</source>
                <target>1.7</target>
                <complianceLevel>1.7</complianceLevel>
                <aspectLibraries>
                    <aspectLibrary>
                        <groupId>com.samsoft</groupId>
                        <artifactId>bean-monitor</artifactId>
                    </aspectLibrary>
                </aspectLibraries>
            </configuration>
        </plugin>

I will try to upload the API to Central Maven Repository after running some more test cases.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM