AspectJ annotation on field that triggers #Before advice - java

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
}

Related

JUnit - test that a method has been called

I'm new to unit testing, and I'm trying to test that a method has been called. The method in question doesn't return anything.
public void example (boolean foo) {
if (foo) {
processFoo(foo);
}
else if (foo==false) {
processSomethingElse(foo);
}
}
I want to be able to test that the processFoo method is being called, but I don't know how to do that.
If mocking is required, then I have to use JMockit. Thanks!
Sorry I'm a little late to the party, but I have a couple of ideas for you.
First, you mention that one option is to use JMockit--that's great as it gives you a lot of flexibility. If you use JMockit, then the visibility of your processFoo() method doesn't much matter. Let's see what that might look like:
public class Subject {
public void example (boolean foo) {
if (foo) {
processFoo(foo);
}
else if (foo==false) {
processSomethingElse(foo);
}
}
private void processFoo(boolean b) {
System.out.println("b = " + b);
}
private void processSomethingElse(boolean bb) {
System.out.println("bb = " + bb);
}
}
So, one caveat with this option, though is that I'm going to assume processFoo() is a method on your test subject and I'm going to use a partial mock to change the test subject--not something I really like to do, but this is an example. In general, it is best to only mock the dependencies of your test subject rather than behavior of the test subject itself--you have been advised! Note that the processFoo() method of the test subject is private. I'm going to substitute a method for the test with JMockit's partial mocking and the visibility of that new method does not have to match the original.
import static org.assertj.core.api.Assertions.assertThat;
import mockit.Mock;
import mockit.MockUp;
import mockit.integration.junit4.JMockit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
#RunWith(JMockit.class)
public class SubjectTest {
private Subject testSubject = new Subject();
private boolean processFooCalled = false;
#Before
public void setup() {
new MockUp<Subject>() {
#Mock
public void processFoo(boolean b) {
processFooCalled = true;
};
};
}
#Test
public void should_call_processFoo() {
testSubject.example(true);
assertThat(processFooCalled).isTrue();
}
#Test
public void should_not_call_processFoo() {
testSubject.example(false);
assertThat(processFooCalled).isFalse();
}
}
Ok, so that was the first option. It's actually a little easier if you forget JMockit for this one, assuming you are able to subclass your test subject and override the processFoo() method:
public class Subject {
public void example (boolean foo) {
if (foo) {
processFoo(foo);
}
else if (foo==false) {
processSomethingElse(foo);
}
}
protected void processFoo(boolean b) { // NOTE: protected access here!
System.out.println("b = " + b);
}
private void processSomethingElse(boolean bb) {
System.out.println("bb = " + bb);
}
}
So, in this case, the strategy is simply to subclass your test subject and replace the implementation of the method you wish to observe being called. It might look like this:
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
public class SubjectTest2 {
private Subject testSubject = new TestableSubject();
private boolean processFooCalled = false;
#Test
public void should_call_processFoo() {
testSubject.example(true);
assertThat(processFooCalled).isTrue();
}
#Test
public void should_not_call_processFoo() {
testSubject.example(false);
assertThat(processFooCalled).isFalse();
}
class TestableSubject extends Subject {
#Override
protected void processFoo(boolean b) {
processFooCalled = true;
}
}
}
Give it a whirl. Hope it helps!
You could use a counter variable in your class and increment it whenever the method is called, or use a print statement. If you don't have access to the processFoo method, a simple approach would be to do this at the time that processFoo is called in another method, if that's the only place where it can possibly be called.
For example:
public static int processFooCalls = 0;
// ...
public void example (boolean foo) {
if (foo) {
processFoo(foo);
processFooCalls += 1;
// and/or
System.out.println("processFoo method was called");
}
// ...
}
public static void main (String[] args) {
// main routine here...
System.out.println("'processFoo' was called " + processFooCalls + " times.");
}
If processFoo can be called elsewhere, and you need to consider this possibility as well, then you'll need to have access to the processFoo code in order to do this, e.g.:
void processFoo( boolean b ) {
// increment number of times processFoo was called here, and/or print, as follows
processFooCalls += 1;
System.out.println("called processFoo method!");
/* some functionality */
}
Looking at the JMockit documentation, you will need the following tools:
Static Mocking: http://jmockit.github.io/tutorial/BehaviorBasedTesting.html#staticPartial
Invocation Counts: http://jmockit.github.io/tutorial/BehaviorBasedTesting.html#constraints
Combining the two in a test (my syntax may be a little off since I'm more accustomed to Mockito, but the concept should hold):
#Test
public void someTestMethod(#Mocked({"processFoo"}) final ExampleClass exampleclass)
{
new Expectations() {{
exampleclass.processFoo(); times = 1;
}};
exampleclass.example(true);
}
This should mock the processFoo method, leaving everything else intact, and checks to make sure it is called exactly once.
Don't consider doing any kind of partial mocking for this, all you're doing in that case is ensuring that if you want to refactor your code your tests will fail. There is a mantra in unit testing - "never test private methods".
What you should be doing is testing that the method you call conforms to the behaviour you want to see. In this case what happens when foo is true is what's important, not that it calls processFoo. So if foo is true you want to be testing that the action processFoo carries out is true and nothing else.

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

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]

Allowing object construction only from some packages

I work on a game-like system. Users can submit .class and .java files for customized behaviour. Some objects are delivered to the user via callback, but if the user can construct these object himself (with custom parameters), it would mean an advantage to him. I will disallow reflection for the user and seal my packages. I can get this working if I abandon all package structure (and make the constructors package-private), but I would like not to do so.
Here is an example:
sscce.mycode.a.SomeClass.java:
package sscce.mycode.a;
import sscce.mycode.b.RestrictedObject;
import sscce.usercode.SomeUserClass;
public class SomeClass {
public static void main(String[] args) {
SomeUserClass userClass=new SomeUserClass();
// If I can create it from here, anyone can...
RestrictedObject object=new RestrictedObject();
userClass.someMethod(object);
}
}
sscce.mycode.b.Interface.java:
package sscce.mycode.b;
public interface Interface {
public void someMethod(RestrictedObject restrictedObject);
}
sscce.mycode.b.RestrictedObject.java:
package sscce.mycode.b;
public class RestrictedObject {
public RestrictedObject() {}
}
sscce.usercode.SomeUserClass.java:
package sscce.usercode;
import sscce.mycode.b.Interface;
import sscce.mycode.b.RestrictedObject;
public class SomeUserClass implements Interface {
#Override
public void someMethod(RestrictedObject restrictedObject) {
// It receives an instance, but cannot create it.
System.out.println("Got "+restrictedObject);
}
}
Motivation: Having everything in one package sounds messy...
Does anyone have ideas on how to accomplish this without flattening the packages?
Thanks in advance for any solutions, ideas or comments, Till
You could do it the following way, however you should carefully consider if you really want to use this approach as it is very slow and quite frankly, bad practice.
I'll put it up anyway as to how you can do it:
public final class Secured {
private static final Set<Class<?>> allowedCallers = new HashSet<>();
static {
allowedCallers.add(Allowed.class);
}
private static final class SecurityManagerExtension extends SecurityManager {
private static final int OFFSET = 4;
#Override
protected Class<?>[] getClassContext() {
return super.getClassContext();
}
private Class<?> getCaller() {
try {
return getClassContext()[OFFSET];
} catch (ArrayIndexOutOfBoundsException e) {
return null;
}
}
}
private Secured() {
// protect against reflection attack
Class<?> caller = new SecurityManagerExtension().getCaller();
if (!this.getClass().equals(caller)) {
throw new IllegalStateException();
}
System.out.println("Secured instance constructed!");
}
public static Secured createInstance() {
// this gets the class name of the calling class
Class<?> caller = new SecurityManagerExtension().getCaller();
if (allowedCallers.contains(caller)) {
System.out.println("Created instance by '" + caller + "'!");
return new Secured();
} else {
System.out.println("No instance created because call was made by '" + caller + "'!");
return null;
}
}
}
Note the final keyword on the class to prevent subclassing. If you need to subclass the class yourself, move the final keyword to the factory method.
Also note that this is not protected against serialization attacks.

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 =(

AspectJ: Access private fields?

I want to use an aspect to add getter and setter for a private id field. I know how to add a method through an aspect, but how can I access the private id field?
I thoght that I just have to make the aspect provileged. I tried the following code, but the aspect cannot access the id field.
public privileged aspect MyAspect {
public String Item.getId(){
return this.id;
}
A possibility would be to user reflection like shown in this blog post: http://blog.m1key.me/2011/05/aop-aspectj-field-access-to-inejct.html
Is reflection the only possibility or is there a way to do it with AspectJ?
Are you sure you can't ? I just tested and it ran. Here's my full code:
package com.example;
public class ClassWithPrivate {
private String s = "myStr";
}
==========
package com.example.aspect;
import com.example.ClassWithPrivate;
privileged public aspect AccessPrivate {
public String ClassWithPrivate.getS() {
return this.s;
}
public void ClassWithPrivate.setS(String str) {
this.s = str;
}
}
==========
package com.example;
public class TestPrivate {
public static void main(String[] args) {
ClassWithPrivate test = new ClassWithPrivate();
System.out.println(test.getS());
test.setS("hello");
System.out.println(test.getS());
}
}
If for some reason, that does not work for you, you can use reflection, or another way as described here:
https://web.archive.org/web/20161215045930/http://blogs.vmware.com/vfabric/2012/04/using-aspectj-for-accessing-private-members-without-reflection.html
However, according to the benchmarks, it may not be worth it.

Categories

Resources