TAP5-2029: copy annotations from service implementation to proxy

  1. tapestry
  2. month-of-tapestry
  3. tapestry-ioc
  4. annotations
  5. syntax-highlight
  6. www

I've just committed the implementation of TAP5-2029 (Copy annotations from service implementation to proxy). One caveat: the annotations are not live-class-reloaded: once the service proxy is created, changes to annotations in the service class will not be reflected in the proxy instance.

The problem

Tapestry-IoC had a limitation when creating services based on interfaces (when you define both a service interface and a service implementation): the service instance is a proxy which doesn't have the annotations the service implementation had, both in the proxy class itself and its annotations. This caused problems when passing service instances to other frameworks. In addition, when implementing a method MethodAdvice, MethodInvocation.hasAnnotation(Class annotationType) and MethodInvocation.getAnnotation(Class annotationType) returned results based on the service interface only.

The workaround was to put annotations in the service interface, but then this is very much against Object-Oriented principles, so this Tapestry-IoC issue was my pet one until now.

The example

Here's an example, which is taken from the test which is part of the fix with some adaptations. We have a service interface, NonAnnotatedServiceInterface, which has no annotations at all.

public interface NonAnnotatedServiceInterface {
	public String execute(int i);

Now we create an implementation, NonAnnotatedServiceInterfaceImpl, which has an annotation on itself (@ReorderProperties) and one annotation in its execute(int i) method (@Advise) and even in its parameter. Notice that, in this example, the annotations themselves have no meaning. I've just used some that already existed so I didn't need to create some just for the tests.

@ReorderProperties("reorder") // no meaning, just for testing whether the proxy will have it
public class NonAnnotatedServiceInterfaceImpl implements NonAnnotatedServiceInterface {

	@Advise(id = "id", serviceInterface = NonAnnotatedServiceInterface.class)
	public String execute(@IntermediateType(String.class) int i) { // annotation just for checking too
		return null;


A Tapestry-IoC module class for declaring the service:

public class TestModule { 
    public static void bind(ServiceBinder binder) {
        binder.bind(NonAnnotatedServiceInterface.class, NonAnnotatedServiceInterfaceImpl.class);

Now some code to test the issue by getting the service and checking whether we can find the annotations we have put in the service class annotation itself, in its method and in the method parameter:

public class Test {

    public static void main(String[] args) throws Exception {
        RegistryBuilder builder = new RegistryBuilder();
        Registry registry = builder.build();
        NonAnnotatedServiceInterface service = registry.getService(NonAnnotatedServiceInterface.class);
        Method method = service.getClass().getMethod("execute", int.class);
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        System.out.println("Annotation on service implementation type: " + 
        System.out.println("Annotation on service implementation method: " + 
        System.out.println("Annotation on service implementation method parameter: " + 
                (parameterAnnotations[0].length > 0 ? parameterAnnotations[0][0] : null));

When running with Tapestry-IoC 5.3.7, we get this output:

Annotation on service implementation type: null
Annotation on service implementation method: null
Annotation on service implementation method parameter: null

The output is different with Tapestry-IoC 5.4-SNAPSHOT (not yet released), proving it's working:

Annotation on service implementation type: @org.apache.tapestry5.beaneditor.ReorderProperties(value=reorder)
Annotation on service implementation method: @org.apache.tapestry5.ioc.annotations.Advise(id=id, serviceInterface=interface service_annotations.NonAnnotatedServiceInterface)
Annotation on service implementation method parameter: @org.apache.tapestry5.ioc.annotations.IntermediateType(value=class java.lang.String)

Month of Tapestry progress

Hours worked so far
21 out of 160 (13%)
Issues fixed