吐血整理!14個(gè)編寫Spring MVC控制器的實(shí)用小技巧
本文介紹了編寫Spring MVC框架的控制器(controller)的基礎(chǔ)技巧和最佳操作。在Spring MVC框架中,編寫控制器類通常是為了處理用戶提出的請求。
編寫完成后,控制器會調(diào)用一個(gè)業(yè)務(wù)類來處理業(yè)務(wù)相關(guān)任務(wù),進(jìn)而重定向客戶到邏輯視圖名。Springdispatcher servlet會對邏輯視圖名進(jìn)行解析,并渲染結(jié)果或輸出。這就是一個(gè)典型的“請求—響應(yīng)”的完整流程。
1. 使用@controllerstereotype
創(chuàng)建一個(gè)能夠處理單個(gè)或多個(gè)請求的控制器類,最簡單的方法就是使用@controllerstereotype注解一個(gè)類,如:
- 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()方法通過重定向跳轉(zhuǎn)到視圖名home來處理應(yīng)用程序內(nèi)容路徑(/)收到的請求。 注意:只有在Spring配置文件中啟用了注解驅(qū)動,才能使用@controllerstereotype。
- <annotation-driven/>
啟用注解驅(qū)動后,Spring的容器(container)會自動掃描如下包中的類:
- <context:component-scanbase-packagecontext:component-scanbase-package="net.codejava.spring"/>
帶有@controller注解的類會被標(biāo)記成控制器。由于其簡單方便,且不再需要對配置文件中的控制器聲明beans,這一方法非常實(shí)用。 注意:使用@controller注解可以創(chuàng)建一個(gè)多動作控制器類,可同時(shí)處理多個(gè)不同的請求。如:
- @Controller
- publicclassMultiActionController {
- @RequestMapping("/listUsers")
- public ModelAndView listUsers() {
- }
- @RequestMapping("/saveUser")
- public ModelAndView saveUser(User user) {
- }
- @RequestMapping("/deleteUser")
- public ModelAndView deleteUser(User user) {
- }
- }
如上所示,有三個(gè)處理器(handler)在分別處理三個(gè)請求,/listUsers,/saveUser,和/deleteUser。
2. 實(shí)現(xiàn)控制器接口
在Spring MVC中創(chuàng)建控制器還可以用另一個(gè)經(jīng)典的方法,即對一個(gè)類實(shí)現(xiàn)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");
- }
- }
實(shí)現(xiàn)類必須重寫handleRequest()方法(當(dāng)收到相匹配的請求時(shí),Spring dispatcher servlet會調(diào)用handleRequest)。由該控制器處理的請求URL模式在Spring的內(nèi)容配置文件中的定義如下:
- <beannamebeanname="/main"class="net.codejava.spring.MainController"/>
這一方法的缺點(diǎn)在于其控制類無法同時(shí)處理多個(gè)請求URL。
3. 繼承AbstractController類
如果想要輕松控制受支持的HTTP方法、會話和內(nèi)容緩存,讓控制類繼承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");
- }
- }
上例創(chuàng)建了一個(gè)配置了受支持的方法、會話和緩存的單動作控制器,能夠在控制器的bean聲明中被指明。如:
- <beannamebeanname="/big"class="net.codejava.spring.BigController">
- <propertynamepropertyname="supportedMethods"value="POST"/>
- </bean>
這一配置表明該控制器handler方法僅支持POST方法。了解更多配置(如會話、緩存),參見AbstractController。 SpringMVC還提供了多個(gè)支持特定目的的控制器類,包括:
- AbstractUrlViewController
- MultiActionController
- ParameterizableViewController
- ServletForwardingController
- ServletWrappingController
- UrlFilenameViewController
4. 為處理器指定URL映射
這是編寫控制器類必不可少的一步,旨在處理一個(gè)及以上特定請求。Spring MVC提供了@RequestMapping注解,用于指定URL映射。如:
- @RequestMapping("/login")
這一步映射了URL模式/login,并用注解或注解類對其進(jìn)行了處理。@RequestMapping注解用于類上時(shí),類變成了單動作控制器。如:
- 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注解用于方法上時(shí),則可生成多動作控制器。如:
- 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注解也可用于指定多個(gè)URL模式,并用單一方法對其進(jìn)行處理。如:
- @RequestMapping({"/hello", "/hi", "/greetings"})
此外,該注解還有其他的屬性,在一些情況下能發(fā)揮作用,如下一小節(jié)將講到的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";
- }
- }
如上所示,對于同一個(gè)URL模式/login,該控制器有兩個(gè)處理方法。第一個(gè)方法用于GET方法,第二個(gè)則用于POST方法。 了解更多@RequestMapping注解相關(guān)知識,參見@RequestMapping注解。
6. 將請求參數(shù)映射至處理器方法
SpringMVC的特征之一,就是可以使用@RequestParam注解將請求參數(shù)作為處理器方法的常規(guī)參數(shù)取回。這是一個(gè)將控制器從ServletAPI的HttpServletRequest接口中解耦出來的好方法。 如:
- @RequestMapping(value = "/login", method = RequestMethod.POSTpublic String doLogin(@RequestParamString username @RequestParamString password) {}
Spring將方法參數(shù)用戶名及密碼和命名相同的HTTP請求參數(shù)綁定到一起。這也就意味著可用如下方式調(diào)用一個(gè)URL(以GET請求方法為例):
http://localhost:8080/spring/login?username=scott&password=tiger 類型轉(zhuǎn)換也自動完成了。如果對一個(gè)integer類型的參數(shù)聲明如下:
- @RequestParamint securityNumber
則Spring會在處理方法中自動將請求參數(shù)的值(String類型)轉(zhuǎn)換為指定類型(integer)。 為防止參數(shù)名與變量名不同,可將參數(shù)實(shí)名指定如下:
- @RequestParam("SSN") int securityNumber
@RequestParam注解還有另外兩個(gè)屬性,可在一些情況下發(fā)揮作用。其中一個(gè)屬性是required,可指定一個(gè)參數(shù)是強(qiáng)制參數(shù)還是可選參數(shù)。如:
- @RequestParam(required = false) String country
這就意味著參數(shù)country是可選的,在請求中可略去。當(dāng)請求中沒有參數(shù)country時(shí),則變量country為空值。 另一個(gè)屬性是defaultValue,可在請求參數(shù)為空時(shí)充當(dāng)回退值(fallbackvalue)。如:
- @RequestParam(defaultValue = "18") int age
當(dāng)方法參數(shù)類型為Map
- doLogin(@RequestParam Map<String, String> params)
則映射參數(shù)包含所有鍵值對形式的請求參數(shù)。了解更多@RequestParam注解相關(guān)知識,參見@RequestParam注解。
7. 返回模型和視圖
處理器方法在處理完業(yè)務(wù)邏輯后,會返回一個(gè)視圖,該視圖隨后由Springdispatcher servlet進(jìn)行解析。Spring支持handler方法返回String對象或ModelAndView對象。如下所示,handler方法返回了一個(gè)String對象,并表示了視圖名LoginForm:
- @RequestMapping(value = "/login", method = RequestMethod.GET)
- public String viewLogin() {
- return"LoginForm";
- }
這是返回視圖名最簡單的方法。但是如果想要發(fā)送其他數(shù)據(jù)到視圖,則必須返回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;
- }
如上所示,該處理器方法返回了一個(gè)ModelAndView對象,該對象視圖名為UserList,并有一個(gè)可用在視圖中的User對象集。 Spring是一個(gè)非常靈活的框架,支持將ModelAndView對象聲明為處理器方法的參數(shù),而無需再重新創(chuàng)建一個(gè)。因此,上例可以重寫為:
- @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類相關(guān)知識,參見ModelAndView類。
8. 將對象放入模型
在MVC架構(gòu)的應(yīng)用程序中,控制器將數(shù)據(jù)輸入到模型中,該模型則被用在視圖中。從上一節(jié)中的舉例中可以看到,ModelAndView類的addObject()用于將對象以名值對的形式放入模型中:
- modelView.addObject("listUser", listUser);
- modelView.addObject("siteName", newString("CodeJava.net"));
- modelView.addObject("users", 1200000);
Spring同樣支持聲明處理器方法中的Map類型參數(shù)。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. 處理器方法中的重定向
當(dāng)條件允許時(shí),只需在URL前加上redirect:/就可將用戶重定向跳轉(zhuǎn)到另一個(gè)URL。如:
- // check login status....
- if (!isLogin) {
- returnnew ModelAndView("redirect:/login");
- }
- // return a list of Users
在上述代碼中,沒有登陸的用戶將會跳轉(zhuǎn)到/loginURL。
10. 處理表單提交和表單驗(yàn)證
Spring中的@ModelAttribute注解支持將表單字段綁定到表單返回對象,BingingRequest接口則支持驗(yàn)證表單字段。這使得處理表單提交變得非常簡單。一個(gè)處理和驗(yàn)證表單數(shù)據(jù)的典型處理器方法的代碼如下所示:
- @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接口相關(guān)知識,參見Spring官方文檔:
- Using @ModelAttribute on a method argument
- Using @ModelAttribute on a method
- Interface BindingResult
11. 處理文件上傳
Spring支持自動將上傳數(shù)據(jù)綁定到CommonsMultiparFile數(shù)組對象,這使得在處理器方法中處理文件上傳變得非常簡單。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. 在處理器中自動注入業(yè)務(wù)類
為了讓控制器將業(yè)務(wù)邏輯處理委托到相關(guān)業(yè)務(wù)類,可以使用@Autowired注解,讓Spring自動將業(yè)務(wù)類的實(shí)際實(shí)現(xiàn)注入到控制器中。如:
- @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);
- }
- }
本例中所有與用戶管理相關(guān)的業(yè)務(wù)邏輯都由UserDAO接口的實(shí)現(xiàn)提供。如:
- interfaceUserDAO {
- List<User> list();
- void save(User user);
- void checkLogin(User user);
- }
如上所示,使用@Autowired注解使處理器方法可以將任務(wù)委托到業(yè)務(wù)類:
- List<User> listUser = userDAO.list();
了解更多@Autowired注解相關(guān)知識,參見Annotation TypeAutowired。
13. 獲取HttpServletRequest和HttpServletResponse
有些情況要求在處理器方法中直接獲取HttpServletRequest或HttpServletResponse對象。在Spring靈活的框架中,僅需給處理器方法加上一個(gè)相關(guān)參數(shù)就可以完成此任務(wù)。如:
- @RequestMapping("/download")
- publicStringdoDownloadFile(
- HttpServletRequest request, HttpServletResponse response) {
- // access the request
- // access the response
- return"DownloadPage";
- }
Spring支持檢測并自動將HttpServletRequest和HttpServletResponse對象注入到方法中。這樣一來,就可以直接獲取請求和響應(yīng),如獲取InputStream、OutputStream或返回特定的HTTP代碼。
14. 遵守單一職責(zé)原則
在Spring MVC中設(shè)計(jì)和編寫控制器時(shí),應(yīng)遵循以下兩個(gè)非常實(shí)用的操作:
- 不要用控制器類來執(zhí)行業(yè)務(wù)邏輯,應(yīng)該用控制器類將業(yè)務(wù)處理委托到相關(guān)的業(yè)務(wù)類。這可以保證控制器專注于其指定職責(zé),即控制應(yīng)用程序的工作流。如:
- @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);
- }
- }
- @Controller
- publicclassUserController {
- }
- @Controller
- publicclassProductController {
- }
- @Controller
- publicclassOrderController {
- }
- @Controller
- publicclassPaymentController {
- }
以上就是本文全部內(nèi)容,希望這14個(gè)小技巧可以幫助讀者準(zhǔn)確且高效地編寫Spring MVC中的控制器類代碼。