java:ParameterizedTypeImpl.toString()的bug

修复Oracle JDK ParameterizedType toString 方法Bug
本文介绍Oracle JDK中ParameterizedType接口的实现类ParameterizedTypeImpl的一个小Bug,该Bug导致toString方法对于内置泛型类显示不正确。通过简单的修改即可修复此问题。

oracle JDK中ParameterizedType接口的实现类sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpltoString方法有一个bug。

就是对于内置泛型类toString方法显示的内容不正确:
下面的代码,返回java.util.Map.entrySet()方法的返回类型:

Type returnType = java.util.Map.class.getMethod("entrySet").getGenericReturnType();
System.out.println(returnType);

我们知道entrySet()方法的返回类型应该是Set<Map.Entry<K, V>>,但上面的代码的执行结果却是

java.util.Set<java.util.Map.java.util.Map$Entry<K, V>>

这是个老问题了,java官网上也有这个bug报告:

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6376382

然并卵,jdk 1.7直到1.8都存在这个bug(我没有装1.9,不知道是否解决)
如果你查看ParameterizedTypeImpl的源码会发现这个问题非常好解决,就是程序员笔误,该用Class.getSimpleName()错误的用了Class.getName()方法,导致,参见下面toString()方法的注释:

    public String toString() {
        StringBuilder sb = new StringBuilder();

        if (ownerType != null) {
            if (ownerType instanceof Class)
                sb.append(((Class)ownerType).getName());
            else
                sb.append(ownerType.toString());

            sb.append(".");

            if (ownerType instanceof ParameterizedTypeImpl) {
                // Find simple name of nested type by removing the
                // shared prefix with owner.
                sb.append(rawType.getName().replace( ((ParameterizedTypeImpl)ownerType).rawType.getName() + "$",
                                         ""));
            } else
	            // 这里应该用getSimpleName
                sb.append(rawType.getName());
        } else
            sb.append(rawType.getName());

        if (actualTypeArguments != null &&
            actualTypeArguments.length > 0) {
            sb.append("<");
            boolean first = true;
            for(Type t: actualTypeArguments) {
                if (!first)
                    sb.append(", ");
                if (t instanceof Class)
                    sb.append(((Class)t).getName());
                else
                    sb.append(t.toString());
                first = false;
            }
            sb.append(">");
        }

        return sb.toString();
    }

这是个非常小的Bug,如果你的项目中不关注也就过去了。但如果想改它,就得自己写个ParameterizedType的实现才行,
为此我写了个ParameterizedTypeImpl.java用于解决这个小bug,完整代码参见gitee仓库:
ParameterizedTypeImpl.java

测试代码参见:
ParameterizedTypeTest.java

<think>我们正在讨论如何对`servletRequestWrapper.toString()`方法进行存根处理或模拟测试。在Java Web开发中,`ServletRequestWrapper`是`HttpServletRequest`的包装类,通常用于修改或增强请求的行为。在单元测试中,我们可能需要模拟这个对象及其`toString()`方法。常见的测试框架如JUnit配合Mockito(一个流行的模拟框架)可以用于模拟对象和方法。以下是一个示例,展示如何使用Mockito来模拟`ServletRequestWrapper`的`toString()`方法。 ###步骤1.添加依赖:确保项目中包含Mockito和JUnit的依赖。2.创建模拟对象:使用Mockito创建一个`ServletRequestWrapper`的模拟对象。 3.设置模拟行为:使用`when(...).thenReturn(...)`方法设置当调用模拟对象的`toString()`方法时返回特定的字符串。4.在测试中使用模拟对象。 ###示例代码```javaimport org.junit.Test; importstatic org.mockito.Mockito.*;import javax.servlet.http.HttpServletRequest; importjavax.servlet.ServletRequestWrapper; publicclass ServletRequestWrapperTest {@Testpublicvoid testToString(){//创建一个HttpServletRequest的模拟对象(因为ServletRequestWrapper需要包装一个ServletRequest)HttpServletRequest mockRequest =mock(HttpServletRequest.class);//创建ServletRequestWrapper的模拟对象,包装上面的mockRequestServletRequestWrapper wrapper= mock(ServletRequestWrapper.class);//设置模拟行为:当调用wrapper.toString()时返回一个特定的字符串when(wrapper.toString()).thenReturn("Mocked ServletRequestWrapper");//现在,在测试中可以使用这个wrapper对象,调用其toString()方法将返回预设的字符串System.out.println(wrapper.toString()); //输出: Mocked ServletRequestWrapper//验证行为(可选)verify(wrapper).toString();}} ```###说明-我们首先模拟了一个`HttpServletRequest`对象,因为`ServletRequestWrapper`的构造需要一个`ServletRequest`对象(`HttpServletRequest`是其子接口)。 -然后我们模拟了`ServletRequestWrapper`对象,并设置其`toString()`方法返回一个特定的字符串。 -在测试中,调用`wrapper.toString()`将返回我们设置的字符串,而不是实际的对象字符串表示。###注意事项-如果实际代码中使用了`ServletRequestWrapper`的子类,那么应该模拟该子类,因为`toString()`方法可能被重写。 -如果`toString()`方法的行为依赖于被包装的请求对象,那么你可能需要更复杂的模拟。例如,如果`toString()`方法调用了被包装对象的`toString()`,那么你也需要模拟被包装对象的行为。 ###替代方法:使用真实对象有时,我们可能不想完全模拟,而是使用一个真实的包装对象,但控制其内部被包装的对象。例如:```java@Testpublicvoid testToStringWithRealWrapper() {//创建一个被包装的请求的模拟HttpServletRequestmockRequest= mock(HttpServletRequest.class);when(mockRequest.toString()).thenReturn("MockedHttpServletRequest");//使用真实的包装类,而不是模拟包装类ServletRequestWrapperwrapper =new ServletRequestWrapper(mockRequest);//现在wrapper.toString()会调用mockRequest.toString(),因为我们没有重写toString方法,所以默认行为是调用被包装对象的toString?//但实际上,ServletRequestWrapper的toString()方法默认返回的是包装类本身的字符串表示,而不是被包装对象的。//查看ServletRequestWrapper的源码:publicString toString() {return "ServletRequestWrapper:" +request.toString(); }//所以实际上,它会返回"ServletRequestWrapper: "+ mockRequest.toString()。//因此,我们需要根据实际情况来模拟。如果我们希望wrapper.toString()返回特定的字符串,我们可以://1.模拟包装类(如上一种方法)或者//2.通过模拟被包装对象来控制(如下)System.out.println(wrapper.toString()); //输出: ServletRequestWrapper: Mocked HttpServletRequest//如果我们希望控制wrapper.toString()的返回值,我们可以通过设置mockRequest.toString()的返回值来间接控制。//但是注意,这样返回的字符串是"ServletRequestWrapper:"加上模拟的字符串。} ```因此,根据你的需求,你可以选择直接模拟`ServletRequestWrapper`(如果不需要包装行为,只需要一个特定的字符串),或者使用真实的包装对象并模拟其内部的请求对象(如果你需要测试包装逻辑)。 ###总结-**直接模拟**:当你不关心包装行为,只需要一个固定的返回值时,直接模拟`ServletRequestWrapper`并设置`toString()`的返回值。 -**模拟被包装对象**:当你需要测试包装类的默认行为(例如,它调用了被包装对象的某个方法)时,模拟被包装的对象并设置其行为,然后使用真实的包装类。§§相关问题§§1.在单元测试中,模拟`HttpServletRequest`对象有哪些常用方法?2.如何测试一个依赖于`HttpServletRequest`的Servlet或过滤器? 3. Mockito框架中`when().thenReturn()`和`doReturn().when()`有什么区别?4.除了Mockito,还有哪些Java测试模拟框架?希望这些信息能帮助你解决问题。</think>### 如何对`servletRequestWrapper.toString()`进行存根处理或模拟测试 在 Java Web 测试中,对 `ServletRequestWrapper.toString()` 进行存根处理或模拟测试的步骤如下: #### 1. **使用 Mockito 框架模拟** ```java // 添加依赖 // JUnit 5, Mockito-core, Jakarta Servlet API import org.junit.jupiter.api.Test; import static org.mockito.Mockito.*; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletRequestWrapper; public class ServletRequestWrapperTest { @Test public void testToStringStubbing() { // 1. 创建底层 ServletRequest 的模拟 ServletRequest mockRequest = mock(ServletRequest.class); // 2. 创建 ServletRequestWrapper 的模拟 ServletRequestWrapper wrapper = mock(ServletRequestWrapper.class); // 3. 存根 toString() 方法 when(wrapper.toString()).thenReturn("Mocked Request Data"); // 4. 在测试中使用 System.out.println(wrapper.toString()); // 输出: "Mocked Request Data" } } ``` #### 2. **使用真实包装对象 + 委托模拟** ```java @Test public void testRealWrapperWithStubbedRequest() { // 1. 创建底层请求的模拟 ServletRequest mockRequest = mock(ServletRequest.class); when(mockRequest.toString()).thenReturn("Stubbed Request Content"); // 2. 创建真实包装器(非模拟) ServletRequestWrapper realWrapper = new ServletRequestWrapper(mockRequest); // 3. 验证包装器的行为 assertEquals("Stubbed Request Content", realWrapper.getRequest().toString()); } ``` #### 3. **验证方法调用** ```java @Test public void verifyToStringInvocation() { ServletRequestWrapper wrapper = mock(ServletRequestWrapper.class); wrapper.toString(); // 调用方法 // 验证方法是否被调用 verify(wrapper).toString(); } ``` ### 关键注意事项 1. **依赖配置**: ```xml <!-- Maven 依赖 --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>5.3.1</version> <scope>test</scope> </dependency> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>6.0.0</version> <scope>provided</scope> </dependency> ``` 2. **行为覆盖**: - 默认 `ServletRequestWrapper.toString()` 返回格式:`"ServletRequestWrapper: [底层请求的toString]"`[^1] - 通过存根可覆盖此行为:`when(wrapper.toString()).thenReturn("自定义内容")` 3. **集成测试建议**: ```java // 使用 Spring-Test 的 MockHttpServletRequest MockHttpServletRequest request = new MockHttpServletRequest(); request.setParameter("key", "value"); ServletRequestWrapper wrapper = new ServletRequestWrapper(request); assertTrue(wrapper.toString().contains("key=value")); ``` ### 应用场景 - **单元测试**:隔离测试包装器的逻辑时 - **调试日志**:模拟特定日志输出 - **安全测试**:控制敏感数据的字符串表示 [^1]: 参考 `javax.servlet.ServletRequestWrapper` 源码实现,其 `toString()` 默认委托给被包装的请求对象。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值