Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Feature Toggling Java Annotations

How can I feature toggle java annotations?

Simple feature toggle:- if(toggle enabled) do x

Spring allows the use of "profiles" to toggle beans.

I use these and they are fine but I'd like to toggle annotation on field or classes.. how can I do that?

Use case, I have a class that has jpa annotations. I want to be able to mark via configuration that some fields are @transient when in certain environments.

like image 455
user48545 Avatar asked Aug 22 '12 02:08

user48545


People also ask

What is feature toggle Java?

Feature Toggles are a very common agile development practices in the context of continuous deployment and delivery. The basic idea is to associate a toggle with each new feature you are working on. This allows you to enable or disable these features at application runtime, even for individual users.

What is @interface annotation in Java?

Annotation is defined like a ordinary Java interface, but with an '@' preceding the interface keyword (i.e., @interface ). You can declare methods inside an annotation definition (just like declaring abstract method inside an interface). These methods are called elements instead.

What is the meaning of feature toggling?

Feature toggles, also known as feature flags, are components of software development that allow specific features of an application to be activated or deactivated at will. This allows developers to safely “toggle” new features on or off for testing.


2 Answers

As mentioned before, trying to "disable" annotation, while it might be possible, isn't really the best way to approach your problem.

Like Adrian Shum said, you should change how the framework processes the annotation. In your case there should be some ORM provider underneath you JPA implementation (such as Hibernate).

Most ORMs have some way to provide custom functionality, for instance in the case of Hibernate, you could create an Interceptor and register it by adding hibernate.ejb.interceptor to the persistence-unit in your JPA configuration as detailed here .

What this interceptor should do is up to you, but I would suggest using a different annotation (such as @ConditionalTransiet) one way is to go over the fields via reflection, check if they have the annotation and if it's in the wrong enviornment then using onLoad and onSave wipe the relevant fields from the Object.

like image 81
Alon Bar David Avatar answered Sep 25 '22 15:09

Alon Bar David


One of the possible options is to use aspectj with its ability to declare annotations and spring's ability of load-time aspect weaving.

I suppose that annotations cannot be declared conditionally, but you can always compile them in a separate jar that can be put into the classpath depending on the certain environment so that load-time weaver will be able to find it.


UPDATE

While there are a lot of useful answers here, I found disabling/enabling annotations quite interesting playing with aspectj, so the sample is below.

The latest versions of aspectj support removing of annotations, but for now this feature is only available for the field annotations, so quite useful way is not to declare annotations at all and if they have to be enabled - to put the jar with precompiled aspects which will enable the annotations into the classpath as I mentioned earlier.


SAMPLE


The first jar

The main class

package org.foo.bar;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        MyClass myObj = context.getBean("myObj", MyClass.class);

        System.out.println(myObj);
        System.out.println(myObj.getValue1());
        System.out.println(myObj.getValue2());
    }

}

The class we will declare annotations in

package org.foo.bar;

public class MyClass {

    @MyAnn("annotated-field-1")
    private String field1;
    private String field2;

    @MyAnn("annotated-method-1")
    public String getValue1() {
        String value = null;
        try {
            MyAnn ann = getClass().getDeclaredMethod("getValue1").getAnnotation(MyAnn.class);
            if(ann != null) {
                value = ann.value();
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        return value;
    }

    public String getValue2() {
        String value = null;
        try {
            MyAnn ann = getClass().getDeclaredMethod("getValue2").getAnnotation(MyAnn.class);
            if(ann != null) {
                value = ann.value();
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        return value;
    }

    @Override
    public String toString() {
        String field1 = null;
        try {
            MyAnn ann = getClass().getDeclaredField("field1").getAnnotation(MyAnn.class);
            if(ann != null) {
                field1 = ann.value();
            }
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }

        String field2 = null;
        try {
            MyAnn ann = getClass().getDeclaredField("field2").getAnnotation(MyAnn.class);
            if(ann != null) {
                field2 = ann.value();
            }
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }

        StringBuilder sb = new StringBuilder();
        sb.append("MyClass");
        sb.append("{field1='").append(field1).append('\'');
        sb.append(", field2='").append(field2).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

The annotation itself

package org.foo.bar;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface MyAnn {

    String value();

}

Application context

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <context:load-time-weaver />

    <bean id="myObj" class="org.foo.bar.MyClass" />

</beans>

The second jar

The aspect

package org.foo.bar;

public aspect ToggleAnnotationAspect {

    declare @field : private String org.foo.bar.MyClass.field1 : -@MyAnn;
    declare @field : private String org.foo.bar.MyClass.field2 : @MyAnn("annotated-field-2");

    declare @method : public String org.foo.bar.MyClass.getValue2() : @MyAnn("annotated-method-2");

}

META-INF/aop.xml

<?xml version="1.0"?>

<aspectj>
    <aspects>
        <aspect name="org.foo.bar.ToggleAnnotationAspect"/>
    </aspects>
</aspectj>

Running the application without the second jar in the classpath

java -javaagent:spring-instrument-3.1.3.RELEASE.jar \
     -classpath app1.jar;<rest_of_cp> org.foo.bar.Main

will print

MyClass{field1='annotated-field-1', field2='null'}
annotated-method-1
null

Running the application with the second jar in the classpath

java -javaagent:spring-instrument-3.1.3.RELEASE.jar \
     -classpath app1.jar;app1-aspects.jar;<rest_of_cp> org.foo.bar.Main

will print

MyClass{field1='null', field2='annotated-field-2'}
annotated-method-1
annotated-method-2

So no modification to the application source were made at all.

like image 37
szhem Avatar answered Sep 24 '22 15:09

szhem