3.2. Reflect

Fabio Piro edited this page May 15, 2016 · 7 revisions

Build Status Maven Central Dependency Status

Low level reflection and introspection functionality for JavaBean properties. It supports bytecode generation, annotations and types. Largely inspired by java.lang.reflect and java.beans.Introspector.

Classes: 2 Status: Stable. Test coverage: 100%. Works on Android: Yes.

Dependencies

Required

  • jdk 1.6+

Optional

  • cglib If found in the classpath then the bytecode proxy generation will be automatically enabled to provide better performance.

Installation

  • Maven:

      <dependencies>
      	<dependency>
      		<groupId>org.minimalcode</groupId>
      		<artifactId>minimalcode-reflect</artifactId>
      		<version>0.5.1</version><!-- or last version -->
      	</dependency>
      
      	<!-- Optional Cglib Dependency (avoid it on Android systems) -->
      	<dependency>
      		<groupId>cglib</groupId>
      		<artifactId>cglib</artifactId>
      		<version>3.1</version>
      	</dependency>
      </dependencies>

Property Accessors

Accessor methods are auto-discovered following the standard JavaBean patterns.

  • Read Method: T getField() || boolean isField()
  • Write Method: void setField(T field)
public class Book {
	// Note: Getter, Isser and Setter methods are here hidden for readability
	public boolean valid;// isValid() and setValid(boolean valid)
	private String title;// getTitle() and setTitle(String title)
	protected List<String> pages;// getPages() and setPages(List<String> pages)
}
  • For normal properties, accessor methods must be public and can be inherited from superclasses\interfaces.

  • For declared properties instead, accessor methods can have any valid modifier (public, protected, private, package-private...), but must be declared in the target class or interface, as any inheritance is ignored.

  • Only one accessor is required: properties can be read-only or write-only.

Bean<Book> bean = Bean.forClass(Book.class);// works with interfaces too

// Get Single Property
Property title = bean.getProperty("title");
Property pages = bean.getDeclaredProperty("pages");
Property iNull = bean.getDeclaredProperty("not-valid");// null, if not found

// All Properties
for(Property property : bean.getProperties()) {
	System.out.println("Property: " + property.getName());
}

// All Declared Properties
for(Property property : bean.getDeclaredProperties()) {
	System.out.println("Property: " + property.getName() + " (declared)");
}

Property Reflection

Property::get and Property::set will invoke the getter\isser\setter methods. The bytecode proxy (if enabled) is automatically used to provide fast property access. The "raw" accessor methods are still available for additional flexibility. Furthermore, if a matching accessible field with the same name and type of the property is present, it will also be provided.

Object obj = new Book();
Bean<?> bean = Bean.forClass(obj.getClass());
Property title = bean.getProperty("title");

// if property exists
if (title != null) {
	// Write
	if (title.isWritable()) {
		title.set(obj, "new-title");
	}
	
	// Read
	if (title.isReadable()) {
		String value = (String) title.get(obj);
		System.out.println(title.getName() + " has value " + value + " in " + obj);
	}
	
	// Raw accessors and field
	Method readMethod = title.getReadMethod();
	Method writeMethod = title.getWriteMethod();
	Field field = title.getField();
}

Property Types

In addition to the plain type and the generic type (if any), a convenient actual type is provided. The actual type is the plain type for simple properties, the resolved element type for List, Iterable, Collection and array properties, or the Map value type for maps properties.

class Book {
	// Note: Getter and Setter methods are here hidden for readability
	private List<String> pages;// getPages() and setPages(List<String> pages)
}

Property pages = Bean.forClass(Book.class).getProperty("pages");

// if property exists
if (pages != null) {
	Class<?> type = pages.getType();// List
	Type genericType = pages.getGenericType();// ParameterizedType
	Class<?> actualType = pages.getActualType();// Resolved element Type: String
	
	if(List.class.isAssignableFrom(type)) {
		System.out.println(pages.getName() + " is a " + type + " of " + actualType);// "pages is a ...List of ...String"
	}
}

Property Annotations

Annotations are statically collected from field, read method and write method accessors. Inheritance of annotations from superclasses is also supported.

class Book {
	@MyAnnotationOne
	private List<String> pages;
	
	public List<String> getPages() { return pages; }
	
	@MyAnnotationTwo
	public void setPages(List<String> pages) { this.pages = pages; }
}

// Annotations
for(Property property : Bean.forClass(Book.class).getProperties()) {
	if(property.isAnnotationPresent(MyAnnotationOne.class)) {
		MyAnnotationOne annotation = property.getAnnotation(MyAnnotationOne.class);
		System.out.println(property.getName() + " has @MyAnnotationOne with value " + annotation.value());
	}
    	
	// All the AnnotatedElement methods are also supported
	... = property.getAnnotations();
	... = property.getAnnotation(MyAnnotationOne.class);
	... = property.getAnnotationsByType(MyAnnotationOne.class);// only Jdk 1.8
	... = property.getDeclaredAnnotations();
	... = property.getDeclaredAnnotation(MyAnnotationTwo.class);
}

That's all... Less is more.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.