Injecting Map with Enum as value in Spring - java

I'm looking on to how I can inject a Map<Integer, CustomEnumType> in Spring, not using app-context.xml but using #Bean(name = "customMap") annotations. When I try to inject it by doing
#Inject
Map<Integer, CustomEnumType> customMap;
it complains because apparently it cannot find any injectable dependency of type CustomEnumType. However CustomEnumType is just an enumeration, not something that is supposed to be injected. I just want to use it as the value type of my map.
One solution is to create an injectable wrapper object that will contain the Map as a field but I'd like to avoid unnecessary clutter. It is also more clean and readable to see the type of Map being injected.

Don't try to inject Enums, instead, try to inject int values.
Enums are indeed classes, however, you can't create an instance/object of them. their constructor access modifier can be private only, that's another proof why you can't have instances of them.
With that being said, you can't have Beans of Enum, because there is no way to construct them.
The solution is to give each member in your enum, an int value, and just inject that int value.
For example:
public enum Color {
white(0),
black(1);
private int innerValue;
private Color(int innerValue) {
this.innerValue = innerValue;
}
public int getInnerValue() {
return innerValue;
}
}
Now, let's say I want to inject the value 1, which is black in my Enum. To another Class via its constructor. Then my constructor will look like this:
public Canvas(String id, int color) {
this.id = id;
this.color = Color.getColorByInt(color);
}
Now, let's say that the xml configuration file containing this:
<bean id="myCanvas" class="com.my.package.Canvas">
<constructor-arg name="id" value="9876543" />
<constructor-arg name="color" value="1" />
<!-- This is the black value for myCanvas bean -->
</bean>

I found the solution. Apparently #Inject and #Autowired fail to correctly find the type of #Bean method they need to use. However, using #Resource(name = "customMap") everything worked perfectly. There was no problem with the map creation even if the values were enumerations. The method used was:
#Bean(name = "customMap")
public Map<Integer, CustomEnumType> getCustomMap() {
Map<Integer, CustomEnumType> map = new HashMap<>();
map.put(1, CustomEnumType.type1);
map.put(2, CustomEnumType.type2);
//...
return map;
}
and injected using
#Resource(name = "customMap")
Map<Integer, CustomEnumType> customMap;
Note that CustomEnumType has no constructors defined and no values are assigned to the enumeration. In my case this was also impossible to do from the beginning since the CustomEnumType class is a dependency we cannot edit.

Related

Invoke method using spring SPEL with property

Is it possible to use a property value to invoke a method while assigning a value?
For instance, I know I can do this:
#Value("${name}")
private String name; // will have the value of the `name` property
I wonder if it's possible to do something like this:
#Value("#{myMethod(${name})}")
private String myModifiedVariable; // will have the result of invoking myMethod
After my research and a bit of testing, I found there is a way shown in this article Spring EL method invocation, But mybean should be a string bean
#Value("#{mybean.myMethod('${name}')}")
private String myModifiedVariable;
And if you want to call a method in the existing class then use the spring bean name of the same class
#Configuration // or any sterotype annoations
public class TestConfig {
#Value("#{testConfig.myMethod('${name}')}")
private String myModifiedVariable;
public String getValue(String val){
return val+"testValue";
}
}
When using interface projection (in Spring Data Repository) it is possible to call a static method like this:
public interface MyProjection {
// Here we are creating a 2 element list with 'Spring Data' and value taken from "MY_COLUMN_NAME" column
#Value("#{T(java.util.Arrays).asList('Spring Data', target.MY_COLUMN_NAME)}")
List<String> getSampleList();
}
You can also obtain a value of an enum (constant) with above notation. Sample for Spring Security check:
#PreAuthorize("hasRole(T(com.acme.UserRoles).ADMINISTRATOR.name())")
Similar notation should work in other places. Simply remember to use full path to class

How to #Autowired a List<Integer> in spring framework

I have a configuration class as below:
#Configuration
public class ListConfiguration {
#Bean
public List<Integer> list() {
List<Integer> ints = new ArrayList<>();
ints.add(1);
ints.add(2);
ints.add(3);
return ints;
}
#Bean
public int number() {
return 4;
}
}
I also have a test class as below
#RunWith(SpringRunner.class)
#ContextConfiguration(classes = ListConfiguration.class)
public class ListTest {
#Autowired
List<Integer> ints;
#Test
public void print() {
System.out.println(ints.size());
System.out.println(ints);
}
}
But the output of the print method is 1 and [4], why not 3 and [1,2,3]? Thank you very much for any help!
You've got a bean of type Integer and a bean of type List<Integer> in your application context.
Now obviously the bean you want to autowire is of type List<Integer>, which does qualify as a candidate for autowiring. To discover how Spring actually autowires fields I had to dive deep into the AutowiredAnnotationBeanPostProcessor class.
TL;DR of my investigation is that Spring will prefer to autowire objects in the following order:
Default Value using #Value
Multiple beans using a type parameter.
Individual beans that match the field type.
That means that if you're autowiring a List<Integer> Spring will attempt to autowire multiple Integer beans into the list before it will attempt to autowire a single List<Integer> bean.
You can see this behaviour in the DefaultListableBeanFactory class.
Relevant snippet below:
public Object doResolveDependency(DependencyDescriptor descriptor, String beanName,
Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException {
Class<?> type = descriptor.getDependencyType();
//Searches for an #Value annotation and
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
if (value != null) {
//Handle finding, building and returning default value
}
/*
* Check for multiple beans of given type. Because a bean is returned here,
* Spring autowires the Integer bean instance.
*/
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
try {
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
// Do more stuff here to try and narrow down to a single instance to autowire.
}
}
Hopefully this explains why you do need to use an #Qualifer annotation when trying to autowire a list of a type when you've got individual beans of that type in your application context.
EDIT:
It's worth noting that this is not good practice. Creating a collection of primitives or primitive wrappers and registering it as a bean is going to cause issues. The best way to do this is with #Value and define your list of primitives in a properties file, that Spring picks up.
Example:
application.properties file
list=1,2,3,4
In your config class declare the following bean:
#Bean
public ConversionService conversionService() {
return new DefaultConversionService();
}
The default conversion service is used to convert comma separated values declared in a properties file into a collection of objects with type safety.
Class to use it:
#Value("${list}")
private List<Integer> anotherList;
anotherList will contain 1,2,3 & 4 as elements in the list.
May be Spring is injecting all the Integer type beans into a List instead of Autowiring List<Integer> bean that you declared.
Probably if you add #Qualifier("list") at your injection point in your Test then it will provide the behavior you are expecting.

get annotation on method from a CDI managed bean

I want to retrieve an annotation (a custom written one) from a method. Usually I can ask the classloader by accessing
class.getMethod("methodName").getAnnotation("annotationName")
But if the bean is managed by a CDI container (I am using OpenWebBeans) the class is enhanced at runtime. Then I have to use the superclass to ask for annotations. Currently I try to detect if the class is managed by looking for "$$" in the classname. But that seems to be a very dirty solution to me.
Is there any good way to retrieve anntations from a CDI managed bean?
In detail my code is something like that:
I created an annotation "Coolnessfactor" to mark a method to be very cool :-)
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Coolnessfactor {
CoolnessValue value();
}
Via the enumeration CoolnessValue I want to specify how cool the method implementation is.
public enum CoolnessValue {
POOR, VERY_COOL, UNBELIEVABLE;
}
Then I mark different methods in my business classe with this annotation, fe:
#Override
#Coolnessfactor(CoolnessValue.POOR)
public void getSingleObjectWithDetails(final Integer techId) {
return this.dao.findCompleteDataByPrimaryKey(techId);
}
Now I want to analyse the value of the annotation which marks the different method. I have to do it in a CDI-Decorator, therefore I cannot do it with an interceptor binding.
At the moment my approach is to use the reflection API to retrieve the annotation value:
public static <A extends Annotation> Map<String, A> getAnnotatedMethodsOfClass(final Class<?> clazz,
final Class<A> annotationClazz) {
final Map<String, A> annotationMap = new HashMap<String, A>();
Method[] declaredMethods;
if (clazz.getName().contains("$$")) {
declaredMethods = clazz.getSuperclass().getDeclaredMethods();
} else {
declaredMethods = clazz.getDeclaredMethods();
}
for (final Method m : declaredMethods) {
if (m.isAnnotationPresent(annotationClazz)) {
annotationMap.put(m.getName(), m.getAnnotation(annotationClazz));
}
}
return annotationMap;
}
But this looks very awful to me. Especcially the detection of a class which is enhanced by the CDI implementation is very bad.
Maybe try it with BeanManager - you will want to use it to get hold of a Bean<?> instance of your bean. The approaches differ here, based on what kind of bean it is. Shuffle through the API and find your way.
Once you get Bean<?> you should be able to use getBeanClass() and with that you gain access to methods and their annotations.

Get spring bean via context using generic

I have a bunch of repository beans that implement type Repository<T ? extends Node>. Now I can get a list of random nodes from the user and I want to get the appropriate repository for each node. Since Spring 4.0RC1 we can autowire repositories like this:
#Autowired Repository<SomeNode> someNodeRepository;
As documented here.
This works fine, but my question is how I can do this dynamically based on the generic type.
What I want to do is something like:
public <T extends Node> T saveNode(T node) {
Repository<T> repository = ctx.getBean(Repository.class, node.getClass());
return repository.save(node);
}
Where the second parameter is the generic type. This of course does not work, although it compiles.
I can't find any/the documentation on this.
You can do something like this:
String[] beanNamesForType = ctx.getBeanNamesForType(ResolvableType.forClassWithGenerics(Repository.class, node.getClass()));
// If you expect several beans of the same generic type then extract them as you wish. Otherwise, just take the first
Repository<T> repository = (Repository<T>) ctx.getBean(beanNamesForType[0]);
Since Spring 5.1 you can get a bean of type Repository<T> like that:
public static <T> Repository<T> getRepositoryFor(Class<T> clazz)
{
ResolvableType type = ResolvableType.forClassWithGenerics(Repository.class, clazz);
return (Repository<T>) context.getBeanProvider(type).getObject();
}
If you could be sure that for every concrete subclass of Node (say SomeNode), every object of type SomeNode will be an actual SomeNode and not a subclass or a proxy, it would be easy. Just use a convention for the repository name (say SomeNodeRepository) and it would be trivial :
Repository<T> repository = ctx.getBean(node.getClass().getSimpleName()
+ "Repository", Repository.class);
But you know that there's a high risk of getting a subclass or proxy ...
So you can try to have each Node subclass to implement a nameForRepo method :
class Node {
...
abstract String getNameForRepo();
}
and then in the subclasses
class SomeNode {
static private final nameForRepo = "SomeNode";
...
String getNameForRepo() {
return nameForRepo;
}
}
That way, even if you get a proxy or subclass, you will be able to do :
public <T extends Node> T saveNode(T node) {
Repository<T> repository = ctx.getBean(node.getNameForRepository()
+ "Repository", Repository.class);
return repository.save(node);
}
Alternatively, the method could directly return the repository name.
If I understand well, you want to get instance of bean with Repository class and different generic type?
I'm afraide you don't have the dynamic way with spring, but I have a work around solution:
Your generic type should be a field in your class, you must have a constructor in your Repository class for setting your generic type, your Repository class should be like this:
public class Repository<T>{
Class<T> nodeClass;
public Repository(Class<?> clazz){
this.nodeClass = clazz;
}
// your codes...
}
declare a Repository bean for each Node, let's say you have Repository and Repository, if you are using xml configuration, you need to add:
<bean id="someNodeRep" class="your.package.Repository">
<constructor-arg>
<value>your.package.SomeNode</value>
</constructor-arg>
</bean>
<bean id="otherNodeRep" class="your.package.Repository">
<constructor-arg>
<value>your.package.OtherNode</value>
</constructor-arg>
</bean>
'autowire' your Repository in this way:
#Autowired
#Qualifier("someNodeRep")
Repository<SomeNode> someNodeRepository;
#Autowired
#Qualifier("otherNodeRep")
Repository<OtherNode> otherNodeRepository;

Spring #Cacheable doesn't cache public methods

I have following method;
#Cacheable(value = "psgSiteToMap", key = "'P2M_'.concat(#siteName)")
public Map getSiteDetail(String siteName) {
Map map = new HashMap();
.....
//construct map variable here
.......
return map;
}
While project startup, cannot autowire class this method belongs to. If i change above method as following;
#Cacheable(value = "psgSiteToMap", key = "'P2M_'.concat(#siteName)")
private Map getSiteDetail(String siteName) {
Map map = new HashMap();
.....
//construct map variable here
................
return map;
}
public Map getSiteDetailPublic(String siteName) {
return this.getSiteDetail(siteName);
}
it works. Is there any restriction on #Cacheable annotation for public methods?
Thanks in advance
Spring AOP works only on public methods by default. You'd need AspectJ and load time or compile time weaving to make it work on private methods.
So it works in your case means that when you move the #Cacheable to the private method the proxy is not created at all and that works is autowireing, but not caching.
You probably have not set proxy-target-class property in your XML configuration or its equivalent annotation attribute. Can you please add the Spring configuration you're using and the class definition line. I'm interested if it implements any interfaces? Than I'll expand my answer with more details.

Categories

Resources