آموزش Spring MVC بخش معرفی Annotation ها

آموزش Spring MVC بخش معرفی Annotation ها

در این بخش به annotation های Web موجود در پکیج org.springframework.web.bind.annotation میپردازیم و خواهیم دید که استفاده از این annotation چقدر توسعه وب را راحت میکند و کمک میکند بجای تمرکز روی نوشتن کد روی منطق برنامه تمرکز داشته باشیم و همچنین توسعه وب را بسیار راحت میکند

RequestParam@PathVariable@ResponseBody@RequestBody@RequestMapping@
Controller@ResponseStatus@ExceptionHandler@RequestHeader@CookieValue@
ModelAttribute@CrossOrigin@RestController@

RequestMapping@ :

در داخل کلاس های Controller@ بروی متد ها میتوانیم درخواست های رسیده را هندل کنیم که دارای چند وظیفه است :

– آدرس دهی Url درخواست و متد مسول رسیدگی به درخواست خاص را به آن درخواست متناظر میکند

– فیلتر کردن name و value مجاز و حتی غیر مجاز از پارامتر های ارسالی کاربر 

– فیلتر کردن Header بر اساس وجود و یا عدم وجود ان هدر خاص

– مشخص کردن نوع Media Type دریافتی Consumer

– مشخص کردن نوع Media Type ارسالی Producer

@Controller
class VehicleController {
 
    @RequestMapping(value = "/vehicles/home", method = RequestMethod.GET)
    String home() {
        return "home";
    }
}

نحوه استفاده :

که میتوان به این صورت هم نوشت که کل کلاس را محدود به دسته ای از آدرس ورودی میکنیم و متد های داخلی مسول رسیدگی به زیر آدرس ها میشوند :

@Controller
@RequestMapping(value = "/vehicles", method = RequestMethod.GET)
class VehicleController {
 
    @RequestMapping("/home")
    String home() {
        return "home";
    }
}

همانطور که در کد بالا میبینید میتوانیم Http Method را هم مشخص کنیم ولی یک راه کوتاه تر هم وجود دارد که استفاده از annotation مختص به آن است :

GetMapping@

PutMapping@

PostMapping@

PathMapping@

DeleteMapping@

فیلتر کردن بر اساس param :

@RequestMapping(
  value = "/ex/foos", 
  headers = { "key1=val1", "key2=val2" }, method = GET)
@ResponseBody
public String getFoosWithHeaders() {
    return "Get some Foos with Header";
}

فیلتر کردن بر اساس header :

@RequestMapping(
  value = "/ex/foos", 
  method = GET, 
  headers = "Accept=application/json")
@ResponseBody
public String getFoosAsJsonFromBrowser() {
    return "Get some Foos with Header Old";
}

از Spring 3.1 به بعد از Producer و Consumer پشتیبانی میکند :

@RequestMapping(
  value = "/ex/foos", 
  method = GET,
  produces = { "application/json", "application/xml" }
)

RequestBody@ :

برای قرار دادن (متناظر کردن) مقادیر ارسالی HttpRequest body (از کاربر) روی Object متناظر با آن میتوانیم استفاده کنیم و دیگر درگیر استخراج مقادیر و ست کردن آنها در field های کلاس Entity را نخواهیم داشت نکته ای که وجود دارد اطلاعات دریافتی باید بصورت JSON باشند و فریم ورک بصورت اتوماتیک آنرا deserialize میکند

@PostMapping("/request")
public ResponseEntity postController(
  @RequestBody LoginForm loginForm) {
  
    exampleService.fakeAuthenticate(loginForm);
    return ResponseEntity.ok(HttpStatus.OK);
}
curl -i \
-H "Accept: application/json" \
-H "Content-Type:application/json" \
-X POST --data 
  '{"username": "johnny", "password": "password"}' "https://localhost:8080/.../request"
public class LoginForm {
    private String username;
    private String password;
    // ...
}

کاربرد استفاده از این annotation بخوبی در Spring REST API نمایان است

ResponseBody@ :

برعکس RequestBody@ ابجکت entity/model را به JSON سریال و آماده ارسال به کلاینت میکند

public class ResponseTransfer {
    private String text; 
     
    // standard getters/setters
}
@Controller
@RequestMapping("/post")
public class ExamplePostController {
 
    @Autowired
    ExampleService exampleService;
 
    @PostMapping("/response")
    @ResponseBody
    public ResponseTransfer postResponseController(
      @RequestBody LoginForm loginForm) {
        return new ResponseTransfer("Thanks For Posting!!!");
     }
}

در کلاینت میتوانیم عبارت JSON زیر را مشاهده کنیم :

{"text":"Thanks For Posting!!!"}

* نکته ای که وجود دارد و بعدا در REST به آن بر خواهیم خورد، نیازی نیست که از RestController@ استفاده کنیم چرا که ResponseBody@ بصورت پیش فرض آنرا دارد

PathVariable@ :

یکی از راه های ارسال مقادیر استفاده از path variable است و دارای شکل زیر میباشد:

http://localhost:8080/spring-rest/ex/foos/value1/bar/value2

حال در Spring MVC امکان ساده ای برای گرفتن مقادیر از path variable داریم :

@RequestMapping(value = "/ex/foos/{id}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariable(
  @PathVariable("id") long id) {
    return "Get a specific Foo with id=" + id;
}

اگر نام placeholder هم نام متغییر موجود در آرگومان باشد میتوانیم از نوشتن نام متغییر موقع استخراج جلوگیری کنیم  :

@RequestMapping("/{id}") 
Vehicle getVehicle(@PathVariable long id) { 
  // ... 
}

بصورت چندتایی :

//http://localhost:8080/spring-rest/ex/foos/1/bar/2

@RequestMapping(value = "/ex/foos/{fooid}/bar/{barid}", method = GET)
@ResponseBody
public String getFoosBySimplePathWithPathVariables
  (@PathVariable long fooid, @PathVariable long barid) {
    return "Get a specific Bar with id=" + barid + 
      " from a Foo with id=" + fooid;
}

فیلتر کردن مقادیر با استفاده از Regex از path variable :

@RequestMapping(value = "/ex/bars/{numericId:[\\d]+}", method = GET)
@ResponseBody
public String getBarsBySimplePathWithPathVariable(
  @PathVariable long numericId) {
    return "Get a specific Bar with id=" + numericId;
}

دو متد نمیتوانند دارای یک آدرس باشند و باید باهم تفاوت داشته باشند وگرنه خطای  IllegalStateException پرتاب خواهد شد :

@GetMapping(value = "foos/duplicate")
public String duplicate() {
    return "Duplicate";
}
 
@GetMapping(value = "foos/duplicate")
public String duplicateEx() {
    return "Duplicate";
}
Caused by: java.lang.IllegalStateException: Ambiguous mapping.
  Cannot map 'fooMappingExamplesController' method 
  public java.lang.String org.baeldung.web.controller.FooMappingExamplesController.duplicateEx()
  to {[/ex/foos/duplicate],methods=[GET]}:
  There is already 'fooMappingExamplesController' bean method
  public java.lang.String org.baeldung.web.controller.FooMappingExamplesController.duplicate() mapped.

که بدین صورت اصلاح شده است :

@GetMapping(value = "foos/duplicate/xml", produces = MediaType.APPLICATION_XML_VALUE)
public String duplicateXml() {
    return "Duplicate";
}
 
@GetMapping(value = "foos/duplicate/json", produces = MediaType.APPLICATION_JSON_VALUE)
public String duplicateJson() {
    return "Duplicate";
}

تنظیمات Spring MVC : 

بسیار ساده است کافی است کلاسی از نوع Controller@ داشته باشیم و درون آن متد های متناظر با آدرس درخواست ها را پیاده سازی کنیم 

و برای فعال کردن Spring MVC بصورت کامل نیاز است که یک کلاس Configuration@ داشته باشیم و تنظیمات زیر را وارد کنیم :

@Configuration
@EnableWebMvc
@ComponentScan({ "org.baeldung.spring.web.controller" })
public class MvcConfig {
    //
}

با EnableWebMvc@ به Spring میگوییم که کلاس های Controller را اسکن و  آدرس های map شده برای درخواست ها و خود کنترلر را ثبت کند، تبدیل کننده داده های به انواع مورد نیاز را ایجاد کند، از validation کردن در وب حمایت کند، تبدیل کننده پیام ها را بسازد و همینطور انواع خطای موجود در حوزه وب را رسیدگی کند 

 و با ComponentScan@ مشخص میکنیم که کلاس های Controller@ ما در کدام class path موجود است تا اسکن شوند

چنانچه نیاز باید ما نوع خاصی از تنظیمات را برای MVC ارائه دهیم کافی است از کلاس WebMvcConfigurer یک فرزند ایجاد کنیم و تغییرات مورد نیاز را در آن اعمال کنیم :

@EnableWebMvc
@Configuration
public class WebConfig implements WebMvcConfigurer {
 
   @Override
   public void addViewControllers(ViewControllerRegistry registry) {
      registry.addViewController("/").setViewName("index");
   }
 
   @Bean
   public ViewResolver viewResolver() {
      InternalResourceViewResolver bean = new InternalResourceViewResolver();
 
      bean.setViewClass(JstlView.class);
      bean.setPrefix("/WEB-INF/view/");
      bean.setSuffix(".jsp");
 
      return bean;
   }
}

در کد بالا یک ViewResolver Bean ای ایجاد کردیم که View هایی از نوع jsp. از مسیر /WEB-INF/view/ بر میگرداند همچنین در متد addViewControllers با استفاده از ابجکت ورودی ViewControllerRegistry میتوانیم برای یک url خاص view خاصی را متصل کنیم 

برای بکار گرفتن تنظیمات بالا نیاز است که یک کلاس Initializer هم تعریف کنیم :

public class MainWebAppInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(final ServletContext sc) throws ServletException {
 
        AnnotationConfigWebApplicationContext root = 
          new AnnotationConfigWebApplicationContext();
         
        root.scan("com.baeldung");
        sc.addListener(new ContextLoaderListener(root));
 
        ServletRegistration.Dynamic appServlet = 
          sc.addServlet("mvc", new DispatcherServlet(new GenericWebApplicationContext()));
        appServlet.setLoadOnStartup(1);
        appServlet.addMapping("/");
    }
}

* برای Spring قبل از ورژن 5 نیاز است که از WebMvcConfigurerAdapter استفاده شود

یک راه دیگر برای ایجاد تنظیمات ست کردن آن در web.xml است :

<context:component-scan base-package="com.baeldung.web.controller" />
<mvc:annotation-driven />    
 
<bean id="viewResolver"
      class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/" />
        <property name="suffix" value=".jsp" />
    </bean>
 
    <mvc:view-controller path="/" view-name="index" />
 
</beans>

RequestParam@ :

دیگر راه انتقال دیتا استفاده از request param است و بدین صورت تعریف میشود :

http://localhost:8080/spring-rest/ex/bars?id=100

همانطور که میشه فهمید در انتهای url بعد از یک علامت سوال کلید مقدار هایی میتوانند قرار بگیرند که حاوی دیتا هستند

طریقه گرفتن مقادیر از Request Param :

//http://localhost:8080/spring-rest/ex/bars?id=100

@RequestMapping(value = "/ex/bars", method = GET)
@ResponseBody
public String getBarBySimplePathWithRequestParam(
  @RequestParam("id") long id) {
    return "Get a specific Bar with id=" + id;
}

حتی میتوان وجود آن پارامتر را قبل از وارد شدن به اجرای متد چک کرد :

//http://localhost:8080/spring-rest/ex/bars?id=100&second=something

@RequestMapping(
  value = "/ex/bars", 
  params = { "id", "second" }, 
  method = GET)
@ResponseBody
public String getBarBySimplePathWithExplicitRequestParams(
  @RequestParam("id") long id) {
    return "Narrow Get a specific Bar with id=" + id;
}

همچنین میتواند در صورت نبود آن کلید و یا حتی خالی بودن مقدار برای آن کلید مقدار پیش فرضی را خودمان به متغییر ارسال کنیم :

@RequestMapping("/buy")
Car buyCar(@RequestParam(defaultValue = "5") int seatCount) {
    // ...
}

اگر دو آدرس دارای یک نوع درخواست بود بطوری که منطق کد یکسانی را نیاز داشت میتوان چند آدرس را روی یک متد متناظر کرد :

//http://localhost:8080/spring-rest/ex/advanced/foos
//http://localhost:8080/spring-rest/ex/advanced/bars

@RequestMapping(
  value = { "/ex/advanced/bars", "/ex/advanced/foos" }, 
  method = { RequestMethod.PUT, RequestMethod.POST })
@ResponseBody
public String getFoosOrBarsByPath() {
    return "Advanced - Get some Foos or Bars";
}

CookieValue@ :

همانند بالا میتوان با این annotation به مقدار کوکی دست پیدا کنیم

RequestHeader@ :

همچنین با این annotation میتوان به مقادیر موجود در header دسترسی پیدا کرد

ExceptionHandler@ :

گاهی نیاز داریم برای رخ دادن یک exception خاص یکسری کار انجام بدیم حال وقتی کلاس controller این خطا را دریافت کند ما نیاز داریم آنرا هندل کنیم با گذاشتن این annotation روی متد مسول رسیدگی به خطا میتوان آنرا هندل کرد

@ExceptionHandler(IllegalArgumentException.class)
void onIllegalArgumentException(IllegalArgumentException exception) {
    // ...
}

ResponseStatus@:

گاهی پیش میاد که Http Status Code خاصی را ارسال کنیم مثلا موقعی که با خطا مواجه شدیم

@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
void onIllegalArgumentException(IllegalArgumentException exception) {
    // ...
}

Controller@:

این annotation کلاس مسئول کنترلر از MVC را مشخص میکند

RestController@ :

ترکیبی از Controller@ و ResponseBody@ است

CrossOrigin@ :

برای ایجاد رفتار CORS میتوان از این annotation استفاده کرد

@CrossOrigin
@RequestMapping("/hello")
String hello() {
    return "Hello World!";
}

با این annotation درخواست از سایر دامین های موجود در صفحه کاربر امکانپذیر خواهد شد

ModelAttribute@ :

طبق الگوی طراحی MVC در لایه کنترل (در اینجا کلاس Controller@) نیاز داریم که Model را داشته باشیم که دیتای View را با آن بسازیم و یا دیتای مورد نیاز جهت پردازش

این دیتا از سمت کاربر ارسال میشوند مانند دیتای فرم ها در Spring MVC با کمک ModelAttribute@ میتوانیم آن دیتای ارسالی کاربر را در قالب یک Model Object داشته باشیم در پشت صحنه فریم ورک کلیدهای دیتا رسیده را با نام فیلد های موجود در Model Object مقایسه کرده و مقادیر را در آن جاگذاری میکند و اینطوری ما بدون اینکه درگیر Parse کردن دیتا شویم آنها را در قالب یک Object خواهیم داشت

و یا ممکن است Model Object را از دیتابیس یا در درون کنترلر توسط متد دیگری که با ModelAttribute@ مشخص شده بسازیم

از این annotation در دو حالت استفاده کنیم :

حالت اول  که در آرگومان ورودی متد RequestMapping@ قرار میگیرد  (باید توجه کنیم که آن متد مسول دریافت دیتای یک فرم یا دیتای خاص دیگر باشد) و اسپرینگ اتوماتیک تشخیص میدهد که باید ابجکت مدل از دیتای آمده را بسازد 

ModelAttribute@ در آرگومان ورودی :

@RequestMapping(value = "/addEmployee", method = RequestMethod.POST)
public String submit(@ModelAttribute("employee") Employee employee) {
    // Code that uses the employee object
 
    return "employeeView";
}

حالت دوم یک متد را مختص به ساخت این Model Object میکنیم و دیتا درون کنترلر ساخته میشود که ممکن است از دیتابیس فراخوانی شده باشد یا باید دیتای خاصی را برای Model آماده کنیم از روش ModelAttribute Method استفاده میکنیم

ModelAttribute@ در Method Level :

@ModelAttribute
public void addAttributes(Model model) {
    model.addAttribute("msg", "Welcome to the Netherlands!");
}
@ModelAttribute("vehicle")
Vehicle getVehicle() {
    // ...
}

در کد اول مقدار برگشتی void است و ما دیتا را مستقیما در model اضافه کردیم ولی در کد دوم Model Object را ساخته ایم همانطور که میشه حس کرد این روش انعطاف پذیری بیشتری دارد ولی معایبی هم دارد :

موقع استفاده از این روش باید توجه داشت که با هر درخواست رسیده کلیه متد های ModelAttribute@ قبل از متد های RequestMapping@ اجرا خواهند شد پس در طراحی Controller@ باید حواسمان باشد که گروهی از RequestMapping@ هایی که وجود دارند یکسان باشند چون به هرکدام از انها که درخواستی ارسال شود کلیه ModelAttribute@ های موجود در controller صدا زده میشوند

مثال دیگر :

@Controller
public class ModelController {
 
    @ModelAttribute(name= "countrieslist")
    public List<String> populateCountries() {
 
        List<String> countries= new ArrayList<String>();
        countries.add("India");
        countries.add("United States");
        countries.add("Japan");
        countries.add("Australia");
        countries.add("Canda");     
 
        return countries;
    }
 
    @RequestMapping(value= "/init", method= RequestMethod.GET)
    public ModelAndView initView(@ModelAttribute(name= "countrieslist") List<String> countries) {
 
        ModelAndView modelview = new ModelAndView();
        modelview.addObject("message", "This is an example of using the @ModelAttribute annotation .....!");
        modelview.setViewName("output");
 
        return modelview;
    }
}

مثالی دیگر از ابتدای ایجاد یک فرم تا استفاده از :

<form:form method="POST" action="/spring-mvc-basics/addEmployee"
  modelAttribute="employee">
    <form:label path="name">Name</form:label>
    <form:input path="name" />
     
    <form:label path="id">Id</form:label>
    <form:input path="id" />
     
    <input type="submit" value="Submit" />
</form:form>

کلاس مدل :

@XmlRootElement
public class Employee {
 
    private long id;
    private String name;
 
    public Employee(long id, String name) {
        this.id = id;
        this.name = name;
    }
 
    // standard getters and setters removed
}

کلاس کنترلر :

@Controller
@ControllerAdvice
public class EmployeeController {
 
    private Map<Long, Employee> employeeMap = new HashMap<>();
 
    @RequestMapping(value = "/addEmployee", method = RequestMethod.POST)
    public String submit(
      @ModelAttribute("employee") Employee employee,
      BindingResult result, ModelMap model) {
        if (result.hasErrors()) {
            return "error";
        }
        model.addAttribute("name", employee.getName());
        model.addAttribute("id", employee.getId());
 
        employeeMap.put(employee.getId(), employee);
 
        return "employeeView";
    }
 
    @ModelAttribute
    public void addAttributes(Model model) {
        model.addAttribute("msg", "Welcome to the Netherlands!");
    }
}

چیزی که میتوانیم در View از Model دریافت کنیم :

<h3>${msg}</h3>
Name : ${name}
ID : ${id}

کاربرد کلاس ControllerAdvise@ برای ModelAttribute@ :

این نوع کلاس های کنترلر کلاس های کمکی برای دیگر کلاس های Controller@ هستند که متدهای ModelAttribute@ داخل آن به کلیه Controller@ های دیگر موقعی که RequestMapping@ ای فراخوانی میشود قبل از آن اجرا میشود

Spring MVC همراه با Spring Boot : 

Spring Boot امکان اضافه ای است که باعث میشود سریعتر و آسانتر بتوانیم تنظیمات Spring را انجام دهیم و کمتر درگیر جزییات اضافی ساخت محصول شویم. 

و کتابخانه آنرا به Spring در pom.xml پروژه اضافه کنیم :

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.6.RELEASE</version>
</parent>

دیدگاهتان را بنویسید