roo.sh
Basis Tecnologia da Informação S.A. - 2019
O que é MVC?
Por que usar?
Configurando
Executando
Annotations
Jackson
Security
Filter
Exception
Model, View, Controller
"Splits user interface interaction into three distinct roles" (Martin Fowler)
"You’d expect there to be three objects, a model object, a controller object, and a view object. The Model handles the business rules. The Controller handles input. The View handles output" (Uncle Bob)


Client - Server over HTTP


Initializr: https://start.spring.io
Maven: pom.xml
Geradores: spring roo, jhipster



Spring Boot, Spring Data, Spring IO…
entity, repository, service…
REST & SOAP scaffolding.
roo.sh
project setup
web mvc setup
jpa setup
Baseado no Yeoman.
npm i -g yo
npm i -g generator-jhipster
yo jhipster
mvn spring-boot:run ou ./mvnw
Padrão desde o Java 1.5 sobre o pacote java.lang.annotation.
Expressam metadados.
Avaliadas em compilation time ou runtime.
Usadas para facilitar configuração, automatização, metaprogramação…
org.springframework.web.bind.annotation
Nas aplicações Spring MVC o RequestDispatcher (Front Controller) é responsável por rotear as requisições HTTP para os métodos dos controlers.
Por ser usada a nível de classe e/ou nível de método.
@RestController
@RequestMapping("/home")
public class IndexController {
@RequestMapping("/")
String get(){
return "Hello from get";
}
@RequestMapping("/index")
String index(){
return "Hello from index";
}
}@RestController
@RequestMapping("/home")
public class IndexController {
@RequestMapping(method = RequestMethod.GET)
String get(){
return "Hello from get";
}
@RequestMapping(method = RequestMethod.DELETE)
String delete(){
return "Hello from delete";
}
}@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
@RestController
@RequestMapping("/home")
public class IndexController {
@GetMapping
String get(){
return "Hello from get";
}
@DeleteMapping
String delete(){
return "Hello from delete";
}
}Associa um parmâmetro de um método à um parâmetro da requisição.
No Spring MVC, @RequestParam mapeia:
query string
form data,
multipart.
body
A Servlet API combina tudo num mapa chamado "parameters".
No Spring WebFlux, @RequestParam mapeia somente query string. Para os demais é necessário usar databinding via ModelAttribute.
Por default torna o parâmetro obrigatório. Ou seja, se o parâmetro estiver presente na requisição, será lançada uma Exception.
public @interface RequestParam {
@AliasFor("name")
String value() default "";
@AliasFor("value")
String name() default "";
boolean required() default true;
String defaultValue() default ValueConstants.DEFAULT_NONE;
}String getName(@RequestParam(value="description", required=false), String description)
String getName(@RequestParam(value="description", defaultValue="foo"), String description)
@RestController
@RequestMapping("/home")
public class IndexController {
@GetMapping(value = "/id")
String getIdByValue(@RequestParam("id") String personId){
System.out.println("ID is "+personId);
return "Get ID from query string of URL with value element";
}
@GetMapping(value = "/personId")
String getId(@RequestParam String personId){
System.out.println("ID is "+personId);
return "Get ID from query string of URL without value element";
}
}Associa um parâmetro de um método à uma variável descrita em um template de uma URI.
@RestController
@RequestMapping("/home")
public class IndexController {
@GetMapping(value = "/fetch/{id}")
String getDynamicUriValue(@PathVariable String id){
System.out.println("ID is "+id);
return "Dynamic URI parameter fetched";
}
@GetMapping(value = "/fetch/{id:[a-z]+}/{name}")
String getDynamicUriValueRegex(@PathVariable("name") String name){
System.out.println("Name is "+name);
return "Dynamic URI parameter fetched using regex";
}
}Ambos extendem org.springframework.http.HttpEntity
HttpEntity encapsula header e body de uma requisição HTTP, tanto do request quanto do response.
RequestEntity: Adicina informações sobre o método e a uri.
Response: Permite adicionar status codes explicitamente.
@Controller
@RequestMapping
public class MyController {
@RequestMapping("test")
public ResponseEntity<String> handleRequest(
RequestEntity<String> requestEntity
) {
System.out.println("request body : " + requestEntity.getBody());
HttpHeaders headers = requestEntity.getHeaders();
System.out.println("request headers : " + headers);
HttpMethod method = requestEntity.getMethod();
System.out.println("request method : " + method);
System.out.println("request url: " + requestEntity.getUrl());
ResponseEntity<String> responseEntity =
new ResponseEntity<>("response body", HttpStatus.OK);
return responseEntity;
}
}Deserializa automaticamente o conteúdo do HttpRequest body para um objeto. (geralmente, transferência ou domínio)
curl -i \ -H "Accept: application/json" \ -H "Content-Type:application/json" \ -X POST --data '{"username": "john Doe", "password": "password"}' "https://localhost:8080/…/request"
@PostMapping("/request")
public ResponseEntity postController(@RequestBody LoginForm loginForm) {
exampleService.fakeAuthenticate(loginForm);
return ResponseEntity.ok(HttpStatus.OK);
}public class LoginForm {
private String username;
private String password;
// ...
}Instrui o controller a serializar auotmaticamente o objeto retornado e associá-lo ao objeto HttpResponse.
public class ResponseExample {
private String text;
// getters/setters
}@Controller
@RequestMapping("/post")
public class ExamplePostController {
@Autowired
ExampleService exampleService;
@PostMapping("/response")
@ResponseBody
public ResponseExample postResponseController(@RequestBody LoginForm loginForm) {
return new ResponseExample("Goodbye cruel world!");
}
}{"text":"Goodbye cruel world!!"}
No Spring MVC, @Controller geralmente cria um mapa que associa um objeto derivado de uma model e sua respectiva view.
@RestController é uma combinação de @Controller e @ResponseBody e simplesmente retorna o objeto que é automaticamente associado ao body em um mime-type HTTP válido, normalmente JSON ou XML.
@Controller
@RequestMapping(value = "/test")
public class TestController {
@GetMapping
public ModelAndView getTestData() {
ModelAndView mv = new ModelAndView();
mv.setViewName("test");
mv.getModel().put("data", "goodbye cruel world!");
return mv;
}
}@Controller
@RequestMapping(value = "/test")
public class TestRestController {
@GetMapping
public @ResponseBody Test getTestData() {
Test test = new Test();
test.setData("goodbye cruel world!");
return test;
}
}@RestController
@RequestMapping(value = "/test")
public class RestAnnotatedController {
@GetMapping
public Test getData() {
Test test = new Test();
test.setData("goodbye cruel world!");
return test;
}
}Associa um parâmetro de um método à um cabeçalho de um request HTTP.
@Controller
public class HelloController {
@RequestMapping(value = "/")
public String hello(@RequestHeader(value="User-Agent") String userAgent)
//...
}
}Associa um parâmetro de um método a um cookie HTTP.
@Controller
public class ExampleController {
@RequestMapping(value="/")
public ModelAndView country(HttpSession session, @CookieValue("JSESSIONID") String cookie) {
String jsessionid = "JSESSIONID:" + cookie;
}
}Serialize, Deserialize, Mapping, Parsing…
Compliance com a JSR-353 (Java JSON API)
Suporte aos tipos da org.json (JSONObject, JSONArray…)
Mongo, geojson…
Mapeia uma propriedade de um objeto (geralmente, trasnferência ou entity) à uma propriedade de um objeto json.
private class Employee {
@JsonProperty("employee-name")
private String name;
@JsonProperty("employee-dept")
private String dept;
private String position;
public String getName() {
return name;
}
@JsonProperty("position")
public String getThePosition() {
return this.position;
}
}Ignora propriedades durante o processo de serializeção/deserialização.
Pode ser usado a nível de classe com @JsonIgnoreProperties(value = { "name" })
Pode ser usado diretamente na propriedade com @JsonIgnore
private class User {
@JsonProperty("user-name")
private String name;
@JsonIgnore
private String password;
}Resolvem problemas de referência cíclica em relações pai/filho.
public class Item {
public int id;
public String itemName;
@JsonManagedReference
public User owner;
}public class User {
public int id;
public String name;
@JsonBackReference
public List<Item> userItems;
}public class ItemSerializer extends StdSerializer<Item> {
public ItemSerializer() {
this(null);
}
public ItemSerializer(Class<Item> t) {
super(t);
}
@Override
public void serialize(
Item value,
JsonGenerator gen,
SerializerProvider provider
) throws IOException, JsonProcessingException {
gen.writeStartObject();
gen.writeNumberField("id", value.id);
gen.writeStringField("itemName", value.itemName);
gen.writeNumberField("owner", value.owner.id);
gen.writeEndObject();
}
}@JsonSerialize(using = ItemSerializer.class)
public class Item {
...
}<dependency>
<groupId>com.bedatadriven</groupId>
<artifactId>jackson-datatype-jts</artifactId>
<version>2.2</version>
</dependency>@Configuration
public class GeoConfiguration {
@Bean
public Module jtsModule() {
return new JtsModule();
}
}Gerenciamento autenticação e autorização.
Proteção para ataques como:
session fixation
clickjacking
cross site request forgery…
Uma classe anotada com @EnableWebSecurity e extendendo WebSecurityConfigurerAdapter
protected void configure(AuthenticationManagerBuilder auth)
Tipo de autenticação (memória, jdbc, ldap…)
Especificar DataSources
Estratégia de encoding das senhas…
protected void configure(HttpSecurity http)
Autorizar, desautorizar requests
Definir tipos de autenticação (basic, bearer…)
Proteger rotas
Adicionar filtros
@Configuration
@EnableWebSecurity
public class BasicConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth
.inMemoryAuthentication()
.withUser("user")
.password("password")
.roles("USER")
.and()
.withUser("admin")
.password("admin")
.roles("USER", "ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
}Especificam uma lista de ROLES que tem acesso à execução de um método.
Não suporta SpEL.
@Secured("ROLE_ADMIN")
public String edit() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return securityContext.getAuthentication().getName();
}É derivada da JSR-250 e tem o mesmo funcionamento da @Secured
@PreAuthorize("hasRole('ROLE_ADMIN')")
public String edit() {
SecurityContext securityContext = SecurityContextHolder.getContext();
return securityContext.getAuthentication().getName();
}Suporta SpEL.
Avalia a expressão ANTES de executar o método
@PreAuthorize("#username == authentication.principal.username")
public String getRoles(String username) {
//...
}Suporta SpEL.
Avalia a expressão DEPOIS de executar o método
@PostAuthorize
("returnObject.username == authentication.principal.nickName")
public CustomUser loadUserDetail(String username) {
return userRoleRepository.loadUserByUserName(username);
}A infraestrutura interna do Spring MVC é totalmente baseada em cadeia de filters.
Pode ser utilizado para diversas coisas como:
interceptar requisições
black/white ip list
negociação de conteúdo
verificação de autenticação

@Component
@Order(1)
public class TransactionFilter implements Filter {
@Override
public void doFilter
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
LOG.info(
"Starting a transaction for req : {}",
req.getRequestURI());
chain.doFilter(request, response);
LOG.info(
"Committing a transaction for req : {}",
req.getRequestURI());
}
// other methods
} http.addFilter(transactionFilter);Definimos um método para captaturar as exceptions usando a annotation @ExceptionHandler
public class FooController{
//...
@ExceptionHandler({ CustomException1.class, CustomException2.class })
public void handleException() {
//
}
}DefaultHandlerExceptionResolver (captura os erros 4xx e 5xx): Define apenas o status code, não adiciona informações do body.
ResponseStatusExceptionResolver: Captura exceptions customizadas anotadas com @ResponseStatus e as mapeia para respectivos status codes.
@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {
@Override
protected ModelAndView doResolveException
(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
try {
if (ex instanceof IllegalArgumentException) {
return handleIllegalArgument((IllegalArgumentException) ex, response, handler);
}
...
} catch (Exception handlerException) {
logger.warn("Handling of [" + ex.getClass().getName() + "]
resulted in Exception", handlerException);
}
return null;
}
private ModelAndView handleIllegalArgument
(IllegalArgumentException ex, HttpServletResponse response) throws IOException {
response.sendError(HttpServletResponse.SC_CONFLICT);
String accept = request.getHeader(HttpHeaders.ACCEPT);
...
return new ModelAndView();
}
}@GetMapping(value = "/{id}")
public Foo findById(@PathVariable("id") Long id, HttpServletResponse response) {
try {
Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
return resourceById;
}
catch (MyResourceNotFoundException exc) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Foo Not Found", exc);
}
}Permite tratar exceções de maneira global.
@ControllerAdvice
public class GlobalExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler(
value={IllegalArgumentException.class, IllegalStateException.class }
)
protected ResponseEntity<Object> handleConflict(
RuntimeException ex, WebRequest request
) {
String body = "Should be unique per request";
return handleExceptionInternal(
ex, body,
new HttpHeaders(),
HttpStatus.CONFLICT,
request
);
}
}Baseado na RFC 7807 - Problem Details for HTTP APIs.
application/problem+json
Baseado em @ControllerAdvice
<dependency>
<groupId>org.zalando</groupId>
<artifactId>problem</artifactId>
<version>${problem.version}</version>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>jackson-datatype-problem</artifactId>
<version>${problem.version}</version>
</dependency>Problem.builder()
.withType(URI.create("https://example.org/out-of-stock"))
.withTitle("Out of Stock")
.withStatus(BAD_REQUEST)
.withDetail("Item B00027Y5QG is no longer available")
.build();