30

吐血整理!14个编写Spring MVC控制器的实用小技巧

 4 years ago
source link: http://developer.51cto.com/art/201911/606100.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文介绍了编写Spring MVC框架的控制器(controller)的基础技巧和最佳操作。在Spring MVC框架中,编写控制器类通常是为了处理用户提出的请求。

编写完成后,控制器会调用一个业务类来处理业务相关任务,进而重定向客户到逻辑视图名。Springdispatcher servlet会对逻辑视图名进行解析,并渲染结果或输出。这就是一个典型的“请求—响应”的完整流程。

7B3umqb.jpg!web

1. 使用@controllerstereotype

创建一个能够处理单个或多个请求的控制器类,最简单的方法就是使用@controllerstereotype注解一个类,如:

import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.RequestMapping; 
@Controller 
publicclassHomeController { 
    @RequestMapping("/") 
    publicString visitHome() { 
        // do something before returning view name 
        return"home"; 
    } 
} 

如上所示,visitHome()方法通过重定向跳转到视图名home来处理应用程序内容路径(/)收到的请求。 注意:只有在Spring配置文件中启用了注解驱动,才能使用@controllerstereotype。

<annotation-driven/> 

启用注解驱动后,Spring的容器(container)会自动扫描如下包中的类:

<context:component-scanbase-packagecontext:component-scanbase-package="net.codejava.spring"/> 

带有@controller注解的类会被标记成控制器。由于其简单方便,且不再需要对配置文件中的控制器声明beans,这一方法非常实用。 注意:使用@controller注解可以创建一个多动作控制器类,可同时处理多个不同的请求。如:

@Controller 
publicclassMultiActionController { 
    @RequestMapping("/listUsers") 
    public ModelAndView listUsers() { 
    } 
    @RequestMapping("/saveUser") 
    public ModelAndView saveUser(User user) { 
    } 
    @RequestMapping("/deleteUser") 
    public ModelAndView deleteUser(User user) { 
    } 
} 

如上所示,有三个处理器(handler)在分别处理三个请求,/listUsers,/saveUser,和/deleteUser。

2. 实现控制器接口

在Spring MVC中创建控制器还可以用另一个经典的方法,即对一个类实现Controller接口。如:

import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import org.springframework.web.servlet.ModelAndView; 
import org.springframework.web.servlet.mvc.Controller; 
publicclassMainControllerimplements Controller { 
    @Override 
    public ModelAndView handleRequest(HttpServletRequest request, 
            HttpServletResponse response) throws Exception { 
        System.out.println("Welcome main"); 
        returnnew ModelAndView("main"); 
    } 
} 

实现类必须重写handleRequest()方法(当收到相匹配的请求时,Spring dispatcher servlet会调用handleRequest)。由该控制器处理的请求URL模式在Spring的内容配置文件中的定义如下:

<beannamebeanname="/main"class="net.codejava.spring.MainController"/> 

这一方法的缺点在于其控制类无法同时处理多个请求URL。

3. 继承AbstractController类

如果想要轻松控制受支持的HTTP方法、会话和内容缓存,让控制类继承AbstractController类是理想的方法。如:

import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import org.springframework.web.servlet.ModelAndView; 
import org.springframework.web.servlet.mvc.AbstractController; 
publicclassBigControllerextends AbstractController { 
    @Override 
    protected ModelAndView handleRequestInternal(HttpServletRequest request, 
            HttpServletResponse response) throws Exception { 
        System.out.println("You're big!"); 
        returnnew ModelAndView("big"); 
    } 
} 

上例创建了一个配置了受支持的方法、会话和缓存的单动作控制器,能够在控制器的bean声明中被指明。如:

<beannamebeanname="/big"class="net.codejava.spring.BigController"> 
    <propertynamepropertyname="supportedMethods"value="POST"/> 
</bean> 

这一配置表明该控制器handler方法仅支持POST方法。了解更多配置(如会话、缓存),参见AbstractController。 SpringMVC还提供了多个支持特定目的的控制器类,包括:

  • AbstractUrlViewController
  • MultiActionController
  • ParameterizableViewController
  • ServletForwardingController
  • ServletWrappingController
  • UrlFilenameViewController

4. 为处理器指定URL映射

这是编写控制器类必不可少的一步,旨在处理一个及以上特定请求。Spring MVC提供了@RequestMapping注解,用于指定URL映射。如:

@RequestMapping("/login") 

这一步映射了URL模式/login,并用注解或注解类对其进行了处理。@RequestMapping注解用于类上时,类变成了单动作控制器。如:

import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.RequestMapping; 
import org.springframework.web.bind.annotation.RequestMethod; 
@Controller 
@RequestMapping("/hello") 
publicclassSingleActionController { 
    @RequestMapping(method = RequestMethod.GET) 
    publicString sayHello() { 
        return"hello"; 
    } 
} 

@RequestMapping注解用于方法上时,则可生成多动作控制器。如:

import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.RequestMapping; 
@Controller 
publicclassUserController { 
    @RequestMapping("/listUsers") 
    publicString listUsers() { 
        return"ListUsers"; 
    } 
    @RequestMapping("/saveUser") 
    publicString saveUser() { 
        return"EditUser"; 
    } 
    @RequestMapping("/deleteUser") 
    publicString deleteUser() { 
        return"DeleteUser"; 
    } 
} 

@RequestMapping注解也可用于指定多个URL模式,并用单一方法对其进行处理。如:

@RequestMapping({"/hello", "/hi", "/greetings"}) 

此外,该注解还有其他的属性,在一些情况下能发挥作用,如下一小节将讲到的method属性。

5. 为处理器方法指定HTTP请求方法

使用@RequestMapping注解的method属性,可以指定处理器方法支持的HTTP方法(包括GET、POST、PUT等)。如:

import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.RequestMapping 
import org.springframework.web.bind.annotation.RequestMethod; 
@Controller 
publicclassLoginController { 
    @RequestMapping(value = "/login", method = RequestMethod.GET) 
    publicString viewLogin() { 
        return"LoginForm"; 
    } 
    @RequestMapping(value = "/login", method = RequestMethod.POST) 
    publicString doLogin() { 
        return"Home"; 
    } 
} 

如上所示,对于同一个URL模式/login,该控制器有两个处理方法。第一个方法用于GET方法,第二个则用于POST方法。 了解更多@RequestMapping注解相关知识,参见@RequestMapping注解。

6. 将请求参数映射至处理器方法

SpringMVC的特征之一,就是可以使用@RequestParam注解将请求参数作为处理器方法的常规参数取回。这是一个将控制器从ServletAPI的HttpServletRequest接口中解耦出来的好方法。 如:

@RequestMapping(value = "/login", method = RequestMethod.POSTpublic String doLogin(@RequestParamString username @RequestParamString password) {} 

Spring将方法参数用户名及密码和命名相同的HTTP请求参数绑定到一起。这也就意味着可用如下方式调用一个URL(以GET请求方法为例):

http://localhost:8080/spring/login?username=scott&password=tiger 类型转换也自动完成了。如果对一个integer类型的参数声明如下:

@RequestParamint securityNumber 

则Spring会在处理方法中自动将请求参数的值(String类型)转换为指定类型(integer)。 为防止参数名与变量名不同,可将参数实名指定如下:

@RequestParam("SSN") int securityNumber 

@RequestParam注解还有另外两个属性,可在一些情况下发挥作用。其中一个属性是required,可指定一个参数是强制参数还是可选参数。如:

@RequestParam(required = false) String country 

这就意味着参数country是可选的,在请求中可略去。当请求中没有参数country时,则变量country为空值。 另一个属性是defaultValue,可在请求参数为空时充当回退值(fallbackvalue)。如:

@RequestParam(defaultValue = "18") int age 

当方法参数类型为Map

doLogin(@RequestParam Map<String, String> params) 

则映射参数包含所有键值对形式的请求参数。了解更多@RequestParam注解相关知识,参见@RequestParam注解。

7. 返回模型和视图

处理器方法在处理完业务逻辑后,会返回一个视图,该视图随后由Springdispatcher servlet进行解析。Spring支持handler方法返回String对象或ModelAndView对象。如下所示,handler方法返回了一个String对象,并表示了视图名LoginForm:

@RequestMapping(value = "/login", method = RequestMethod.GET) 
public String viewLogin() { 
    return"LoginForm"; 
} 

这是返回视图名最简单的方法。但是如果想要发送其他数据到视图,则必须返回ModelAndView对象。如:

@RequestMapping("/listUsers") 
public ModelAndView listUsers() { 
    List<User> listUser = new ArrayList<>(); 
    // get user list from DAO... 
    ModelAndView modelView = new ModelAndView("UserList"); 
    modelView.addObject("listUser", listUser); 
    return modelView; 
} 

如上所示,该处理器方法返回了一个ModelAndView对象,该对象视图名为UserList,并有一个可用在视图中的User对象集。 Spring是一个非常灵活的框架,支持将ModelAndView对象声明为处理器方法的参数,而无需再重新创建一个。因此,上例可以重写为:

@RequestMapping("/listUsers") 
public ModelAndView listUsers(ModelAndView modelView) { 
    List<User> listUser = new ArrayList<>(); 
    // get user list from DAO... 
    modelView.setViewName("UserList"); 
    modelView.addObject("listUser", listUser); 
    return modelView; 
} 

了解更多ModelAndView类相关知识,参见ModelAndView类。

8. 将对象放入模型

在MVC架构的应用程序中,控制器将数据输入到模型中,该模型则被用在视图中。从上一节中的举例中可以看到,ModelAndView类的addObject()用于将对象以名值对的形式放入模型中:

modelView.addObject("listUser", listUser); 
modelView.addObject("siteName", newString("CodeJava.net")); 
modelView.addObject("users", 1200000); 

Spring同样支持声明处理器方法中的Map类型参数。Spring使用这一映射存储将放入模型的对象。如:

@RequestMapping(method = RequestMethod.GET) 
publicStringviewStats(Map<String, Object> model) { 
    model.put("siteName", "CodeJava.net"); 
    model.put("pageviews", 320000); 
    return"Stats"; 
} 

这一方法比使用ModelAndView对象更加简单。Spring支持用户灵活选择Map对象和ModelAndView对象。

9. 处理器方法中的重定向

当条件允许时,只需在URL前加上redirect:/就可将用户重定向跳转到另一个URL。如:

// check login status.... 
if (!isLogin) { 
    returnnew ModelAndView("redirect:/login"); 
} 
// return a list of Users 

在上述代码中,没有登陆的用户将会跳转到/loginURL。

10. 处理表单提交和表单验证

Spring中的@ModelAttribute注解支持将表单字段绑定到表单返回对象,BingingRequest接口则支持验证表单字段。这使得处理表单提交变得非常简单。一个处理和验证表单数据的典型处理器方法的代码如下所示:

@Controller 
publicclassRegistrationController { 
    @RequestMapping(value = "/doRegister", method = RequestMethod.POST) 
    publicString doRegister( 
        @ModelAttribute("userForm") User user, BindingResult bindingResult) { 
        if (bindingResult.hasErrors()) { 
            // form validation error 
        } else { 
            // form input is OK 
        } 
        // process registration... 
        return"Success"; 
    } 
} 

了解更多@ModelAttribute注解和BindingResult接口相关知识,参见Spring官方文档:

  • Using @ModelAttribute on a method argument
  • Using @ModelAttribute on a method
  • Interface BindingResult

11. 处理文件上传

Spring支持自动将上传数据绑定到CommonsMultiparFile数组对象,这使得在处理器方法中处理文件上传变得非常简单。Spring使用Apache CommonsFileUpload作为深层多部分解析器(underlyingmultipart resolver)。 简单上传用户文件的代码如下所示:

@RequestMapping(value = "/uploadFiles", method = RequestMethod.POST) 
publicStringhandleFileUpload( 
        @RequestParam CommonsMultipartFile[] fileUpload) throws Exception { 
    for (CommonsMultipartFile aFile : fileUpload){ 
        // stores the uploaded file 
        aFile.transferTo(new File(aFile.getOriginalFilename())); 
    } 
    return"Success"; 
} 

了解Spring MVC处理文件上传的完整方法,参见Spring MVC 文件上传教程。

12. 在处理器中自动注入业务类

为了让控制器将业务逻辑处理委托到相关业务类,可以使用@Autowired注解,让Spring自动将业务类的实际实现注入到控制器中。如:

@Controller 
publicclassUserController { 
    @Autowired 
    private UserDAO userDAO; 
    publicString listUser() { 
        // handler method to list all users 
        userDAO.list(); 
    } 
    publicString saveUser(User user) { 
        // handler method to save/update a user 
        userDAO.save(user); 
    } 
    publicString deleteUser(User user) { 
        // handler method to delete a user 
        userDAO.delete(user); 
    } 
    publicString getUser(int userId) { 
        // handler method to get a user 
        userDAO.get(userId); 
    } 
} 

本例中所有与用户管理相关的业务逻辑都由UserDAO接口的实现提供。如:

interfaceUserDAO { 
    List<User> list(); 
    void save(User user); 
    void checkLogin(User user); 
} 

如上所示,使用@Autowired注解使处理器方法可以将任务委托到业务类:

List<User> listUser = userDAO.list(); 

了解更多@Autowired注解相关知识,参见Annotation TypeAutowired。

13. 获取HttpServletRequest和HttpServletResponse

有些情况要求在处理器方法中直接获取HttpServletRequest或HttpServletResponse对象。在Spring灵活的框架中,仅需给处理器方法加上一个相关参数就可以完成此任务。如:

@RequestMapping("/download") 
publicStringdoDownloadFile( 
        HttpServletRequest request, HttpServletResponse response) { 
    // access the request 
    // access the response 
    return"DownloadPage"; 
} 

Spring支持检测并自动将HttpServletRequest和HttpServletResponse对象注入到方法中。这样一来,就可以直接获取请求和响应,如获取InputStream、OutputStream或返回特定的HTTP代码。

14. 遵守单一职责原则

在Spring MVC中设计和编写控制器时,应遵循以下两个非常实用的操作:

  • 不要用控制器类来执行业务逻辑,应该用控制器类将业务处理委托到相关的业务类。这可以保证控制器专注于其指定职责,即控制应用程序的工作流。如:
@Controller 
publicclassUserController { 
    @Autowired 
    private UserDAO userDAO; 
    publicString listUser() { 
        // handler method to list all users 
        userDAO.list(); 
    } 
    publicString saveUser(User user) { 
        // handler method to save/update a user 
        userDAO.save(user); 
    } 
    publicString deleteUser(User user) { 
        // handler method to delete a user 
        userDAO.delete(user); 
    } 
    publicString getUser(int userId) { 
        // handler method to get a user 
        userDAO.get(userId); 
    } 
} 
给每个业务领域创建一个独立的控制器。如,用UserController控制用户管理的工作流,用OrderController控制订单处理的工作流,等等:
@Controller 
publicclassUserController { 
} 
@Controller 
publicclassProductController { 
} 
@Controller 
publicclassOrderController { 
} 
@Controller 
publicclassPaymentController { 
} 

以上就是本文全部内容,希望这14个小技巧可以帮助读者准确且高效地编写Spring MVC中的控制器类代码。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK