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.
Related
I have a simple enum class in which I would like to have a field called name.
enum class DeviceFieldQuery(val clazz: Class<*>) {
id(Int::class.java),
name(String::class.java),
}
Unfortunately, this does not seem to work in Kotlin. Compilation fails with the message:
Error:(9, 5) Kotlin: Conflicting declarations: enum entry name, public final val name: String
The same Enum class as Java code works fine. How may I solve this in Kotlin?
Enums in Kotlin already have a name property already defined (like Java). This is conflicting with your enum called name. To fix it, you could capitalize it, which is more idiomatic:
enum class DeviceFieldQuery(val clazz: Class<*>) {
Id(Int::class.java),
Name(String::class.java),
}
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.
I am facing some problems with kotlin in conjunction with spring.
I have a controller bean (without an interface btw) which has an auto-wired service bean via the primary constructor.
It works perfectly unless I use caching annotations for the controller. Apparently springs caching generates a proxy class under the hood which deals with the caching.
My code looks like this:
#RestController
#RequestMapping("/regions/")
open class RegionController #Autowired constructor(val service: RegionService) {
#RequestMapping("{id}", method = arrayOf(RequestMethod.GET))
#Cacheable(cacheNames = arrayOf("regions"))
fun get(#PathVariable id: Long): RegionResource {
return this.service.get(id)
}
}
The problem now is a null pointer exception when the method is executed, actually this.service is null which technically is not possible as it is a nonnull variable in kotlin.
I assume that class proxies generated by spring initialize the class with null values instead of the autowired bean. This must be a common pitfall using kotlin and spring. How did you circumvent this problem?
In Kotlin both classes and members are final by default.
For the proxying library (CGLIB, javaassist) to be able to proxy a method it has to be declared non final and in a non final class (since those libraries implement proxying by subclassing). Change your controller method to:
#RequestMapping("{id}", method = arrayOf(RequestMethod.GET))
#Cacheable(cacheNames = arrayOf("regions"))
open fun get(#PathVariable id: Long): RegionResource {
return this.service.get(id)
}
You probably see a warning in console regarding RegionController methods not being subject to proxying.
The Kotlin compiler plugin
The Kotlin team has acknowledged this difficulty and created a plugin that marks the standard AOP proxy candidates e.g. #Component with open.
You can enable the plugin by in your build.gradle:
plugins {
id "org.jetbrains.kotlin.plugin.spring" version "1.1.60"
}
Soon this might not be a problem any longer.
There is work in progress that any lib (including spring for example) can specify a list of annotations a file in META-INF. Once a class is annotated with one of these, it will default to open for the class itself and all its functions. This is also true for classes inheriting from an annotated class.
For more details, have a look at https://github.com/Kotlin/KEEP/pull/40#issuecomment-250773204
I'm trying to create an annotation to log all methods in annotated class, but I have a problem with my pointcut, it's not applied (AspectJ version 1.7.4, aspectj-maven-plugin version 1.7).
(advice defined in com.test.util.log.Logger has not been applied
[Xlint:adviceDidNotMatch]).
Pointcut:
#Pointcut(value = "execution(* (#Loggable *).*(..))"))
Annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(value = { ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE })
public #interface Loggable {
public enum Level {
TRACE, DEBUG, INFO, WARN, ERROR, FATAL
};
boolean entry() default true;
boolean exit() default true;
String prefix() default "";
String suffix() default "";
Level level() default Level.DEBUG;
}
Thank you
I assume that the annotation is not in the unnamed top level package but in a package like com.company.application.subpackage. If this is true you need to use the fully qualified package name in annotation-style #AspectJ. In native syntax that would not be necessary because you could use imports there. So the pointcut should be:
#Pointcut("execution(* (#com.company.application.subpackage.Loggable *).*(..))"))
The way you use the parentheses makes the pointcut only match methods of classes annotated by #Loggable. The annotation's #Target definition says that it can also be applied to methods and constructors. Those will not be matched by your pointcut, you would have to modify it for that purpose. I hope you know that, I am just mentioning it for safety.
[Xlint:adviceDidNotMatch]) means that your point cut was not applied in the compiled project. Most likely you didn't place your annotation on any method.
PS I also recommend not to reinvent the wheel and try
aspect4log
before(): execution(* YourOwnPackage.*.*(..))
{
//packages is com
System.out.println(" TEST");
}
Use this as a start to find your own advice.
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.