spring mvc:控制层梳理

    技术2022-05-19  17

    言归正传,研究一下注解下的控制层。 我习惯于使用JSTL展示页面,因此需要在原lib基础上增加jstl.jar和standard.jar,详细lib依赖如下:

    引用 aopalliance-1.0.jar commons-logging-1.1.1.jar log4j-1.2.15.jar spring-beans-2.5.6.jar spring-context-2.5.6.jar spring-context-support-2.5.6.jar spring-core-2.5.6.jar spring-tx-2.5.6.jar spring-web-2.5.6.jar spring-webmvc-2.5.6.jar standard.jar jstl.jar

    上一篇文中,我们定义了控制器AccountController: AccountController.java

    Java代码 /**   * 2010-1-23   */  package org.zlex.spring.controller;     import javax.servlet.http.HttpServletRequest;   import javax.servlet.http.HttpServletResponse;     import org.springframework.beans.factory.annotation.Autowired;   import org.springframework.stereotype.Controller;   import org.springframework.web.bind.ServletRequestUtils;   import org.springframework.web.bind.annotation.RequestMapping;   import org.springframework.web.bind.annotation.RequestMethod;   import org.zlex.spring.service.AccountService;     /**   *    * @author <a href="mailto:zlex.dongliang@gmail.com">梁栋</a>   * @version 1.0   * @since 1.0   */  @Controller  @RequestMapping("/account.do")   public class AccountController {         @Autowired      private AccountService accountService;         @RequestMapping(method = RequestMethod.GET)       public void hello(HttpServletRequest request, HttpServletResponse response)               throws Exception {             String username = ServletRequestUtils.getRequiredStringParameter(                   request, "username");           String password = ServletRequestUtils.getRequiredStringParameter(                   request, "password");           System.out.println(accountService.verify(username, password));       }   }   /** * 2010-1-23 */ package org.zlex.spring.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.zlex.spring.service.AccountService; /** * * @author <a href="mailto:zlex.dongliang@gmail.com">梁栋</a> * @version 1.0 * @since 1.0 */ @Controller @RequestMapping("/account.do") public class AccountController { @Autowired private AccountService accountService; @RequestMapping(method = RequestMethod.GET) public void hello(HttpServletRequest request, HttpServletResponse response) throws Exception { String username = ServletRequestUtils.getRequiredStringParameter( request, "username"); String password = ServletRequestUtils.getRequiredStringParameter( request, "password"); System.out.println(accountService.verify(username, password)); } }

    先说注解@RequestMapping 这里使用注解@RequestMapping(method = RequestMethod.GET)指定这个方法为get请求时调用。同样,我们可以使用注解@RequestMapping(method = RequestMethod.POST)指定该方法接受post请求。

    Java代码 @Controller  @RequestMapping("/account.do")   public class AccountController {         @RequestMapping(method = RequestMethod.GET)       public void get() {       }         @RequestMapping(method = RequestMethod.POST)       public void post() {       }   }   @Controller @RequestMapping("/account.do") public class AccountController { @RequestMapping(method = RequestMethod.GET) public void get() { } @RequestMapping(method = RequestMethod.POST) public void post() { } }

    这与我们久别的Servlet很相像,类似于doGet()和doPost()方法! 我们也可以将其改造为多动作控制器,如下代码所示:

    Java代码 @Controller  @RequestMapping("/account.do")   public class AccountController {         @RequestMapping(params = "method=login")         public void login() {       }         @RequestMapping(params = "method=logout")         public void logout() {       }   @Controller @RequestMapping("/account.do") public class AccountController { @RequestMapping(params = "method=login") public void login() { } @RequestMapping(params = "method=logout") public void logout() { }

    这样,我们可以通过参数“method”指定不同的参数值从而通过请求("/account.do?method=login"和"/account.do?method=logout")调用不同的方法! 注意:使用多动作控制器必须在配置文件中加入注解支持!

    Xml代码 <bean          class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />   <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />

    当然,我们也可以将注解@RequestMapping指定到某一个方法上,如:

    Java代码 @Controller  public class AccountController {              @RequestMapping("/a.do")       public void a() {}         @RequestMapping("/b.do")       public void b() {}   }   @Controller public class AccountController { @RequestMapping("/a.do") public void a() {} @RequestMapping("/b.do") public void b() {} }

    这样,请求“a.do”和“b.do”将对应不同的方法a() 和b()。这使得一个控制器可以同时承载多个请求! @RequestMapping("/account.do")是@RequestMapping(value="/account.do")的简写! 再说输入参数! 这里的方法名可以随意定义,但是参数和返回值却又要求! 为什么?直接看源代码,我们就能找到答案! AnnotationMethodHandlerAdapter.java部分源代码——有关参数部分:

    Java代码 @Override  protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest)           throws Exception {         HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();       HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse();         if (ServletRequest.class.isAssignableFrom(parameterType)) {           return request;       }       else if (ServletResponse.class.isAssignableFrom(parameterType)) {           this.responseArgumentUsed = true;           return response;       }       else if (HttpSession.class.isAssignableFrom(parameterType)) {           return request.getSession();       }       else if (Principal.class.isAssignableFrom(parameterType)) {           return request.getUserPrincipal();       }       else if (Locale.class.equals(parameterType)) {           return RequestContextUtils.getLocale(request);       }       else if (InputStream.class.isAssignableFrom(parameterType)) {           return request.getInputStream();       }       else if (Reader.class.isAssignableFrom(parameterType)) {           return request.getReader();       }       else if (OutputStream.class.isAssignableFrom(parameterType)) {           this.responseArgumentUsed = true;           return response.getOutputStream();       }       else if (Writer.class.isAssignableFrom(parameterType)) {           this.responseArgumentUsed = true;           return response.getWriter();       }       return super.resolveStandardArgument(parameterType, webRequest);   }   @Override protected Object resolveStandardArgument(Class parameterType, NativeWebRequest webRequest) throws Exception { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse(); if (ServletRequest.class.isAssignableFrom(parameterType)) { return request; } else if (ServletResponse.class.isAssignableFrom(parameterType)) { this.responseArgumentUsed = true; return response; } else if (HttpSession.class.isAssignableFrom(parameterType)) { return request.getSession(); } else if (Principal.class.isAssignableFrom(parameterType)) { return request.getUserPrincipal(); } else if (Locale.class.equals(parameterType)) { return RequestContextUtils.getLocale(request); } else if (InputStream.class.isAssignableFrom(parameterType)) { return request.getInputStream(); } else if (Reader.class.isAssignableFrom(parameterType)) { return request.getReader(); } else if (OutputStream.class.isAssignableFrom(parameterType)) { this.responseArgumentUsed = true; return response.getOutputStream(); } else if (Writer.class.isAssignableFrom(parameterType)) { this.responseArgumentUsed = true; return response.getWriter(); } return super.resolveStandardArgument(parameterType, webRequest); }

    也就是说,如果我们想要在自定义的方法中获得一些个“标准”输入参数,参数类型必须包含在以下类型中:

    引用 ServletRequest ServletResponse HttpSession Principal Locale InputStream OutputStream Reader Writer

    当然,上述接口其实都是对于HttpServletRequest和HttpServletResponse的扩展。 此外,我们还可以定义自己的参数。 注意:自定义参数必须是实现类,绝非接口!Spring容器将帮你完成对象初始化工作! 比如说上文中,我们需要参数username和password。我们可以这么写:

    Java代码 @RequestMapping(method = RequestMethod.GET)   public void hello(String username,String password) {       System.out.println(accountService.verify(username, password));   }   @RequestMapping(method = RequestMethod.GET) public void hello(String username,String password) { System.out.println(accountService.verify(username, password)); }

    如果参数名不能与这里的变量名保持一致,那么我们可以使用注解@RequestParam进行强制绑定,代码如下所示:

    Java代码 @RequestMapping(method = RequestMethod.GET)   public void hello(@RequestParam("username") String u,           @RequestParam("password") String p) {       System.out.println(accountService.verify(u, p));   }   @RequestMapping(method = RequestMethod.GET) public void hello(@RequestParam("username") String u, @RequestParam("password") String p) { System.out.println(accountService.verify(u, p)); }

    这比起我们之前写的代码有所简洁:

    Java代码 @RequestMapping(method = RequestMethod.GET)   public void hello(HttpServletRequest request, HttpServletResponse response)           throws Exception {         String username = ServletRequestUtils.getRequiredStringParameter(               request, "username");       String password = ServletRequestUtils.getRequiredStringParameter(               request, "password");       System.out.println(accountService.verify(username, password));   }   @RequestMapping(method = RequestMethod.GET) public void hello(HttpServletRequest request, HttpServletResponse response) throws Exception { String username = ServletRequestUtils.getRequiredStringParameter( request, "username"); String password = ServletRequestUtils.getRequiredStringParameter( request, "password"); System.out.println(accountService.verify(username, password)); }

    ServletRequestUtils类的工作已经由Spring底层实现了,我们只需要把参数名定义一致即可,其内部取参无需关心! 除了传入参数,我们还可以定义即将传出的参数,如加入ModelMap参数:

    Java代码 @SuppressWarnings("unchecked")   @RequestMapping(method = RequestMethod.GET)   public Map hello(String username, String password, ModelMap model) {         System.out.println(accountService.verify(username, password));              model.put("msg", username);         return model;   }   @SuppressWarnings("unchecked") @RequestMapping(method = RequestMethod.GET) public Map hello(String username, String password, ModelMap model) { System.out.println(accountService.verify(username, password)); model.put("msg", username); return model; }

    这时,我们没有定义页面名称,Spring容器将根据请求名指定同名view,即如果是jap页面,则account.do->account.jsp! 不得不承认,这样写起来的确减少了代码量! 接着说输出参数! 通过ModelMap,我们可以绑定输出到的页面的参数,但最终我们将要返回到何种页面呢?再次查看AnnotationMethodHandlerAdapter源代码! AnnotationMethodHandlerAdapter.java部分源代码——有关返回值部分:

    Java代码 @SuppressWarnings("unchecked")   public ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue,           ExtendedModelMap implicitModel, ServletWebRequest webRequest) {         if (returnValue instanceof ModelAndView) {           ModelAndView mav = (ModelAndView) returnValue;           mav.getModelMap().mergeAttributes(implicitModel);           return mav;       }       else if (returnValue instanceof Model) {           return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap());       }       else if (returnValue instanceof Map) {           return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map) returnValue);       }       else if (returnValue instanceof View) {           return new ModelAndView((View) returnValue).addAllObjects(implicitModel);       }       else if (returnValue instanceof String) {           return new ModelAndView((String) returnValue).addAllObjects(implicitModel);       }       else if (returnValue == null) {           // Either returned null or was 'void' return.           if (this.responseArgumentUsed || webRequest.isNotModified()) {               return null;           }           else {               // Assuming view name translation...               return new ModelAndView().addAllObjects(implicitModel);           }       }       else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) {           // Assume a single model attribute...           ModelAttribute attr = AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class);           String attrName = (attr != null ? attr.value() : "");           ModelAndView mav = new ModelAndView().addAllObjects(implicitModel);           if ("".equals(attrName)) {               Class resolvedType = GenericTypeResolver.resolveReturnType(handlerMethod, handlerType);               attrName = Conventions.getVariableNameForReturnType(handlerMethod, resolvedType, returnValue);           }           return mav.addObject(attrName, returnValue);       }       else {           throw new IllegalArgumentException("Invalid handler method return value: " + returnValue);       }   }    @SuppressWarnings("unchecked") public ModelAndView getModelAndView(Method handlerMethod, Class handlerType, Object returnValue, ExtendedModelMap implicitModel, ServletWebRequest webRequest) { if (returnValue instanceof ModelAndView) { ModelAndView mav = (ModelAndView) returnValue; mav.getModelMap().mergeAttributes(implicitModel); return mav; } else if (returnValue instanceof Model) { return new ModelAndView().addAllObjects(implicitModel).addAllObjects(((Model) returnValue).asMap()); } else if (returnValue instanceof Map) { return new ModelAndView().addAllObjects(implicitModel).addAllObjects((Map) returnValue); } else if (returnValue instanceof View) { return new ModelAndView((View) returnValue).addAllObjects(implicitModel); } else if (returnValue instanceof String) { return new ModelAndView((String) returnValue).addAllObjects(implicitModel); } else if (returnValue == null) { // Either returned null or was 'void' return. if (this.responseArgumentUsed || webRequest.isNotModified()) { return null; } else { // Assuming view name translation... return new ModelAndView().addAllObjects(implicitModel); } } else if (!BeanUtils.isSimpleProperty(returnValue.getClass())) { // Assume a single model attribute... ModelAttribute attr = AnnotationUtils.findAnnotation(handlerMethod, ModelAttribute.class); String attrName = (attr != null ? attr.value() : ""); ModelAndView mav = new ModelAndView().addAllObjects(implicitModel); if ("".equals(attrName)) { Class resolvedType = GenericTypeResolver.resolveReturnType(handlerMethod, handlerType); attrName = Conventions.getVariableNameForReturnType(handlerMethod, resolvedType, returnValue); } return mav.addObject(attrName, returnValue); } else { throw new IllegalArgumentException("Invalid handler method return value: " + returnValue); } } }

    返回值的定义十分庞大,或者说可怕的if-else多少有点让我觉得厌恶! 我们可以定义以下类型的返回值:

    引用 ModelAndView Model View Map String null

    ModelAndView、Model和View都是Spring之前版本所特有的元素,Map对应于传入参数ModelMap,String定义页面名称,null即对应void类型方法! 最常用的实现方式如下:

    Java代码 @SuppressWarnings("unchecked")   @RequestMapping(method = RequestMethod.GET)   public String hello(String username, String password, ModelMap model) {         System.out.println(accountService.verify(username, password));         model.put("msg", username);         return "account";   }   @SuppressWarnings("unchecked") @RequestMapping(method = RequestMethod.GET) public String hello(String username, String password, ModelMap model) { System.out.println(accountService.verify(username, password)); model.put("msg", username); return "account"; }

    当然,对于我来说在返回值中写入这么一个字符串多少有点不能接受,于是我还是乐于使用输入参数ModelMap+输出参数Map的方式。 给出一个完整的AccountController实现: AccountController.java

    Java代码 /**   * 2010-1-23   */  package org.zlex.spring.controller;     import java.util.Map;     import org.springframework.beans.factory.annotation.Autowired;   import org.springframework.stereotype.Controller;   import org.springframework.ui.ModelMap;   import org.springframework.web.bind.annotation.RequestMapping;   import org.springframework.web.bind.annotation.RequestMethod;   import org.zlex.spring.service.AccountService;     /**   *    * @author <a href="mailto:zlex.dongliang@gmail.com">梁栋</a>   * @version 1.0   * @since 1.0   */  @Controller  @RequestMapping("/account.do")   public class AccountController {         @Autowired      private AccountService accountService;         @SuppressWarnings("unchecked")       @RequestMapping(method = RequestMethod.GET)       public Map hello(String username, String password, ModelMap model) {             System.out.println(accountService.verify(username, password));             model.put("msg", username);           return model;       }   }   /** * 2010-1-23 */ package org.zlex.spring.controller; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.zlex.spring.service.AccountService; /** * * @author <a href="mailto:zlex.dongliang@gmail.com">梁栋</a> * @version 1.0 * @since 1.0 */ @Controller @RequestMapping("/account.do") public class AccountController { @Autowired private AccountService accountService; @SuppressWarnings("unchecked") @RequestMapping(method = RequestMethod.GET) public Map hello(String username, String password, ModelMap model) { System.out.println(accountService.verify(username, password)); model.put("msg", username); return model; } }

    最后说注解@Session 如果想将某个ModelMap中的参数指定到Session中,可以使用@Session注解,将其绑定为Session熟悉,代码如下所示:

    Java代码 @Controller  @RequestMapping("/account.do")   @SessionAttributes("msg")   public class AccountController {         @Autowired      private AccountService accountService;         @SuppressWarnings("unchecked")       @RequestMapping(method = RequestMethod.GET)       public Map hello(String username, String password, ModelMap model) {             System.out.println(accountService.verify(username, password));             model.put("msg", username);           return model;       }     }   @Controller @RequestMapping("/account.do") @SessionAttributes("msg") public class AccountController { @Autowired private AccountService accountService; @SuppressWarnings("unchecked") @RequestMapping(method = RequestMethod.GET) public Map hello(String username, String password, ModelMap model) { System.out.println(accountService.verify(username, password)); model.put("msg", username); return model; } }

    当然,我们还需要配置一下对应的视图解析器,给出完整配置: servelt.xml

    Xml代码 <?xml version="1.0" encoding="UTF-8"?>  <beans      xmlns="http://www.springframework.org/schema/beans"      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"      xmlns:p="http://www.springframework.org/schema/p"      xmlns:context="http://www.springframework.org/schema/context"      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">      <context:component-scan          base-package="org.zlex.spring.controller" />      <bean          id="urlMapping"          class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />      <bean          class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />      <bean          id="jstlViewResolver"          class="org.springframework.web.servlet.view.InternalResourceViewResolver"          p:viewClass="org.springframework.web.servlet.view.JstlView"          p:prefix="/WEB-INF/page/"          p:suffix=".jsp" />  </beans>   <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.zlex.spring.controller" /> <bean id="urlMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" /> <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" /> <bean id="jstlViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:viewClass="org.springframework.web.servlet.view.JstlView" p:prefix="/WEB-INF/page/" p:suffix=".jsp" /> </beans>

    这里使用了JstlView作为视图解析器。同时,指定前缀路径为"/WEB-INF/page/",后缀路径为".jsp"。也就是说,Spring容器将会在这个路径中寻找匹配的jsp文件! 注意加入xmlns:p="http://www.springframework.org/schema/p"命名空间! 再给出页面内容: taglib.jsp

    Jsp代码 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>   <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>   <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql"%>   <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml"%>   <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>   <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>   <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>   <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> <%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql"%> <%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml"%> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

    account.jap

    Jsp代码 <%@ page language="java" contentType="text/html; charset=UTF-8"      pageEncoding="UTF-8"%>   <%@ include file="/WEB-INF/page/taglib.jsp"%>   <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">   <html>   <head>   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">   <title>Account</title>   </head>   <body>   <c:out value="${msg}"></c:out>   </body>   </html>   <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="/WEB-INF/page/taglib.jsp"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Account</title> </head> <body> <c:out value="${msg}"></c:out> </body> </html>

    目录结构如图所示: 启动应用,最后将得到一个带有内容的页面,如图: 代码见附件!


    最新回复(0)