我正在尝试使用泛型类作为Spring表单支持bean,但是当Spring框架尝试将Object强制转换为实际类型时,最终会出现ClassCastException。
提交表单时,尝试在SrvRecord对象上调用方法时会出现以下错误(第105行,标记为注释):
java.lang.ClassCastException: java.lang.Object cannot be cast to com.[...].portal.entity.SrvRecord
at com.[...].portal.controller.SrvController.add(SrvController.java:105)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:176)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:436)
at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:424)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:790)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:669)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:585)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:511)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:390)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:440)
at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230)
at org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
at org.mortbay.jetty.Server.handle(Server.java:326)
at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:542)
at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:943)
at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:756)
at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:218)
at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:404)
at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:410)
at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:582)
Form Bean:
public class RecordBean<T>
{
private T original;
private T modified;
public RecordBean()
{
super();
}
public RecordBean(T original)
{
this.original = original;
this.modified = original;
}
public T getOriginal()
{
return original;
}
public void setOriginal(T original)
{
this.original = original;
}
public T getModified()
{
return modified;
}
public void setModified(T modified)
{
this.modified = modified;
}
}
控制器方法:
@RequestMapping(value = "new", method = RequestMethod.GET)
public String add(Model model)
{
SrvRecord srvRecord = getSrvRecord();
RecordBean<SrvRecord> record = new RecordBean<SrvRecord>(srvRecord);
model.addAttribute("record", record);
return "generic/new";
}
@RequestMapping(value = "new", method = RequestMethod.POST)
public String add(Model model, @ModelAttribute("record") RecordBean<SrvRecord> record)
{
// Call a method on the SrvRecord object
doSomething(record.getModified().getZone().getName()); // line 105
doSomething(record.getOriginal().getZone().getName());
// ...
}
查看:
<c:url value="/edit" var="formUrl" />
<form:form commandName="record" action="${formUrl}">
<form:input type="hidden" path="original.zone" />
<form:input type="hidden" path="original.name" />
<!-- ... -->
<form:input path="modified.zone" /><br />
<form:input path="modified.name" /><br />
<!-- ... -->
</form:form>
任何想法或建议都会很棒。能够使用通用表单bean将从基线中消除大量不必要的代码。
仅供参考,使用的Spring版本是3.0.6.RELEASE。
谢谢, 博
答案 0 :(得分:3)
你可以在Spring上提交一个bug。问题是Spring使用反射来确定参数类型,并且它们忽略了泛型,因此只是实例化没有泛型的普通RecordBean对象。因此,RecordBean中的对象只是作为Object创建,并且无法将其强制转换为SrvRecord。唯一的解决方法是不使用泛型。
<强>背景强>
Spring内部使用MethodParameter类来读出方法参数。有一种叫做
的方法public Class<?> getParameterType()
调用:
this.method.getParameterTypes()[this.parameterIndex]
此代码在没有泛型的情况下读出参数。他们需要调用它来正确处理它
this.method.getGenericParameterTypes()[this.parameterIndex]
后来的方法
HandlerMethodInvoker.resolveModelAttribute()
通过执行调用来实例化命令类
bindObject = BeanUtils.instantiateClass(paramType);
但是paramType的值是“com.test.RecordBean”而不是“com.test.RecordBean&lt; SrvRecord&gt;”正如预期的那样
答案 1 :(得分:1)
您可以尝试使您的通用更具体。
public class RecordBean<T extends interfaceOrSuperclassOfSrvRecord>
答案 2 :(得分:1)
您可以通过为您的类实现检查泛型来找到错误的来源,即在对象中保留Class
引用并在必要时使用显式强制转换:
public class RecordBean<T>
{
private Class<T> clazz;
private T original;
private T modified;
public RecordBean(Class<T> clazz)
{
super();
this.clazz = clazz;
}
public RecordBean(T original, Class<T> clazz)
{
this.clazz = clazz;
this.original = original;
this.modified = original;
}
public RecordBean(T original)
{
this(original, (Class<T>) original.getClass());
}
public T getOriginal()
{
return original;
}
public void setOriginal(T original)
{
this.original = clazz.cast(original);
}
public T getModified()
{
return modified;
}
public void setModified(T modified)
{
this.modified = clazz.cast(modified);
}
}
答案 3 :(得分:0)
这里存在同样的问题,我发现的唯一解决方案是为每个控制器创建一个额外的类(FormModelObjectCity),所以我的类如下所示:
public class CityDTO {
private Long id;
private String name;
public CityDTO() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
...
public class FormModelObjectCity {
private CityDTO data;
private String action;
public CityDTO getData() {
return data;
}
public void setData(CityDTO data) {
this.data = data;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
}
...
@Controller
public class CitiesController {
@RequestMapping(value = "/cities/city", method = RequestMethod.GET)
public String cityForm(Model model, FormModelObjectCity command) {
...
}
@RequestMapping(value = "/cities/city", method = RequestMethod.POST)
public String cityFormSubmit(@ModelAttribute("command") FormModelObjectCity command, BindingResult errors, Model model) {
...
}
}
form.html,在<>
中替换[][form action="#" th:action="@{/cities/city}" th:object="${command}" method="post"]
[input type="hidden" th:field="*{action}"]
[input type="hidden" th:field="*{data.id}"]
[div class="form-group" style="width: 40%;"]
[label th:text="#{city.name}"][/label]
[input type="text" class="form-control" th:field="*{data.name}"]
[small th:if="${#fields.hasErrors('data.name')}" th:errors="*{data.name}" class="form-text" style="color: red;"] [/small]
[/div]
[/form]
...