Spring bean initialization - Clojure - spring

I have a clojure class which I initialize using spring bean initialization.
My setter method is as follows
(defn -setCompanyName [currency] (println (str "company : " company)))
Bean initialization is as follows
<bean id="company" class="test.Company"
p:companyName="orce"/>
I'm getting following error.
Invalid property 'companyName' of bean class [test.Company]: Bean property
'companyName' is not writable or has an invalid setter method. Does
the parameter type of the setter match the return type of the getter?
Does anyone knows the root cause for this issue.
Regards
Isuru.

There are several possible causes for this particular issue, so without all your code it is difficult to say what is failing.
Here is the code that works for me:
(ns test)
(gen-class
:main false
:name test.Company
:methods [[setCompanyName [String] void]])
(defn -setCompanyName [this company] (println (str "company : " company)))
Notes:
you do not need any getter
the signature of the method is specified in the :methods vector.
your functions should have an additional "this" parameter
gen-class macro generates a class based on the parameters of the macro, so it does not look at the -setCompanyName function definition at all.
I find very useful the javap command to see what gen-class is generating:
javap.exe -classpath classes/ test.Company
public class test.Company extends java.lang.Object{
public static {};
public test.Company();
public java.lang.String toString();
public boolean equals(java.lang.Object);
public java.lang.Object clone();
public int hashCode();
public void setCompanyName(java.lang.String);
}
I will also recommend you to look at the second example on http://clojuredocs.org/clojure_core/clojure.core/gen-class to see how to manage state.

Don't you need another function parameter? The first acts as a 'this' pointer. I can't test this right now as I'm on my phone.

Related

How to use Kotlin's `is` operator in SpEL expression?

I have a simple sealed class
sealed class Target {
class User(val id: Long) : Target()
class City(val id: String) : Target()
}
that is used as a parameter of s Spring bean method. I'd like to cache the method via the #Cacheable conditionally only when the parameter is User.
#Cacheable(CACHE_NAME, condition = "#target is Target.User")
open fun getFeed(target: Target): Map<String, Any?> { ... }
However I get an error: '(' or <operator> expected, got 'is'
How can I use is in the condition string?
Thanks to Raphael's answer I was able to find out that
Instead of Kotlin's is there's Java's instanceof.
SpEL has a special syntax for using instanceof where you need to use a wrapper around the class: filterObject instanceof T(YourClass).
The fully qualified class name must be used for classes from any other package than java.lang.
The fully qualified name available on runtime for a class defined inside the body of a sealed class is <package>.<SealedClass>$<SubClass>. In my case it was net.goout.feed.model.Target$User.
Putting all this together yeilds this SpEL
#target instanceof T(net.goout.feed.model.Target$User)
As far as I know, SpEL is java-based, and Java does not have an operator called 'is'. The Java equivalent of 'is' is 'instanceof'. Since Java and Kotlin are interoperable and you can work with Kotlin classes in a Java context, #target instanceof FeedTarget.User should work fine.

ConditionalOnExpression fails to compare configuration property to enum type, works as string

I want to load a #Configuration class based on an enum in properties file, so I have the following class:
#Configuration
#ConditionalOnExpression("#{ ${demo.spel.demo-enum} eq T(demo.spel.DemoEnum).VALUE }")
public class DemoConfig {}
And I have: demo.spel.demo-enum=value in application.properties
This does not work, and throws the exception:
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'value' cannot be found on object of type 'org.springframework.beans.factory.config.BeanExpressionContext' - maybe not public or not valid?
The odd thing is, that if I add single quotes to the property part, and a toString() to the enum part of the expression, there is no exception, the condition is true, and the bean is created (verified by checking console output in debug logging):
#ConditionalOnExpression("#{ '${demo.spel.demo-enum}' eq T(demo.spel.DemoEnum).VALUE.toString() }")
Questions:
Why is comparing an enum like this fails ? How come Spring can successfully convert the values and compare as string but not as their types ?
This is on Spring Boot 2.0.4
It should be pretty obvious, really.
Consider the following Java code:
foo.equals(DemoEnum.VALUE)
It would require an object foo, perhaps a field on this:
this.foo.equals(DemoEnum.VALUE)
If your property placeholder evaluates to 'foo', your first SpEL expression is the equivalent of
#this.foo eq T(DemoEnum).VALUE
So SpEL looks for a property foo on #this
EDIT
If you create a Class:
public class Foo {
#Value("${some.property}")
private DemoEnum enum;
public getEnum() {
return this.enum;
}
}
And add a bean to the context called "foo" you could then use
foo.enum eq ...
Since #this is a BeanExpressionContext allowing you to reference other beans.
I had a similar problem.
I had a feature, which was enabled by default. To disable it, application config file, should have it disabled explicitly. demo.feature.disable:true
I had a spring bean conditional on this property (enabled by default).
#ConditionalOnExpression("#{ ${demo.feature.disable} != true }")
#Component
public class FeatureModule {
}
The problem was, when demo.spel.value was not defined in the config file - application.yml, initialization of this component will fail with
Caused by: org.springframework.expression.spel.SpelParseException: EL1041E: After parsing a valid expression, there is still more data in the expression: 'lcurly({)'
To solve it, I provided default value.
#ConditionalOnExpression("#{ ${demo.feature.disable:false} != true }")
#Component
public class FeatureModule {
}
Now, when I test it.
By default this component is initialized.
If config file does not have demo.feature.disable, this component will be initialized.
If config file has demo.feature.disable:true, this component will not be initialized.
If config file has demo.feature.disable:false this component will be initialized.

Define Spring bean with a Map-typed constructor argument

In my Grails app, I'm trying to define a Spring bean in resources.groovy that requires a Map-typed constructor arg. I tried this:
Map<Class, String> mapArg = [(String): 'foo']
myBean(MyBeanImpl, mapArg)
But I get the error message:
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'myBean': Could not resolve matching
constructor (hint: specify index/type/name arguments for simple
parameters to avoid type ambiguities)
The implementation class has a single constructor which is defined thus
MyBeanImpl(Map<Class, String> map) {
// impl omitted
}
My guess is that the problem is caused by the fact that I've defined a constructor that takes a single Map arg which has the same signature as the default constructor Groovy adds to every class.
If so, a solution would appear to be to add a factory method such as
MyBean getInstance(Map map) {
// impl omitted
}
But I'm not sure how I can call this to define a bean (in resources.groovy) that is constructed from a factory method that requires a parameter.
As far as I can tell the syntax you're using ought to work. Does the alternative syntax:
Map<Class, String> mapArg = [(String): 'foo']
myBean(MyBeanImpl) { bean ->
bean.constructorArgs = [mapArg]
}
work any better in your case? Failing that, declaring the map as a bean in its own right should definitely do it:
import org.springframework.beans.factory.config.MapFactoryBean
classMap(MapFactoryBean) {
sourceMap = [(String):'foo']
}
myBean(MyBeanImpl, classMap /* this is a RuntimeBeanReference */)

Spring-Wicket: optional but named bean still required

Consider following (wicket) code:
#SpringBean(required=false)
private StatusCheckService service;
In my use case there is no bean of type StatusCheckService in my context.
This is not a problem, it is marked as optional so the (wicket) page will initialize just fine.
However:
#SpringBean(name = "statusCheckService", required=false)
private StatusCheckService service;
This fails:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'statusCheckService' is defined
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1095)
at org.springframework.beans.factory.support.AbstractBeanFactory.isSingleton(AbstractBeanFactory.java:400)
at org.springframework.context.support.AbstractApplicationContext.isSingleton(AbstractApplicationContext.java:1113)
at org.apache.wicket.spring.SpringBeanLocator.isSingletonBean(SpringBeanLocator.java:100)
at org.apache.wicket.spring.injection.annot.AnnotProxyFieldValueFactory.getFieldValue(AnnotProxyFieldValueFactory.java:140)
I would expect it makes no difference. OK, there is no bean with such a name but I marked it as optional so why the exception? Is there any way around this?
By doing a usage search in eclipse, you can see that the only time the annotation is queried for its required value is on line 215 of AnnotProxyFieldValue:
if (names.isEmpty())
{
if (annot.required())
{
throw new IllegalStateException("bean of type [" + clazz.getName() + "] not found");
}
return null;
}
Here you can see that the 'required' field of the #SpringBean annotation is only used if the names list (set higher in the function) is empty...
This explains the behaviour you are experiencing, because when you don't specify a name, the names list is empty (because it could not find your class), and null is returned. However when you do specify a name it doesn't bother trying to see if the class exists, and so sends your supplied class-name for spring to look up which proceeds to complain when it cannot find the class.
This could be a bug depending on designers intentions, personally I think if you have specified that the field is not required then the function should return early... If you agree then maybe consider putting in a jira ticket with a quick start.
As for a solution, you haven't mentioned much about your use case, and why you need to set the name even though you do not require the bean to be injected. But assuming you are doing it to dynamically inject the bean based on certain criteria, you could do something like the following: (untested)
In your class where the SpringBean is injected, replace:
#SpringBean(name="statusCheckService", required=false)
private StatusCheckService service;
With:
MyCustomSpringBeanInjector injectedService;
And then MyCustomSpringBeanInjector class is something like:
import org.apache.wicket.injection.Injector;
import org.apache.wicket.spring.injection.annot.SpringBean;
public class MyCustomSpringBeanInjector
{
#SpringBean public StatusCheckService service;
public MyCustomSpringBeanInjector()
{
Injector.get().inject(this);
}
}
Then back in the class where the SpringBean was being injected, put something like:
if(statusCheckServiceRequired)
injectedService = new MyCustomSpringBeanInjector();
and replace "service." with "injectedService.service" where necessary.
Obviously this is a qwik-e fix off the top of my head, and I'm sure there's a better way of doing it, but you get the idea! :)
Best of luck!

Spring dependency injection - reflection / byte code instrumentation

When I want to use dependency injection with some non-default constructor, i.e. with parameters, spring must be using byte code instrumentation for that, right? Because AFAIK reflection only supports default constructor?
Reflections supports any number of arguments, say for instance I have a class TestClass which takes two arguments in one of its constructors:
public TestClass(int test1, String test) {
System.out.println(test1 + test);
}
I would invoke this constructor, through reflection, like so:
Constructor<TestClass> constructor = TestClass.class.getConstructor(Integer.class, String.class);
TestClass test = constructor.newInstance(1, "test");
Reflection.
Please check source code for the class
org.springframework.beans.factory.support.ConstructorResolver
Method: protected BeanWrapper autowireConstructor(...)
invokes =>
org.springframework.beans.factory.support.SimpleInstantiationStrategy
Method: public Object instantiate(...)
invokes =>
org.springframework.beans.BeanUtils
Method: public static Object instantiateClass(Constructor ctor, Object[] args)
which uses Reflection to create the bean

Resources