#AliasFor doesn't work on attribute in custom annotation - java

I'm using SpringBoot 2.4.2. And I'm struggling with #AliasFor with custom annotation.
I implemented below custom annotation.
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CustomAnnotation {
#AliasFor("aliasAttribute")
String value() default "";
#AliasFor("value")
String aliasAttribute() "";
}
And using it like this.
#CustomAnnoatation("test")
#Component
public class TestClass() {
// codes here
}
And this test code fails.
#SpringBootTest(classes = TestClass.class)
public class CustomAnnotationTest {
#Autowired
TestClass testClass;
#Test
public void valueTest1() {
Annotation annotation = testClass.getClass().getAnnotation(CustomAnnotation.class);
assertThat(((CustomAnnotation) annotation).value()).isEqualTo(((CustomAnnotation) annotation).aliasAttribute());
}
}
with message
org.opentest4j.AssertionFailedError:
Expecting:
<"">
to be equal to:
<"test">
I don't know why, anyone know?

An annotation is static metadata for a class, field etc. so Spring cannot change anything about it. To make features as #AliasFor possible Spring uses, what they call, synthesized annotations. For those to be used/detected you need to utilize the Spring internals to obtain that synthesized annotation and for the #AliasFor to work. For this use AnnotationUtils.findAnnotation (Spring uses that internally as well).
#AliasFor is a Spring feature so without using the Spring components this won't work.
Your test method is basically the same as
#Test
public void valueTest1() {
Annotation annotation = TestClass.class.getAnnotation(CustomAnnotation.class);
assertThat(((CustomAnnotation) annotation).value()).isEqualTo(((CustomAnnotation) annotation).aliasAttribute());
}
Both this test and your test will fail, because they simply don't use the Spring infrastructure for detecting annotations and apply the features of Spring.
When using AnnotationUtils.findAnnotation the test will pass.
class CustomAnnotationTest {
#Test
void testStandardJava() {
CustomAnnotation annotation = TestClass.class.getAnnotation(CustomAnnotation.class);
assertThat(annotation.value()).isEqualTo(annotation.aliasAttribute());
}
#Test
void testWithSpring() {
CustomAnnotation annotation = AnnotationUtils.findAnnotation(TestClass.class, CustomAnnotation.class);
assertThat(annotation.value()).isEqualTo(annotation.aliasAttribute());
}
}
The testStandardJava will fail, the testWithSpring will pass because it uses the proper mechanisms.

Related

Spring AOP: #annotation() pointcut does not match type annotation

I'm writing an aspect to log Request and Response of each API call in a controller.
I want to be able to use this annotation on a class, hence used #Target(ElementType.TYPE)
Previously I had added #Target(ElementType.Method) and I was using this annotation on methods and it was working fine.
Now I want to change it to #Target(ElementType.TYPE)
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ReLogger {}
#Aspect
#Component
public class ReLoggerAspect {
public static final Logger log = LoggerFactory.getLogger("ReLoggerAspect");
#PostConstruct
private void postConstruct() {
log.info("ReLoggerAspect Created");
}
#Around("#annotation(ReLogger)")
private Object reqLoggingAspect(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("Request {}",jointPoint.getArgs()[0);
}
}
Using #ReLoggerAspect on a class
#RestController
#RequestMapping(value = "....", produces = { "application/json" })
#ReLogger
public class Samplecontroller {
/** Some logic here**/.....
}
It doesn't print the Request when an API SampleController is invoked
Your premise that #annotation would match type annotations is wrong, see (Spring AOP manual](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-pointcuts-designators):
#within: Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP).
#annotation: Limits matching to join points where the subject of the join point (the method being executed in Spring AOP) has the given annotation.
Thus, you ought to use #within(fully.qualified.AnnotationType).

Overriding #Value in Integration Test

For one of my Spring beans(say Application class), I'm fetching the value of a property(my.property.flag=true/false) from a properties file(prop.properties) using #Value annotation. That works perfectly fine.
I need to write an integration test(say ApplicationIt class) where I need to test with both the values of the property i.e. for both true and false.
In my properties file, the value of the property is set to true. Is it possible to set the value dynamically to false from my Integration test?
For Example,
prop.properties:
my.property.flag=true
Application class file:
#Component
class Application {
//This value is fetched from properties file
//the value is set to true.
#Value(${my.property.flag})
private String isTrue;
......
..........
}
Integration Test:
class ApplicationIT {
//how can I set the value of isTrue here to false?
}
You can specify test properties on the test class as follows:
#RunWith(SpringRunner.class)
#TestPropertySource(properties = {"spring.main.banner-mode=off", "my.property.flag=false"})
public class MyTest {
Since Spring has a whole hierarchy of property overrides, this works pretty well, the downside being you need separate test classes for different values. If you're using Spring Boot, there's another annotation that provides the same functionality but also has more options for configuring your test environment. Example:
#SpringBootTest(properties = {"spring.main.banner-mode=off", "my.property.flag=false"})
Again, you will need separate test classes to handle hard-coded test properties.
I was bugged with this for a while and found this neat way to override the properties. It is quite useful if you need some programmatic initialization of the application context such as registering property sources like in that case but not only. The following approach uses ContextConfiguration's initializers.
example for Spring Boot 1.5.x :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"management.port=0"})
#ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
#DirtiesContext
public abstract class AbstractIntegrationTest {
private static int REDIS_PORT = 6379;
#ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(REDIS_PORT);
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ctx) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(ctx,
"spring.redis.host=" + redis.getContainerIpAddress(),
"spring.redis.port=" + redis.getMappedPort(REDIS_PORT));
}
}
}
example for Spring Boot 2.x :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = {"management.port=0"})
#ContextConfiguration(initializers = AbstractIntegrationTest.Initializer.class)
#DirtiesContext
public abstract class AbstractIntegrationTest {
private static int REDIS_PORT = 6379;
#ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.6").withExposedPorts(REDIS_PORT);
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ctx) {
TestPropertyValues.of(
"spring.redis.host:" + redis.getContainerIpAddress(),
"spring.redis.port:" + redis.getMappedPort(REDIS_PORT))
.applyTo(ctx);
}
}
}
I want to mention good old reflection way. You can use spring provided utility class for it after you wired in your component:
ReflectionTestUtils.setField(component, "isTrue", true)
You can change it to any value you want in consequent tests
Preferably, use constructor injection instead of field injection:
#Component
class Application {
Application(#Value("${my.property.flag}") boolean flag) {
...
}
}
This makes using mocks or test values as simple as passing an argument.

Why can not get annotation from beanClass?

#Transactional
#Component
#EntranceLog
public class TransferServiceImpl implements TransferService {
xxxx
}
I hava a class with Transactional annotation and Component annotation. EntranceLog is my customize annotation to print log by aop.
public class LogProxyCreator extends AbstractAutoProxyCreator implements ApplicationContextAware {
private static final LogInterceptor LOG = new LogInterceptor();
private static Logger log = LoggerFactory.getLogger(LogProxyCreator.class);
#Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String s, TargetSource targetSource) throws BeansException {
Annotation anno = null;
for (Annotation annotationTemp : beanClass.getAnnotations()) {
Log temp = annotationTemp.annotationType().getAnnotation(EntranceLog.class);
if (temp != null) {
anno = temp;
break;
}
}
if (anno == null) {
return null;
}
Object[] additional = new Object[]{LOG};
log.error(beanClass.getName() + " has register the fc log.");
return additional;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
LOG.setContext(applicationContext);
}
}
When my app is starting, the bean transferServiceImpl start, but beanClass.getAnnotations() can not get any annotation. Why?
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
#Log(logName = "entrance")
public #interface EntranceLog {
#AliasFor(
annotation = Log.class,
attribute = "subLogName"
)
String logName() default "";
#AliasFor(
annotation = Log.class,
attribute = "openInfoLog"
)
boolean openInfoLog() default false;
}
This is my annotation.
In Spring #Transactionalis already an AOP processed annotation, so adding your own will require some additional work. Let me explain how Spring AOP and #Transactional works.
Spring has two ways of doing AOP, if the class implements an interface it can use a standard JDK Proxy, if the class does not implement an interface it will create a new subclass by using CGLib to emit bytecode at runtime. Unless you are very careful you will almost always get a CGLib proxy with Spring AOP.
When Spring encounters a #Transactional (class or method level) it creates a new subclass using CGLib, you can think of this class as a decorator, which forwards all calls to your implementation class. Before and after (around Advice) it check the #Transactional annotation properties, and check Thread Local storage to see if a transaction already exist, if there is no transaction it creates one, and remembers it so it can commit it afterwards. If you set a breakoint inside a Transactional method and look at the callstack you will see the call to your implementation came from the decorater class, and that there is no source code for it.
In your case the bean that is added to the Application Context, is not your TransferServiceImplbean, but the CGLib proxy created by Spring when it found the #Transactional annotation on your class, it will be named something like TransferServiceImpl$$FastClassBySpringCGLIB$$<hexstring> - This class does not have the #EntranceLog annotation, which is why your own aspect is not working.
I have never encountered this problem myself, as I try to avoid AOP in general, or at always on classes that are already being CGLib proxied by Spring. Unless you want to dig deep into the Spring source, or find someone on the Spring dev team to help you with this, I suggest that you create another layer of indirection, so you don't need to handle two aspects in the same class.
For anyone who may be unwilling or unable to alter their code structure in order to avoid this issue, the following can probably help:
As Klaus mentioned, Spring creates a decorator class when it encounters a class tagged with #Transactional. However, because this new class is just that--a decorator--you should be able to call getSuperclass() on beanClass to give you the actual class Spring is decorating, like so:
beanClass.getSuperclass().getAnnotations()
If you're using your own Annotation, ensure it also persists through runtime by annotating the Annotation class with:
#Retention(RetentionPolicy.RUNTIME)

Spring #Validated in service layer

Hej,
I want to use the #Validated(group=Foo.class) annotation to validate an argument before executing a method like following:
public void doFoo(Foo #Validated(groups=Foo.class) foo){}
When i put this method in the Controller of my Spring application, the #Validated is executed and throws an error when the Foo object is not valid. However if I put the same thing in a method in the Service layer of my application, the validation is not executed and the method just runs even when the Foo object isn't valid.
Can't you use the #Validated annotation in the service layer ? Or do I have to do configure something extra to make it work ?
Update:
I have added the following two beans to my service.xml:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
and replaced the #Validate with #Null like so:
public void doFoo(Foo #Null(groups=Foo.class) foo){}
I know it is a pretty silly annotation to do but I wanted to check that if I call the method now and passing null it would throw an violation exception which it does. So why does it execute the #Null annotation and not the #Validate annotation ? I know one is from javax.validation and the other is from Spring but I do not think that has anything to do with it ?
In the eyes of a Spring MVC stack, there is no such thing as a service layer. The reason it works for #Controller class handler methods is that Spring uses a special HandlerMethodArgumentResolver called ModelAttributeMethodProcessor which performs validation before resolving the argument to use in your handler method.
The service layer, as we call it, is just a plain bean with no additional behavior added to it from the MVC (DispatcherServlet) stack. As such you cannot expect any validation from Spring. You need to roll your own, probably with AOP.
With MethodValidationPostProcessor, take a look at the javadoc
Applicable methods have JSR-303 constraint annotations on their
parameters and/or on their return value (in the latter case specified
at the method level, typically as inline annotation).
Validation groups can be specified through Spring's Validated
annotation at the type level of the containing target class, applying
to all public service methods of that class. By default, JSR-303 will
validate against its default group only.
The #Validated annotation is only used to specify a validation group, it doesn't itself force any validation. You need to use one of the javax.validation annotations like #Null or #Valid. Remember that you can use as many annotations as you would like on a method parameter.
As a side note on Spring Validation for methods:
Since Spring uses interceptors in its approach, the validation itself is only performed when you're talking to a Bean's method:
When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces, you'll be talking to the default Validator of the underlying ValidatorFactory. This is very convenient in that you don't have to perform yet another call on the factory, assuming that you will almost always use the default Validator anyway.
This is important because if you're trying to implement a validation in such a way for method calls within the class, it won't work. E.g.:
#Autowired
WannaValidate service;
//...
service.callMeOutside(new Form);
#Service
public class WannaValidate {
/* Spring Validation will work fine when executed from outside, as above */
#Validated
public void callMeOutside(#Valid Form form) {
AnotherForm anotherForm = new AnotherForm(form);
callMeInside(anotherForm);
}
/* Spring Validation won't work for AnotherForm if executed from inner method */
#Validated
public void callMeInside(#Valid AnotherForm form) {
// stuff
}
}
Hope someone finds this helpful. Tested with Spring 4.3, so things might be different for other versions.
#pgiecek You don't need to create a new Annotation. You can use:
#Validated
public class MyClass {
#Validated({Group1.class})
public myMethod1(#Valid Foo foo) { ... }
#Validated({Group2.class})
public myMethod2(#Valid Foo foo) { ... }
...
}
Be careful with rubensa's approach.
This only works when you declare #Valid as the only annotation. When you combine it with other annotations like #NotNull everything except the #Valid will be ignored.
The following will not work and the #NotNull will be ignored:
#Validated
public class MyClass {
#Validated(Group1.class)
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated(Group2.class)
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
In combination with other annotations you need to declare the javax.validation.groups.Default Group as well, like this:
#Validated
public class MyClass {
#Validated({ Default.class, Group1.class })
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated({ Default.class, Group2.class })
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
As stated above to specify validation groups is possible only through #Validated annotation at class level. However, it is not very convenient since sometimes you have a class containing several methods with the same entity as a parameter but each of which requiring different subset of properties to validate. It was also my case and below you can find several steps to take to solve it.
1) Implement custom annotation that enables to specify validation groups at method level in addition to groups specified through #Validated at class level.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ValidatedGroups {
Class<?>[] value() default {};
}
2) Extend MethodValidationInterceptor and override determineValidationGroups method as follows.
#Override
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
final Class<?>[] classLevelGroups = super.determineValidationGroups(invocation);
final ValidatedGroups validatedGroups = AnnotationUtils.findAnnotation(
invocation.getMethod(), ValidatedGroups.class);
final Class<?>[] methodLevelGroups = validatedGroups != null ? validatedGroups.value() : new Class<?>[0];
if (methodLevelGroups.length == 0) {
return classLevelGroups;
}
final int newLength = classLevelGroups.length + methodLevelGroups.length;
final Class<?>[] mergedGroups = Arrays.copyOf(classLevelGroups, newLength);
System.arraycopy(methodLevelGroups, 0, mergedGroups, classLevelGroups.length, methodLevelGroups.length);
return mergedGroups;
}
3) Implement your own MethodValidationPostProcessor (just copy the Spring one) and in the method afterPropertiesSet use validation interceptor implemented in step 2.
#Override
public void afterPropertiesSet() throws Exception {
Pointcut pointcut = new AnnotationMatchingPointcut(Validated.class, true);
Advice advice = (this.validator != null ? new ValidatedGroupsAwareMethodValidationInterceptor(this.validator) :
new ValidatedGroupsAwareMethodValidationInterceptor());
this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
}
4) Register your validation post processor instead of Spring one.
<bean class="my.package.ValidatedGroupsAwareMethodValidationPostProcessor"/>
That's it. Now you can use it as follows.
#Validated(groups = Group1.class)
public class MyClass {
#ValidatedGroups(Group2.class)
public myMethod1(Foo foo) { ... }
public myMethod2(Foo foo) { ... }
...
}

setting annotation attributes in spring xml

I have been trying to set the logTime attribute in my annotation in the spring xml. I am seeing that this is not as easy as I first thought.
#Component
#Retention(RetentionPolicy.RUNTIME)
public #interface LogExecTime {
public boolean logTime() default true;
}
I have tried to use the #Value annotation with the interface with no luck:
I)
#Component
#Retention(RetentionPolicy.RUNTIME)
public #interface LogExecTime {
#Value("#{ConfigureAnnotation.doLogging}")
public boolean logTime() default true;
}
and also
II)
#LogExecTime(logTime=#Value("#{ConfigureAnnotation.doLogging}"))
Any ideas how I can do this at xml level or is this not possible with annotation dependency injection?
Yeah -- that's not ever going to work.
#LogExecTime(logTime=#Value("#{ConfigureAnnotation.doLogging}"))
will never even compile. Annotations are not executable code, they're just markers -- extra bit of information that are inserted into the class file whole sale.
You could either put this:
#Value("#{ConfigureAnnotation.doLogging}")
boolean logTime = true;
As a real field on a spring managed bean somewhere, or have change your annotation to be like:
#Component
#Retention(RetentionPolicy.RUNTIME)
public #interface LogExecTime {
public String logTime() default "true";
}
and have whatever is processing that annotation at run time also accept a spring EL expression and resolve it appropriately, and your component would look like this:
#LogExecTime(logTime = "#{ConfigureAnnotation.doLogging}")
public class SomeComponent {
// blah blah blah
}

Categories

Resources