Spring MVC argument metody z @RequestMapping

0

@Shalom więc pisze :)

Zastanawia mnie jedna rzecz, bo czytałem w książce jeden przypadek, ze tak:
metoda zwracala stringa z jakąś tam nazwą strony, a jako argument tej metody była mapa< string, object> o nazwie model. I do niej autor zrobił model.put("nazwa",obiekt); a potem w pliku .jsp odwoływał się przez: ${nazwa}. I myślę skąd spring wiedział ze ma te dane wziąć z tej mapy? :D zawsze argumentów w metodzie moze byc pare i co wtedy?

2

Ok, więc po kolei :)

  1. Metoda z @RequestMapping może przyjmować dane (np. formularz który ktoś wysłał) albo je przekazywać do widoku.
  2. Metoda może mieć wiele argumentów i spring je sobie automatycznie zbinduje po typie, a jeśli to nie będzie możliwe to ewentualnie możemy specyfikować im nazwy za pomocą @ModelAttribute: http://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-method-args
    Alternatywnie możemy przyjąć jako argument całą mapę ze wszystkimi obiektami modelu które lecą z widoku, ale to nas mocno ogranicza w kontekście na przykład walidacji: http://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/validation.html#validation-mvc-triggering .
  3. Jeśli chcemy przesłać dane do Widoku to nie wystarczy nam zwrócenie z metody samego stringa (ktory określa nazwę widoku) ale musimy też przekazać obiekty modelu. Możemy to zrobić na kilka sposobów.
  • Możemy stworzyć mapę obiektów a potem wepchnąć tą mapę do kontruktora ModelAndView
  • Możemy stworzyć ModelAndView a potem wywoływać na nim metodę dodającą kolejne obiekty do modelu (ja preferuje to rozwiązanie)
0

@Shalom wielkie dzięki za odpowiedz ;)
rozumiem już jaka jest różnica miedzy Stringiem a ModelAndView, ale mam wątpliwości co do punktu 1 i 2

  1. Załóżmy, ze mam na stronie formularz w ktorym mam pola do wpisania np. Imie, Nazwisko, Wiek. Na pewno bede miał gdzieś tam POJO: User które ma pola imie nazwisko wiek. I teraz skąd mój kontroler będzie wiedział, że dane z tego formularza ma wrzucic akurat w te właściwe pola obiektu User?

  2. a te argumenty metody to może być np. co? właśnie tutaj mam zrobić argumenty String imie, String nazwisko, int wiek i je w jakiś sposób zbindować z danymi z widoku (strony jsp)?

Szperałem w internecie i znalazłem taki example: http://viralpatel.net/blogs/spring-3-mvc-handling-forms/
Wygląda na bardzo krótki i zrozumiały i pewnie dla ciebie taki będzie :D ale ja troszke nie jarze takich.. "powiązań" między odpowiednimi rzeczami w kodzie

Trochę wydaje mi się to pokićkane, przez ostatnie dni musiałem isc do pracy i trochę zastój mam :P
z góry dziekuje za cierpliwość

1
  1. To dość proste. Do modelu dodajesz obiekt User i nadajesz mu pewną nazwę, bo przechowujesz to w mapie. W warstwie widoku odwołujesz się do tego obiektu za pomocą jego nazwy i bindujesz formularz do konkretnych pól tego obiektu.
  2. Jeśli bardzo chcesz to możesz tak zrobić, ale to niewygodne. Lepiej zrobić obiekt User który ma takie pola, settery i gettery i przekazywać cały taki obiekt :)

Ten example jest trochę słaby bo korzysta z pewnych "ułatwień" które nie są zbyt oczywiste ;] Jeśli nie zdefiniujesz nazwy obiektu do którego bindujesz pola formularza to Spring zakłada że obiekt nazywa się "command". Dlatego też w tym example masz potem bindowanie bezpośrednio do konkretnych nazw pól, bez specyfikowania do jakiego obiektu się to odnosi. Moim zdaniem to bardzo nieładne rozwiązanie.
Patrz tutaj:
https://github.com/pawel-k/graph-planner/blob/master/client/src/main/webapp/WEB-INF/pages/input.ftl
obiekt do którego chce bindować zostaje przesłany z kontrolera pod nazwą "formBean" i ładnie widać tutaj jak jest bindowany do formularza.

BTW moim zdaniem warto zainteresować się Freemarkerem albo Velocity zamiast JSTL, ale to tylko taka moja sugestia :)

0

1.Hmm w takim razie moim modelem będzie mapa (o której pisałeś wyżej, że ciężko będzie wykonać walidacje?) i w niej klucz w postaci Stringa i obiekt new User() do którego będę bindował pola z jsp do odpowiednich pól obiektu new User(). Po przejrzeniu tego exampla Freemarkera co wysłałeś z githuba, wygląda mi to trochę jak w JSF podpisywanie pod pola bean'a ( ManagedBean(name = "nazwa") i używanie w stronie .xhtml: #{nazwa.imie}, #{nazwa.nazwisko}, #{nazwa.wiek} itp)

2.Czyli da się :) muszę zrozumieć mechanizm działania tych metod, tak dokładnie bo mnie to ciekawi :P

Samo to, co określasz słowem "model" zaczyna swój żywot w momencie wywołania metody z @RequestMapping i ten model to jest Map/ModelMap jako argument tej metody?

Co do Freemarkera i Velocity - taki właśnie miałem zamiar, ale doszedłem do wniosku ze poznawanie logiki działania Springa MVC to i tak dla mnie duża nowość, a że JSTL troszeczke juz liznąłem to postanowiłem, przynajmniej na czas obeznania ze springiem mvc, zostac przy jstl. Potem najwyżej zmienie :)

#edit
czytam teraz książke o której wcześniej wspominałem. używają tutaj czegoś takiego co sie nazywa Tiles. Co o tym myślisz @Shalom?

1
  1. Nie nie. Modelem zawsze jest mapa. Ale możesz ją przesłać do metody w calości po prostu jako mapę, albo przesłać obiekty "osobno", co jest wygodniejsze i ładniejsze :) Ale ogólnie tak, przesyłasz mapę i w Widoku masz dostęp do jej elementów na podstawie ich nazw w mapie.

Model jest mapa obiektów które lecą do Widoku - to co widok ma wyświetlić. Ewentualnie to są obiekty do ktorych widok coś będzie przypisywał. Trochę jak ManageBeany z JSF ale musisz ręcznie wskazać co leci do widoku a co nie i scope jest RequestScope (chociaż można utworzyć SessionAttributes)
Nigdy nie korzystałem z Tiles, ale widzę że nowy spring ma już integracje z tym frameworkiem:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/view.html#view-tiles
więc jak chcesz możesz korzystać, ale nie wiem czy będzie to takie wygodne jak z Freemarkerem albo Velocity

0

Skodziłem taki mały "prototyp":

HomeController:

@Controller
public class HomeController {
    private SthService sthService;

    @Autowired
    public HomeController(SthService sthService) {
        this.sthService = sthService;
    }

    @RequestMapping(value = {"/", "/home"})
    public String showHomePage(@RequestParam(value = "napis", required = false, defaultValue = "nie podales parametru")
                                   String napis, ModelMap model){
        model.addAttribute("tekst", sthService.serviceGetString());
        model.addAttribute("juzer", sthService.returnSomeUser());
        model.addAttribute("napisik", napis);
        return "home";
    }
}

sthService to jakis tam prosty komponent @Service z 2 metodami zwracajacymi kolejno: stringa i User

I strona JSP:

<html>
<head>
    <title></title>
    <link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
     <div id="content">
         ${tekst}
         <br>${juzer.imie}
         <br>${juzer.nazwisko}
         <br>${juzer.wiek}
         <br>
         <br>${napisik}
     </div>
</body>
</html>

I generalnie już rozumiem mniej-więcej jak to działa :) nadajesz się na nauczyciela @Shalom, ale programiście lepiej płacą haha
Tylko zastanawiam się jak przesłać obiekty osobno tak jak mówisz, nie już "spakowane" w mapie? pewnie jakoś za pomocą tej adnotacji @ModelAttribute? Jeśli użyje jej na argumencie metody:

public String showHomePage(@ModelAttribute("uzytkownik") User user, ModelMap model)

to wtedy do modelu (tylko pod jaką nazwą bedzie wtedy widniała mapa z tymi atrybutami) dodaje sobie kolejną wartość z kluczem(String): "uzytkownik" i wartością (Object - a dokładniej User) user?

Odnośnie Tiles - rozumiem ze ten framework robi tą samą robotę co Freemarker/Velocity/JSTL - tylko to po prostu kolejna propozycja? Gdzieś czytałem, ze "Tiles can be used with Freemarker and Velocity" i zastanawiam się czy czytałem prawde :P

#edit
tak właściwie to jeszcze nie napisałem pełnego prototypu, bo nie zrobiłem przesyłania formularza

dodanie znaczników <code class="html"> i <code class="java"> - Furious Programming

0
  1. Tak, możesz przesłać to w ten sposób. Możesz też nie dawać adnotacji i wtedy nawa będzie taka jak nazwa argumentu metody. Nie przesyłaj argumentu ModelMap jeśli nie jest ci potrzebny (a prawie nigdy nie jest), zaciemniasz niepotrzebnie sygnaturę metody.
  2. Tiles bazuje na "składni" JSTL ale można go zintegrować ze składnią Freemarkera ;]
0

1.Ale jeśli nie prześle ModelMap to do czego wtedy wrzuce np. dane z formularza, albo do czego dodam te informacje które dostaje od SthService?
edit: wtedy model nazywać sie bedzie "command"?
2.to spróbuje coś porobić, przynajmniej na potrzeby książki :P a nuż się przyda kiedyś

0
  1. Nie. Zrobisz to wtedy jak normalny człowiek. Tworząc obiekt ModelAndView i dodając do niego obiekty które chcesz przekazac do formularza. A potem po prostu tworząc argumenty metody która ma przyjąć dane z formularza. Przykład:
    https://github.com/pawel-k/graph-planner/blob/master/client/src/main/java/pl/edu/agh/ztis/client/controllers/MainController.java
0

Kurcze.. faktycznie, przecież można to zrobić ModelAndView :)
W pierwszym poście napisałeś, że do widoku dane do wyświetlenia (model) można przekazać na kilka sposobów:

  • Możemy stworzyć mapę obiektów a potem wepchnąć tą mapę do kontruktora ModelAndView
  • Możemy stworzyć ModelAndView a potem wywoływać na nim metodę dodającą kolejne obiekty do modelu (ja preferuje to rozwiązanie)

1.Jednak widzę, że tutaj można też jako argument metody podać ModelMap/Model/Map< string, object> i też można jako-tako, ale przekazać te dane do widoku. Ciekawi mnie jakie zalety ma (jeśli ma) tworzenie ModelMap zamiast zwracanie ModelAndView?

  1. co do obsługi formularza, to rozumiem, ze widok (np pliki .jsp) są jakoś powiązane z modelem który w momencie akceptacji wypełniam danymi i wysyłam do controllera jako atrybut tej metody z @RequestMapping?
    Inaczej: chce zrobic formularz gdzie podaje imie i nazwisko i zapisywac je gdzieś tam w bazie itp. robie tak:
    1.tworze obiekt User, pola imie nazwisko, gettery i settery
    2.tworze kontroller z metodą @RequestMapping, jako parametr podaje "User user" (daje tutaj @ModelAttribute?)
    3.robie widok jsp, tworze formularz w którym przy inputach w atrybucie path podaje nazwe zmiennych z klasy User - imie i nazwisko
    4.w kontrolerze robie jakies wywołanie serwisu ktory zapisze usera

Mniej-wiecej dobrze to rozumiem @Shalom? zaraz spróbuje napisać jakiś prosty przykład

1

1.Jednak widzę, że tutaj można też jako argument metody podać ModelMap/Model/Map< string, object> i też można jako-tako, ale przekazać te dane do widoku. Ciekawi mnie jakie zalety ma (jeśli ma) tworzenie ModelMap zamiast zwracanie ModelAndView?

Moim zdaniem nie ma ;] Tzn ułatwia trochę pracę jeśli przekazujesz "dalej" dużo obiektów Modelu. Tzn np. ładujesz jakieś obiekty w kontrolerze, przesyłasz do widoku, z tego widoku wysyłasz formularz, który powoduje że dochodzą ci dodatkowe obiekty do Widoku, ale te poprzednie też chcesz zachować. Wyobraź sobie taki wielostopniowy formularz na przykład ;] Może się okazać że wygodniej operować wtedy na całej mapie z obiektami przechowującymi stan wypełnionych stron formularza niż na obiektach osobno i dodawać je do ModelAndView przy przechodzeniu na kolejną podstronę formularza.

2.tworze kontroller z metodą @RequestMapping, jako parametr podaje "User user" (daje tutaj @ModelAttribute?)

Tak, dajesz tutaj @ModelAttribute bo tak jest elegancko i od razu masz w konkretnym miejscu w kodzie nadaną nazwę dla tego atrybutu :) Ale ja rozumiem że to jest twoja metoda "obsługująca" zapisanie usera. Bo rozumiesz że potrzebne ci są 2 metody. Jedna do wyświetlania formularza a druga do obsługi "submita". To co opisujesz pasuje do tej drugiej. Teoretycznie mógłbyś także tą pierwszą metodę w ten sposób zrobić z @ModelAttribute ale nie chce ci tu za bardzo mieszać ;) Bo możesz też tak zrobić, że dasz argumenty metody kontrolera z takimi ModelAttribute i one automatycznie zostaną utworzone i dodane do modelu. Ma to tez pewne zalety kiedy na przykład submitujesz formularz a chciałbyś żeby po submitowaniu nadal widoczny był ten wypełniony formularz. Patrz przykład na dole.

3.robie widok jsp, tworze formularz w którym przy inputach w atrybucie path podaje nazwe zmiennych z klasy User - imie i nazwisko

Prawie. Musisz podać też nazwę obiektu - nazwę którą podajesz jak dodajesz obiekt do ModelAndView.

    @RequestMapping(value = "/display1",  method = RequestMethod.GET)
    public ModelAndView display1(@ModelAttribute("inputBean") InputFormBean inputBean) {
        return new ModelAndView("display");
    }

    @RequestMapping(value = "/display2",  method = RequestMethod.GET)
    public ModelAndView display2() {
        ModelAndView mav = new ModelAndView("display");
        mav.addObject("inputBean", new InputFormBean());
        return mav
    }

    @RequestMapping(value = "/add",  method = RequestMethod.POST)
    public ModelAndView addNewEntry(@ModelAttribute("inputBean") InputFormBean inputBean) {
        service.save(inputBean);
        return display1(inputBean);
    }

Teoretycznie metody display1 i 2 są tożsame. Ale zauważ co sie dzieje kiedy ktoś submituje formularz -> w przypadku display1 wyświetli mu sie Widok z wypełnionym formularzem bo bindowanie działa w 2 strony. Jak bindujesz do "pustego" obiektu to pola są puste, ale jak bindujesz do obiektu który ma wartości to te wartości widać w formularzu :)

0

Oparłem się trochę o to co wysłałeś z githuba .ftl i ten controller, o to co napisałeś przed chwilą w poście i o książkę i wyszło mi cus takiego

PLIKI KTÓRE WYKORZYSTAŁEM:

HomeController:

@Controller
public class HomeController {
    // ta metoda z tego co rozumiem powinna otworzyć formularz i zbindować na odwrót - z modelu dodać dane do pól w stronie jsp (jeśli nie sa puste ofc).
    //  w tym momencie jestem pewien, ze taki atrybut jak "atryb" jest w modelu
    @RequestMapping(value = "/form", method = RequestMethod.GET)
    public ModelAndView displayForm(@ModelAttribute("atryb") User user){ 
        return new ModelAndView("formek");                                                
    }

    //TE METODY PONIŻEJ NIE BYŁY RAZEM, NAJPIERW DALEM JEDNA, POTEM ZAMIENIŁEM NA DRUGĄ ALE PRZY OBU MAM TEN SAM BŁĄD

    // PIERWSZA METODA
   //ta metoda dokładnie nie wiem w czym ma mi pomóc haha generalnie wyglada ona niemalże tak samo jak metoda displayForm(user) tylko jest POST i zwraca i 
    //tak metode displayForm(user). Z reguły po submicie powinna zapisac dane w modelu i jeszcze raz wyswietlic formularz z wpisanymi 
    // danymi już (zbindowane na odwrót) 
    @RequestMapping(value = "/add", method = RequestMethod.POST)          
    public ModelAndView add(@ModelAttribute("atryb") User user){            
        //tu moze byc jakis service dla modelu                                           
        return displayForm(user);
    }

    // DRUGA METODA
    //ta za to powinna pobrac(zbindowac do modelu user) dane z formularza, potem stworzyc ModelAndView("successForemek"); dodac tam obiekt o nazwie
    // "uzytk" i wartoscia tego usera ktorego pobralem z formularza i na koncu zwrocic ten ModelAndView - czyli pokazac strone successForemek.jsp
    @RequestMapping(value = "/add", method = RequestMethod.POST)          
    public ModelAndView add(@ModelAttribute("atryb") User user){              
        ModelAndView modelAndView = new ModelAndView("successForemek");
        modelAndView.addObject("uzytk", user);
        return modelAndView;
    }
} 

formek.jsp

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
</head>

<body>
<form:form method="POST" modelAttribute="atryb" action="/form">
    Imie:
    <form:input path="imie"/><br>
    Nazwisko:
    <form:input path="nazwisko"/><br>
    Wiek:
    <form:input path="wiek"/><br>
    <br><input type="submit" value="Accept"/>
</form:form>
</body>
</html>
 

successFormek.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
</head>
<body>
    ${atryb.imie}<br>
    ${atryb.nazwisko}<br>
    ${atryb.wiek}
</body>
</html>
 

Jednak problem sie pojawia, pokazuje mi się błąd:
Obrazek

I nie bardzo wiem czym to jest spowodowane. Jak otworze nie /form a /add to jest ten sam błąd tylko zamiast słowa POST jest GET
W gogle troche poszukałem to różne rady dawali ale nie pomogły

Co jeszcze zrobiłem źle?:P

0

Po pierwsze zbindowałeś dwie metody na ten sam URL...
Poza tym nie wiem jak wszedłeś na tą stronę /form ale powinieneś przez wpisanie tego w pasku adresu. Jak próbowałeś tam przejść za pomocą POST z formularza to nie zadziała bo przeciez kontroler ma tylko metodę obsługującą GET.
Poza tym czy twój bean User ma domyślny kontruktor bezparametrowy oraz settery i gettery?

0

W komentarzu w kodzie pisałem, że te metody nie były w kontrolerze w tym samym momencie, tylko najpierw jedna, potem usunalem i napisalem drugą.
Na strone wchodzilem wpisujac w pasek adresu cały adres: localhost:8080/form

User ma gettery settery bezparametrowy i 3-parametrowy konstruktor.

Próbowałem też zrobić tak, żeby w <form 1. nie dawać w ogóle atrybutu method 2. zmienic na GET 3. zostawic POST. Ale niestety nic to nie dało.
w czym może być problem? :)

0

Powieś to w jakiejś zjadliwej (mavenowej...) formie na githubie jakimś i zobaczę, bo błąd pewnie leży w zupełnie innym miejscu ;) Ale ja gdzieś o 1:30 idę spać, miej to na uwadze :D

0

okej @Shalom zgodnie z obietnicą wrzuciłem to na githuba: https://github.com/azalut/spring
czekam na reprymendę :D

edit
Przyszło mi na myśl coś takiego, powiedz mi czy to:

@RequestMapping(value = "/form", method = RequestMethod.GET)
    public ModelAndView displayForm(@ModelAttribute("atryb") User user){
        return new ModelAndView("formek");
    }
 

jest równe temu: ?

 
@RequestMapping(value = "/form", method = RequestMethod.GET)
    public ModelAndView displayForm(){
        ModelAndView mav = new ModelAndView("formek");
        mav.addObject("atryb", new User());
        return mav;
    }

Inaczej mówiąc - w obu przypadkach dodaje do modelu pusty obiekt User?

edit
kolejny edit, zauważyłem, że ten przykład który mi napisałeś wyżej jest właściwie analogiczny do tego co ja napisalem w tym poscie :P czyli tam gdzie mam @ModelAttribute beda pola już zbindowane z tymi z formularza na podstawie "path", a w tym drugim bedzie czysty formularz? Pogięte to, jak to sie w ogóle dzieje :)

0

Abstrahuje od tego co tam w tym kodzie masz (polskie nazwy itd). Błąd polega na tym że w Widoku masz złe form-action ...
Masz je ustawione na "/form" zamiast na "/add". W efekcie próbujesz wywołać akcję POST na /form w kontrolerze /from ma tylko obsługę jako GET.

Co do twojego pytania to ModelAttribute które są argumentami metody kontrolera automatycznie wskoczą do modelu, ot takie ułatwienie życia ;]

0

co do polskich nazw i reszty to tłumacząc się: pisze to tylko na potrzeby zrozumienia więc nie zwracam za bardzo uwagi na to czy wpisze polską czy angielską nazwę itp, liczy sie zrozumienie zasady działania, a jak bede pisał coś poważniejszego to wtedy wszystko bedzie jak należy :P
Faktycznie zmieniłem action i zaczelo działać, dzieki!

Tzn dodając jakis atrybut do obiektu ModelAndView przez .addObject to mniej wiecej to samo co dodanie @ModelAttribute? Tyle tu ułatwień, że na końcu wychodzi, że utrudniają, bo 1 rzecz robi sie na 5 różnych sposobów, a efekt daje ten sam :D

Finalnie stwierdzam, że to niby jako tako rozumiem, ale nie czuje sie w tym za mocno, żeby napisać coś ciekawszego, czego się teraz chwycić? :)

0

Tzn dodając jakis atrybut do obiektu ModelAndView przez .addObject to mniej wiecej to samo co dodanie @ModelAttribute?

Prawie. Stosuje się to tylko jeśli dana metoda jest wywoływana z takim parametrem z jakiegoś powodu. Jeśli przekazujesz do widoku takie rzeczy jak jakieś dane do wyświetlenia czy coś takiego, to raczej stosuje sie jednak addObject :)

A bo ja wiem czego? Klep :P

0

wielkie dzięki @Shalom dużo mi pomogłeś ;) to pozostaje klepać i się dowiadywać
warto od razu zainteresować się integracją aplikacji spring mvc z modułami tj. WebFlow czy Security, możę Od razu Hibernate (czy też JPA)? czy zostawić to na trochę późniejszy etap nauki? nie namieszam sobie zbytnio? :P

Jeszcze małe pytanie nie odnośnie tematu, zastanawiam się jak ma się Spring Data JPA do JPA? to już jest implementacja tj. Hibernate czy dopiero specyfikacja jakaś inniejsza?

0

Moim zdaniem nie ma co mieszać, szczególnie że te elementy są niejako "obok", tzn można je dość łatwo dodać bez ingerencji w istniejący już projekt.
Spring Data to nie jest implementacja JPA ;) To jest tylko zestaw często stosowanych rozwiązań. Bo zwykle jest tak ze w każdym projekcie klepie się jakieś GenericDao i tego typu interfejsy/klasy, w internetach jest milion gotowców na to, bo to jest potrzebne zawsze i zwykle wygląda tak samo ;] W efekcie w Springu dodali trochę takich gotowców żeby nie trzeba bylo n-ty raz tego samego kopiować.

1 użytkowników online, w tym zalogowanych: 0, gości: 1