Reputation: 11
I have a service where a method is called. Also in this service there is a field that needs to be assigned a value using custom annotation through the aspect
Service
@Service
public class TestServiceImpl implements TestService {
@MyAnnotation(value = "someName")
private Boolean isActive;
@Override
public String success(String name) {
return (isActive) ? "Yes" : "No";
}
}
Annotation
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.FIELD)
public @interface MyAnnotation{
String value() default "";
}
By the value passed in the annotation, there should be a record in the database that has the is_active field and this value should be assigned to the isActive field in the service
Upvotes: 1
Views: 1983
Reputation: 67297
If you want to stick with Spring AOP or with more flexible application design, I also recommend you to implement another application design. For example, why not annotate the success(..)
method and intercept that one instead in an @Around
advice which returns the database configuration value based on the annotation value?
If you absolutely insist in annotating fields, you have to agree to switching from Spring AOP to native AspectJ using load-time weaving (LTW), because AspectJ can intercept field read/write access via get()
and set()
pointcuts. But if your fields are private, you need a privileged aspect and cannot use annotation-style syntax.
Here is my full MCVE, using AspectJ outside of Spring to show you how it is basically done:
Marker annotation with configuration key:
package de.scrum_master.app;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(FIELD)
public @interface Marker {
String value() default "";
}
Driver application:
package de.scrum_master.app;
import java.io.PrintStream;
public class Application {
@Marker int number;
@Marker("settingA") private boolean isActive;
@Marker("settingB") private boolean isNice;
public String success(String name) {
return isActive ? "Yes" : "No";
}
public String nice(String name) {
return isNice ? "Yes" : "No";
}
public static void main(String[] args) {
Application app = new Application();
app.number = 11;
PrintStream out = System.out;
out.println(app.number);
out.println(app.success("foo"));
out.println(app.nice("bar"));
}
}
Aspect:
Please read the comments in order to understand the details better. Advices #1-#4 are more like a general how-to tutorial for using get()
and set()
, the last advice (scroll towards the end) is what you actually asked for in this specific case.
package de.scrum_master.aspect;
import de.scrum_master.app.Application;
import de.scrum_master.app.Marker;
/**
* Please note 'privileged'. This only works in native AspectJ syntax, see
* https://www.eclipse.org/aspectj/doc/next/adk15notebook/ataspectj-aspects.html:
* "Privileged aspects are not supported by the annotation style."
*/
public privileged aspect MyAspect {
/**
* During field write access, there is no generic way to get the old field value.
* But we can get the new value via 'args' and manipulate it when proceeding.
*/
void around(int newValue) :
set(@Marker * *) && args(newValue)
{
int newValueUpdated = 3 * newValue;
System.out.println(thisJoinPoint + " AROUND: newV = " + newValue + " -> " + newValueUpdated);
proceed(newValueUpdated);
}
/**
* If we exactly know which field to target, we can of course access its old value directly by binding the
* target instance to an advice parameter or by just directly accessing a static field.
* But if the field is private, the aspect must be privileged.
*/
void around(int newValue, Application application) :
set(int Application.number) && args(newValue) && target(application)
{
int oldValue = application.number;
int newValueUpdated = newValue + 7;
System.out.println(thisJoinPoint + " AROUND: " + "oldV = " + oldValue + ", newV = " + newValue + " -> " + newValueUpdated);
proceed(newValueUpdated, application);
}
/**
* During field read access, it is easy to get the old field value by just proceeding.
* A new value can be set by returning it from the advice.
*
* '!within(MyAspect)' avoids triggering the advice when reading the field from within this aspect.
*/
int around():
get(@Marker int *) && !within(MyAspect)
{
int oldValue = proceed();
int newValue = oldValue + 1000;
System.out.println(thisJoinPoint + " AROUND: " + oldValue + " -> " + newValue);
return newValue;
}
/**
* For just intercepting the newly set field value, 'after() returning' can be used.
* For this simple case we do not need an 'around()' advice.
* A new value cannot be set here, of course, because that has already happened.
*
* Please note how this advice also captures the access to System.out in the application.
*/
after() returning(Object newValue) :
get(* *) && !within(MyAspect)
{
System.out.println(thisJoinPoint + " AFTER RET: " + newValue);
}
/**
* After the more general part, this advice actually answers the question under
* https://stackoverflow.com/q/66563533/1082681:
*
* We look for annotated boolean fields (can be adjusted to other types easily),
* get the annotation value from the value bound via '@annotation()' and
* use the value to fetch the desired config value from the DB.
*/
boolean around(Marker marker):
get(@Marker boolean *) && !within(MyAspect) && @annotation(marker)
{
System.out.println(thisJoinPoint + " Fetching config value for " + marker + " from DB");
return getConfigValueFromDB(marker.value());
}
/**
* DB access mock-up
*/
private boolean getConfigValueFromDB(String key) {
switch (key) {
case "settingA": return true;
case "settingB": return false;
default: return false;
}
}
}
Console log:
set(int de.scrum_master.app.Application.number) AROUND: newV = 11 -> 33
set(int de.scrum_master.app.Application.number) AROUND: oldV = 0, newV = 33 -> 40
get(PrintStream java.lang.System.out) AFTER RET: java.io.PrintStream@279f2327
get(int de.scrum_master.app.Application.number) AROUND: 40 -> 1040
get(int de.scrum_master.app.Application.number) AFTER RET: 1040
1040
get(boolean de.scrum_master.app.Application.isActive) Fetching config value for @de.scrum_master.app.Marker(value=settingA) from DB
Yes
get(boolean de.scrum_master.app.Application.isNice) Fetching config value for @de.scrum_master.app.Marker(value=settingB) from DB
No
Disclaimer: I wanted to show you the power of AspectJ. You can do it this way, but like I said before, it does not mean you should. You can easily redesign your application to get this working with Spring AOP by annotating accessor methods instead of fields. Or like Alexander Katsenelenbogen said, there are ways to do this completely without AOP, even though I think AOP is an appropriate way to do this, because reading config values from an external source is a cross-cutting concern.
Upvotes: 1