Skip to content
Joshua Rodriguez edited this page May 25, 2016 · 15 revisions

Our reflection system uses the proxy pattern plus the enhancements in Java 8 to create a fast reflection system.

Take a look at the unit tests to see how the system works.

Java Signatures Below are signature types for Java types. For a method you use ([Ljava/lang/String;)V represents a method with an argument of String[] and returns void.

  • V void
  • J long
  • I int
  • B byte
  • Z boolean
  • D double
  • S short
  • F float
  • C char
  • [ array
  • L; object
  • () method

Proxy Interface

The proxy interface lets us access an object's instance at runtime. This is allows us to create code for a system that we know is available at runtime but not possible to have at compile time.

Every proxy object has special handling for $this(). It will grab the underlying instance that the proxy is proxing. We use the return type Object for instances that are not known at compile time. If the instance is known at compile time you may substitute the return type.

Standard Practices

  • Proxy objects should include a static method that will create the proxy instance.
  • Proxy objects should include Object $this()
  • Setters and Getter should avoid set and get prefix as visibility views are ignored
  • Use default methods to provide additional functionality

This snippet shows how to set up a very basic Proxy interface and standard practices

@Proxied("path.to.Object")
public interface ProxyObject {
    // standard practice - allow creating proxies of the instance
    static ProxyObject of(Object instance) {
        return Gateways.proxy(ProxyObject.class, instance);
    }

    // standard practice - allow retrieving the proxying instance
    Object $this();
}

Getter Annotation

The getter annotation tells the proxy to retrieve a field. It will ignore the visibility modifier of the field. When the method matches the name of the field you do not need to provide a value. Read more about the @Getter.

@Proxied("path.to.Object")
public interface ProxyObject {
    // Will access the field of `name`
    @Getter("name")
    String firstName();

    // Will access the index of the fields by the signature
    @Getter(signature = "Ljava/lang/String;", index = 0)
    String lastName();
}

Setter Annotation

The setter annotation tells the proxy to mutate a field. It will ignore the visibility modifier of the field. When the method matches the name of the field you do not need to provide a value. Read more about the @Setter.

@Proxied("path.to.Object")
public interface ProxyObject {
    // Will mutate the field of `name`
    @Setter("name")
    void firstName(String name);

    // Will mutate the index of the fields by the signature
    @Setter(signature = "Ljava/lang/String;", index = 0)
    void lastName(String name);
}

Invoke Annotation

The invoke annotation tells the proxy to invoke a method. It will ignore the visibility modifier of the method. When the method matches the name of the method you do not need to provide a value. Read more about the @Invoke.

@Proxied("path.to.Object")
public interface ProxyObject {
    // Will invoke the method of `name`
    @Invoke("display")
    void displayFirstName();

    // Will invoke the method by the signature
    @Invoke(signature = "()V", index = 0)
    void displayLastName();
}

Bridge Annotation

The bridge annotation allows to bridge the return types of the method. Even though the bridge accepts any Class<?> for the value the class must be an interface with @Proxied annotation. This allows you to chain proxy interfaces together without dealing with only Object. Read more about the @Bridge.

@Proxied("path.to.other.Object")
public interface ProxyOtherObject {
    Object $this();
}

@Proxied("path.to.Object")
public interface ProxyObject {
    // Will bridge the return type to the proxy
    @Bridge(ProxyOtherObject.class)
    @Invoke(signature = "()Lpath/to/other/Object;", index = 0)
    ProxyOtherObject otherObject();
}

Implements Annotation

The implements annotation is a power tool to allow the proxied instances to implement other runtime interfaces. This allows for the proxies to be DuckTyped to other types. While the Implements accepts the array of @Proxied interfaces in this context it used to tell the Proxied class to implement the other interfaces. Read more about the @Implements.

@Proxied("path.to.object")
@Implements({
    @Proxied("path.to.interface"),
    @Proxied("path.to.other.interface")
})
public interface ProxyObject {
    static ProxyObject of(Object instance) {
        return Gateways.proxy(ProxyObject.class, instance);
    }
}

Static Annotation

This is just for reading and has no effect. It just states that the @Setter, @Getter, @Invoke are on a static method or field.


Reflections Utility

There are times where you want to simply use standard reflection without the need of the proxy system. The Reflections utility class is the answer to this question. The following system uses the Value system, to encapsulate the exceptions from the method. When there was an error the value will be empty.

Note - When the value is empty it will either mean there was an error or the return value is empty.

Get a Method

Class<?> clazz = ...;
Value<Method> method = Reflections.method(clazz, "toString");

Invoke a Method

Method method = ...;
Object instance = ...;
Value<Object> returnType = Reflections.invoke(instance, method);

Get a Field

Class<?> clazz = ...;
Value<Field> field = Reflections.field(class, "fieldName");

Set a Field's Value

Object instance = ...;
Field field = ...;
Reflections.setter(instance, field, "Hello World");

Get a Field's Value

Object instance = ...;
Field field = ...;
Value<T> value = Reflections.getter(instance, field);

Create an Instance

Class<?> clazz = ...;
Value<T> value = Reflections.instance(clazz);

Get a Class

Value<Class<?>> clazz = Reflections.clazz("path.to.Class");