1

I have multiple interfaces each defining multiple methods as below:

public interface X {
    void methodX1;

    void methodX2(String s);
}

public interface Y {
    void methodY1;

    void methodY2(int i);

    void methodY3(SomeType s);
}

....

Currently the method implementation looks like this:

public class XImpl implements X {
        public void methodX1() {
            // method implementation
        }
    }

For every implementation of the interface, I need to wrap the method implementation with a try-with-resource block as below:

public class XImpl implements X {
    public void methodX1() {
        try (SomeResource res = new SomeResource()) {
            // method implementation
        }
    }
}

With my limited notion of AOP, I believe we can do things before and after the JoinPoint i.e. method in this case, but how can we wrap implementation as above? I am looking to see if it can be done using annotations or lambda, i.e. something where I do not have to go changing each method individually.

Any thought on how this can be done will be greatly appreciated.

techjourneyman
  • 1,701
  • 3
  • 33
  • 53
  • Why not use an AOP library, eg AspectJ? – Bohemian Dec 13 '18 at 22:07
  • If you have many concrete classes implementing X you can write a single proxy class to add that aspect -- but that will still require an implementation for each method on the interface. You could also roll your own AOP using java.lang.reflect.Proxy, which would not require any repetition. Or as @Bohemian suggests, use an existing AOP library. – tgdavies Dec 13 '18 at 22:16
  • It'll be dependent on the architecture used: container-based, compiler-time resolution / runtime resolution... Thinking about it, you won't have to change each method but you'll still have to add the annotation to each method – Perdi Estaquel Dec 13 '18 at 23:55

2 Answers2

2

With my limited notion of AOP, I believe we can do things before and after the JoinPoint i.e. method in this case, but how can we wrap implementation as above?

Have you ever read the very good Spring AOP manual? One of the first things you would notice would be the explanation of advice types and that there is not only before and after, but also around advice. This is what you want to use.

Here is basically how it works:

Java helper classes:

package de.scrum_master.app;

public class SomeType {}
package de.scrum_master.app;

public class SomeResource implements AutoCloseable {
  @Override public void close() throws Exception {}
}

Interfaces:

package de.scrum_master.app;

public interface X {
  void methodX1();
  int methodX2(String s);
}
package de.scrum_master.app;

public interface Y {
  void methodY1();
  String methodY2(int i);
  void methodY3(SomeType s);
}

Interface implementation + driver application:

I implemented the example in AspectJ, not in Spring AOP. Thus you see no application context, but you know how to do that, don't you?

package de.scrum_master.app;

import org.springframework.stereotype.Component;

@Component
public class MyImpl implements X, Y {
  @Override public void methodY1() { System.out.println("Y1"); methodX2("bar"); }
  @Override public String methodY2(int i) { System.out.println("Y2"); return "dummy"; }
  @Override public void methodY3(SomeType s) { System.out.println("Y3"); }
  @Override public void methodX1() { System.out.println("X1"); methodY1(); }
  @Override public int methodX2(String s) {  System.out.println("X2"); return 42; }

  public static void main(String[] args) {
    MyImpl myImpl = new MyImpl();
    myImpl.methodX1();
    myImpl.methodX2("foo");
    myImpl.methodY1();
    myImpl.methodY2(11);
    myImpl.methodY3(new SomeType());
  }
}

Please note that both methodX1() and methodY1() call methods internally. This will be important with regard to the difference between Spring AOP and AspectJ later.

Aspect:

In Spring AOP you can omit the execution(* *(..)) && part, I just use it here in order to avoid other joinpoints such as call() being intercepted and the log to get bloated. As Spring AOP does not know much else than execution() it is not necessary there. The parentheses around the ... || ... block of the pointcut can also go away.

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

import de.scrum_master.app.SomeResource;

@Component
@Aspect
public class WrapMethodsAspect {
  @Around("execution(* *(..)) && (within(de.scrum_master.app.X+) || within(de.scrum_master.app.Y+))")
  public Object wrapperAdvice(ProceedingJoinPoint thisJoinPoint) throws Throwable {
    System.out.println("Wrapping " + thisJoinPoint);
    try (SomeResource res = new SomeResource()) {
      return thisJoinPoint.proceed();
    }
    finally {
      System.out.println("Unwrapping " + thisJoinPoint);
    }
  }
}

Console output with AspectJ:

Wrapping execution(void de.scrum_master.app.MyImpl.main(String[]))
Wrapping execution(void de.scrum_master.app.MyImpl.methodX1())
X1
Wrapping execution(void de.scrum_master.app.MyImpl.methodY1())
Y1
Wrapping execution(int de.scrum_master.app.MyImpl.methodX2(String))
X2
Unwrapping execution(int de.scrum_master.app.MyImpl.methodX2(String))
Unwrapping execution(void de.scrum_master.app.MyImpl.methodY1())
Unwrapping execution(void de.scrum_master.app.MyImpl.methodX1())
Wrapping execution(int de.scrum_master.app.MyImpl.methodX2(String))
X2
Unwrapping execution(int de.scrum_master.app.MyImpl.methodX2(String))
Wrapping execution(void de.scrum_master.app.MyImpl.methodY1())
Y1
Wrapping execution(int de.scrum_master.app.MyImpl.methodX2(String))
X2
Unwrapping execution(int de.scrum_master.app.MyImpl.methodX2(String))
Unwrapping execution(void de.scrum_master.app.MyImpl.methodY1())
Wrapping execution(String de.scrum_master.app.MyImpl.methodY2(int))
Y2
Unwrapping execution(String de.scrum_master.app.MyImpl.methodY2(int))
Wrapping execution(void de.scrum_master.app.MyImpl.methodY3(SomeType))
Y3
Unwrapping execution(void de.scrum_master.app.MyImpl.methodY3(SomeType))
Unwrapping execution(void de.scrum_master.app.MyImpl.main(String[]))

Two things you notice here:

  • The execution of the static main(..) method is logged. This would not happen with Spring AOP.
  • The internal method calls are logged. This would also not happen with Spring AOP.

Console output with Spring AOP:

Wrapping execution(void de.scrum_master.app.MyImpl.methodX1())
X1
Unwrapping execution(void de.scrum_master.app.MyImpl.methodX1())
Wrapping execution(int de.scrum_master.app.MyImpl.methodX2(String))
X2
Unwrapping execution(int de.scrum_master.app.MyImpl.methodX2(String))
Wrapping execution(void de.scrum_master.app.MyImpl.methodY1())
Y1
Unwrapping execution(void de.scrum_master.app.MyImpl.methodY1())
Wrapping execution(String de.scrum_master.app.MyImpl.methodY2(int))
Y2
Unwrapping execution(String de.scrum_master.app.MyImpl.methodY2(int))
Wrapping execution(void de.scrum_master.app.MyImpl.methodY3(SomeType))
Y3
Unwrapping execution(void de.scrum_master.app.MyImpl.methodY3(SomeType))

In most cases, Spring AOP is enough for Spring users. But if you need a more powerful approach in order to capture other types of pointcuts or e.g. internal, nested method calls, you would use AspectJ via load-time weaving (LTW).

kriegaex
  • 63,017
  • 15
  • 111
  • 202
0

Perhaps something like this:

public abstract class ResourceProcessingService {
    protected <T> T processResource(Resource resource, Function<Reader, T> function) {
        try (InputStream fileInputStream = resource.getInputStream()) {
            Reader reader = new BufferedReader(new InputStreamReader(fileInputStream, StandardCharsets.UTF_8));
            return function.apply(reader);
        } catch (IOException e) {
            throw new YourRuntimeException("Could not process resource: " + resource.getFilename() + ", " + e.getMessage());
        }
    }
}

In your concrete implementations you could now do something like this:

public class XImpl extends ResourceProcessingService implements X {

    public double performSomeResourceProcessing() {
        return processResource(yourResource, (reader) -> readTheResource(reader));
    }

    private double readTheResource(Reader reader) {
        // perform the resource reading
    }
}
Jonck van der Kogel
  • 2,983
  • 3
  • 23
  • 30