JavaEE workshop #7

Viktor Martiš

(spring-mvc, E2E testy)

Previous workshop

  • IDEA shortcut: Alt + insert, Ctrl +J
  • Adapter pattern
  • Proxing in spring framework
  • AOP
  • Transactions in detail
  • Mockito

Contents

  • IDEA shortcut: Ctrl + D, Ctrl + Y
  • Facade pattern
  • Spring MVC
  • E2E testing

IDEA shortcut

  • IDEA shortcut: Ctrl + D, Ctrl + Y
    • duplicate, delete
  • provides a simplified interface

Web applications

  • Server side rendering
    • client-server
    • server is responsible for render HTML pages
  • Simple page application
    • used in our courses
    • server exposes REST API
    • client side is javascript application
    • client is responsible for view rendering and view routing

Spring MVC - Introduction

  • designed around a DispatcherServlet that dispatches requests to handlers, with configurable handler mappings, view resolution, locale, time zone and theme resolution as well as support for uploading files
  • handler is based on the @Controller and @RequestMapping
  • @Controller mechanism also allows you to create RESTful Web sites and applications, through the @PathVariable

Spring MVC

DispatcherServlet registration

<web-app>
    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>/example/*</url-pattern>
    </servlet-mapping>

</web-app>

DispatcherServlet registration

  •  In a Servlet 3.0+ environment
public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        ServletRegistration.Dynamic registration = 
            container.addServlet("dispatcher", new DispatcherServlet());
        registration.setLoadOnStartup(1);
        registration.addMapping("/example/*");
    }

}

Spring MVC

  •  each DispatcherServlet has its own WebApplicationContext, which inherits all the beans already defined in the root WebApplicationContext

Spring MVC configuration

  • AbstractAnnotationConfigDispatcherServletInitializer  or AbstractDispatcherServletInitializer (initialized by SpringServletContainerInitializer)
  • @EnableWebMvc
  • WebMvcConfigurerAdapter
  • Separated context configuration
  • controllers and some other MVC required beans

Special Bean Types

HandlerMapping Maps incoming requests to handlers and a list of pre- and post-processors (handler interceptors) based on some criteria the details of which vary by HandlerMapping implementation. The most popular implementation supports annotated controllers but other implementations exists as well.
HandlerAdapter Helps the DispatcherServlet to invoke a handler mapped to a request regardless of the handler is actually invoked. For example, invoking an annotated controller requires resolving various annotations. Thus the main purpose of a HandlerAdapter is to shield the DispatcherServlet from such details.
HandlerExceptionResolver Maps exceptions to views also allowing for more complex exception handling code.

Special Bean Types

ViewResolver Resolves logical String-based view names to actual View types.
LocaleResolver & LocaleContextResolver Resolves the locale a client is using and possibly their time zone, in order to be able to offer internationalized views
ThemeResolver Resolves themes your web application can use, for example, to offer personalized layouts
MultipartResolver Parses multi-part requests for example to support processing file uploads from HTML forms.
FlashMapManager Stores and retrieves the "input" and the "output" FlashMap that can be used to pass attributes from one request to another, usually across a redirect.

Controllers

  • provide access to the application behavior that you typically define through a service interface
  • interpret user input and transform it into a model that is represented to the user by the view
  • uses annotations such as @RequestMapping, @RequestParam, @ModelAttribute, ...
@Controller
public class HelloWorldController {

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

@RequestMapping

  • URI Template Patterns -  access to selected parts of a URL 
  • When a @PathVariable annotation is used on a Map<String, String> argument, the map is populated with all URI template variables.
  • Regulal expressions support
  •  Ant-style path patterns (for example, /myPath/*.do)
@RequestMapping(path="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
    Owner owner = ownerService.findOwner(ownerId);
    model.addAttribute("owner", owner);
    return "displayOwner";
}


@RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{extension:\\.[a-z]+}")
    public void handle(@PathVariable String version, @PathVariable String extension) {
        // ...
    }
}
ServletRequest or HttpServletRequest Request or response objects (Servlet API).
java.util.Locale current request locale
java.io.InputStream / java.io.Reader access to the request’s content.
ava.io.OutputStream / java.io.Writer aw OutputStream/Writer as exposed by the Servlet API.
java.security.Principal containing the currently authenticated user.
@PathVariable annotated parameters for access to URI template variables.
@RequestParam  specific Servlet request parameters
@RequestHeader specific Servlet request HTTP headers
@RequestBody HTTP request body
java.util.Map / org.springframework.ui.Model / org.springframework.ui.ModelMap model that is exposed to the web view
org.springframework.validation.Errors / org.springframework.validation.BindingResult validation results for a preceding command or form object
java.security.Principal containing the currently authenticated user.
... ...
ModelAndView  object, with the model implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.
Model  object, with the view name implicitly determined through a RequestToViewNameTranslator and the model implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.
Map  object for exposing a model, with the view name implicitly determined through a RequestToViewNameTranslator and the model implicitly enriched with command objects and the results of @ModelAttribute annotated reference data accessor methods.
View  object, with the model implicitly determined through command objects and @ModelAttribute annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above).
String  value that is interpreted as the logical view name, with the model implicitly determined through command objects and @ModelAttribute annotated reference data accessor methods. The handler method may also programmatically enrich the model by declaring a Model argument (see above).
... ...

@RequestParam

  • bind request parameters to a method parameter in your controller
@Controller
@RequestMapping("/pets")
@SessionAttributes("pet")
public class EditPetForm {

    // ...

    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}
  • indicates that a method parameter should be bound to the value of the HTTP request body
@RequestMapping(path = "/something", method = RequestMethod.PUT)
public void handle(@RequestBody String body, Writer writer) throws IOException {
    writer.write(body);
}
  •  indicates that the return type should be written straight to the HTTP response body
@RequestMapping(path = "/something", method = RequestMethod.PUT)
@ResponseBody
public String helloWorld() {
    return "Hello World";
}
  • very common use case to have Controllers implement a REST API, thus serving only JSON, XML or custom MediaType content
  • combines @ResponseBody and @Controller
  • result of data binding there may be errors such as missing required fields or type conversion errors
  • to check for such errors add a BindingResult argument immediately following the @ModelAttribute argument

 Data binding

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}

 Data binding

@RequestMapping(path = "/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {

    if (result.hasErrors()) {
        return "petForm";
    }

    // ...

}
  • validation invoked automatically by adding the JSR-303 @Valid annotation
  • allows you to configure web data binding directly within your controller class
  • initialize the WebDataBinder that will be used to populate command and form object arguments of annotated handler methods
  • support all arguments that @RequestMapping supports, except for command/form objects and corresponding validation result objects
@Controller
public class MyFormController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}
  • filter contextually the object that will be serialized to the HTTP response body
  • @JsonView - specifying the view class or interface to be used
@RestController
public class UserController {

    @RequestMapping(path = "/user", method = RequestMethod.GET)
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}
  • Servlet 3 based asynchronous request processing
  • instead of returning a value method can now return a java.util.concurrent.Callable
  • Spring MVC invokes the Callable in a separate thread with the help of a TaskExecutor and when the Callable returns, the request is dispatched back to the Servlet container to resume processing using the value returned by the Callable.
@RequestMapping(method=RequestMethod.POST)
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };
}
  • ResponseBodyEmitter return value type which can be used to send multiple Objects
@RequestMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();
  • write result directly to the response OutputStream
  • by StreamingResponseBody return value type
@RequestMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}
  • enable with MultipartResolver
<bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">

    <!-- one of the properties available; the maximum file size in bytes -->
    <property name="maxUploadSize" value="100000"/>

</bean>
@Controller
public class FileUploadController {

    @RequestMapping(path = "/form", method = RequestMethod.POST)
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }

        return "redirect:uploadFailure";
    }

}
  • HandlerExceptionResolver implementations deal with unexpected exceptions that occur during controller execution.
  • @ExceptionHandler method for array of Exception types
  • @ControllerAdvice - global configuration of excetption handlers
@Controller
public class SimpleController {

    // @RequestMapping methods omitted ...

    @ExceptionHandler(IOException.class)
    public ResponseEntity<String> handleIOException(IOException ex) {
        // prepare responseEntity
        return responseEntity;
    }

}

Spring MVC testing

  • doc
  • server-side REST tests
  • @SpringJUnitWebConfig and MockMvc
  • E2E test

Q&A

ITA-04-JavaEE, Workshop 7

By IT-absolvent

ITA-04-JavaEE, Workshop 7

Workshop #7

  • 457