覆盖外部对象的方法

时间:2014-04-25 14:37:28

标签: java

我有一种情况,我想拦截/覆盖外部(我无法访问它们)对象的方法。我的情况的一个例子:

我有一个外部对象:

public class ExternalObject {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

外部女巫使用它:

public class ExternalClass {
    public static void main(String[] args) {
        ExternalObject o = new ExternalObject();
        o.setName("Tom");

        MyClass.doSomething(o);

        o.setName("Jerry");

        System.out.print(o.getName());
    }
}

然后有一个MyClass女巫有一个机会来访问外部对象:

public class MyClass {
    public static void doSomething(ExternalObject o){

    }
}

是否可以覆盖或修改MyClass中的setName方法? 我需要的是setName方法来检查名称是否是" jerry"然后把它改回" tom"如果没有,那么做原始方法。

这样的事情:

public void mySetName(String name){
  if(name.equals("Jerry"){
    name = "Tom";
  }
  doWhatOriginalMethodDoes();
}

因此,如果有人像现在一样运行外部类,那么Tom将被打印两次。

顺便说一句,外部(原始)对象和方法非常复杂。 我已经四处搜索,这应该可以与reflect.Proxy做,但我不能让它工作。

感谢您的帮助! :)

6 个答案:

答案 0 :(得分:5)

最简单的方法是使用继承:

public class MySubClass extends ExternalObject {

    private ExternalObject obj;

    public MySubClass(ExternalObject obj) {
        this.obj = obj;
    }

    @Override
    public void setName(String name){
        if(name.equals("Jerry") {
            super.setName("Tom");
        } else {
            super.setName(name);
        }
    }

    // override all public method to call super method
    @Override
    public AClass otherMethod1(BClass arg){
        return super.otherMethod1(arg);
    }

    @Override
    public CClass otherMethod2(DClass arg){
        return super.otherMethod2(arg);
    }
}

由于MySubClass是一个ExternalObject,你可以调用:

MySubClass subObject = new MySubClass(o);
MyClass.doSomething(subObject);

或者,如果您的类实现了一个接口,您可以使用代理:

首先,定义一个InvocationHandler

public Class ExternalObjectInterfaceInvocationHandler implements java.lang.reflect.InvocationHandler {

    // keep a reference to the wrapped object
    private ExternalObjectInterface obj;

    public ExternalObjectInterfaceInvocationHandler(ExternalObjectInterface obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
        try {
            if (m.getName().equals("setName")) {
                // do something
            }
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        } catch (Exception e) {
            throw e;
        }
        // return something
    }
}

然后使用代理包装您的对象:

ExternalObjectInterface obj = ...

//wrap the obj with a proxy
ExternalObjectInterface wrappedInstance = (ExternalObjectInterface) Proxy.newProxyInstance(
    ExternalObjectInterface.class.getClassLoader(),
    new Class[] {ExternalObjectInterface.class},
    new ExternalObjectInterfaceHandler(  obj  )
);

然后致电:

MyClass.doSomething(wrappedInstance);

如果您的对象没有实现接口,则第三种解决方案是使用CGLib:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

要包装您的对象,您可以使用以下内容:

public static <S,W> W createWrapper(final S source, final Class<W> wrapperClass) {

    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(wrapperClass);
    enhancer.setInterfaces(wrapperClass.getInterfaces());
    enhancer.setCallback(new MethodInterceptor() {

        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

            if ("setName".equals(method.getName())) {
                //do something
                return null;
            }

            if (Arrays.asList(wrapperClass.getDeclaredMethods()).contains(method)) {
                return methodProxy.invokeSuper(proxy, args);
            }

            return methodProxy.invoke(source, args);
        }
    });

    return (W) enhancer.create();
}

使用CGLib herehere

的一些示例

答案 1 :(得分:1)

这不是标准Java API的一部分。唯一真正需要的是它可能正在测试这样的&#34;外部对象&#34;,或者当你需要&#34;扩展&#34;来自第三方图书馆的最终课程。通常,您只需创建并使用派生类。

您请求的功能由Mockito之类的模拟框架提供。它们允许替换方法实现(&#34; stubbing&#34;)或监视实际存在的方法的调用(&#34; spying&#34;)。通常,这些框架用于测试。我从来没有听过任何人在生产中使用它们,但如果找不到另一种解决方法,可能会有可能。

如果&#34;外部对象&#34;可以表示为接口,另一种方法是创建和使用proxy class。但是代理只能替换接口。

答案 2 :(得分:0)

您可以编写一个子类并使用它。它的编写,使用和理解与反思相比要容易得多。

public class MyExternalObject extends ExternalObject {
    @Override
    public void mySetName(String name){
        if(name.equals("Jerry")
            name = "Tom";
        else
            this.name = name;
    }
}

答案 3 :(得分:0)

如果你无法控制对象的创建,那么我想到的唯一方法是使用instrumentation和java代理用你自己的子类替换类。它会有点难看,但它会起作用(只要在代理程序代码运行时类尚未加载)。

我会使用javassist创建替换类,然后为该类注册一个ClassFileTransformer。

可以找到参考文献here

答案 4 :(得分:0)

你可以这样做:

ExternalObject.java:

public class ExternalObject {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

ExternalClass.java:

public class ExternalClass {

    public static void main(String[] args) {
        ExternalObject object = new ExternalObject();
        object.setName("tom");
        System.out.println(object.getName());
        MyClass.doSomeThing("jerry");
    }

}

和MyClass.java:

public class MyClass {

    public static void doSomeThing(String name) {
        ExternalObject object = new ExternalObject() {
            @Override
            public void setName(String name) {
                if (name.equals("jerry")) {
                    super.setName("tom");
                } else {
                    super.setName(name);
                }
            }
        };
        object.setName(name);
        System.out.println(object.getName());
    }

}

答案 5 :(得分:0)

您应该只在myclass中实现代码以执行所需的操作(您可以将其称为预处理),然后根据您的结果将值传递给setname方法预处理