Anyway to create a pointcut to methods of a class' members? - java

Given a class with a bunch of members, each with their own getter/setter/etc methods, is there a way to design a pointcut that will trigger only on members' methods when contained within the parent class?
For example:
public MyClass{
List myList = new ArrayList<String>();
}
If I want to create a pointcut to advise myList.add(), is there a way to do this? I do not wish to advise all ArrayList.add() calls. Only to Collections.add() that are members of MyClass.
I've tried playing around with within and cflow, but to no avail:
pointcut addPointcut() : cflow( execution( * *.getMyList() ) ) && call( * *.add(..));
but it does not seem to work. I presume that given that that the add() calls are not actually part of the get() control flow, it doesn't seem to trigger properly.
After some more playing around, I've noticed the following solution seems to work:
pointcut addPointcut(): within( MyClass ) && call( * *.add(..) );
Is this the correct implementation?
I've tried to limit the pointcut to only advise calls to add() when passing an #Entity object, but it does not work. Ex:
pointcut addEntityPointcut(): within( MyClass ) && call( * *.add(#javax.persistence.Entity *) );
and yet the addPointcut() works when called with an #Entity as a parameter.
Is the argument type based on the actual calling method, or based on the add() signature?
EDIT
I was too quick to jump to the wrong conclusion. After sleeping, I've come to recognize that my pointcut will not work.
public class FirstClass{
List<String> strings = new ArrayList<>();
// getters and setters
}
public class Execute{
public main(){
FirstClass fc = new FirstClass();
fc.getStrings().add( "This call is advised" ); // <---- Is there any way to advise this add() method?
List<String> l = new ArrayList<>();
l.add( "This call is not advised" ); // <---- this one should not be advised
}
}
I'm looking for a way to advise the add() method called from any class. However, I'm only looking to advise the add() method on the member List contained within FirstClass, even when called from outside FirstClass.

Is the argument type based on the actual calling method, or based on the add() signature?
In AspectJ for the call() pointcut you need to specify method or constructor signatures. The add() method in this case does not have any parameters annotated by #Entity, thus what you are trying to do does not work. This is a workaround using reflection:
Sample annotation:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface Entity {}
Sample entity:
package de.scrum_master.app;
#Entity
public class MyEntity {}
Driver application:
package de.scrum_master.app;
import java.util.ArrayList;
import java.util.List;
public class Application {
List<Object> myList = new ArrayList<>();
public static void main(String[] args) {
Application application = new Application();
application.myList.add("foo");
application.myList.add(new MyEntity());
application.myList.add("bar");
application.myList.add(new MyEntity());
}
}
Aspect:
package de.scrum_master.aspect;
import de.scrum_master.app.Application;
import de.scrum_master.app.Entity;
public aspect EntityAddInterceptor {
pointcut addEntity(Object addedObject) :
within(Application) && call(* *.add(*)) && args(addedObject);
before(Object addedObject) : addEntity(addedObject) {
if (addedObject.getClass().isAnnotationPresent(Entity.class))
System.out.println(thisJoinPointStaticPart + " -> " + addedObject);
}
}
Output:
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#19dc6592
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#54906181
As for the control flow matching variant, I think from the naming perspective it makes sense to assume that getMyList() does not add anything, but just return a list. Probably you rather do something like application.getMyList().add("foo"), and in this case the add() is really outside (after) the control flow of getMyList() because it operates on its result.
If OTOH you have a hypothetical method addToList(Object element) which really calls add() you can use cflow(). Let us modify the code sample:
Modified driver application:
package de.scrum_master.app;
import java.util.ArrayList;
import java.util.List;
public class Application {
List<Object> myList = new ArrayList<>();
public void addToMyList(Object element) { reallyAddToMyList(element); }
private void reallyAddToMyList(Object element) { myList.add(element); }
public static void main(String[] args) {
Application application = new Application();
application.myList.add("foo");
application.myList.add(new MyEntity());
application.addToMyList("bar");
application.addToMyList(new MyEntity());
}
}
Modified aspect:
package de.scrum_master.aspect;
import de.scrum_master.app.Entity;
public aspect EntityAddInterceptor {
pointcut addEntity(Object addedObject) :
cflow(execution(* *.addToMyList(*))) && (call(* *.add(*)) && args(addedObject));
before(Object addedObject) : addEntity(addedObject) {
if (addedObject.getClass().isAnnotationPresent(Entity.class))
System.out.println(thisJoinPointStaticPart + " -> " + addedObject);
}
}
New output:
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#323ba00
As you can see, only one call is logged. It is the one from reallyAddToMyList(), not the one from main().
Update 2014-07-21 - better aspect modification:
Credits for this more elegant solution go to Andy Clement (AspectJ maintainer) who has mentioned it on the AspectJ mailing list. It shows both of my variants from above, but uses && #args(Entity) instead of if (addedObject.getClass().isAnnotationPresent(Entity.class)):
package de.scrum_master.aspect;
import de.scrum_master.app.Application;
import de.scrum_master.app.Entity;
public aspect EntityAddInterceptor {
pointcut addEntity(Object addedObject) :
within(Application) && call(* *.add(*)) && args(addedObject) && #args(Entity);
before(Object addedObject) : addEntity(addedObject) {
System.out.println(thisJoinPointStaticPart + " -> " + addedObject);
}
pointcut addEntitySpecial(Object addedObject) :
cflow(execution(* *.addToMyList(*))) && (call(* *.add(*)) && args(addedObject)) && #args(Entity);
before(Object addedObject) : addEntitySpecial(addedObject) {
System.out.println(thisJoinPointStaticPart + " -> " + addedObject + " [special]");
}
}
The output with both variants active looks like this:
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#229ff6d1
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#1976bf9e
call(boolean java.util.List.add(Object)) -> de.scrum_master.app.MyEntity#1976bf9e [special]

Related

General Information about how to check for non unique IDs in Java code

In a very huge project, there are alot of ExceptionClasses where exception details are implemented as enum.
NAME_OF_EXCEPTION_DETAIL(ID, PRIORITY)
e.g:
NO_LICENSE_FOUND(100, 0)
UNSUPPORTED_LICENSE(101, 1)
WRONG_LICENSE_FOR_VERSION(101, 0)
In some cases the exception details ID is the same, wich should never have happend. It is pretty damn possible that there are more duplicates but this project is to huge to check this by hand ...
Is there a smart way to check the code for duplicates, e.g. UnitTesting - never used it befor.
Thank's for your help!
Edit (Bens answer is a good way to solve this problem when using just one enum):
To be more clear in my specific task this are the circumstances.
There are alot of Classes that are written to handle ExceptionDetails e.g.
TopicOneExceptionDetails.java
TopicTwoExceptionDetails.java (and so on)
Each of these classes defindes a enum for this topic like so:
public enum TopicOneExceptionDetails implements ApplicationException.ExceptionDetails { .... }
followed by the declaration of errors related to TopicOne errors e.g.
SETTING_TIMEZONE_FAILED(55, ...)
Setting_DATETIME_FAILED(56, ...)
in this enum for TopicOne every error ID has to be unique. But for example ID 55 (here used for SETTING_TIMEZONE_FAILED) can be used in the declaration of errors related to TopicTwo without a problem.
Edit 2 (Use reflections ?)
I found a pretty informative answer about java Reflections written by Matt Sheppard. I think this could solve my problem. I need to take a break but i will report back. My current thinkings are:
Reflections reflections = new Reflections("project.de")
for(Class<? extends ApplicationException.ExceptionDetails> exceptionDetailsClasses : reflections.getSubTypesOf(ApplicationException.ExceptionDetails.class)){
for ( ... ) .. }
I should be able to check for the IDs e.g. like Ben advised. I'll report back later.
EDIT 3 (Question Solved!)
Like in edit 2 described i was able to easily solve this issue using reflections.
public class ApplicationExceptionDetailsErrorCodeValidationTest {
#Test
public void validateErrorCodeUniqueness() throws ErrorCodeUniquenessBroken{
Reflections reflections = new Reflections("de.xyz");
for (Class<? extends ApplicationException.ExceptionDetails> exceptionDetailsClass : reflections.getSubTypesOf(ApplicationException.ExceptionDetails.class)) {
if (exceptionDetailsClass.getSimpleName().equals("TestExceptionDetails")) {
continue;
}
List<Integer> errorCodes = new ArrayList<Integer>();
for (ApplicationException.ExceptionDetails exceptionDetails : exceptionDetailsClass.getEnumConstants()) {
if (errorCodes.contains(exceptionDetails.getErrorCode().getId())) throw new ErrorCodeUniquenessBroken("ErrorCode uniqueness broken! ErrorCode: " + exceptionDetails.getMessageKey() + " has repeated ErrorCode! Change it!");
else errorCodes.add(exceptionDetails.getErrorCode().getId());
}
}
}
}
Special thanks to Ben and Spotted for their effort. The both answered with some pretty good examples in how to resolve the topics question in general. In my case it just got a bit more complicated.
One way to check your Enum for duplicate IDs (It's not tested, but should work).
You can use parts of this code for your unit-test (or use it standalone):
import java.util.HashSet;
import java.util.Set;
public class NewClass
{
public static enum Errors
{
ERROR_A(1),
ERROR_B(2),
ERROR_C(3),
ERROR_D(2), //duplicate is here!
ERROR_E(5);
int m_id;
Errors(int id)
{
m_id = id;
}
public int getId()
{
return m_id;
}
}
public static void main(String[] args)
{
Set<Integer> usedIds = new HashSet<>();
for (Errors e : Errors.values()) //iterate over all Error-Enum-Items
{
int id = e.getId(); //fetch id from enum-item
if (usedIds.contains(id)) //check if id was used before
System.err.println("ID ALREADY USED: " + e.name() + ":" + id);
else
usedIds.add(id); //remember new used id here
}
}
}
Cheers!
Sure unit testing is a great way to detect (with certitude) such problem.
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import org.junit.Test;
public class ErrorSpec {
#Test
public void itShouldNotHaveTwoEnumsWithSameId() {
Set<Integer> idsWithoutDuplicates = Arrays.stream(Error.values())
.map(Error::getId)
.collect(Collectors.toSet());
assertEquals(Error.values().length, idsWithoutDuplicates.size());
}
}
If you wish to use AssertJ you can make the test even more explicit (and also point out which ID is duplicated !):
import static org.assertj.core.api.Assertions.*;
import java.util.Arrays;
import org.junit.Test;
public class ErrorSpec {
#Test
public void itShouldNotHaveTwoEnumsWithSameId() {
int[] ids = Arrays.stream(Error.values())
.mapToInt(Error::getId)
.toArray();
assertThat(ids).doesNotHaveDuplicates();
}
}

where to code ThreadLocal.remove() in aspectj class

/*
We are using Aspect to do AOP on some existing application and we also used threadlocal to store GUId. we are using #Around annotation.
At the start of the transaction we are setting the GUID in transaction with initialValue() method.
Issue is as we know when we are using threadlocal we should also take care about removing the data from threadlocal otherwise it may result i outofmemory execption. if i am removing it at
the last of the aspect it is corrupting the code and changing the UUID value.
Please suggest how we can achieve it without outofmemory.
Code :-
*/
#Aspect
public class DemoAspect {
#Pointcut("execution(* *.*(..)) ")
public void logging() {}
private static ThreadLocal<String> id = new ThreadLocal<String>() {
#Override
protected String initialValue(){
return UUID.randomUUID().toString();
}
};
#Around("logging()")
public Object tracing(ProceedingJoinPoint thisJoinPoint) throws Throwable {
String methodSignature=thisJoinPoint.getSignature().toString();
if(id.get().toString()==null || id.get().toString().length()==0)
id.set(UUID.randomUUID().toString());
System.out.println("Entering into "+methodSignature);
Object ret = thisJoinPoint.proceed();
System.out.println(id.get().toString());
System.out.println("Exiting into "+methodSignature);
//id.remove();
return ret;
}
}
Before we start a little hint: If you write #Around("logging()") your pointcut method should be renamed from loggingResponseTime() to actually logging(), otherwise the aspect will not work.
Now as for your real problem: You are making a typical beginners' mistake by advising code too broadly, i.e. you are intercepting all method executions (outside the JDK). If you use Eclipse and AJDT and you put your cursor into the tracing() advice you will see something like this in the AspectJ "cross reference" window using your current pointcut:
You can immediately see your problem: Your pointcut captures code in your anonymous ThreadLocal subclass. This leads to endless recursion and finally to the StackOverflowError as you can see in your own callstack if you inspect it.
Now here is some sample code demonstrating the problem for other people's reference:
Driver application:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
System.out.println(bar(foo()));
}
public static String bar(String text) {
return text + "bar";
}
private static String foo() {
return "foo";
}
}
Aspect:
package de.scrum_master.aspect;
import java.util.UUID;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class DemoAspect {
private static ThreadLocal<String> id = new ThreadLocal<String>() {
#Override
protected String initialValue() {
return UUID.randomUUID().toString();
}
};
#Pointcut("execution(* *(..))")
public void logging() {}
#Around("logging()")
public Object tracing(ProceedingJoinPoint thisJoinPoint) throws Throwable {
String methodSignature = thisJoinPoint.getSignature().toString();
if (id.get().toString() == null || id.get().toString().length() == 0)
id.set(UUID.randomUUID().toString());
System.out.println("Entering into " + methodSignature);
Object ret = thisJoinPoint.proceed();
System.out.println(id.get().toString());
System.out.println("Exiting from " + methodSignature);
id.remove();
return ret;
}
}
Console output:
Exception in thread "main" java.lang.StackOverflowError
at org.aspectj.runtime.reflect.SignatureImpl$CacheImpl.get(SignatureImpl.java:217)
at org.aspectj.runtime.reflect.SignatureImpl.toString(SignatureImpl.java:50)
at org.aspectj.runtime.reflect.SignatureImpl.toString(SignatureImpl.java:62)
at de.scrum_master.aspect.DemoAspect$1.initialValue_aroundBody1$advice(DemoAspect.aj:29)
at de.scrum_master.aspect.DemoAspect$1.initialValue(DemoAspect.aj:1)
at de.scrum_master.aspect.DemoAspect$1.initialValue(DemoAspect.aj:1)
at java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:160)
at java.lang.ThreadLocal.get(ThreadLocal.java:150)
at de.scrum_master.aspect.DemoAspect$1.initialValue_aroundBody1$advice(DemoAspect.aj:30)
at de.scrum_master.aspect.DemoAspect$1.initialValue(DemoAspect.aj:1)
at de.scrum_master.aspect.DemoAspect$1.initialValue(DemoAspect.aj:1)
at java.lang.ThreadLocal.setInitialValue(ThreadLocal.java:160)
at java.lang.ThreadLocal.get(ThreadLocal.java:150)
(...)
So what can you do? It is actually quite simple: Just exclude the joinpoints you do not really want to intercept from your pointcut. For that you have several options. I am just naming a few:
A) Put your aspects into a specific package and exclude all (aspect) classes in that package:
#Pointcut("execution(* *(..)) && !within(de.scrum_master.aspect..*)")
B) Exclude all classes annotated by #Aspect:
#Pointcut("execution(* *(..)) && !within(#org.aspectj.lang.annotation.Aspect *)")
C) Exclude all (aspect) classes matching a certain naming scheme like *Aspect:
#Pointcut("execution(* *(..)) && !within(*..*Aspect)")
D) Exclude code from all ThreadLocal subclasses (+ syntax):
#Pointcut("execution(* *(..)) && !within(ThreadLocal+)")
In each case the result will be the same:
Entering into void de.scrum_master.app.Application.main(String[])
Entering into String de.scrum_master.app.Application.foo()
d2b83f5f-7282-4c06-9b81-6601c8e0499d
Exiting from String de.scrum_master.app.Application.foo()
Entering into String de.scrum_master.app.Application.bar(String)
0d1c9463-4bbd-427d-9d64-c7f3967756cf
Exiting from String de.scrum_master.app.Application.bar(String)
foobar
aa96bbbd-a1a1-450f-ae6e-77ab204c5fb2
Exiting from void de.scrum_master.app.Application.main(String[])
By the way: I have strong doubts about your usage of UUIDs because I see no value in creating expensive objects here. How about just logging timestamps? Why do you need globally unique IDs for logging? They tell you nothing. Furthermore, not only are you creating one ID per thread, but if you use the uncommented id.remove() you even create one per call! Sorry, but this is bloat, it slows down your code and creates lots of unnecessary objects. I do not think this is wise.
Update:
I forgot to explain the reason for the endless recursion: Your advice calls ThreadLocal.get(), assuming it could be null. Actually it cannot be because if the value has not been initialised, get() does so by utilising initialValue(). Even if you manually call remove(), the next time you call get() it will again initialise the value again and so forth. This is documented behaviour:
Returns the value in the current thread's copy of this thread-local variable. If the variable has no value for the current thread, it is first initialized to the value returned by an invocation of the initialValue() method.
So what happens, step by step?
A method is called.
Your around advice kicks in.
You call id.get() from the advice.
ThreadLocal.get() checks if a value is set, notices that there is none and calls your overridden initialValue() method.
Because the overridden initialValue() method is captured by your match-all pointcut execution(* *(..)), again your advice kicks in before the initial value has been set. The end result is that the loop starts again and so forth - endless recursion, quod erat demonstrandum.
So actually your problem boils down to calling get() on an uninitialised ThreadLocal subclass from an advice while simultaneously targetting its user-defined initialValue() method with the same advice. This is what creates the endless recursion and ultimately makes your stack overflow.
My recommendation is to exclude your aspect from the pointcut, see example pointcuts above. You should also get rid of the null check for the ThreadLocal value because it is superfluous. Last but not least, I assume you want one ThreadLocal value per thread and not one per method call. So you can do without any set() or remove() calls altogether.
Modified driver class, creating an additional thread:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
#Override
public void run() {
System.out.println(bar(foo()));
}
}).start();
Thread.sleep(200);
}
public static String bar(String text) {
return text + "bar";
}
private static String foo() {
return "foo";
}
}
Improved aspect:
package de.scrum_master.aspect;
import java.util.UUID;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class DemoAspect {
private static ThreadLocal<UUID> id = new ThreadLocal<UUID>() {
#Override
protected UUID initialValue() {
return UUID.randomUUID();
}
};
#Pointcut("execution(* *(..)) && !within(DemoAspect)")
public void logging() {}
#Around("logging()")
public Object tracing(ProceedingJoinPoint thisJoinPoint) throws Throwable {
Signature methodSignature = thisJoinPoint.getSignature();
System.out.println(
"Thread " + Thread.currentThread().getId() +
"[" + id.get() +
"] >>> " + methodSignature
);
Object result = thisJoinPoint.proceed();
System.out.println(
"Thread " + Thread.currentThread().getId() +
"[" + id.get() +
"] <<< " + methodSignature
);
return result;
}
}
Console output:
Thread 1[549d0856-0a92-4031-9331-a1317d6a43c4] >>> void de.scrum_master.app.Application.main(String[])
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] >>> void de.scrum_master.app.Application.1.run()
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] >>> String de.scrum_master.app.Application.access$0()
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] >>> String de.scrum_master.app.Application.foo()
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] <<< String de.scrum_master.app.Application.foo()
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] <<< String de.scrum_master.app.Application.access$0()
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] >>> String de.scrum_master.app.Application.bar(String)
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] <<< String de.scrum_master.app.Application.bar(String)
foobar
Thread 9[32c8444c-0f1f-4023-9b97-69d5beda3b4c] <<< void de.scrum_master.app.Application.1.run()
Thread 1[549d0856-0a92-4031-9331-a1317d6a43c4] <<< void de.scrum_master.app.Application.main(String[])
As you can see, threads already have unique IDs, so maybe you want consider implementing your aspect without any UUIDs altogether.

AspectJ annotation on field that triggers #Before advice

I have already written AspectJ aspects that perform #Around advice triggered by method annotations. Now I want to do the same, but where fields are annotated instead of methods. So with each method invocation of the class below, it must set the accountSummary field to the correct implementation. Is there a way to accomplish this? I presume using #Before advice would be the best way of going about it. Using CDI is not an option - the solution must use AspectJ.
public class PoolableBusinessLogic {
#InjectServiceClientAdapter(legacy=LegacyAccountSummary.class,new=NewAccountSummary.class)
private AccountSummary accountSummary;
public void foo() {
// use correct accountSummary impl, decided in #Before code
}
public void bar() {
// use correct accountSummary impl, decided in #Before code
}
}
I am not sure what exactly you want to achieve, so I am presenting two alternative solutions.
First, let us create some application classes in order to have a fully compileable sample:
package de.scrum_master.app;
public interface AccountSummary {
void doSomething();
}
package de.scrum_master.app;
public class LegacyAccountSummary implements AccountSummary {
#Override
public void doSomething() {
System.out.println("I am " + this);
}
}
package de.scrum_master.app;
public class NewAccountSummary implements AccountSummary {
#Override
public void doSomething() {
System.out.println("I am " + this);
}
}
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface InjectServiceClientAdapter {
Class<?> legacyImpl();
Class<?> newImpl();
}
package de.scrum_master.app;
public class PoolableBusinessLogic {
#InjectServiceClientAdapter(legacyImpl = LegacyAccountSummary.class, newImpl = NewAccountSummary.class)
private AccountSummary accountSummary;
public void foo() {
accountSummary.doSomething();
}
public void bar() {
System.out.println("Account summary is " + accountSummary);
}
}
Now we need an entry point:
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
PoolableBusinessLogic businessLogic = new PoolableBusinessLogic();
businessLogic.foo();
businessLogic.bar();
System.out.println();
}
}
}
Obviously this yields an error because the member accountSummary has not been initialised:
Exception in thread "main" java.lang.NullPointerException
at de.scrum_master.app.PoolableBusinessLogic.foo(PoolableBusinessLogic.java:8)
at de.scrum_master.app.Application.main(Application.java:7)
Now we have two options, depending on what you want to achieve:
Option A: dynamic injection
Scenario: For each field access (even in the same PoolableBusinessLogic instance) decide dynamically what type of object instance to return. Here in this example I will just be randomising in order to simulate another if-else criterion.
BTW, I hope it is okay that I use the more expressive native AspectJ syntax. You can easily convert the aspect to annotation style.
package de.scrum_master.aspect;
import java.util.Random;
import org.aspectj.lang.SoftException;
import de.scrum_master.app.InjectServiceClientAdapter;
public aspect DynamicInjectionAspect {
private static final Random RANDOM = new Random();
Object around(InjectServiceClientAdapter adapterAnn) :
get(* *) && #annotation(adapterAnn)
{
try {
Class<?> implClass = RANDOM.nextBoolean() ? adapterAnn.legacyImpl() : adapterAnn.newImpl();
return implClass.newInstance();
} catch (Exception e) {
throw new SoftException(e);
}
}
}
This yields the following output:
I am de.scrum_master.app.LegacyAccountSummary#4d9cfefb
Account summary is de.scrum_master.app.NewAccountSummary#7e28388b
I am de.scrum_master.app.NewAccountSummary#2986e62
Account summary is de.scrum_master.app.LegacyAccountSummary#6576e542
I am de.scrum_master.app.NewAccountSummary#60c58418
Account summary is de.scrum_master.app.LegacyAccountSummary#4763754a
I am de.scrum_master.app.NewAccountSummary#52a971e3
Account summary is de.scrum_master.app.NewAccountSummary#7274187a
I am de.scrum_master.app.LegacyAccountSummary#23f32c4a
Account summary is de.scrum_master.app.LegacyAccountSummary#31e0c0b6
As you can see, within each of the five output groups (i.e. for each PoolableBusinessLogic instance) there are different account summary object IDs and sometimes (not always) even different class names.
Option B: static injection
Scenario: Per PoolableBusinessLogic instance decide dynamically what type of object instance to statically assign to the annotated member if its value is null. After that, do not overwrite the member anymore but return the previously initialised value. Again I will just be randomising in order to simulate another if-else criterion.
Attention: Do not forget to deactivate the first aspect, e.g. by prepending if(false) && to its pointcut. Otherwise the two aspects will be conflicting with each other.
package de.scrum_master.aspect;
import java.lang.reflect.Field;
import java.util.Random;
import org.aspectj.lang.SoftException;
import de.scrum_master.app.InjectServiceClientAdapter;
public aspect StaticInjectionAspect {
private static final Random RANDOM = new Random();
before(InjectServiceClientAdapter adapterAnn, Object targetObj) :
get(* *) && #annotation(adapterAnn) && target(targetObj)
{
try {
Field field = targetObj.getClass().getDeclaredField(thisJoinPoint.getSignature().getName());
field.setAccessible(true);
if (field.get(targetObj) != null)
return;
Class<?> implClass = RANDOM.nextBoolean() ? adapterAnn.legacyImpl() : adapterAnn.newImpl();
field.set(targetObj,implClass.newInstance());
} catch (Exception e) {
throw new SoftException(e);
}
}
}
This is a bit uglier because it involves using reflection for finding the member field. Because it might be (and in our example really is) private we need to make it accessible before doing anything with it.
This yields the following output:
I am de.scrum_master.app.NewAccountSummary#20d1fa4
Account summary is de.scrum_master.app.NewAccountSummary#20d1fa4
I am de.scrum_master.app.NewAccountSummary#2b984909
Account summary is de.scrum_master.app.NewAccountSummary#2b984909
I am de.scrum_master.app.LegacyAccountSummary#1ae3043b
Account summary is de.scrum_master.app.LegacyAccountSummary#1ae3043b
I am de.scrum_master.app.LegacyAccountSummary#2e2acb47
Account summary is de.scrum_master.app.LegacyAccountSummary#2e2acb47
I am de.scrum_master.app.LegacyAccountSummary#7b87b9fe
Account summary is de.scrum_master.app.LegacyAccountSummary#7b87b9fe
Now the output looks different: Within each of the five output groups (i.e. for each PoolableBusinessLogic instance) both output lines show exactly the same object ID.
For Option A: dynamic injection in kriegaex's answer, the annotation-style aspect will look like this:
#Aspect
public class InjectServiceClientAdapterAspect {
#Pointcut("get(* *) && #annotation(injectAnnotation)")
public void getServiceClientAdapter(InjectServiceClientAdapter injectAnnotation) {
}
#Around("getServiceClientAdapter(injectAnnotation)")
public Object injectServiceClientAdapter(final ProceedingJoinPoint joinPoint, final InjectServiceClientAdapter injectAnnotation) {
// injection code goes here
}

AspectJ pointcut for constructor using java.lang.reflection

The following example is a reduction of the real problem in that it tries to simplify is as much as possible.
I have a java interface, and several objects that implement that interface, like:
public interface Shape{
public void draw();
public void erase();
public boolean isDrawn();
}
public class Square implements Shape{
#Override
public void draw(){
//TODO: method implementation
}
#Override
public void erase(){
//TODO: method implementation
}
Override
public boolean isDrawn(){
//TODO: method implementation
return false;
}
}
public Triangle implements Shape{
//same as above
}
public Circle implements Shape{
//same as above
}
This is the structure of my program. By using AspectJ I want to have a map that holds each object that implements the interface. To do so I was trying to capture the constructors by using the following aspect:
public aspect ShapeHolderAspect{
private Map<Integer, Shape> map = new HashMap<>();
private int count = 0;
pointcut shapeInit(): call((Shape+).new(..));
Object around(): shapeInit() {
System.out.println("capturing new");
Shape shapeType = (Shape)proceed();
map.put(++count, shapeType);
return shapeType;
}
}
This code will work if I create a Shape using the following scenario:
public static void main(String[] args){
Shape myShape = new Circle();
}
However, I am using java language reflection, and so technically I don't call the "new" constructor. Instead I locate the path of the package, and create the object passing a string with the name of the class:
public static void main(String[] args){
String shapeClassName = args[0];
Class<?> classType = Class.forName("myPackage.figures" + "." + shapeClassName);
Shape myShape =(Shape)classType.getConstructor().newInstance();
}
By doing this way, AspectJ cannot detect that I am creating shapes. How do I fix this?
New, better version:
Well, while the old version below actually catches all constructor executions, an around advice on constructor execution returns null because the object in question has not been initialised yet. So you would end up with a map of null pointers in your aspect. In order to fix this you need to bind this() to a variable (sample code uses default package name):
public class Application {
public static void main(String[] args) throws Exception {
new Circle().draw();
((Shape) Class.forName("Triangle").getConstructor().newInstance()).isDrawn();
((Shape) Class.forName("Square").getConstructor().newInstance()).erase();
}
}
import java.util.HashMap;
import java.util.Map;
public aspect ShapeHolderAspect {
private Map<Integer, Shape> map = new HashMap<Integer, Shape>();
private int count = 0;
after(Shape shape): execution(Shape+.new(..)) && this(shape) {
System.out.println(thisJoinPointStaticPart);
map.put(++count, shape);
}
after() : execution(* Application.main(..)) {
System.out.println("\nList of shapes:");
for (int key : map.keySet())
System.out.println(" " + key + " -> " + map.get(key));
}
}
The output looks like this:
initialization(Circle())
initialization(Triangle())
initialization(Square())
List of shapes:
1 -> Circle#1a2961b
2 -> Triangle#12d03f9
3 -> Square#5ffb18
BTW, if you absolutely need an around advice because you want to do other things before and after object creation, it would look like this:
void around(Shape shape): execution(Shape+.new(..)) && this(shape) {
System.out.println(thisJoinPointStaticPart);
proceed(shape);
map.put(++count, shape);
}
Old, incomplete version:
Quite simply, just intercept constructor execution instead of call:
pointcut shapeInit(): execution(Shape+.new(..));
This way you weave into the called code (callee), not the calling code (caller). Consequently, it does not matter if the caller issues a reflective or normal call.
Found that the following pointcut will do the job:
pointcut lockReflectInit(): call(public Object java.lang.reflect.Constructor.newInstance(..));
This will however catch ALL calls of newInstance, and not just the ones that return Shape =(

Method parameter annotations access

It took me a while to figure out that I was not making a mistake in annotating my method parameters.
But I am still not sure why, in the following code example, the way no. 1 does not work:
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
public class AnnotationTest {
#Retention(RetentionPolicy.RUNTIME)
#interface MyAnnotation {
String name() default "";
}
public void myMethod(#MyAnnotation(name = "test") String st) {
}
public static void main(String[] args) throws NoSuchMethodException, SecurityException {
Class<AnnotationTest> clazz = AnnotationTest.class;
Method method = clazz.getMethod("myMethod", String.class);
/* Way no. 1 does not work*/
Class<?> c1 = method.getParameterTypes()[0];
MyAnnotation myAnnotation = c1.getAnnotation(MyAnnotation.class);
System.out.println("1) " + method.getName() + ":" + myAnnotation);
/* Way no. 2 works */
Annotation[][] paramAnnotations = method.getParameterAnnotations();
System.out.println("2) " + method.getName() + ":" + paramAnnotations[0][0]);
}
}
Output:
1) myMethod:null
2) myMethod:#AnnotationTest$MyAnnotation(name=test)
Is it just a flaw in the annotation imnplementation in Java?
Or is there a logical reason why the class array returned by Method.getParameterTypes() does not hold the parameter annotations?
This is not a flaw in the implementation.
A call to Method#getParameterTypes() returns an array of the parameters' types, meaning their classes. When you get the annotation of that class, you are getting the annotation of String rather than of the method parameter itself, and String has no annotations (view source).

Categories

Resources