1

Is it possible to intercept object method call and modify those object properties at that moment?

What I have so far

@Pointcut("execution(* java.net.HttpURLConnection.setRequestProperty(..))")
public void connectMethodCall() {
}

@Around("connectMethodCall()")
public Object onGetUrlConnection(ProceedingJoinPoint pjp) throws Throwable {
    HttpURLConnection connection = (HttpURLConnection) pjp.proceed();
    connection.setRequestProperty("header key", "header value");
    return pjp.proceed();
}

I want to at this example set connection headers and return the object to execution point. Weaving is done at compile time. I try to log headers after this but there are no headers that I have set in @Around advice. No errors are thrown either.

ddog
  • 670
  • 1
  • 10
  • 25
  • Can you please refer to [this question](https://stackoverflow.com/questions/14017860/how-to-change-the-return-value-by-spring-aop), it might be similar to your case. – Shankar kota Dec 26 '18 at 07:54
  • Ok, so I need to use @Around advice, but I am not sure how to modify the object from the pointcut or am I using a valid pointcut declaration at all? – ddog Dec 26 '18 at 08:07
  • I can confirm that an "around" advice is what you want to use. You can do two things first: read the AspectJ documentation and try to do as described there, it will answer your question. Then if you still have problems, just edit your question, showing us the code of your full [MCVE](http://stackoverflow.com/help/mcve), i.e. full aspect and target classes with package names, exact pointcuts etc. Then you describe what is not working (error message?) and what kind of weaving you use (load-time, compile-time)? Then I will be glad to help, but I am not doing the whole job for you. – kriegaex Dec 26 '18 at 09:05
  • @kriegaex I don't expect you to do my job, I will modify the question with what I have managed to do so far – ddog Dec 26 '18 at 09:12
  • Thanks, it is getting clearer already, but I asked for more information (MCVE, I even listed a few thing), please read my previous comment again. Thank you very much. – kriegaex Dec 26 '18 at 11:57

2 Answers2

1

The answer to your follow-up question about how to get hold of an instance of the target object is simple, if I understand the question correctly: Just use the target() parameter binding. A quick look into the AspectJ documentation would have showed you that, e.g. the part about pointcut parameters. I do believe it is much easier and less time-consuming (also with regard of having to wait for answers on SO) than asking questions here. But anyway, this is a place where developers help each other. So here we go:

Disregarding the fact that your MVCE sample code does not do anything meaningful with the Google API, let's just add one line of diagnostic output in order to verify that the aspect actually did add a request parameter:

// (...)
      urlConnection.connect();

      // Just in order to check if the property has indeed been set in the aspect
      System.out.println(urlConnection.getRequestProperty("From"));

      OutputStream outputStream = urlConnection.getOutputStream();
// (...)

Then use this aspect:

package de.scrum_master.aspect;

import java.net.HttpURLConnection;

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 MyAspect {
  @Pointcut("call(* java.net.HttpURLConnection.connect()) && target(connection)")
  public void connectMethodCall(HttpURLConnection connection) {}

  @Around("connectMethodCall(connection)")
  public Object onGetUrlConnection(ProceedingJoinPoint pjp, HttpURLConnection connection) throws Throwable {
    connection.setRequestProperty("From", "user@example.com");
    return pjp.proceed();
  }
}

Or a little bit more compact, if you do not need the poinctut to be re-useable because you only use it in one advice:

@Aspect
public class MyAspect {
  @Around("call(* java.net.HttpURLConnection.connect()) && target(connection)")
  public Object onGetUrlConnection(ProceedingJoinPoint pjp, HttpURLConnection connection) throws Throwable {
    connection.setRequestProperty("From", "user@example.com");
    return pjp.proceed();
  }
}

The console log would be:

user@example.com
false : 405
kriegaex
  • 63,017
  • 15
  • 111
  • 202
0

I have managed to do this like this

@Pointcut("call(* java.net.URL.openConnection())")
public void connectMethodCall() {
}

@Around("connectMethodCall()")
public Object onGetUrlConnection(ProceedingJoinPoint pjp) throws Throwable {
    HttpURLConnection connection = (HttpURLConnection) pjp.proceed();
    connection.setRequestProperty("From", "user@example.com");
    return connection;
}

and use it here to set headers

public static void main(String[] args) {
    HttpURLConnection urlConnection;
    String result;
    try {
        urlConnection = (HttpURLConnection) (new URL("http://www.google.com").openConnection());
        urlConnection.setDoOutput(true);
        urlConnection.setRequestProperty("Content-Type", "application/json");
        urlConnection.setRequestProperty("Accept", "application/json");
        urlConnection.setRequestMethod("POST");
        urlConnection.setConnectTimeout(10000);
        urlConnection.connect();
        OutputStream outputStream = urlConnection.getOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
        writer.write("test");
        writer.close();
        outputStream.close();
        int responseCode = urlConnection.getResponseCode();
        if (responseCode == HttpsURLConnection.HTTP_OK) {
            //Read
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StandardCharsets.UTF_8));

            String line;
            StringBuilder sb = new StringBuilder();

            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line);
            }

            bufferedReader.close();
            result = sb.toString();

        } else {
            result = "false : " + responseCode;
        }
        System.out.println(result);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

The issue with code in question was that I have intercepted method call that returns void and pjp.proceed() actually returns null in that case. (still not sure if there is a way to dig out a calling object from void method pointcut?). Ideally i would intercept urlConnection.connect(); and get the urlConnection object from pointcut. Is there a way to do that?

ddog
  • 670
  • 1
  • 10
  • 25
  • Not just that, you also called `proceed()` twice, which probably was not your intention. Furthermore you used an `execution()` pointcut on a JDK class which also does not work. Here you use `call()`, which helps at least in cases where your own code calls the method. – kriegaex Dec 27 '18 at 00:22
  • And as for your question: You get a calling object with `call()` + parameter binding. What you want, though, is the called object (which is only the same if the calling class is the same as the called one), i.e. `target()` + parameter binding. If you just want that without manipulating the method's return value, `@Before` or `@After` are fine, you do not even need `@Around`. But as I said, I wanted to see your [MCVE](http://stackoverflow.com/help/mcve) in order to properly answer those questions. You just refused to provide one, thus you had to answer your own question, at least part of it. – kriegaex Dec 27 '18 at 00:28
  • Yes, it does, thanks, but you posted it **after** your own answer and I think my last comment already answered your follow-up question. Is there any open issue the MCVE can still help answer now? – kriegaex Dec 28 '18 at 00:36
  • @kriegaex I am having trouble with providing state, somehow I need to know what instance of intercepted object am I working with. Is there a way to add an id to the targeted class so it can provide a unique identifier? – ddog Jan 17 '19 at 08:24