1

我在修改游戏时遇到了问题。我需要修改方法返回的内容而不编辑该方法本身。我尝试使用 power-mock 进行此操作,但无济于事。有谁知道如何使用 power-mock 或任何其他字节码操作库,甚至任何标准库来做到这一点?我愿意使用任何库,只要许可证允许它用于我的目的。

我希望能够编辑静态方法的返回,以及所有对象的非静态方法的返回。

这是我尝试过的 power-mock

import org.powermock.api.easymock.PowerMock;

import static org.easymock.EasyMock.expect;

public class BeanTest
{
   public static void main(String[] args) throws Exception
   {
      Bean beanMock = PowerMock.createMock(Bean.class);
      expect(beanMock.convert("world")).andReturn("WORLD");
      System.out.println(beanMock.convert("world"));
   }
}

public class Bean
{
   protected String convert(String name) 
   {
      throw new UnsupportedOperationException("not implemented yet");
   }
}
4

2 回答 2

3

在 java 中,返回类型不是方法签名的一部分,因此您可以随心所欲地使用它。

有两种方法可以透明地实现这一目标:

  1. 如果您可以控制调用该方法的对象的注入,请使用 Java 动态代理。
  2. 使用带有环绕建议的 AspectJ 方面,您可以操作您无法控制的代码(第三方库)。通过编译时编织或加载时编织使用 AspectJ

编辑:

使用 java 动态代理(用于接口)或 CGlib(用于类),您可以将接口指向代理,您可以从该代理与调用相交,对其进行处理,并将其委托给真正的实现。要实现这一点,您必须控制依赖注入。这是一个很好的解释

例子:

SomeInterface t = (SomeInterface) Proxy.newProxyInstance(SomeInterface.class.getClassLoader(),
                       new Class<?>[] {SomeInterface.class},
                       new TestInvocationHandler(new TestImpl())); 

它的作用:TestInvocationHandler 扩展了 InvocationHandler 接口。TestImpl 是一个实现 SomeInterface 的类。当您从 SomeInterface 调用方法时,它将在 TestInvocationHandler 中结束。InvocationHandler 有一个方法调用,如下所示:

public Object invoke(Object proxy, Method method, Object[] args);
  • 代理 - 对对象的引用

  • method - 被调用的方法

  • args - 方法的参数

例如,使用 AspectJ,您可以使用切入点指定应该包含在环绕通知中的内容。AspectJ 的优点是您可以操作已经编译的代码或您自己的代码,而无需任何显式编程。

例子:

@Aspect
public class MyAspect {

    @Around("execution(* org.example.yourMethod(..))")
    public Object doNothing(ProceedingJoinPoint pjp) {

        // You can call the method or ignore the call and do your logic
        return pjp.proceed();
    }

 }

这个http://www.hubberspot.com/2012/12/how-to-implement-around-advice-using_12.html是一个很好的例子,如何将 AspectJ 与 spring 一起使用。也许它可以帮助你作为一个起点。

用 CGlib 解释它要困难得多,所以我用 Java 动态代理来解释它。对于您的情况

 // I suppose Bean implements IBean interface
 IBean beanMock =  (IBean) Proxy.newProxyInstance (
                        IBean.getClass().getClassLoader(),
                        new Class[] { IBean.class },
                        new InvocationHandler() {
                            public Object invoke(Object proxy, Method method, 
                              Object[] args) throws Throwable {
                                if(method.getName().equals("convert")) {
                                    if(args[0].toString().equals("world")) {
                                        return "WORLD"
                                    }
                                } else {
                                    // Implement default case
                                }
                            }
                        });

 System.out.println(beanMock.convert("world"));
于 2013-03-19T22:27:15.437 回答
0

你非常接近。看起来您正在使用PowerMock的EasyMock风格。我更熟悉Mockito风格,所以我编写了一个简单的单元测试,它根据您的尝试通过:

import static org.junit.Assert.assertEquals;
import static org.powermock.api.mockito.PowerMockito.mock;
import static org.powermock.api.mockito.PowerMockito.when;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
public class BeanTest {
    @Test
    public void testConvert() throws Exception {
        String worldLower = "world";
        String worldUpper = "WORLD";
        Bean beanMock = mock(Bean.class);
        when(beanMock.convert(worldLower)).thenReturn(worldUpper);
        assertEquals(worldUpper, beanMock.convert(worldLower));
    }
}

我所做的唯一额外更改是放置BeanBeanTest放入单独的类文件中。

于 2013-03-20T12:22:47.590 回答