74

I have the DataPrepareService that prepare data for reports and I have an Enum with report types, and I need to inject ReportService into Enum or have access to ReportService from enum.

my service:

@Service
public class DataPrepareService {
    // my service
}

my enum:

public enum ReportType {

    REPORT_1("name", "filename"),
    REPORT_2("name", "filename"),
    REPORT_3("name", "filename")

    public abstract Map<String, Object> getSpecificParams();

    public Map<String, Object> getCommonParams(){
        // some code that requires service
    }
}

I tried to use

@Autowired
DataPrepareService dataPrepareService;

, but it didn't work

How can I inject my service into enum?

Andrej Soroj
  • 1,103
  • 1
  • 9
  • 10

8 Answers8

89
public enum ReportType {

    REPORT_1("name", "filename"),
    REPORT_2("name", "filename");

    @Component
    public static class ReportTypeServiceInjector {
        @Autowired
        private DataPrepareService dataPrepareService;

        @PostConstruct
        public void postConstruct() {
            for (ReportType rt : EnumSet.allOf(ReportType.class))
               rt.setDataPrepareService(dataPrepareService);
        }
    }

[...]

}

weekens' answer works if you change inner class to static so spring can see it

Tobias
  • 7,238
  • 10
  • 46
  • 77
user3195004
  • 891
  • 6
  • 3
  • 4
    Something to note here, the above code won't compile because the static class is defined before the enum constants.. According to java specs enum constants comes first before any class declarations. Simply put the constants up and your class down. – prettyvoid Oct 02 '17 at 09:04
  • Don't forget to add it as a bean to your `config.xml`. – Mahdi Jun 05 '18 at 13:31
  • 1
    Probably it goes without saying but should have a setter in the enum class itself – Gilad Dahan Sep 21 '22 at 08:38
15

Maybe something like this:

public enum ReportType {
    @Component
    public class ReportTypeServiceInjector {
        @Autowired
        private DataPrepareService dataPrepareService;

        @PostConstruct
        public void postConstruct() {
            for (ReportType rt : EnumSet.allOf(ReportType.class))
               rt.setDataPrepareService(dataPrepareService);
        }
    }

    REPORT_1("name", "filename"),
    REPORT_2("name", "filename"),
    ...
}
weekens
  • 8,064
  • 6
  • 45
  • 62
4

There is one another approach you may like to explore. However instead of injecting a bean into enum it associates a bean with an enum

Say you have an enum WidgetType and Widget class

public enum WidgetType {
  FOO, BAR;
}

public class Widget {

  WidgetType widgetType;
  String message;

  public Widget(WidgetType widgetType, String message) {
    this.widgetType = widgetType;
    this.message = message;
  }
}

And you want to create Widgets of this type using a Factory BarFactory or FooFactory

public interface AbstractWidgetFactory {
  Widget createWidget();
  WidgetType factoryFor();
}

@Component
public class BarFactory implements AbstractWidgetFactory {
  @Override
  public Widget createWidget() {
    return new Widget(BAR, "A Foo Widget");
  }
  @Override
  public WidgetType factoryFor() {
    return BAR;
  }
}

@Component
public class FooFactory implements AbstractWidgetFactory {
  @Override
  public Widget createWidget() {
    return new Widget(FOO, "A Foo Widget");
  }
  @Override
  public WidgetType factoryFor() {
    return FOO;
  }
}

The WidgetService is where most of the work happens. Here I have a simple AutoWired field which keeps tracks of all the registered WidgetFactories. As a postConstruct operation we create a map of the enum and the associated factory.

Now clients could inject the WidgetService class and get the factory for the given enum type

@Service
public class WidgetService {

  @Autowired
  List<AbstractWidgetFactory> widgetFactories;

  Map<WidgetType, AbstractWidgetFactory> factoryMap = new HashMap<>();

  @PostConstruct
  public void init() {
    widgetFactories.forEach(w -> {
      factoryMap.put(w.factoryFor(), w);
    });
  }

  public Widget getWidgetOfType(WidgetType widgetType) {
    return factoryMap.get(widgetType).createWidget();
  }

}
aristotll
  • 8,694
  • 6
  • 33
  • 53
diduknow
  • 1,624
  • 2
  • 13
  • 10
  • `WidgetService` is a singleton, shouldn't `factoryMap` be an instance of an immutable map like f.e. `java.lang.Object.com.google.common.collect.ImmutableMap` ? – user3529850 Oct 20 '18 at 09:33
2

Enums are static, so you have to figure out a way to access to the beans from a static context.

You can create a class named ApplicationContextProvider that implements the ApplicationContextAware interface.

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class ApplicationContextProvider implements ApplicationContextAware{

 private static ApplicationContext appContext = null;

 public static ApplicationContext getApplicationContext() {
   return appContext;
 }

 public void setApplicationContext(ApplicationContext appContext) throws BeansException {
   this.appContext = appContext;
 }
}

then add this your application context file:

<bean id="applicationContextProvider" class="xxx.xxx.ApplicationContextProvider"></bean>

after that you could access to the application context in a static way like this:

ApplicationContext appContext = ApplicationContextProvider.getApplicationContext();
Josema
  • 1,807
  • 1
  • 17
  • 15
1

it will be hard to control that the spring container is already up and running at the time the enum is instantiated (if you had a variable with this type in a test-case, your container will usually not be there, even aspectj autowiring won't help there). i would recommend to just let the dataprepare-service or something give you the specific-params with a lookup-method with the enum-parameter.

cproinger
  • 2,258
  • 1
  • 17
  • 33
0

I think this what you need

public enum MyEnum {
    ONE,TWO,THREE;
}

Autowire the enum as per usual

@Configurable
public class MySpringConfiguredClass {

          @Autowired
      @Qualifier("mine")
          private MyEnum myEnum;

}

Here is the trick, use the factory-method="valueOf" and also make sure lazy-init="false"

so the container creates the bean upfront

<bean id="mine" class="foo.bar.MyEnum" factory-method="valueOf" lazy-init="false">
    <constructor-arg value="ONE" />
</bean>

and you are done!

Ashish Shetkar
  • 1,414
  • 2
  • 18
  • 35
-1

Just pass it to the method manually

public enum ReportType {

    REPORT_1("name", "filename"),
    REPORT_2("name", "filename"),
    REPORT_3("name", "filename")

    public abstract Map<String, Object> getSpecificParams();

    public Map<String, Object> getCommonParams(DataPrepareService  dataPrepareService){
        // some code that requires service
    }
}

As long as you call the method only from managed beans, you can inject it in these beans and pass the reference to the enum on each call.

Wanja Krah
  • 34
  • 3
-1

Maybe you can use this solution ;

public enum ChartTypes {
AREA_CHART("Area Chart", XYAreaChart.class),
BAR_CHART("Bar Chart", XYBarChart.class),

private String name;
private String serviceName;

ChartTypes(String name, Class clazz) {
    this.name = name;
    this.serviceName = clazz.getSimpleName();
}

public String getServiceName() {
    return serviceName;
}

@Override
public String toString() {
    return name;
}
}

And in another class which you need the bean of the Enum :

ChartTypes plotType = ChartTypes.AreaChart
Object areaChartService = applicationContext.getBean(chartType.getServiceName());
  • The question clearly was hwo to inject the dependency into the enum. this does not answer it. – IARI Apr 13 '20 at 09:51