enabling aspectj with environment variables - spring-boot

How can we enable/disable an aspect using environment variables?
I know it is possible to enable/disable aspectj in spring boot application using following properties
spring:
aop:
auto: true
Or:
spring.aop.auto=true
And removing #EnableAspectJAutoProxy, but this stops all of our other aspects / joins.
This is the one I want to disable, how do I do it
#Aspect
#Component
public class SomeAspect {
#Around("#annotation(someAnnotation)")
public Object doSomething(ProceedingJoinPoint joinPoint, SomeAnnotation sa) throws Throwable {
// ...
}
//others
}

In order to dynamically deactivate a single advice inside an aspect class, you can use an if() pointcut.
If you want to completely disable an aspect (or any other Spring bean or component) based on conditions like e.g. a property in application.config, have a look at #Conditional and its special cases #ConditionalOn*. For example:
#Aspect
#Component
#ConditionalOnProperty(prefix = "org.acme.myapp", name = "aspect_active")
public class SomeAspect {
// ...
}
Something like this in application.config would deactivate the aspect:
org.acme.myapp.aspect_active=false
The aspect would also be inactive if there was no such property in the application config at all. If you rather want to default to an active aspect, just use
#ConditionalOnProperty(prefix = "org.acme.myapp", name = "aspect_active", matchIfMissing = true)
You can further fine-tune the behaviour as described in the javadoc.
See also:
https://www.baeldung.com/spring-conditional-annotations
https://www.baeldung.com/spring-conditionalonproperty
Update:
In order to dynamically deactivate a single advice inside an aspect class, you can use an if() pointcut.
Oops, sorry, I am a native AspectJ user and forgot that Spring AOP does not support the if() pointcut designator. So probably the best you can do is an if expression at the beginning of your advice, depending on a #Value property.
#Value("${org.acme.myapp.advice_active:false}")
private boolean adviceActive;
#Around("#annotation(someAnnotation)")
public Object doSomething(ProceedingJoinPoint joinPoint, SomeAnnotation sa) throws Throwable {
// Skip advice logic if inactive, simply proceed and return original result
if (!adviceActive)
return joinPoint.proceed();
// Regular advice logic if active
System.out.println(joinPoint);
// Either also proceed or do whatever else is the custom advice logic, e.g.
// - manipulate method arguments,
// - manipulate original return value,
// - skip proceeding to the original method altogether and return something else.
return joinPoint.proceed();
}
Of course, you can also use my original solution and just factor out the advice you wish to deactivate into a separate aspect class, if you need that kind of granularity. That would be less hassle, and the advice method code would be more readable.

Related

Creating default pointcut spring aop

While to utilise the AOP to authorize request in my controllers, I used the approach that
there is default validation and specfic validation, so that the request for which I don't have specfic pointcut defined they are authorized by the default approach.
#Before("execution(* com.example.controller.ExampleController.*(..))")
public void validate() {
validatePermissions("user", "create");
}
#Before("execution(* com.example.controller.*.*(..))")
public void validateUser() {
validatePermissions("admin", "admin");
}
Problem is that even though I have specfic validation defined for User case Its even in case of request to my Example controller request is always going to default one.
what I want is that only request not already covered by other pointcut should go to the default pointcut
You would make the specific pointcut(s) reusable and reference them in && !specificPointcut() conditions, roughly like this (untested, just written in the browser):
#Pointcut("execution(* com.example.controller.ExampleController.*(..))")
public void specificPointcut() {}
#Before("specificPointcut()")
public void validate() {
validatePermissions("user", "create");
}
#Before("execution(* com.example.controller.*.*(..)) && !specificPointcut()")
public void validateUser() {
validatePermissions("admin", "admin");
}
You can easily extend that to multiple specific pointcuts, either serially or hierarchically, depending on your situation.
Serially excluding pointcuts would work like && !(specificPc1() || specificPc2()) and hierarchically by a more common level always excluding the next more specific one.
From the reference documentation : Advice Ordering
When two pieces of the same type of advice (for example, two #After advice methods) defined in the same #Aspect class both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the source code declaration order through reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each #Aspect class or refactor the pieces of advice into separate #Aspect classes that you can order at the aspect level via Ordered or #Order.
Considering the ordering is undefined and two different PCDs target the same join point , it is not possible to have a fallback or default pointcut among these two.
You could order the aspects after collapsing them to separate #Aspect classes , check the state of authorization ( may be from a thread local variable) or from an agrument you can get hold of from the join point.

#Bean method requires to return null conditionally

I have basic authentication enabled in my system and now I am trying to integrate the SAML auth using Spring-security-SAML. I have created a method that returns the RelyingPartRegistrationRepository bean. In a condition While the user-configured values are not sufficient to Create the RelyingPartRegistration, I would have to either create a RelyingPartRegistrationRepository with Empty array which is not possible because there are checks to be not empty. another option is to return null from the Bean method. which is also a failure case because context initialization will fail in that case. All I want is to either not initialize this bean or at least not prevent context initialization. So that I can at least switch back to Basic Authentication.
You can use #ConditionalOn... to selectively enable/disable particular beans, based on things like properties
class MyConfiguration {
#Bean
#ConditionalOnProperty(name = "saml-enabled", havingValue = "true")
public RelyingPartRegistrationRepository() { ... }
}
See this article on Baeldung, and another article which covers a few other conditionals which may be useful, and of course the official documentation.

Spring AOP - annotation with args

I am stuck with a problem in spring boot. I am trying to give extra functionality to some RestControllers, and I am trying to achieve it with some custom annotations. Here is an example.
My annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyCustomAnnotation {
String someArg();
}
My aspect:
#Aspect
#Component
public class MyAspect {
#Around(
value = "#annotation(MyCustomAnnotation)",
argNames = "proceedingJoinPoint,someArg"
)
public Object addMyLogic(ProceedingJoinPoint proceedingJoinPoint, String someArg)
throws Throwable
{
System.out.println(someArg);
return proceedingJoinPoint.proceed();
}
}
My method:
#MyCustomAnnotation(someArg = "something")
#GetMapping("/whatever/route")
public SomeCustomResponse endpointAction(#RequestParam Long someId) {
SomeCustomResult result = someActionDoesNotMatter(someId);
return new SomeCustomResponse(result);
}
Mostly based on the docs (https://docs.spring.io/spring/docs/3.0.3.RELEASE/spring-framework-reference/html/aop.html - 7.2.4.6 Advice parameters) I am pretty sure, it should work.
I am here, because it does not...
What drives me crazy, is that even Intellij, when tries to help with argNames (empty string -> red underline -> alt+enter -> Correct argNames attribute) gives me this, and keeps it red...
Based on the docs, proceedingJoinPoint is not even needed (it does not work without it either): "If the first parameter is of the JoinPoint, ProceedingJoinPoint..."
With the current setup, it says "Unbound pointcut parameter 'someArg'"
At this point, I should also note, that without the args it is working fine.
I have two questions, actually:
Why does this does not work? (That was pretty obvious)
If I would like to give some extra functionality to some controllers, and I would like to parameterise it from the outside, is it the right pattern in spring boot? (With python, it was quite easy to do this with decorators - I am not quite sure, that I am not misguided by the similar syntax)
One example (the example above was pretty abtract):
I would like to create a #LogEndpointCall annotation, and the developer of a route can later just put it on the endpoint that he is developing
...however, it would be nice, if he could add a string (or more likely, an enum) as a parameter
#LogEndpointCall(EndpointCallLogEnum.NotVeryImportantCallWhoCares)
or
#LogEndpointCall(EndpointCallLogEnum.PrettySensitiveCallCheckItALot)
so that the same logic is triggered, but with a different param -> and a save to a different destination will be made.
You cannot directly bind an annotation property to an advice parameter. Just bind the annotation itself and access its parameter normally:
#Around("#annotation(myCustomAnnotation)")
public Object addMyLogic(
ProceedingJoinPoint thisJoinPoint,
MyCustomAnnotation myCustomAnnotation
)
throws Throwable
{
System.out.println(thisJoinPoint + " -> " + myCustomAnnotation.someArg());
return thisJoinPoint.proceed();
}
It will print the something like this with Spring AOP
execution(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something
and something like this with AspectJ (because AJ also knows call joinpoints, not just execution)
call(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something
execution(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something
If you want your method to intercept method that take on consideration args you must explicit mention that in you pointcut expression , to make it work here is what you should do :
#Around(
value = "#annotation(MyCustomAnnotation) && args(someArg)",
argNames = "someArg")
notice that i add && args(someArg), you can add as much arguments as you want, in argNames you can omit proceedingJoinPoint.

How to get all self injected Beans of a special type?

I would like to build a Spring application, where new components can be added easily and without much configuration. For example: You have different kinds of documents. These documents should be able to get exported into different fileformats.
To make this functionality easy to maintain, it should (basically) work the following way:
Someone programs the file format exporter
He/ She writes a component, which checks if the file format exporter is licensed (based on Spring Conditions). If the exporter is licensed a specialized Bean is injected in the application context.
The "whole rest" works dynamically based on the injected beans. Nothing needs to be touched in order to display it on the GUI, etc.
I pictured it the following way:
#Component
public class ExcelExporter implements Condition {
#PostConstruct
public void init() {
excelExporter();
}
#Bean
public Exporter excelExporter(){
Exporter exporter= new ExcelExporter();
return exporter;
}
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
}
In order to work with those exporters (display them, etc.) I need to get all of them. I tried this:
Map<String, Exporter> exporter =BeanFactoryUtils.beansOfTypeIncludingAncestors(appContext, Exporter.class, true, true);
Unfortunate this does not work (0 beans returned). I am fairly new to this, would anyone mind to tell me how this is properly done in Spring? Maybe there is a better solution for my problem than my approach?
You can get all instances of a given type of bean in a Map effortlessly, since it's a built in Spring feature.
Simply autowire your map, and all those beans will be injected, using as a key the ID of the bean.
#Autowired
Map<String,Exporter> exportersMap;
If you need something more sophisticated, such as a specific Map implementation or a custom key. Consider defining your custom ExporterMap, as follows
#Component
class ExporterMap implements Map{
#Autowired
private Set<Exporter> availableExporters;
//your stuff here, including init if required with #PostConstruct
}

Using Aspect to annotate methods with #InsightOperation for Spring Insight

I wanted to instrument a large number of classes to use with Spring Insight and instead of adding the #InsightOperation manually to the methods, I wrote an aspect to annotate the methods using point cuts.
However, this is not working. While the manual annotation affects the Spring Insight trace logging, the AspectJ method does not work.
Is there anything I am doing wrong here? (I decompiled the classes after aspectizing and do find the annotation in the class methods)
This is the aspect code snippet:
declare #method :public * com.example.IExample.execute(..) : #InsightOperation;
Spring documentation says this:
Use of the #Insight* annotations are
optional. They make it easy for end
users to define custom operation
frames and end points without needing
to create a plug-in. Because end user
code modification is required to use
the annotations, they are an option
for users who cannot or do not wish to
write aspects.
http://static.springsource.com/projects/tc-server/2.5/devedition/htmlsingle/devedition.html
So looks like the only way is to write a custom plugin
http://static.springsource.com/projects/tc-server/2.5/devedition/htmlsingle/devedition.html#tutorial-plugin
It is possible that the Insight LTW does not pick up your introduced annotations. I'll have to dig deeper on that.
In the meantime, you can try a more low-level annotation:
com.springsource.insight.collection.method.MethodOperationsCollected
If you look at the spring-core plugin, you will see that it does something similar:
public aspect RepositoryMethodOperationCollectionAspect {
declare #type: #Repository * : #MethodOperationsCollected;
}
An easy work around is to call another method from within your aspect method to continue executing the join point. I only tried calling a static method in a static class. See below my code for adding the #InsightOperation to all my JSON serialization.
My aspect:
#Aspect
public class JSONSerializerAspect {
#Around("call(* *.JSONSerializer.serialize(..)) && args(target)")
public Object serialize(ProceedingJoinPoint joinPoint, Object target) throws Throwable {
return JSONSerializationWrapper.serialize(joinPoint, target);
}
}
The static class it is calling:
public class JSONSerializationWrapper {
#InsightOperation(label = "JSON_SERIALIZATION")
public static Object serialize(ProceedingJoinPoint joinPoint, Object target) throws Throwable {
return joinPoint.proceed(new Object[]{target});
}
}
I'm using this myself and tested that it works.

Resources