Overriding a method in an instantiated Java object

2019-03-14 08:10发布

问题:

I would like to override a method in an object that's handed to me by a factory that I have little control over.

My specific problem is that I want to override the getInputStream and getOutputStream of a Socket object to perform wire logging.

The generic problem is as follows:

public class Foo {
    public Bar doBar() {
        // Some activity
    }
}

Where I'd like to take an instantiated Foo and replace the doBar with my own that would work as follows:

Bar doBar() {
    // My own activity
    return original.doBar();
}

For the Socket I'm going to return an InputStream and OutputStream that are wrapped by logging to intercept the data.

回答1:

Since Java uses class-based OO, this is impossible. What you can do is use the decorator pattern, i.e. write a wrapper for the object that returns the wrapped streams.



回答2:

I think there is a way to achieve the effect you want. I saw it orriginally used in swing with buttons to allow the programmer to make the button do something when it is clicked.

Say you have your Foo class:

public class Foo {
  public Bar doBar() {
    // Some activity
  }
}

Then you have a runner class or something similar. You can override the doBar() method at the point of instantiation and it will only affect that specific object.

that class may look like this:

public class FooInstance{
  Foo F1 = new Foo(){
    public Bar doBar(){
      //new activity
    }
  }

  Foo F2 = new Foo();

  F1.doBar(); //does the new activity
  F2.doBar(); //does the original activity found in the class
}

I'm not entirely sure that will do the trick for you but maybe it'll set you in the right direction. If nothing else it is possible to override a method outside of the class, maybe that will help you.



回答3:

You can't replace methods in existing objects - you can't change an existing object's type, for one thing.

You could create a new instance of another class which delegated to the existing instance, but that has limitations too.

In your real world case is there no way you can simply make a separate call to wrap the streams returned by the socket? Can you give more details.



回答4:

Using a decorator is the right way to go:

Some very similar code to the requirement you have with sockets is here:

http://www.javaspecialists.eu/archive/Issue058.html



回答5:

I'm not sure if this is possible. Have you considered creating your own class, having the object returned by the factory as a member, and then writing the doBar() method for that class.



回答6:

two options:

  1. easy : if Foo were you implemetn an interface you can use a Dynamic proxy to add new functionality.
  2. more work: what you have is an "around" advice of AOP - you can use any of the existing AOP tools to make that possible. Spring Framework can do it for you, if you are using it already.


回答7:

You can't really change an object on the fly in java.

You could have something which do what you want by wrapping your Foo into another similar objet which will delegate every call to Foo and at the same log everything you want. (see Proxy)

But if you want to do logging, maybe aspect is a better choice. (see AspectJ)



回答8:

Another proxying-related solution: you could use Aspects to override a method on a given object without subclassing it yourself. This is especially appropriate and common for logging. This example uses spring-aop.

class Example {

    final Foo foo;

    Example(Foo original) {
        AspectJProxyFactory factory = new AspectJProxyFactory();
        factory.setTarget(original);
        factory.addAspect(FooAspect.class);
        foo = (Foo) factory.getProxy();
    }

    @Aspect
    static class FooAspect {

        @Before("execution(Foo.doBar())")
        Object beforeDoBar() {
            // My own activity
        }
    }


回答9:

If Socket was an interface then you could create a dynamic proxy. Below is an example. I put this here in case other people need to do this and the target instance is an instance of an interface.

The main reason this will not work for Socket is because java.lang.reflect.Proxy.newProxyInstance requires an array of interfaces for its second argument, so classes won't work here. As such for this example I had to create an interface called ParentInterface, which just has the three print methods.

public class Parent implements ParentInterface {

    @Override
    public void print1() {
        System.out.println("parent 1");
    }

    @Override
    public void print2() {
        System.out.println("parent 2");
    }

    @Override
    public void print3() {
        System.out.println("parent 3");
    }

    public static void main(String[] args) {
        Parent originalInstance = new Parent();
        ParentInterface proxied = (ParentInterface) java.lang.reflect.Proxy.newProxyInstance(
                Parent.class.getClassLoader(),
                new Class[]{ParentInterface.class},
                new ParentProxy(originalInstance));

        proxied.print1();
        proxied.print2();
        proxied.print3();

    }

    static class ParentProxy implements InvocationHandler {

        final Object realObject;

        public ParentProxy(Object real) {
            realObject = real;
        }

        @Override
        public Object invoke(Object target, Method m, Object[] args) throws Throwable {
            try {
                if (m.getName().equals("print2")) {
                    print2();
                    return null;
                } else {
                    return m.invoke(realObject, args);
                }
            } catch (java.lang.reflect.InvocationTargetException e) {
                throw e.getTargetException();
            }
        }

        public void print2() {
            System.out.println("wrapper 2");
        }

    }

}