gdiquest
gdiquest

Reputation: 125

Implement custom annotation in Spring

I want to implement an annotation which registers classes (not instances of classes) with a factory as soon as the application is started. I am using Spring Framework 4.2.7.

Consider a system with a dashboard and multiple widgets. The dashboard has a configuration file which contains a list of widgets to display for the current user. When displayed it reads the configuration and creates the widgets. The widgets will receive additional parameters from the configuration.

Here is a bit of code illustrating this:

public class TestDashboard implements Dashboard {
    public void dashboardPreDisplay() {
        List<String> widgets = getWidgetList(/* current user in session */);
        for (String widgetId : widgets) {
            // create instance of DashboardWidget with given ID
            DashboardWidget x = widgetFactory.createWidget(widgetId);
        }
    }

    public List<String> getWidgetList(String user) {
        // load list of IDs of DashboardWidgets to be displayed for the user
    }

    @Autowired
    private WidgetFactory widgetFactory;
}

@Service
public class WidgetFactory {
    public DashboardWidget createWidget(String widgetId) {
        // look up Class<> of DashboardWidget with given id in widgetClasses
        // construct and initialize DashboardWidget
    }

    private HashMap<String, Class<?>> widgetClasses;
}

When implementing my widgets I don't want to deal with registering the widget with the factory class. Ideally I would just annotate the widget like that:

@DashboardWidget(id = "uniqueId")
public class DashboardWidgetA implements DashboardWidget {
    // ...
}

When the application starts it should scan the classpath for @DashboardWidget annotations and register the classes with the factory, so that the widgets can be constructed by giving the createWidget-method the id of the widget.

At the moment I am a little bit confused. I think Spring has every tool on board to achieve this behavior. But I cannot think of a way how to do it.

Do you have some advice for me?

Upvotes: 1

Views: 219

Answers (1)

Igor Konoplyanko
Igor Konoplyanko

Reputation: 9374

Nothing prevents you to create your custom annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DashboardWidget {}

Then you can annotate your Widget's classes and make them spring beans. You have to keep in mind if you want to have them as singletons (scope=singleton) , or separate instances per user (scope=prototype).

You have to implement:

public class WidgetInitializationListener implements ApplicationListener<ContextRefreshedEvent> {

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {

    ApplicationContext context = event.getApplicationContext();
    String[] beanDefinitionNames = context.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        String originalClassName = getOriginalClassName(beanDefinitionName, event);
        if (originalClassName != null) {
            Class<?> clazz = Class.forName(originalClassName);
            if (hasWidgetAnnotation(clazz)) {
                registerSomewhereYourWidget(context, beanDefinitionName, originalClassName);
            }
        }
    }
}

private String getOriginalClassName(String name, ContextRefreshedEvent event) {
    try {
        ConfigurableListableBeanFactory factory =
                (ConfigurableListableBeanFactory)event.getApplicationContext().getAutowireCapableBeanFactory();
        BeanDefinition beanDefinition = factory.getBeanDefinition(name);
        return beanDefinition.getBeanClassName();
    } catch (NoSuchBeanDefinitionException e) {
        LOG.debug("Can't get bean definition for : " + name);
        return null;
    }
}

So mostly here is nothing to do with spring except you just run through your beans to find annotated ones.

Upvotes: 2

Related Questions