Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[aspectj-users] Extending java semantics to pickup methods

Hello all,
as you probably know many frameworks are currently based on dynamic
bindings of Java beans, for example Hibernate just to name one. Many if
not all these frameworks uses the .class syntax to design a class, but
unfortunately fall back to plain strings when it comes to identify a
method or a field. This is because Java lacks a syntax to easily access
to fields and methods in a way that the compiler could check our access.
For example, this is what happens in a typical hibernate call :

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.like("name", "Fritz%") )
    .add( Restrictions.between("weight", minWeight, maxWeight) )
    .list();


As you can see, Cat.class is used to designate a class in a compiler
friendly way, while the strings "name" and "weight" are used to
designate the two fields/getters in a way that is impossible for the
compiler (except with an annotation processor) to tell us if there has
been an error.

What Java is missing is something like Cat.methods.getName() or
Cat.fields.name or Cat.properties.name.

Recently a number of syntactic solutions emerged to overcome this
limitation. For example JMock and recently JUnit 4.4 introduced a number
of language constructs that makes it possible to say "when this method
is invoked, return this, and check that" in a way which is compiler
friendly :

        final HttpServletRequest req = mock.mock(HttpServletRequest.class);
      
        mock.checking(new Expectations() {{
            one(req).getPathInfo(); will(returnValue("/test/report"));
            allowing(req).getSession(); will(returnValue(session));
       }});

This is amazing. Unfortunately, it uses Java proxies to obtain this
results. Basically, "req" is a java dynamic proxy implementing the
interface HttpServletRequest, and that connects calls with the rules
defined in Expectations. This limits the JMock framework to interfaces.
If I wanted to apply the same rules to a normal concrete bean I have no
way to use that amazing syntax.

AspectJ makes it possible to overcome this limitation, in the sense that
it's possible to write pointcuts on concrete classes. So, I was thinking
about a syntax similar to the one used by JMock, that could make it
possible to retrieve a method or a field in a compiler friendly way, for
example:

method(from(Cat.class).getWeight());

field(from(Cat.class).weight);

property(from(Cat.class).getWeight());

and then rewrite the hibernate call like :

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.between(property(from(Cat.class).getWeight()), minWeight, maxWeight) )
    .list();



It could even be simpler :

Cat c = from(Cat.class);

List cats = sess.createCriteria(Cat.class)
    .add( Restrictions.like(propertyName(c.getName()), "Fritz%") )
    .add( Restrictions.between(propertyName(c.getWeight()), minWeight, maxWeight) )
    .list();


I'm currently prototyped this with a simple class with the static
methods from, property, method etc.., an abstract aspect to intercept
calls to the target class, and then one concrete aspect for each class
(like Cat) so that not all classes get instrumented. Being a prototype,
i got it simple and used ThreadLocals, assumed that the target class is
a simple bean and instantiating it causes no side effects, and similar
naive approaches .. you can find the code at the end of this email. This
prototype allows for the "method(from(Cat.class).getWeight())" syntax
only, but the idea is there.
 
WDYT about it? Are there some powerful AspectJ features I'm missing here
that could make it easier/more performant/whatever else? Is there an
already done and better written library to obtain the same results?

Simone

-------------------
import java.lang.reflect.Method;

public class Designator {

    public static <T> T from(Class<T> clazz){
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            throw new RuntimeException("Error creating mock instance of
" + clazz.getSimpleName(), e);
        }
    }
   
    public static Method method(Object o) {
        return Intercept.method.get();
    }
   
}

-------------------
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import org.aspectj.lang.reflect.MethodSignature;

public abstract aspect Intercept<T> {

    private ThreadLocal<Boolean> intercepting = new ThreadLocal<Boolean>();
   
    public static ThreadLocal<Method> method = new ThreadLocal<Method>();
   
    pointcut fromExecuted() : execution(* Designator.from(..));
   
    before() : fromExecuted() {
        System.out.println("I'm intercepting");
        intercepting.set(true);
    }
   
    pointcut otherExecuted() : execution(* Designator.*(..)) &&
!fromExecuted();
   
    after() : otherExecuted() {
        System.out.println("I'm not");
        intercepting.set(false);
    }
   
    pointcut methodInFrom() : call(* T.*(..));
   
    Object around() : methodInFrom() {
        System.out.println("Is intercepting " + intercepting.get());
        if(!intercepting.get()) return proceed();
        MethodSignature s = (MethodSignature)thisJoinPoint.getSignature();
        method.set(s.getMethod());
        return null;
    }
   
}

--------------------

public aspect CatIntercept extends Intercept<TestBean> {

}

-------------------

import java.lang.reflect.Method;

import org.junit.Test;
import static Designator.*;
import static org.junit.Assert.*;

public class TestMethod {

    @Test
    public void getter() throws Exception {
        Method m = method(from(Cat.class).getWeight());
        assertNotNull(m);
        assertEquals("getWeight", m.getName());
    }
   
}




Back to the top