Spring+JSP頁面傳遞值的筆記

平常都在用Spring+REST api的設計,在參數的傳遞也習慣用JSON,突然間最基本的Spring+Servlet+JSP的寫法反而忘記,趕緊來筆記一下。

✦ Controller裡面的 HttpServletRequest request:

request.getParameter() 一般是指Client透過URL Query傳值到Server。一般只能傳送String。

https://www.ajoshow.com/servlet?parameter=1

request.getAttribute() 一般用於Server端取值,例如從Servlet設值,JSP取值,可以傳遞的值不限於String,也可以是物件。

✦ 在Spring MVC中,三個主要傳遞值用的物件為Model、ModelMap、ModelAndView。

三個主要的作用都像request.setAttribute(),唯不同的是Model是interface;ModelMap是class,有點像Map加強版,也是把值最後傳遞至View上;最後ModelAndView則較像是ModelMap和View的容器,方便把設值跟喧染畫面放在同一個回傳。

@RequestMapping(...)
public String sample(Model model) {
    model.addAttribute("message", "Hello World!!");
    return "hello";
}

@RequestMapping(...)
public String sample2(ModelMap modelMap) {
    modelMap.addAttribute("message", "Hello World!!");
    return "hello";
}

@RequestMapping(...)
public ModelAndView sample3(ModelMap modelMap) {
    return new ModelAndView("hello", "message", "Hello World!!");
}

✦ 頁面跳轉有兩種方式redirect、forward。

redirect:伺服器通知客戶端,讓其重新請求一次。其原本參數狀態不被保留。

forward:伺服器端內部重新定向。通常是導向至JSP (View)。參數狀態會被傳遞。

可以透過HttpServletRequest、HttpServletResponse達到上述動作。

redirect方式:

response.sendRedirect("/page.jsp"); // HttpServletResponse

forward方式:

request.getRequestDispatcher("/page.jsp").forward(request, response);

在Controller導頁也可以下列方式實現:

return new ModelAndView("redirect:/index");
// or
return "redirect:/index";

✦ 如果要帶參數一個方式是直接加在URL後面如redirect:/index?param=hello。或是可以透過RedirectAttributes來做到導頁參數傳遞:

@RequestMapping(...)
public String sample(RedirectAttributes redirectAttributes) {
    redirectAttributes.addAttribute("param", "hello");
    return "redirect:/index";
}

你會發現不管使用哪種方式,最後頁面上的URL都會有?param=hello在後面。

如果參數是想要從某一頁面導到index JSP上使用,例如${attr},那麼可以使用RedirectAttributes.addFlashAttribute()來實現。該參數利用FlashMap暫存在Session裡面,導向至該頁面後將被予刪除。

@RequestMapping(...)
public String sample(RedirectAttributes redirectAttributes) {
    redirectAttributes.addFlashAttribute("attr", "world");
    return "redirect:/index";
}

再來一個example:

@RequestMapping("/page1")
private String page1(@RequestParam String param1, RedirectAttributes ra) {
    ra.addAttribute("param1", param1);
    ra.addFlashAttribute("attr1", "attr1");
    System.out.println(param1);
    return "redirect:/page2";
}

@RequestMapping("/page2")
private ModelAndView page2(@RequestParam String param1, @ModelAttribute("attr1") String attr1) {
    System.out.println(param1);
    System.out.println(attr1);
    return new ModelAndView("/page2");
}

我們可以透過@RequestParam來接url後面的參數,而用@ModelAttribute來接伺服器端設置的attribute。這邊需要注意的部分是@ModelAttribute有特別帶”attr1″,因為預設name會是宣告類型的class name。

上述提到ModelAndView也可以實現導頁,而Model當然也可以取得到透過addFlashAttribute()裡面的值:

model.asMap().get("attr1");

✦ 另一種導頁方式是透過回傳RedirectView。使用這個物件提供一些便於設定url parameters和導址的方法,而其中一個值得注意的是setContextRelative()。假設有一個網址為https://www.ajoshow.com/app/index,當這個值為true時,會以application context為首,也就是/app/{your_path};為false時,那就是以host為root位置,也就是 /{your_path}。一般在同一個內部應用來說,預設為false,建議設定為true。

@RequestMapping(...)
public RedirectView  save(RedirectAttributes redirectAttrs) {
  redirectAttrs.addAttribute("msg", "Hello World!");
  redirectAttrs.addFlashAttribute("attr1", "attr1");
    
  RedirectView redirectView = new RedirectView();
  redirectView.setContextRelative(true);
  redirectView.setUrl("/page/{msg}");
  return redirectView;
}

RedirectView雖然在設定url parameter挺方便的,也提供url placeholder使用,但如果要傳遞attribute時,可能還是需要RedirectAttributes的輔助。RedirectView在某一層面上必須讓Controller綁定回傳RedirectView物件,如果突然要取消導頁,這樣的綁定回傳其實並不太友善,反而是透過回傳ModelAndView的方式會相對彈性許多。

@RequestMapping(...)
private ModelAndView sample() {
 return new ModelAndView(new RedirectView("/page2", true), "attr1", "attr1");
}

✦ 基本的參數傳遞這邊先告一個段落,接著說說參數綁定,可以透過下列幾個annotation實現:@RequestHeader、@CookieValue、@PathVariable、@RequestParam、@RequestBody、@SessionAttributes、@SessionAttribute、@ModelAttribute、@RequestAttribute。

@RequestHeader :取Header的值

/* Sample header
 *
 * Host                    localhost:8080  
 * Accept-Encoding         gzip,deflate  
 * Keep-Alive              300 
 */

@RequestMapping(...)  
public void getHeader(@RequestHeader("Accept-Encoding") String encoding,  
                              @RequestHeader("Keep-Alive") long keepAlive)

@CookieValue:取Header中Cookie參數中各個項目的值

/* Sample cookie
 *
 * JSESSIONID=678A4AC47B213DCBE0B2A1AA689CBC84  
 */

@RequestMapping(...)  
public void getCookie(@CookieValue("JSESSIONID") String cookie)

@PathVariable:取URL路徑上的變數

@RequestMapping("/books/{bookId})  
public void getPath(@PathVariable String bookId)

@RequestParam:取URL後面的參數,也可拿來取Post Body裡面data的值,適合用來處理簡單型別的綁定,一般用於取Content-Type為application/x-www-form-urlencoded的POST、GET。

@RequestBody:常用於取Content-Type為非application/x-www-form-urlencoded類型,通常如application/json 、application/xml等,複雜型別的綁定,透過HttpMessageConverters解析Post data body,綁定到相對應的bean。

@SessionAttributes:綁定Session裡的attribute。

@Controller
@RequestMapping(...)
@SessionAttributes(value={"attr1","attr2"})
public class Demo {
    
    @RequestMapping("index")
    public ModelAndView index() {
        ModelAndView mav = new ModelAndView("page2");
        mav.addObject("attr1", "attr1");
        mav.addObject("attr2", "attr2");
        return mav;
    }
    
    @RequestMapping("/page2")
    public ModelAndView page2(@ModelAttribute("attr1")String attr1, @ModelAttribute("attr2")String attr2) {
        ModelAndView mav = new ModelAndView("end");
        return mav;
    }
}

*Scope 的層級可分為 application、session、request、page。上述程式碼在沒有用@SessionAttributes時attr1、attr2的scope是在request級別,有用時則會同步該參數至session裡面。如果要刪除該值,則在Controller裡面調入SessionStatus.setComplete()即可。

@RequestMapping(...)
public ModelAndView sample(SessionStatus status) {
  status.setComplete();
  return new ModelAndView(...);
}

@SessionAttribute:從Session中取值。

@ModelAttribute:可用於方法上或是參數上。用於方法上會在呼叫@RequestMapping前調用,用於參數上則是拿來取session或request裡的attribute。

// Add one attribute
// The return value of the method is added to the model under the name "account"
// You can customize the name via @ModelAttribute("myAccount")

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountManager.findAccount(number);
}

// Add multiple attributes

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountManager.findAccount(number));
    // add more ...
}

// 參考: https://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-method

@RequestAttribute:新增於Spring 4.3,從 controller裡取request attribute。該attribute通常是在controller之前被設置,例如從filter或interceptor裡面的request.setAttribute後所傳遞的值。

✦ 綜合以上來個簡單的應用範例,嘗試傳遞多個Person的物件 。Post至page1 controller,自動產生5個Person物件並將陣列存放至People物件裡,導頁傳遞至page2 controller,再forward到page2 JSP,將剛剛產生的Person一一列出,該頁面可以再重新submit到page2 controller,然後於此取得到手動輸入的People物件,再重新forward至 page2 JSP。

public class People {
    private List<Person> persons;

    public List<Person> getPersons() {
        return persons;
    }

    public void setPersons(List<Person> persons) {
        this.persons = persons;
    }
}
public class Person {
    private String firstname;
    private String lastname;
    private int age;

    public String getFirstname() {
        return firstname;
    }

    public void setFirstname(String firstname) {
        this.firstname = firstname;
    }

    public String getLastname() {
        return lastname;
    }

    public void setLastname(String lastname) {
        this.lastname = lastname;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Person{");
        sb.append("firstname='").append(firstname).append('\'');
        sb.append(", lastname='").append(lastname).append('\'');
        sb.append(", age=").append(age);
        sb.append('}');
        return sb.toString();
    }
}

// page2.jsp

<form:form method="post" action="/page2" modelAttribute="people">
     <c:forEach varStatus="status" var="person" items="${ people.persons }">
          <input type="text" name="persons[${status.index}].firstname" value="${person.firstname}" />
          <input type="text" name="persons[${status.index}].lastname" value="${person.lastname}" />
          <input type="text" name="persons[${status.index}].age" value="${person.age}" />
          <br/>
     </c:forEach>
     <input type="submit" value="submit" />
</form:form>

// controller

@RequestMapping(value = "/page1")
    private ModelAndView page1(RedirectAttributes ra) {
        List<Person> persons = new ArrayList<>();
        for(int i=0; i<5; i++){
            Person person = new Person();
            person.setFirstname("person" + i);
            person.setLastname("person" + i);
            person.setAge(i);
            persons.add(person);
        }
        People ppl = new People();
        ppl.setPersons(persons);
        ra.addFlashAttribute("people", ppl);
        return new ModelAndView("redirect:/page2" );
    }

    @RequestMapping(value = "/page2")
    private ModelAndView page2(@ModelAttribute("people") People people) {
        for(Person person : people.getPersons()){
            System.out.println(person);
        }
        return new ModelAndView("/page2", "people", people);
    }

 

參考:

  • https://stackoverflow.com/questions/5243754/difference-between-getattribute-and-getparameter
  • https://docs.spring.io/spring/docs/4.3.14.RELEASE/spring-framework-reference/htmlsingle/#mvc-coc-modelmap
  • https://www.concretepage.com/spring/spring-mvc/spring-mvc-redirectview-example-add-fetch-flash-attributes-redirectattributes-model-requestcontextutils
  • https://stackoverflow.com/questions/14470111/spring-redirectattributes-addattribute-vs-addflashattribute
  • https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/view/RedirectView.html
  • https://blog.csdn.net/walkerjong/article/details/7946109
  • https://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-methods

Leave a Reply

當第一個留言者!

avatar