




已阅读5页,还剩28页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
7.4 Spring MVC提供的更多功能除了直接实现Controller接口外,Spring还提供了许多功能更多的Controller的实现,可以选择继承一个合适类型的Controller来简化编码。相对于Struts或WebWork,Spring提供的Controller层次极为丰富,如图7-24所示。图7-24AbstractController提供了一个最顶层的Controller模板,用来完成一些基本的任务。AbstractController可以注入以下属性。(1)supportedMethods:设定允许的HTTP请求方式,默认为GET和POST。如果需要PUT、HEAD之类的请求,则需要覆盖默认的设定,不过,通常不需要设定其他HTTP请求方式。(2)requireSession:设定是否需要Session支持,默认为false。如果设定为true,则要求当前请求必须和Session关联,这样可以保证子类在任何时候调用request.getSession()时不会得到null。(3)cacheSeconds:设定HTTP响应头的缓存,默认值为-1,表示不添加任何缓存指令到HTTP响应头;如果设为0,表示完全不缓存;如果设为大于0,表示应当缓存的秒数。(4)synchronizeOnSession:表示来自同一用户的请求是否能并行处理,默认值为false,表示允许同一用户同时向服务器发送多个请求。如果设定为true,则同一用户的请求只能被依次处理,这个设置可以有效控制同一用户对服务器的并发请求,例如,禁止使用多线程下载由Controller生成的文件。由于AbstractController位于Controller继承体系的上端,其他子类也可以非常方便地设定上述属性。为了演示如何使用Spring内置的常用的Controller,我们在SpringMVC工程的基础上扩展。在Eclipse中新建SpringMVC_Controllers工程,结构如图7-25所示。图7-25Spring提供了一套标签库,能大大简化表单的绑定和验证任务,为了使用Spring内置的Tag和JSP标准标签库,需要将c.tld和spring-form.tld复制到/web/WEB-INF/目录下,并且在web.xml中声明。 /jsp/jstl/core /WEB-INF/c.tld /tags/form /WEB-INF/spring-form.tld前面讲到了使用BeanNameUrlHandlerMapping能极大地简化从URL到Controller的映射,在实际的项目中,完全可以采用Ant+XDoclet自动生成Spring Web应用程序所需的XML配置文件,这样,对URL映射的配置就变成了在相应的Controller类的源代码中简单地添加一个XDoclet注释,极大地降低了手动维护XML配置文件带来的成本。在第3章中我们已经介绍了如何使用XDoclet生成Spring的XML配置文件,并对XDoclet做了一定的扩展,使其支持Spring 2.0的XML配置文件。对于这个SpringMVC_ Controllers Web应用程序也同样适用,先将XDoclet和Ant的相关文件复制到工程的lib目录下,然后编写Ant的build.xml脚本。 $web.dir/WEB-INF/dispatcher-servlet.xml 不熟悉XDoclet的读者请参考第3章关于如何使用XDoclet自动生成Spring配置文件的相关章节,这里不再做更多的介绍。下面我们要讨论的是Spring提供的几种非常有用的控制器。7.4.1 SimpleFormControllerSimpleFormController可以处理简单的表单,这和Struts的ActionForm类似,但是Spring对表单类不要求实现某个特定的接口。此外,SimpleFormController可以同时完成显示表单和提交表单两个功能。显示表单无须编写代码,Spring会自动处理,我们只需处理提交表单。SimpleFormController主要通过以下几个属性来决定如何显示和提交表单。(1)commandClass:表单类(或称为命令类),Spring据此来实例化一个表单类,请读者注意,在Spring中,表单对象被称为Command对象,这和Struts中的ActionForm类似,但Spring的Command对象不要求实现任何特定接口。(2)formView:显示表单的视图名称。(3)successView:提交表单成功后的视图名称。以用户登录为例,我们设计一个LoginController,从SimpleFormController派生。当用户以GET方式请求/login.do时,LoginController就会向用户显示一个登录表单,该视图的名称就是由formView指定的“login.jsp”,显示表单不需要编写任何代码,Spring替我们自动完成这一步骤,唯一需要处理的是覆写onSubmit方法,处理表单提交。/* 处理用户登录表单* spring.bean name=/login.do* perty name=commandClass value=example.chapter7.User* perty name=formView value=login* perty name=successView value=loginSuccess*/public class LoginController extends SimpleFormController private UserService userService; /* * perty ref=userService */ public void setUserService(UserService userService) this.userService = userService; Override protected ModelAndView onSubmit(HttpServletRequest request, HttpServlet Response response, Object command, BindException errors) throws Exception User user = (User)command; try userService.login(user.getUsername(), user.getPassword(); / 登录成功,在Session中标记: request.getSession().setAttribute(USERNAME, user.getUsername(); / 然后返回successView: Map model = new HashMap(); model.put(username, user.getUsername(); return new ModelAndView(getSuccessView(), model); catch(RuntimeException e) / 登录失败,返回formView让用户重新填写表单: Map model = new HashMap(); model.put(command, command); model.put(error, e.getMessage(); return new ModelAndView(getFormView(), model); 由于使用XDoclet在注释中就配置好了LoginController,因此运行Ant,生成的XML配置片断最终如下。 对于作为formView的login.jsp视图文件,为了让Spring自动绑定表单的内容,使用Spring的标签库即可完成此功能。 用户名: 口令: 用于定义一个表单,和HTML的标签对应,对应一个INPUT,其path属性指定了这个文本输入框对应Command对象的username属性,对应一个类型为PASSWORD的INPUT,其path属性指定了这个口令输入框对应Command对象的password属性。运行Ant编译,然后启动Resin,输入http:/localhost:8080/login.do,就可以看到Spring自动绑定表单的效果,如图7-26所示。可以看到,整个处理流程非常清晰。commandClass 必须是具有默认构造方法,可以被实例化的类,通常都是简单的JavaBean对象,这里我们使用的commandClass是User类,但是仅使用了username和password这两个属性,其余属性由于不出现在登录表单中,将直接被Spring忽略。7.4.2 验证表单对于用户输入的表单,通常需要在服务器端进行验证,以确保数据的完整性和一致性。Spring提供了一个Validation框架来验证用户输入的表单,并可以将错误信息绑定到合适的字段上。以登录表单为例,我们可以编写一个LoginValidator来验证登录表单。public class LoginValidator implements Validator public boolean supports(Class clazz) return clazz=User.class; public void validate(Object target, Errors errors) ValidationUtils.rejectIfEmptyOrWhitespace(errors, username, error. username.required, 必须填写用户名); ValidationUtils.rejectIfEmpty(errors, password, error.password. required, 必须填写口令); 所有的验证器都必须实现Validator接口,该接口需要实现两个方法。(1)boolean supports(Class clazz):返回是否支持该类型。(2)void validate(Object target, Errors errors):验证表单对象。Spring提供了一个ValidationUtils来简化验证,如果某个字段未通过验证,就将其放入Errors对象中,也可以直接使用Errors的rejectValue()方法来加入一个验证错误。有了验证器后,就可以将其注入到BaseCommandController及其子类中。LoginController也是从BaseCommandController继承而来的,一旦注入了Validator,Spring对表单的处理流程就如图7-27所示。加入了验证功能的login.jsp由标签显示验证错误,其内容如下。 用户名: 口令: 其中,path属性必须和ValidationUtils.rejectIf Empty(Errors errors, String field, String errorCode)或Errors.rejectValue(String field, String errorCode, String defaultMessage)中的field参数一致,这样,Spring的标签就可以自动绑定错误信息。对于用户登录表单,如果没有输入用户名或口令,就会显示验证错误的信息,如图7-28所示。7.4.3 MultiActionController如果需要处理多个类似的请求,可以考虑使用一个MultiActionController来实现,而不必分别编写多个单一功能的Controller。例如,在显示用户详细资料时,考虑到用户资料的内容较多,可以分为“基本资料”、“联系方式”和“详细地址”3大类显示给用户,由于这3个页面都从User对象中获取数据,因此,将3个页面的显示放到一个MultiActionController中不仅更方便,也便于减少Controller的数量。ViewProfileController便是从MultiActionController派生的,能够查看3个页面。/* spring.bean name=/*Profile.do*/public class ViewProfileController extends MultiActionController public class ViewProfileController extends MultiActionController private UserService userService; /* * perty ref=userService */ public void setUserService(UserService userService) this.userService = userService; private String getUsername(HttpServletRequest request) String username = (String) request.getSession().getAttribute(USERNAME); if(username=null) throw new NeedLoginException(); return username; public ModelAndView basicProfile(HttpServletRequest request, HttpServlet Response response) throws Exception String username = getUsername(request); User user = userService.query(username); Map model = new HashMap(); model.put(username, user.getUsername(); model.put(role, user.getRole()=User.ADMIN ? Admin : User); return new ModelAndView(basicProfile, model); public ModelAndView contactProfile(HttpServletRequest request, HttpServlet Response response) throws Exception String username = getUsername(request); User user = userService.query(username); Map model = new HashMap(); model.put(email, user.getEmail(); model.put(blog, user.getBlog(); model.put(website, user.getWebsite(); return new ModelAndView(contactProfile, model); public ModelAndView addressProfile(HttpServletRequest request, HttpServlet Response response) throws Exception String username = getUsername(request); User user = userService.query(username); Map model = new HashMap(); model.put(province, user.getProvince(); model.put(city, user.getCity(); model.put(zip, user.getZip(); return new ModelAndView(addressProfile, model); 其中,basicProfile()、contactProfile()和addressProfile()这3个方法都能分别独立地处理用户请求,并返回User对象中对应的部分数据,那么,Spring如何根据URL来确定应该调用ViewProfileController的哪个方法来处理用户请求呢?答案是使用methodName Resolver的设定。在使用ViewProfileController之前,我们还需要配置一个MethodName Resolver。MultiActionController默认使用InternalPathMethodNameResolver,从URL中提取方法名,然后调用相应的方法来处理请求。对于上面的ViewProfileController,传入“http:/localhost:8080/basicProfile.do”、“http:/ localhost:8080/contactProfile.do”和“http:/ localhost:8080/addressProfile.do”就可以分别调用basicProfile()、contactProfile()和addressProfile()方法来处理用户请求。由于要从URL中提取methodName,所以配置ViewProfileController的name为“/*Profile.do”,使用通配符“*”来匹配这3个方法名,因此,方法命名一定要符合一定的规则,才便于使用URL映射。现在,我们在一个ViewProfileController中就同时实现了3个页面的处理。启动服务器,登录后可以看到ViewProfileController的效果如图7-29图7-31所示。 图7-30 图7-31如果不喜欢通过URL来调用方法,Spring同样提供了多种MethodNameResolver的实现,最常见的一种是ParameterMethodNameResolver,它可以从参数中提取方法名。 在上面的XML配置片断中,ParameterMethodNameResolver根据URL的action参数来确定方法名称。将methodNameResolver注入到ViewProfileController后,若用户请求“/viewProfile.do?action=basicProfile”,则methodNameResolver将根据参数action= basicProfile来决定调用ViewProfileController的basicProfile()方法处理用户请求,若用户请求“/viewProfile.do?action=contactProfile”,则调用contactProfile()方法,若没有找到action参数,则methodNameResolver根据defaultMethodName属性来调用basicProfile()方法。事实上,我们自己也可以手动编写代码来确定调用哪个方法。public ModelAndView handleRequest(HttpServletRequest request, HttpServlet Response response) throws Exception String methodName = request.getParameter(action); if(basicProfile.equals(methodName) return basicProfile(request, response); if(contactProfile.equals(methodName) return contactProfile(request, response); if(addressProfile.equals(methodName) return addressProfile(request, response); / default method: return basicProfile(request, response);Spring为我们所做的工作不过是将上述代码以配置文件的形式更灵活地实现而已。除了ParameterMethodNameResolver外,Spring还提供了PropertiesMethodNameResolver,不过,最简单也最有用的还是ParameterMethodNameResolver。7.4.4 AbstractWizardFormController从名字上就可以看出,AbstractWizardFormController能够实现向导式的页面。如果用户需要填写的表单内容很多,就有必要将其拆为几个页面,使用户能通过“上一步”和“下一步”按钮方便地在向导页面间导航,例如,设计一个在线调查的向导,就可以方便地引导用户一步一步完成调查表单的填写。我们以注册新用户为例,RegisterController需要用户填写基本资料、联系方式和详细地址,由于表单内容较多,我们让用户分3个页面分步完成注册。我们无须处理“下一步”和“上一步”按钮,Spring会自动显示正确的页面,我们只需要处理最后用户单击“完成”按钮提交的整个表单对象。/* 用户注册向导* spring.bean name=/register.do* perty name=commandClass value=example.chapter7.User* perty name=pages list=registerStep0,registerStep1, registerStep2*/public class RegisterController extends AbstractWizardFormController private UserService userService; /* * perty ref=userService */ public void setUserService(UserService userService) this.userService = userService; / 当用户单击_finish按钮时,触发processFinish()方法: protected ModelAndView processFinish(HttpServletRequest request, HttpServletResponse response, Object command, BindException errors) throws Exception User user = (User)command; userService.register(user); Map model = new HashMap(); model.put(username, user.getUsername(); return new ModelAndView(registerSuccess, model); 那么,Spring是如何知道下一个或上一个需要显示的页面呢?除了指定command Class为User对象外,我们还需要将几个View的逻辑名称注入到RegisterController的pages属性中,注意到AbstractWizardController的pages是从下标0开始计数的,因此,我们将注册的3个页面依次命名为registerStep0.jsp、registerStep1.jsp和registerStep2.jsp。除了指定pages属性外,我们还需要按照一定的规则来编写JSP页面,才能告诉Spring如何显示下一页或上一页。在表单的提交按钮上,必须以_target+索引命名按钮,例如:将前进到索引为1的页面,即registerStep1.jsp。将返回到索引为0的页面,即registerStep0.jsp。最后一个“完成”按钮必须以“_finish”命名。当用户单击“完成”按钮后,Spring将调用processFinish()方法处理表单。如果需要验证表单,在AbstractWizardController中,就无法使用Validator来进行验证,因为用户在每个页面仅填写了部分内容,直到用户单击“完成”按钮时,整个表单对象才被填充完毕,因此,在任何一个页面中验证Command都将失败,为此,验证必须在AbstractWizardController的validatePage()方法中进行,Spring将传入page参数,我们就根据这个参数对command对象进行部分验证。/ 每当用户单击_target?准备前进到下一步时,触发validatePage()来验证当前页:protected void validatePage(Object command, Errors errors, int page) User user = (User)command; if(page=0) / 验证username,password: if(!user.getUsername().matches(a-zA-Z0-93,20) errors.rejectValue(username, error.username, 用户名不符合要求); if(userService.isExist(user.getUsername() errors.rejectValue(username, error.username, 用户名已存在); if(user.getPassword()=null | user.getPassword().length()6) errors.rejectValue(password, error.password, 口令至少为6个字符); else if(page=1) / 验证email,blog,website: if(user.getEmail()=null) errors.rejectValue(email, error.email.empty, 电子邮件不能为空); else if(!user.getEmail().matches(a-zA-Z0-9_-+a-zA-Z0-9_ -+.a-zA-Z0-9_-+1,2) errors.rejectValue(email, error.email, 电子邮件地址无效); if(user.getBlog()=null | user.getBlog().trim().equals() errors.rejectValue(blog, error.blog, 博客地址不能为空); if(user.getWebsite()=null | user.getWebsite().trim().equals() errors.rejectValue(website, error.website, 网址地址不能为空); else if(page=2) / 验证province,city,zip:略过 若验证未通过,则将停留在当前页,并可以通过显示相应的错误信息,待用户更正后,才可以继续前进到下一页。编译工程,启动Resin服务器,可以看到整个用户注册的流程如图7-32图7-35所示。 图7-32 图7-33 图7-34 图7-35读者也许注意到了,第一个页面有两个口令框,其中,第二个口令框名称为password2,在User对象中并没有对应的属性,Spring不会自动绑定它。那么,如何验证用户两次输入的口令是否一致呢?我们一般不愿意更改User对象,因为User对象很可能对应数据库中的某个表,而数据库表不会存储同一用户的两份相同的口令。此时,可以通过JavaScript来验证,既方便,又能避免修改User对象。因此,在Web应用程序的设计中,不要仅仅拘泥于JavaEE框架,对于JavaScript、AJAX等技术也要充分利用。为了让读者能更方便地看到Controller和对应的JSP视图,我们将Controller的源码也放入到每个页面中。在每一个页面中,读者都可以通过单击“查看源码”非常方便地阅读对应的Controller代码,如图7-36所示。图7-367.4.5 输出二进制内容虽然大多数时候用户请求的都是HTML页面,不过,某些情况下仍然需要向用户发送动态生成的二进制内容,例如,图片认证码、Excel报表等,从HTTP协议上看来,向用户发送二进制内容只需要设置好输出响应的MIME类型,然后直接写入二进制流即可。对应到JavaEE Web应用程序,就是设置HttpServletResponse对象的ContentType,然后将二进制数据写入OutputStream即可。我们以动态生成一个图片认证码为例,实现一个ImageController如下。public class ImageController implements Controller public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception / 在内存中绘图: String code = String.valueOf(int)(Math.random() * 9000) + 1000); BufferedImage image = new BufferedImage (100, 50, BufferedImage. TYPE_INT_RGB); Graphics2D g = image.createGraphics(); g.setColor(Color.GRAY); g.fillRect(0, 0, 100, 50); g.setColor(Color.RED); g.drawRect(0, 0, 99, 49); g.setColor(Color.BLACK); g.drawString(code, 20, 20); g.dispose(); image.flush(); / 设置ContentType: response.setContentType(image/jpeg); response.setHeader(Cache-Control, no-cache); / 输出到ServletOutputStream: ServletOutputStream output = response.getOutputStream(); ImageIO.write(image, jpeg, output); return null; 上述代码的运行效果如图7-37所示。图7-37从上面的代码可以看出,输出二进制内容的关键是设置正确的ContentType及ContentLength,然后获得输出流,将二进制内容写入即可。
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年经济师职称考试经济基础模拟试题(实战演练)
- 2025年电梯安装改造维修作业特种作业操作证考试试卷实战技巧与案例分析集
- 2025年造价工程师案例分析经典案例与实战经验分享模拟试卷
- 2025年会计职称考试《初级会计实务》财务报表编制冲刺备考试题
- 旅游酒店客房预订服务合作协议
- 2025年成人高考《语文》模拟冲刺题库:作文构思与立意创新试题
- 2025年护士执业资格考试题库(护理科研方法与实践)护理科研方法与实践案例分析及预测
- 农民专业合作组织运营协议
- 经济管理案例分析题库
- 网络运营安全责任协议内容
- 医学专题杏林中人乳腺穴位敷贴
- 公路水运工程施工安全标准化指南(42页)
- 人教版 2021-2022学年 五年级下册数学期末测试试卷(一)含答案
- 锡槽缺陷手册(上
- (完整版)全国校园篮球特色学校申报材料
- 西门子SAMA图DEH逻辑讲解
- 检测交通视频中运动目标的程序设计
- 施工现场安全、文明施工检查评分表
- 管道支架重量计算表常用图文精
- 国家开放大学《数据结构(本)》单元测试参考答案
- 中药化学成分的预试验大全
评论
0/150
提交评论