I have a problem with using Aspect with annotation. The aspect method is triggered in some methods, but not in some other methods.
What I have checked is the methods are public, and the class it doesn't work is #Component.
#Aspect
#Component
public class PrometheusCounterMetricAspect {
private static final Map<String, Counter> counters = new HashMap<>();
#Before(
"execution(* *(..)) && #annotation(package.aspects.PrometheusCounterMetric)")
void beforeInsert(JoinPoint joinPoint) {
.....}
This is annotation interface
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface PrometheusCounterMetric {
String metricName();
}
And in the project the flow is controller -> service -> storage. It works in service, but not in storage, despite the fact that It is component.
Related
I have an annotation.
#Target(value = {ElementType.METHOD, ElementType.TYPE})
#Retention(value = RetentionPolicy.RUNTIME)
#Inherited
#Documented
public #interface MyCustomAnnotation{
}
My Aspect class is like that
#Component
#Aspect
public class MyCustomAsspect{
#AfterReturning(
pointcut="#annotation(MyCustomAnnotation)",
returning="retVal")
public void publishMessage(JoinPoint jp, Object retVal) throws Throwable {
}
}
My Service class is
#Service
public class ServiceClass{
#MyCustomAnnotation
public Object someMethod(){
return new Object();
}
}
Above are mentioned classes i am not sure why my aspect not working. I am new to Spring AOP . Please help me it shall be very thankful.
Issue is due to pointcut declaration. As spring documentation says
#annotation - limits matching to join points where the subject of the
join point (method being executed in Spring AOP) has the given
annotation
So I order to make this work
#Aspect
public class MyCustomAsspect{
#AfterReturning(
pointcut="execution(public * *(..)) and #annotation(MyCustomAnnotation)",
returning="retVal")
public void publishMessage(JoinPoint jp, Object retVal) throws Throwable {
}
}
I have developed a simple Annotation Interface
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface CustomAnnotation {
String foo() default "foo";
}
then I test it annotating a Class
#CustomAnnotation
public class AnnotatedClass {
}
and call it using a method
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
logger.info(customAnnotation.foo());
}
and all works fine because it logs foo. I try also change the annotated class to #CustomAnnotation(foo = "123") and all works fine too, becuase it logs 123.
Now I want that the value passed to the annotation is retrieved by the application.properties, so I have changed my annotated class to
#CustomAnnotation(foo = "${my.value}")
public class AnnotatedClass {
}
but now the log returns the String ${my.vlaue} and not the value in application.properties.
I know that is possible use ${} instruction in annotation because I always use a #RestController like this #GetMapping(path = "${path.value:/}") and all works fine.
My solution on Github repository: https://github.com/federicogatti/annotatedexample
Spring Core-based approach
First off, I want to show you a standalone application that doesn't utilise Spring Boot auto-configurable facilities. I hope you will appreciate how much Spring does for us.
The idea is to have a ConfigurableBeanFactory set up with StringValueResolver which will be aware of our context (particularly, of the application.yaml properties).
class Application {
public static void main(String[] args) {
// read a placeholder from CustomAnnotation#foo
// foo = "${my.value}"
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String foo = customAnnotation.foo();
// create a placeholder configurer which also is a properties loader
// load application.properties from the classpath
PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("application.properties"));
// create a factory which is up to resolve embedded values
// configure it with our placeholder configurer
ConfigurableListableBeanFactory factory = new DefaultListableBeanFactory();
configurer.postProcessBeanFactory(factory);
// resolve the value and print it out
String value = factory.resolveEmbeddedValue(foo);
System.out.println(value);
}
}
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#interface CustomAnnotation {
String foo() default "foo";
}
#CustomAnnotation(foo = "${my.value}")
class AnnotatedClass {}
Spring Boot-based approach
Now, I will demonstrate how to do it within your Spring Boot application.
We are going to inject ConfigurableBeanFactory (which has already been configured) and resolve the value similarly to the previous snippet.
#RestController
#RequestMapping("api")
public class MyController {
// inject the factory by using the constructor
private ConfigurableBeanFactory factory;
public MyController(ConfigurableBeanFactory factory) {
this.factory = factory;
}
#GetMapping(path = "/foo")
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String foo = customAnnotation.foo();
// resolve the value and print it out
String value = factory.resolveEmbeddedValue(foo);
System.out.println(value);
}
}
I don't like mixing up low-level Spring components, such as BeanFactory, in business logic code, so I strongly suggest we narrow the type to StringValueResolver and inject it instead.
#Bean
public StringValueResolver getStringValueResolver(ConfigurableBeanFactory factory) {
return new EmbeddedValueResolver(factory);
}
The method to call is resolveStringValue:
// ...
String value = resolver.resolveStringValue(foo);
System.out.println(value);
Proxy-based approach
We could write a method that generates a proxy based on the interface type; its methods would return resolved values.
Here's a simplified version of the service.
#Service
class CustomAnnotationService {
#Autowired
private StringValueResolver resolver;
public <T extends Annotation> T getAnnotationFromType(Class<T> annotation, Class<?> type) {
return annotation.cast(Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class<?>[]{annotation},
((proxy, method, args) -> {
T originalAnnotation = type.getAnnotation(annotation);
Object originalValue = method.invoke(originalAnnotation);
return resolver.resolveStringValue(originalValue.toString());
})));
}
}
Inject the service and use it as follows:
CustomAnnotation customAnnotation = service.getAnnotationFromType(CustomAnnotation.class, AnnotatedClass.class);
System.out.println(customAnnotation.foo());
You can't do something like directly as an annotation attribute's value must be a constant expression.
What you can do is, you can pass foo value as string like #CustomAnnotation(foo = "my.value") and create advice AOP to get annotation string value and lookup in application properties.
create AOP with #Pointcut, #AfterReturn or provided others to match #annotation, method etc and write your logic to lookup property for corresponding string.
Configure #EnableAspectJAutoProxy on main application or setting up by configuration class.
Add aop dependency: spring-boot-starter-aop
Create #Aspect with pointcut .
#Aspect
public class CustomAnnotationAOP {
#Pointcut("#annotation(it.federicogatti.annotationexample.annotationexample.annotation.CustomAnnotation)")
//define your method with logic to lookup application.properties
Look more in official guide : Aspect Oriented Programming with Spring
Make sure Annotated Class has #Component annotation along with #CustomAnnotation(foo = "${my.value}"), then Spring will recognize this class as Spring component and makes the necessary configurations to insert the value in.
You can use ConfigurableBeanFactory.resolveEmbeddedValue to resolve ${my.value} into the value in application.properties.
#CustomAnnotation(foo="${my.value}")
#lombok.extern.slf4j.Slf4j
#Service
public class AnnotatedClass {
#Autowired
private ConfigurableBeanFactory beanFactory;
public void foo() {
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String fooValue = customAnnotation.foo().toString();
String value = beanFactory.resolveEmbeddedValue(fooValue);
log.info(value);
}
}
If you also want to resolve expressions you should consider using EmbeddedValueResolver.
EmbeddedValueResolver resolver = new EmbeddedValueResolver(beanFactory);
final String value = resolver.resolveStringValue(fooValue);
You can look at Spring's RequestMappingHandlerMapping to see how they do it, which is using a EmbeddedValueResolver. You can inject the bean factory into any spring component and then use it to build your own resolver:
#Autowired
public void setBeanFactory(ConfigurableBeanFactory beanFactory)
{
this.embeddedValueResolver = new EmbeddedValueResolver(beanFactory);
CustomAnnotation customAnnotation = AnnotatedClass.class.getAnnotation(CustomAnnotation.class);
String fooValue = customAnnotation.foo();
System.out.println("fooValue = " + fooValue);
String resolvedValue = embeddedValueResolver.resolveStringValue(fooValue);
System.out.println("resolvedValue = " + resolvedValue);
}
Assuming you set foo.value=hello in your properties, the output would look something like:
fooValue = ${foo.value}
resolvedValue = hello
I tested this with Spring Boot 2.0.2 and it worked as expected.
Keep in mind this is a minimal example. You would want to handle the error cases of missing annotations on the class and missing resolved value (if the value isn't set and there's no default).
To read property from application.propertie, one need to define PropertyPlaceholderConfigurer and map it with properties file.
XML based configuration:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="locations" value="classpath:application.properties" />
</bean>
For annotation based: one can use as below:
#Configuration
#PropertySource(
value{"classpath:properties/application.properties"},ignoreResourceNotFound=true)
public class Config {
/**
* Property placeholder configurer needed to process #Value annotations
*/
#Bean
public static PropertySourcesPlaceholderConfigurer propertyConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
I have problem with AspectJ. I added arguments to annotation before which Aspect will be woven and as a result it doesn't work.
Annotation interface:
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface Logged {
Event event();
System system();
}
My Aspect:
#Aspect
#Component
public class Aspect {
#Pointcut("#annotation(Logged) && args(event, system)")
public void invoke(Event event, System system) { }
#Around("invoke(event, system)")
public void aspectMethod (ProceedingJoinPoint, Event event, System system) {
System.out.println(event + " " + system);
}
}
Event and System are Enums.
and added annotation before some method like that:
#Logged(event = Event.USER_LOGGED, system = System.WIN)
someTestingMethod();
It works only when I leave Aspect as:
#Aspect
#Component
public class Aspect {
#Pointcut("#annotation(Logged)")
public void invoke() { }
#Around("invoke()")
public void aspectMethod (ProceedingJoinPoint) {
System.out.println("Hey");
}
}
I don't know how to pass arguments into Aspect with annotation.
The basic solution is to bind the annotation:
#Aspect
class MyAspect {
#Pointcut("execution(* *(..)) && #annotation(l)")
public void invoke(Logged l) {}
#Around("invoke(l)")
public void aspectMethod (ProceedingJoinPoint pjp, Logged l) {
java.lang.System.out.println(l.event()+" "+l.system());
}
}
I've used the execution() pointcut to select only methods (so we want annotated methods) otherwise it will bind other users of the annotation (on fields/types/etc). As someone pointed out, args is for binding method parameters, rather than annotations.
I have created a custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface ValidateBeforeBuild {
}
And an aspect as:
#Aspect
#Component
public class AspectForBuildInBuilders {
private static final Logger LOGGER = LoggerFactory.getLogger(AspectForBuildInBuilders.class);
#Before("#annotation(validateBeforeBuild )")
public void validateBusinessModelAdvice(JoinPoint jp, ValidateBeforeBuild validateBeforeBuild ) throws Throwable {
LOGGER.info("Executing class: {}", jp);
}
}
I have a build() that is marked with above annotation. When I am trying to call the build(), I am not getting the log message from the validateBusinessModelAdvice(). I also have #EnableAspectJAutoProxy in one of the configuration classes. Am I missing something? Is there any more information required?
You defined your annotation as ValidateBeforeBuild and in your aspect you specified validateBeforeBuild (notice the upper V in your annotation)
Try changing
#Before("#annotation(validateBeforeBuild)")
for
#Before("#annotation(ValidateBeforeBuild)")
I'd like to weave an advice on a method that is NOT part of a Spring bean (Spring Boot 1.4.4.RELEASE) :
#Component
#Aspect
...
#Around("execution(public * com.netflix.appinfo.InstanceInfo.getId())")
I added aspectjrt and spring-instrument (??) dependencies
I added #EnableAspectJAutoProxy and #EnableLoadTimeWeaving(aspectjWeaving = AspectJWeaving.ENABLED) annotations
I added VM arguments:
-javaagent:d:\.m2\repository\org\springframework\spring-instrument\4.3.6.RELEASE\spring-instrument-4.3.6.RELEASE.jar
-javaagent:d:\.m2\repository\org\aspectj\aspectjweaver\1.8.9\aspectjweaver-1.8.9.jar
The bean is handled (postconstruct log) but the execution isn't intercepted.
Does anyone has a clue on something I could miss ? Thx in advance
Ok, here is the trick for those interested, a singleton pattern is handling access to a singleton for both LTW and Spring, so it can be injected with Spring dependencies after being weaved by LTW:
#Configuration
#Aspect
public class MyAspect {
#Value("${mycompany.property}")
private String myKey;
#Around("execution(public * com.mycompany.NotASpringean.getProperty())")
public String weave(ProceedingJoinPoint jp) throws Throwable {
String value = (String) jp.proceed();
// transform the value thx to injected myKey value
return value;
}
#Bean("post-construct-aspect")
public MyAspect init() {
return MyAspect.aspectOf(); // get existing instance via factory method
}
private static MyAspect instance = new MyAspect();
/** Singleton pattern used by LTW then Spring */
public static MyAspect aspectOf() {
return instance;
}
}
Try using the #Pointcut annotation too like this:
#Pointcut("execution(public * com.netflix.appinfo.InstanceInfo.getId())")
public void pointcut() {}
#Around("pointcut()")
public Object whatever(ProceedingJoinPoint joinPoint) throws {...}