ddog
ddog

Reputation: 670

Use AspectJ to change object properties

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.

Upvotes: 1

Views: 1421

Answers (2)

kriegaex
kriegaex

Reputation: 67297

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", "[email protected]");
    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", "[email protected]");
    return pjp.proceed();
  }
}

The console log would be:

[email protected]
false : 405

Upvotes: 1

ddog
ddog

Reputation: 670

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", "[email protected]");
    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?

Upvotes: 0

Related Questions