Is there a way to set up such enum values via Spring IoC at construction time?
What I would like to do is to inject, at class load time, values that are hard-coded in the code snippet below:
public enum Car
{
NANO ("Very Cheap", "India"),
MERCEDES ("Expensive", "Germany"),
FERRARI ("Very Expensive", "Italy");
public final String cost;
public final String madeIn;
Car(String cost, String madeIn)
{
this.cost= cost;
this.madeIn= madeIn;
}
}
Let's say that the application must be deployed in Germany, where Nanos are "Nearly free", or in India where Ferraris are "Unaffordable". In both countries, there are only three cars (deterministic set), no more no less, hence an enum, but their "inner" values may differ. So, this is a case of contextual initialization of immutables.
Do you mean setting up the enum itself?
I don't think that's possible. You cannot instantiate enums because they have a static nature. So I think that Spring IoC can't create enums as well.
On the other hand, if you need to set initialize something with a enum please check out the Spring IoC chapter. (search for enum) There's a simple example that you can use.
I don't think it can be done from Spring's ApplicationContext configuration. But, do you really need it done by Spring, or can you settle for simple externalization using ResourceBundle; like this:
public enum Car
{
NANO,
MERCEDES,
FERRARI;
public final String cost;
public final String madeIn;
Car()
{
this.cost = BUNDLE.getString("Car." + name() + ".cost");
this.madeIn = BUNDLE.getString("Car." + name() + ".madeIn");
}
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(...);
}
In the properties file, one for each specific locale, enter the keys describing the possible internal enum values:
Car.NANO.cost=Very cheap
Car.NANO.madeIn=India
Car.MERCEDES.cost=Expensive
...
The only drawback of this approach is having to repeat the name of enum fields (cost, madeIn) in Java code as strings. Edit: And on the plus side, you can stack all properties of all enums into one properties file per language/locale.
OK, it's quite fiddly, but it CAN be done.
It's true that Spring cannot instantiate enums. But that's not a problem - Spring can also use factory methods.
This is the key component:
public class EnumAutowiringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private final List<Class<? extends Enum>> enumClasses = new ArrayList<>();
public EnumAutowiringBeanFactoryPostProcessor(Class<? extends Enum>... enumClasses) {
Collections.addAll(this.enumClasses, enumClasses);
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (Class<? extends Enum> enumClass : enumClasses) {
for (Enum enumVal : enumClass.getEnumConstants()) {
BeanDefinition def = new AnnotatedGenericBeanDefinition(enumClass);
def.setBeanClassName(enumClass.getName());
def.setFactoryMethodName("valueOf");
def.getConstructorArgumentValues().addGenericArgumentValue(enumVal.name());
((BeanDefinitionRegistry) beanFactory).registerBeanDefinition(enumClass.getName() + "." + enumVal.name(), def);
}
}
}
}
Then the following test class shows that it works:
#Test
public class AutowiringEnumTest {
public void shouldAutowireEnum() {
new AnnotationConfigApplicationContext(MyConig.class);
assertEquals(AutowiredEnum.ONE.myClass.field, "fooBar");
assertEquals(AutowiredEnum.TWO.myClass.field, "fooBar");
assertEquals(AutowiredEnum.THREE.myClass.field, "fooBar");
}
#Configuration
public static class MyConig {
#Bean
public MyClass myObject() {
return new MyClass("fooBar");
}
#Bean
public BeanFactoryPostProcessor postProcessor() {
return new EnumAutowiringBeanFactoryPostProcessor(AutowiredEnum.class);
}
}
public enum AutowiredEnum {
ONE,
TWO,
THREE;
#Resource
private MyClass myClass;
}
public static class MyClass {
private final String field;
public MyClass(String field) {
this.field = field;
}
}
}
Why not provide a setter (or constructor argument) that takes a String, and simply call Enum.valueOf(String s) to convert from a String to an enum. Note an exception will get thrown if this fails, and your Spring initialisation will bail out.
You can't create new enum values via Spring, they must be declared in the class. However, since the enum values will be singletons anyway (created by the JVM), any configurations that should be set, or services to be injected, can be done via invoking static methods in the enum class:
http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/beans/factory/config/MethodInvokingFactoryBean.html
I have done it in the following way:
#Component
public class MessageSourceHelper {
#Inject
public MessageSource injectedMessageSource;
public static MessageSource messageSource;
public static String getMessage(String messageKey, Object[] arguments, Locale locale) {
return messageSource.getMessage(messageKey, arguments, locale);
}
#PostConstruct
public void postConstruct() {
messageSource = injectedMessageSource;
}
}
That way you can easily use it in the enum to get messages in the following way:
MessageSourceHelper.getMessage(key, arguments, locale);
I have faced the same issue when I was working to localize my enum label in different locales.
Enum Code:
public enum Type {
SINGLE("type.single_entry"),
MULTIPLE("type.multiple_entry"),
String label;
Type(String label) {
this.label = label;
}
public String getLabel() {
String translatedString = I18NTranslator.getI18NValue(getLocale(), label);
return StringUtils.isEmpty(translatedString) ? label : translatedString;
}
}
My I18NTranslator class which basically load the message source to get localized content. I18Ntransalator class depends on springContext if you don't write you might face a peculiar bug. Some time might face a dependency related which causes null pointer exception. I had put a lot of effort to resolve this issue.
#Component
#DependsOn({"springContext"})
public class I18NTranslator {
private static MessageSource i18nMessageSource;
public static String getI18NValue(Locale locale, String key) {
if (i18nMessageSource != null)
return i18nMessageSource.getMessage(key, null, locale);
return key;
}
#PostConstruct
public void initialize() {
i18nMessageSource = SpringContext.getBean("i18nMessageSource", MessageSource.class);
}
}
We have to set the spring context
#Component
#Slf4j
public class SpringContext implements ApplicationContextAware {
private static ApplicationContext context;
public static <T extends Object> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
public static <T extends Object> T getBean(String beanClassName, Class<T> beanClass) {
return context.getBean(beanClassName, beanClass);
}
#Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
SpringContext.context = context;
}
}
Now it is time to define the bean for I18NMessageSource.
#Configuration
public class LocaleConfiguration implements WebMvcConfigurer {
#Bean(name = "i18nMessageSource")
public MessageSource getMessageResource() {
ReloadableResourceBundleMessageSource messageResource = new ReloadableResourceBundleMessageSource();
messageResource.setBasename("classpath:i18n/messages");
messageResource.setCacheSeconds(3600);
messageResource.setDefaultEncoding("UTF-8");
return messageResource;
}
#Bean(name = "localeResolver")
public LocaleResolver getLocaleResolver() {
return new UrlLocaleResolver();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
//UrlLocalInterceptor is custom locale resolver based on header paramter.
UrlLocaleInterceptor localeInterceptor = new UrlLocaleInterceptor();
registry.addInterceptor(localeInterceptor);
}
}
PS: if you need the custom interceptor code I can share in the comment.
Defines all local properties files inside resources/i18n folder with messages prefix like messages_en.properties for english and messages_fr.properties fro french.
What do you need to set up? The values are created when the class loads, and as it's an enum, no other values can be created (unless you add them to the source and recompile).
That's the point of an enum, to be able to give limit a type to an explicit range of constant, immutable values. Now, anywhere in your code, you can refer to a type Car, or its values, Car.NANO, Car.MERCEDES, etc.
If, on the other hand, you have a set of values that isn't an explicit range, and you want to be able to create arbitrary objects of this type, you'd use the same ctor as in your post, but as a regular, not enum class. Then Spring provides various helper clases to read values from some source (XML file, config file, whatever) and create Lists of that type.
<bean id="car" class="Foo">
<property name="carString" value="NANO" />
</bean>
And then in your class Foo, you would have this setter:
public void setCar(String carString) {
this.carString = Car.valueOf(carString);
}
Attempting to mutate an Enum is well silly and goes completely against their design objectives. An enum by definition represents a distinct value within a group. If you ever need more / less values you will need to update the source. While you can change an enums state by adding setters (after all they are just objects) your hacking the system.
All right, this is a bit complex but you may find a way to integrate it. Enums are not meant to change at runtime, so this is a reflection hack. Sorry I don't have the Spring implementation part, but you could just build a bean to take in the enum class or object, and another field that would be the new value or values.
Constructor con = MyEnum.class.getDeclaredConstructors()[0];
Method[] methods = con.getClass().getDeclaredMethods();
for (Method m : methods) {
if (m.getName().equals("acquireConstructorAccessor")) {
m.setAccessible(true);
m.invoke(con, new Object[0]);
}
}
Field[] fields = con.getClass().getDeclaredFields();
Object ca = null;
for (Field f : fields) {
if (f.getName().equals("constructorAccessor")) {
f.setAccessible(true);
ca = f.get(con);
}
}
Method m = ca.getClass().getMethod(
"newInstance", new Class[] { Object[].class });
m.setAccessible(true);
MyEnum v = (MyEnum) m.invoke(ca, new Object[] {
new Object[] { "MY_NEW_ENUM_VALUE", Integer.MAX_VALUE } });
System.out.println(v.getClass() + ":" + v.name() + ":" + v.ordinal());
This is taken from this site.
Here is the solution I came to (thanks to Javashlook whose answer put me on track). It works, but it's most probably not a production-grade way of doing it.
But better than a thousand words, here is the code, I'll let you judge by yourself.
Let's take a look at the revised Car enum :
public enum Car {
NANO(CarEnumerationInitializer.getNANO()), MERCEDES(
CarEnumerationInitializer.getMERCEDES()), FERRARI(
CarEnumerationInitializer.getFERRARI());
public final String cost;
public final String madeIn;
Car(ICarProperties properties) {
this.cost = properties.getCost();
this.madeIn = properties.getMadeIn();
}
}
And here are the "plumbling" classes :
//Car's properties placeholder interface ...
public interface ICarProperties {
public String getMadeIn();
public String getCost();
}
//... and its implementation
public class CarProperties implements ICarProperties {
public final String cost;
public final String madeIn;
public CarProperties(String cost, String madeIn) {
this.cost = cost;
this.madeIn = madeIn;
}
#Override
public String getCost() {
return this.cost;
}
#Override
public String getMadeIn() {
return this.madeIn;
}
}
//Singleton that will be provide Car's properties, that will be defined at applicationContext loading.
public final class CarEnumerationInitializer {
private static CarEnumerationInitializer INSTANCE;
private static ICarProperties NANO;
private static ICarProperties MERCEDES;
private static ICarProperties FERRARI;
private CarEnumerationInitializer(ICarProperties nano,
ICarProperties mercedes, ICarProperties ferrari) {
CarEnumerationInitializer.NANO = nano;
CarEnumerationInitializer.MERCEDES = mercedes;
CarEnumerationInitializer.FERRARI = ferrari;
}
public static void forbidInvocationOnUnsetInitializer() {
if (CarEnumerationInitializer.INSTANCE == null) {
throw new IllegalStateException(CarEnumerationInitializer.class
.getName()
+ " unset.");
}
}
public static CarEnumerationInitializer build(CarProperties nano,
CarProperties mercedes, CarProperties ferrari) {
if (CarEnumerationInitializer.INSTANCE == null) {
CarEnumerationInitializer.INSTANCE = new CarEnumerationInitializer(
nano, mercedes, ferrari);
}
return CarEnumerationInitializer.INSTANCE;
}
public static ICarProperties getNANO() {
forbidInvocationOnUnsetInitializer();
return NANO;
}
public static ICarProperties getMERCEDES() {
forbidInvocationOnUnsetInitializer();
return MERCEDES;
}
public static ICarProperties getFERRARI() {
forbidInvocationOnUnsetInitializer();
return FERRARI;
}
}
Finally, the applicationContext definition :
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="nano" class="be.vinkolat.poc.core.car.CarProperties">
<constructor-arg type="java.lang.String" value="Cheap"></constructor-arg>
<constructor-arg type="java.lang.String" value="India"></constructor-arg>
</bean>
<bean id="mercedes"
class="be.vinkolat.poc.core.car.CarProperties">
<constructor-arg type="java.lang.String" value="Expensive"></constructor-arg>
<constructor-arg type="java.lang.String" value="Germany"></constructor-arg>
</bean>
<bean id="ferrari" class="be.vinkolat.poc.core.car.CarProperties">
<constructor-arg type="java.lang.String"
value="Very Expensive">
</constructor-arg>
<constructor-arg type="java.lang.String" value="Italy"></constructor-arg>
</bean>
<bean id="carInitializer"
class="be.vinkolat.poc.core.car.CarEnumerationInitializer"
factory-method="build" lazy-init="false">
<constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
ref="nano" />
<constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
ref="mercedes" />
<constructor-arg type="be.vinkolat.poc.core.car.CarProperties"
ref="ferrari" />
</bean>
</beans>
It works, but there is one major weakness : CarEnumerationInitializer MUST be instantiated BEFORE any reference is made to Car enumeration, otherwise CarProperties are null, meaning that Car's properties can't be set when Car is loaded (hence the IllegalStateException thrown, to at least make it crashes in a predictable and documentated way). carInitializer bean's property lazy-init set to an explicit false, to put emphasis on the need to load it as soon as possible.
I would say it may be useful in a simple application, one where you can easely guess where a first call to Car will be made. For a larger one, it will probably be such a clutter that I didn't encourage you to use it.
Hope this help, comments and vote (up and down) very welcome :) I'll wait for a few days to make this one the accepted answer, to let you react.
You can use Enum class as factory bean. Example: setting serializationInclusion field with enum value:
<property name="serializationInclusion">
<bean class="org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion" factory-method="valueOf">
<constructor-arg>
<value>NON_NULL</value>
</constructor-arg>
</bean>
</property>
But actually (Spring 3.1) a simpler solution works: you just write the enum value and Spring recognizes what to do:
<property name="serializationInclusion" value="NON_NULL"/>
Related
I have an old code base that I need to refactor using Java 8, so I have an interface, which tells whether my current site supports the platform.
public interface PlatformSupportHandler {
public abstract boolean isPaltformSupported(String platform);
}
and I have multiple classes implementing it and each class supports a different platform.
A few of the implementing classes are:
#Component("bsafePlatformSupportHandler")
public class BsafePlatoformSupportHandler implements PlatformSupportHandler {
String[] supportedPlatform = {"iPad", "Android", "iPhone"};
Set<String> supportedPlatformSet = new HashSet<>(Arrays.asList(supportedPlatform));
#Override
public boolean isPaltformSupported(String platform) {
return supportedPlatformSet.contains(platform);
}
}
Another implementation:
#Component("discountPlatformSupportHandler")
public class DiscountPlatoformSupportHandler implements PlatformSupportHandler{
String[] supportedPlatform = {"Android", "iPhone"};
Set<String> supportedPlatformSet = new HashSet<>(Arrays.asList(supportedPlatform));
#Override
public boolean isPaltformSupported(String platform) {
return supportedPlatformSet.contains(platform);
}
}
At runtime in my filter, I get the required bean which I want:
platformSupportHandler = (PlatformSupportHandler) ApplicationContextUtil
.getBean(subProductType + Constants.PLATFORM_SUPPORT_HANDLER_APPEND);
and call isPlatformSupported to get whether my current site supports the following platform or not.
I am new to Java 8, so is there any way I can refactor this code without creating multiple classes? As the interface only contains one method, can I somehow use lambda to refactor it?
If you want to stick to the current design, you could do something like this:
public class MyGeneralPurposeSupportHandler implements PlatformSupportHandler {
private final Set<String> supportedPlatforms;
public MyGeneralPurposeSupportHandler(Set<String> supportedPlatforms) {
this.supportedPlatforms = supportedPlatforms;
}
public boolean isPlatformSupported(String platform) {
return supportedPlatforms.contains(platform);
}
}
// now in configuration:
#Configuration
class MySpringConfig {
#Bean
#Qualifier("discountPlatformSupportHandler")
public PlatformSupportHandler discountPlatformSupportHandler() {
return new MyGeneralPurposeSupportHandler(new HashSefOf({"Android", "iPhone"})); // yeah its not a java syntax, but you get the idea
}
#Bean
#Qualifier("bsafePlatformSupportHandler")
public PlatformSupportHandler bsafePlatformSupportHandler() {
return new MyGeneralPurposeSupportHandler(new HashSefOf({"Android", "iPhone", "iPad"}));
}
}
This method has an advantage of not creating class per type (discount, bsafe, etc), so this answers the question.
Going step further, what happens if there no type that was requested, currently it will fail because the bean does not exist in the application context - not a really good approach.
So you could create a map of type to the set of supported platforms, maintain the map in the configuration or something an let spring do its magic.
You'll end up with something like this:
public class SupportHandler {
private final Map<String, Set<String>> platformTypeToSuportedPlatforms;
public SupportHandler(Map<String, Set<String>> map) {
this.platformTypeToSupportedPlatforms = map;
}
public boolean isPaltformSupported(String type) {
Set<String> supportedPlatforms = platformTypeToSupportedPlatforms.get(type);
if(supportedPlatforms == null) {
return false; // or maybe throw an exception, the point is that you don't deal with spring here which is good since spring shouldn't interfere with your business code
}
return supportedPlatforms.contains(type);
}
}
#Configuration
public class MyConfiguration {
// Configuration conf is supposed to be your own way to read configurations in the project - so you'll have to implement it somehow
#Bean
public SupportHandler supportHandler(Configuration conf) {
return new SupportHandler(conf.getRequiredMap());
}
}
Now if you follow this approach, adding a new supported types becomes codeless at all, you only add a configuration, by far its the best method I can offer.
Both methods however lack the java 8 features though ;)
You can use the following in your config class where you can create beans:
#Configuration
public class AppConfiguration {
#Bean(name = "discountPlatformSupportHandler")
public PlatformSupportHandler discountPlatformSupportHandler() {
String[] supportedPlatforms = {"Android", "iPhone"};
return getPlatformSupportHandler(supportedPlatforms);
}
#Bean(name = "bsafePlatformSupportHandler")
public PlatformSupportHandler bsafePlatformSupportHandler() {
String[] supportedPlatforms = {"iPad", "Android", "iPhone"};
return getPlatformSupportHandler(supportedPlatforms);
}
private PlatformSupportHandler getPlatformSupportHandler(String[] supportedPlatforms) {
return platform -> Arrays.asList(supportedPlatforms).contains(platform);
}
}
Also, when you want to use the bean, it is again very easy:
#Component
class PlatformSupport {
// map of bean name vs bean, automatically created by Spring for you
private final Map<String, PlatformSupportHandler> platformSupportHandlers;
#Autowired // Constructor injection
public PlatformSupport(Map<String, PlatformSupportHandler> platformSupportHandlers) {
this.platformSupportHandlers = platformSupportHandlers;
}
public void method1(String subProductType) {
PlatformSupportHandler platformSupportHandler = platformSupportHandlers.get(subProductType + Constants.PLATFORM_SUPPORT_HANDLER_APPEND);
}
}
As it was written in Mark Bramnik's answer you can move this to configuration.
Suppose that it would be in yaml in that way:
platforms:
bsafePlatformSupportHandler: ["iPad", "Android", "iPhone"]
discountPlatformSupportHandler: ["Android", "iPhone"]
Then you can create config class to read this:
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties
public class Config {
private Map<String, List<String>> platforms = new HashMap<String, List<String>>();
// getters and setters
You can than create handler with checking code.
Or place it in your filter like below:
#Autowired
private Config config;
...
public boolean isPlatformSupported(String subProductType, String platform) {
String key = subProductType + Constants.PLATFORM_SUPPORT_HANDLER_APPEND;
return config.getPlatforms()
.getOrDefault(key, Collections.emptyList())
.contains(platform);
}
I want implement strategy design pattern in spring boot application. I create BeanPostProcessor for construct strategy resolver:
#Component
public class HandlerInAnnotationBeanPostProcessor implements BeanPostProcessor {
private final UnpHandlersResolver unpHandlersResolver;
public HandlerInAnnotationBeanPostProcessor(UnpHandlersResolver unpHandlersResolver) {
this.unpHandlersResolver = unpHandlersResolver;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Annotation[] annotations = bean.getClass().getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof HandlerIn) {
if (bean.getClass() != UnpHandler.class)
throw new RuntimeException("Not UnpHandler bean annotated by HandlerIn");
SmevMessageType[] type = ((HandlerIn) annotation).type();
for (SmevMessageType smevMessageType : type) {
unpHandlersResolver.setHandler(smevMessageType, (UnpHandler) bean);
}
}
}
return bean;
}
}
And I create resolver:
#Slf4j
#Component
public class UnpHandlersResolverImpl implements UnpHandlersResolver {
private Map<SmevMessageType, UnpHandler> map = new HashMap<>();
#Override
public void setHandler(SmevMessageType messageType, UnpHandler unpHandler) {
map.put(messageType, unpHandler);
}
#Override
public UnpHandler getUnpHandler(SmevMessageType type) {
UnpHandler sendRequestHandler = map.get(type);
if (sendRequestHandler == null)
throw new IllegalArgumentException("Invalid SendRequestHandler type: " + type);
return sendRequestHandler;
}
}
My BeanPostProcessor scan all beans with annotation HandlerIn and add to resolver's mup. I think it's wrong to do that:
unpHandlersResolver.setHandler(smevMessageType, (UnpHandler) bean);
But I not understand how can I add find beans to resolver. Before this implementation I faind beans in #Postconstruct method of resolver like:
context.getBeansWithAnnotation(HandlerIn.class);
But in this solution I have context in resolver and I think is bad.
Tell me how to properly implement what I want? In short, I want to have a set of classes that implement different behaviors. And the class that controls them. Give the class a parameter so that he chooses the right strategy and gives it to me. Like this:
Handler handler = handlersResolver.getHandler(messageType);
Result result = handler.somthing(param);
I'm going to try to make a simple example.
Interface Greeting {
void sayHello();
String getSupportedLanguage();
}
Then you have X number of implementations and you can loop through them in your "resolver"'s constructor to build the map. (I've seen this called a Proxy or a Decorator in code though, i.e. GreetingProxy or GreetingDecorator)
#Service
public GreetingResolver {
private Map<String, Greeting> languageToGreetingMap = new HashMap<>();
#Autowired
public GreetingResolver(List<Greeting> greetings) {
for (Greeting greeting : greetings) {
languageToGreetingMap.put(greeting.getSupportedLanguage(), greeting);
}
}
public void sayGreetingForLanguage(String language) {
languageToGreetingMap.get(language).sayHello();
}
}
This is a basic example of how one can do the strategy pattern in Spring. Every interface implementation of "Greeting" only knows about itself and what it can support. We then autowire all implementations in a list and loop through to create the map once and then during runtime only the relevant entry from the map in retrieved and used.
Note: this was typed "free hand" directly in the web page so please forgive any typos in the code.
My Spring Boot application contains several #KafkaListeners, and each listener performs the same steps before and after actually processing the payload: Validate the payload, check whether the event has been processed already, check whether it's a tombstone (null) message, decide whether processing should be retried in case of failure, emit metrics, etc.
These steps are currently implemented in a base class, but because the topics passed to #KafkaListener must be constant at runtime, the method annotated with #KafkaListener is defined in the subclass, and does nothing but pass its parameters to a method in the base class.
This works just fine, but I wonder if there's a more elegant solution. I assume my base class would have to create a listener container programmatically, but after a quick look at KafkaListenerAnnotationBeanPostProcessor, it seems to be quite involved.
Does anyone have any recommendadtions?
Having stumbled upon this question while looking to implement something similar, I first started with Artem Bilan's answer. However this did not work because annotations by default are not inherited in child classes unless they are themselves annotated with #Inherited. Despite this there may yet be a way to make an annotation approach work and I will update this answer if and when I get it to work. Thankfully though I have achieved the desired behavour using programtic registration of the Kafka listeners.
My code is something like the following:
Interface:
public interface GenericKafkaListener {
String METHOD = "handleMessage";
void handleMessage(ConsumerRecord<String, String> record);
}
Abstract Class:
public abstract class AbstractGenericKafkaListener implements GenericKafkaListener {
private final String kafkaTopic;
public AbstractGenericKafkaListener(final String kafkaTopic) {
this.kafakTopic = kafkaTopic;
}
#Override
public void handleMessage(final ConsumerRecord<String, String> record) {
//do common logic here
specificLogic(record);
}
protected abstract specificLogic(ConsumerRecord<String, String> record);
public String getKafkaTopic() {
return kafkaTopic;
}
}
We can then programtically register all beans of type AbstractGenericKafkaListener in a KafkaListenerConfigurer:
#Configuration
public class KafkaListenerConfigurataion implements KafkaListenerConfigurer {
#Autowired
private final List<AbstractGenericKafkaListener> listeners;
#Autowired
private final BeanFactory beanFactory;
#Autowired
private final MessageHandlerMethodFactory messageHandlerMethodFactory;
#Autowired
private final KafkaListenerContainerFactory kafkaListenerContainerFactory;
#Value("${your.kafka.consumer.group-id}")
private String consumerGroup;
#Value("${your.application.name}")
private String service;
#Override
public void configureKafkaListeners(
final KafkaListenerEndpointRegistrar registrar) {
final Method listenerMethod = lookUpMethod();
listeners.forEach(listener -> {
registerListenerEndpoint(listener, listenerMethod, registrar);
});
}
private void registerListenerEndpoint(final AbstractGenericKafkaListener listener,
final Method listenerMethod,
final KafkaListenerEndpointRegistrar registrar) {
log.info("Registering {} endpoint on topic {}", listener.getClass(),
listener.getKafkaTopic());
final MethodKafkaListenerEndpoint<String, String> endpoint =
createListenerEndpoint(listener, listenerMethod);
registrar.registerEndpoint(endpoint);
}
private MethodKafkaListenerEndpoint<String, String> createListenerEndpoint(
final AbstractGenericKafkaListener listener, final Method listenerMethod) {
final MethodKafkaListenerEndpoint<String, String> endpoint = new MethodKafkaListenerEndpoint<>();
endpoint.setBeanFactory(beanFactory);
endpoint.setBean(listener);
endpoint.setMethod(listenerMethod);
endpoint.setId(service + "-" + listener.getKafkaTopic());
endpoint.setGroup(consumerGroup);
endpoint.setTopics(listener.getKafkaTopic());
endpoint.setMessageHandlerMethodFactory(messageHandlerMethodFactory);
return endpoint;
}
private Method lookUpMethod() {
return Arrays.stream(GenericKafkaListener.class.getMethods())
.filter(m -> m.getName().equals(GenericKafkaListener.METHOD))
.findAny()
.orElseThrow(() ->
new IllegalStateException("Could not find method " + GenericKafkaListener.METHOD));
}
}
How about this:
public abstract class BaseKafkaProcessingLogic {
#KafkaHandler
public void handle(Object payload) {
}
}
#KafkaListener(topics = "topic1")
public class Topic1Handler extends BaseKafkaProcessingLogic {
}
#KafkaListener(topics = "topic2")
public class Topic2Handler extends BaseKafkaProcessingLogic {
}
?
I needed the same functionality and came up with solution close to Artem Bilan answer. Yes, #KafkaHandler annotation is not inherited by the child classes but defined in interface it is. Here is the solution:
interface AbstractKafkaListener<T> {
default Class<T> getCommandType() {
TypeToken<T> type = new TypeToken<>(getClass()) {};
return (Class<T>) type.getRawType();
}
#KafkaHandler
default void handle(String message) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
T value = objectMapper.readValue(message, getCommandType());
handle(value);
}
void handle(T message);
}
The class should implement the handle method only:
#Component
#KafkaListener(topics = "my_topic")
public class KafkaListenerForMyCustomMessage implements AbstractKafkaListener<MyCustomMessage> {
#Override
public void handle(MyCustomMessage message) {
System.out.println(message);
}
}
The 2 implemented methods in the interface should be private/protected but because they are in interface this cannot be done. default methods are always public. Actually, all methods defined in interface are always public.
I use this solution to dynamically parse the message from kafka (received in String) to the custom class.
getCommandType method returns the class of the T generic param. TypeToken is from Google Guava package.
Long story short:
Is there a way to interpret the string resulting from ${my.property} as a SpEL expression within a #Value annotation without using converters, e.g. something like #Value("#{${my.property}})?
I have an abstract factory (simplified) that lets me build some common objects that are part of the configuration of my system.
#Component
public class Factory {
public Product makeVal(int x) { return new Product(5); }
}
In order to be more flexible, I'd like to let users write SpEL expressions in the app.properties file, so that the factory can directly be accessed:
my.property = #Factory.makeVal(12)
Now, in the class needing this property, to achieve my goal I wrote the following code.
#Value("#{${my.property}}")
private Product obj;
I thought that ${my.property} would be be macro-expanded and then evaluated by #{} as the corresponding SpEL expression, #Factory.makeVal(12) in the example above. Unfortunately, this wasn't the case, and loading the Spring context resulted in an error saying that it could not convert a string (the property's value ${my.property}) to the destination type Product.
Now, I solved this by writing a class implementing Converter<String, Product>, but it's very convoluted as I need there to programmatically evaluate the string as a SpEL expression by instantiating the ExpressionParser and so on.
But is there a simpler solution? Is there a single SpEL expression to be put in #Value annotations that lets me simply evaluate ${my.property} as a SpEL expression by itself, please?
Maybe it just a matter of replacing #Factory with factory in the property value. This test passes for me:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { SpelTest.Config.class })
public class SpelTest
{
#Value("#{${my.property}}")
Product _product;
#Test
public void evaluating_spel_from_property_value() throws Exception
{
Assert.assertEquals(1234, _product.value);
}
#Component
public static class Factory
{
public Product makeVal(int x) { return new Product(x); }
}
public static class Product
{
public final int value;
public Product(final int value) { this.value = value; }
}
#Configuration
#ComponentScan(basePackageClasses = SpelTest.class)
public static class Config
{
#Bean
public Factory factory() { return new Factory(); }
#Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
final PropertySourcesPlaceholderConfigurer psc = new PropertySourcesPlaceholderConfigurer();
final MutablePropertySources sources = new MutablePropertySources();
sources.addFirst(new MockPropertySource()
.withProperty("my.property",
"factory.makeVal(1234)"));
psc.setPropertySources(sources);
return psc;
}
}
}
I'm aware of that this topic might be considered as offtopic or convention/opinion based, but I have not found any other place that I could find solution for my problem.
I'm writing and Spring application, fully configured with annotations in Java. I'm loading the properties file with #PropertySource annotation:
#Configuration
#ComponentScan("myapp.app")
#PropertySource("app.properties")
public class ApplicationConfig {
#Bean
public static PropertySourcesPlaceholderConfigurer getPropertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Let's assume, that I have app.properties file of following content:
property1=someProperty1Value
property2=someProperty2Value
I'm loading this value with following code:
#Service
public class MyServiceImpl implements MyService {
#Value("${property1}")
private String property1Value;
#Value("${property2}")
private String property2Value;
#Override
public void doStuff() {
System.out.println(property1Value);
System.out.println(property2Value);
}
}
This is working perfectly fine. On the other hand, I find it hard to maintain - if some will think that "property1" is not the best name for a property and would like to rename it, then it will be needed to find all strings "${property1}" and rename it. I tought that I could extract it to the constant class:
public final class Properties {
public static final String PROPERTY_1 = "${property1}";
public static final String PROPERTY_2 = "${property2}";
private Properties() {
}
}
This requires refactoring of the existing bindings to new constant values:
#Value(Properties.PROPERTY_1)
private String property1Value;
Looks nice, but I do not like the mess in the Properties class, I think it will be better to the constant values without bracelets:
public static final String PROPERTY_1 = "property1";
Which leads to another refactoring in the MyServiceImpl class:
#Value("${" + Properties.PROPERTY_1 + "}")
private String property1Value;
But boy, that's really ugly. I thought about extracting constant values to the Enum:
public enum Properties {
PROPERTY_1("property1"),
PROPERTY_2("property2");
private final String key;
private Properties(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public String getSpringKey() {
return "${" + getKey() + "}";
}
}
and use it like
#Value(Properties.PROPERTY_1.getSpringKey())
private String property1Value;
but then IDE reminded me, that annotation value has to be a constant.
After creating this enum, I thought that I might be over-thinking it, and it should be kept as simple as possible. Currently I came back to the solution with constants in format of
public static final String PROPERTY_1 = "${property1}";
Finally, I would like to ask you to provide another, nice-looking solution, or some reference links where I could read about some common solution.