1

I'd like to decorate the interface PreparedStatement, in order to custom close it (just an example).

This means that I want to decorate an existing instance of PreparedStatement, thus, invoking other code, when close() is being invoked.

For that, I need to default implement all tens of methods of PreparedStatement decorator just to delegate the calls to the inner object, like done here. The downfall is that it's just a lot of work and code with little added value.

Another option is to try and use Java's Proxy and InvocationHandler in order to provide a default implementation that does the delegate for all the methods in a single method. If a custom method exists, the InvocationHandler, directs the call to it. See example here. The problem with this solution is that the custom method cannot be marked as @Override and its signature cannot be checked for correctness, as it will require an abstract PreparedStatement, which the Proxy will not be able to instantiate.

So, can this be done? How?

* Must be able to implement using Java 7 max, but feel free to provide Java 8 answers.

Community
  • 1
  • 1
AlikElzin-kilaka
  • 34,335
  • 35
  • 194
  • 277
  • What do you mean by custom closing, can you please explain it more because that's the main key to write the decorator. – PyThon Jun 27 '16 at 13:13
  • Can you use Mockito? – xiaofeng.li Jun 27 '16 at 13:13
  • You can achieve the functionality by using AOP. But one question, why do you need to decorate `PreparedStatement`? – Dimitri Jun 27 '16 at 13:14
  • `PreparedStatement` is just an example of an interface with many methods. – AlikElzin-kilaka Jun 27 '16 at 13:15
  • One possibility would be to use some kind of java dialect supporting it. Lombok is probably easiest to integrate with plain java https://projectlombok.org/features/Delegate.html – Artur Biesiadowski Jun 27 '16 at 13:22
  • "The problem with this solution is that the custom method cannot be marked as @Override and its signature cannot be checked for correctness, as it will require an abstract PreparedStatement, which the Proxy will not be able to instantiate." -- can you clarify what it is that you desire here? What do you mean by 'require an abstract PreparedStatement'? – obataku Jun 27 '16 at 13:38
  • @oldrinb - In order to provide a custom method, having the exact same signature as the original, `@Override` is required. This means that the a class needs to be defined and be set as abstract (in Java 7) in order not to define all the other methods. – AlikElzin-kilaka Jun 27 '16 at 17:38
  • no, `@Override` is never required--it is a hint to the compiler for your benefit, not a language mandate; just don't have your `InvocationHandler` delegate code implement `PreparedStatement` – obataku Jun 27 '16 at 19:42
  • @oldrinb - I meant required by me :) in order to validate that the signature stays the same. For example, someone could rename my custom `close` method to `close666`. Though evil intentions were might not be meant ;-) , the change is still evil. The new close method would never be invoked and no way to know it in compile time. – AlikElzin-kilaka Jun 28 '16 at 04:55
  • In this specific example, the correctness can be enforced by requiring the holder of the custom method to implement `AutoCloseable`. But there is no general solution for arbitrary methods, besides dropping the dynamic discovering. If the `InvocationHandler` knows which custom methods ought to be there and invokes them directly, the absence of a method will immediately raise a compiler error. – Holger Jun 28 '16 at 13:28

3 Answers3

0

As far as I understood you want to provide to the interface PreparedStatement concrete implementation. The only way I can think of is by creating abstract class that implements the interface. By doing so you don't need to implement all the methods from the interface and you'll have your desired implementation.

I'd try something like this:

public abstract class MyPreparedStatement implements PreparedStatement {

@Override
public void close() throws SQLException {
    System.out.println("Closing");
}

public static void main(String[] args) throws SQLException {
    Connection con = null;
    MyPreparedStatement statement = (MyPreparedStatement) con.prepareStatement("sql");
}
}
Ivo
  • 1,228
  • 1
  • 15
  • 33
  • 2
    I think you missed the `Decorator` notion. I don't control what instance of `PreparedStatement` I receive. I just control how to use it. You solution will result in ClassCastException. – AlikElzin-kilaka Jun 27 '16 at 13:28
  • yeah sorry, missed it indeed. – Ivo Jun 27 '16 at 14:04
0

Can you explain in clearer terms what the Proxy solution is lacking? Consider something like this, which relies on a AOP-esque 'hook':

final PreparedStatement original = ...;
final InvocationHandler delegator = new InvocationHandler() {

  void onClose() {
    /* do stuff */
  }

  Object invoke(final Object proxy, final Method method, final Object[] args) {
    if (method.getName().equals("close")) {
      onClose();
    }

    return method.invoke(original, args);
  }
};
final PreparedStatement wrapped = (PreparedStatement) Proxy.newProxyInstance(this.getClass().getClassLoader(),
    new Class<?>[] { PreparedStatement.class }, delegator);
obataku
  • 29,212
  • 3
  • 44
  • 57
  • Imagine that the discussed method receives several arguments. Now you'll need to call the custom method by reflection (in order to not convert the args manually), right? So, it means that in compile time, no one invokes the custom close method, right? So it means that someone can think that he can change the signature of the custom `close` method. My purpose is to keep the custom method tied to the actual interface `PreparedStatement`- preferably by `@Override`. Another reason to have the custom method tied to the interface is to have it found when doing searches. – AlikElzin-kilaka Jun 29 '16 at 05:17
  • @AlikElzin-kilaka then the obvious solution is to write a delegating `PreparedStatementDecorator` like you mentioned before; it accomplishes everything you want with minimal overhead – obataku Jun 29 '16 at 12:16
-1

If you don't have access to the methods in order to do the usual inheritance thing with them, you can accomplish what you are attempting to do with Aspect Oriented Programming, leveraging AspectJ or the Spring Framework aspect functionality to provide advice on your desired methods.

A simple aspect basically comes down to:

@Aspect
public class MyAspect {

    @Pointcut("execution(* *(..))") //Replace expression with target method; this example 
    //will hit literally every method ever.
    public void targetmethod() {}; //Intentionally blank.
    //AspectJ uses byte code manipulation (or "black magic voodoo", if you 
    // will) to make this method a surrogate for any real one that matches the pointcut

    @Before("targetmethod()") //Or @After, or @Around, etc...
    public void doStuff() throws Throwable {
        //Put your code here
    }
}

Once you have your aspects together, add them to your aop.xml and weave your aspects (you can do this at compile time with appropriate build manager configuration, or at run time by running aspectjweaver with java -javaagent:/path/to/aspectjweaver.jar).

This does come with a disclaimer however: doing things like this to java.* classes allows you break things in new and interesting ways with all the side-effects you're introducing (in fact, AspectJWeaver refuses to weave into java.* classes by default, though you can override that setting). Be very aware of what you are doing, and use your aspects and aspected methods wisely.

Brandon McKenzie
  • 1,655
  • 11
  • 26
  • Where's the exact signature of the close method with `@Override`? – AlikElzin-kilaka Jun 27 '16 at 17:35
  • Are you asking what the pointcut for that method should be? That depends entirely on which close methods you want your aspect to touch. Something like `"call(public java.sql.PreparedStatement+.close(..))"` should match `close()` on any PreparedStatement or subclasses of. [This AspectJ pointcut reference](https://eclipse.org/aspectj/doc/next/quick5.pdf) might be of use. – Brandon McKenzie Jun 27 '16 at 20:01