Spring 是一款开源的 J2EE 框架,它有许多项目,为 Java 应用开发提供了一整套的工具,其中最核心的就是 Spring Framework 和 Spring Boot 项目。
文本是一个系列文章的第一篇,下面就这两个项目的核心内容做一些速查整理,同时辅以生产源码,便于理解。
Spring 一个很重要的功能就是开发 Web 应用,有基于 Servlet 的 Spring MVC,也有基于响应式的 Spring WebFlux。本期我们重点讲讲 Spring Web MVC。
使用 Spring 框架和 Spring Boot 的自动化配置可以非常方便地构建现代化的 Web 应用,无论是 Restful、XML 还是页面模版、JSP 等。
Spring MVC 的设计核心就是一系列的前置处理器围绕着一个中央的 Servlet。这个 Servlet(DispatcherServlet)提供了公共的请求处理流程,并使用可配置的代理组件来完成各个环节的处理。而 DispatcherServlet 使用了 WebApplicationContext(继承自 ApplicationContext)作为应用上下文管理,它提供了一系列 Servlet 特定 Bean (如 Controllers, view resolvers, handler mappings)的管理。下面是一些 DispatcherServlet 的常用 Bean。
DispatcherServlet 处理请求的流程如下:
控制器就是用来接收请求并处理的 Bean,Spring MVC 使用 @Controller 或 @RestController 注解标记类来声明控制器。@RestController 是 @Controller 注解和 @ResponseBody 注解的组合,可用于处理 JSON 请求。控制器中使用 @RequestMapping 注解来映射请求。
@GetMapping、@PostMapping、@PutMapping、@PatchMapping、@DeleteMapping 注解分别用于特定的 HTTP 方法,是 @RequestMapping 的简化变体(但只能注解方法)。一般可以在类上使用 @RequestMapping 注解指定公共配置(如路径前缀),而在各个处理方法上使用特定的方法注解。
@RestController @RequestMapping("/users") public class MyRestController {  	private final UserRepository userRepository;  	private final CustomerRepository customerRepository;  	public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) { 		this.userRepository = userRepository; 		this.customerRepository = customerRepository; 	}  	@GetMapping("/{userId}") 	public User getUser(@PathVariable Long userId) { 		return this.userRepository.findById(userId).get(); 	}  	@GetMapping("/{userId}/customers") 	public List getUserCustomers(@PathVariable Long userId) { 		return this.userRepository.findById(userId).map(this.customerRepository::findByUser).get(); 	}  	@DeleteMapping("/{userId}") 	public void deleteUser(@PathVariable Long userId) { 		this.userRepository.deleteById(userId); 	}  }   同一个类或方法不能有多个 @RequestMapping 注解。
直达源码
URI 支持通配符和变量的模式匹配。
捕获的路径变量可以使用 @PathVariable 注解获取。
@GetMapping("/owners/{ownerId}/pets/{petId}") public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) { 	// ... }  可以组合类上和方法上的注解规则。
@Controller @RequestMapping("/owners/{ownerId}") public class OwnerController {  	@GetMapping("/pets/{petId}") 	public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) { 		// ... 	} }  还可以使用正则表达式匹配更复杂的路径。
// 可匹配路径 /spring-web-3.0.5.jar @GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}") public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) { 	// ... }  当有多个路径规则匹配时,最接近的或最特异的会被选中。例如,越长的路径优先级越高,越多的变量或通配符的优先级越低。默认路径 /** 总是最低的优先级。
详细文档可参考官方文档。
我们可以使用 consumes 参数来限制请求的 Content-Type(也就是请求的媒体类型)。
@PostMapping(path = "/pets", consumes = "application/json") public void addPet(@RequestBody Pet pet) { 	// ... }  也支持反向限制,如
!text/plain则匹配除了text/plain之外的。
我们还可以使用 produces 参数来限制请求的 Accept(也就是接受的响应媒体类型)。
@GetMapping(path = "/pets/{petId}", produces = "application/json") @ResponseBody public Pet getPet(@PathVariable String petId) { 	// ... }  同样也支持反向限制。
我们可以使用 params 参数限制特定的请求参数,headers 参数限制特定的请求头。
// 限制请求参数 myParam = myValue @GetMapping(path = "/pets/{petId}", params = "myParam=myValue") public void findPet(@PathVariable String petId) { 	// ... }  // 限制请求头 myHeader = myValue @GetMapping(path = "/pets/{petId}", headers = "myHeader=myValue") public void findPet(@PathVariable String petId) { 	// ... }  另外可以使用编程的方式显式注册处理方法,可用于动态注册。
@Configuration public class MyConfig {  	@Autowired 	public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) 			throws NoSuchMethodException {         // 准备请求映射元数据 		RequestMappingInfo info = RequestMappingInfo 				.paths("/user/{id}").methods(RequestMethod.GET).build();         // 获取方法 		Method method = UserHandler.class.getMethod("getUser", Long.class);         // 注册方法 		mapping.registerMapping(info, handler, method); 	} }  处理器的方法支持如下类型或注解标记的参数:
@RequestParam 是非常常用的用于获取请求参数或表单数据的注解,它有一个参数,用于指定参数名。另外还有一个参数 required,用于指定是否必填(默认是)。
@Controller @RequestMapping("/pets") public class EditPetForm {  	@GetMapping 	public String setupForm(@RequestParam("petId") int petId, Model model) { 		Pet pet = this.clinic.loadPet(petId); 		model.addAttribute("pet", pet); 		return "petForm"; 	}  	// ...  }  如果声明类型为数组或 List,则会解析重名的参数值(如多个相同名称的请求参数)。如果声明的类型为 Map 或者 MultiValueMap 且不提供参数名,则会获取所有提供的参数。
直达源码
要获取请求头数据,可以使用 @RequestHeader 注解绑定。如果注解标记的类型是 Map、 MultiValueMap 或 HttpHeaders,则会获取所有的请求头数据。
@GetMapping("/demo") public void handle( 		@RequestHeader("Accept-Encoding") String encoding, 		@RequestHeader("Keep-Alive") long keepAlive) { 	//... }  可以使用 MultipartFile 类型接收 POST 请求的 multipart/form-data 数据,用来处理上传文件。
@Controller public class FileUploadController {  	@PostMapping("/form") 	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"; 	} }  可以使用 List 类型来接收同名参数的多个上传文件。还可以定义一个对象来接收整个表单数据。
class MyForm {  	private String name;  	private MultipartFile file;  	// ... }  @Controller public class FileUploadController {  	@PostMapping("/form") 	public String handleFormUpload(MyForm form, BindingResult errors) { 		if (!form.getFile().isEmpty()) { 			byte[] bytes = form.getFile().getBytes(); 			// store the bytes somewhere 			return "redirect:uploadSuccess"; 		} 		return "redirect:uploadFailure"; 	} }  使用 @RequestBody 注解可以转换请求体,这对于 JSON 请求非常方便。
@PostMapping("/accounts") public void handle(@RequestBody Account account) { 	// ... }  处理器方法支持返回以下类型:
使用 @ResponseBody 注解可以转换响应体,对于 JSON 响应非常方便。
@GetMapping("/accounts/{id}") @ResponseBody public Account handle() { 	// ... }  这个注解也可以标记在类上,会对所有的方法生效。如果控制器使用了 @RestController 注解,则默认启用了 @ResponseBody 注解。
推荐使用 ResponseEntity 类型作为返回值,它包含了状态码、响应头和响应体信息,对于 JSON 响应也非常方便。
@GetMapping("/something") public ResponseEntity handle() { 	String body = ... ; 	String etag = ... ; 	return ResponseEntity.ok().eTag(etag).body(body); }   而 ResponseEntity 类型可以用作下载文件。
使用 @Controller 和 @ControllerAdvice、@RestControllerAdvice(即 @ControllerAdvice 和 @ResponseBody 的组合)注解标记的类可以使用 @ExceptionHandler 标记的方法来处理控制器方法的异常。
@Controller public class SimpleController {  	// ...  	@ExceptionHandler 	public ResponseEntity handle(IOException ex) { 		// ... 	} }   参数的异常类型可限制处理的异常,如果要处理多种类型的异常,可以在注解的参数中声明。
@ExceptionHandler({FileSystemException.class, RemoteException.class}) public ResponseEntity handle(Exception ex) { 	// ... }   但是这样异常的类型会变为更通用的父类,建议对于特定的异常,使用特定的 @ExceptionHandler 来捕获处理,再对父类的异常进行处理。
直达源码
@ControllerAdvice 和 @RestControllerAdvice 可以处理所有控制器的异常,但比控制器上定义的异常处理方法优先级较低。它们由 RequestMappingHandlerMapping 和 ExceptionHandlerExceptionResolver 进行加载。
Spring MVC 包含了 WebMvc.fn,提供了一个轻量的函数式编程接口来处理请求。请求由 HandlerFunction 处理,该函数接受 ServerRequest 并返回 ServerResponse。HandlerFunction 相当于基于注解的编程模型中 @RequestMapping 方法的主体。
传入的请求通过 RouterFunction 路由到处理函数:一个接受 ServerRequest 并返回可空的 HandlerFunction(即 Optional)的函数。当路由器函数匹配时,返回处理函数,否则返回一个空的 Optional。RouterFunctions.route() 提供路由创建的方式:
@Configuration(proxyBeanMethods = false) public class WebTest {    @Bean   public RouterFunction person() {     return route().GET("/person", accept(MediaType.APPLICATION_JSON), request -> ServerResponse.status(HttpStatus.OK).body("Hello World")).build() ;   }  }   ServerRequest 提供了获取请求的 HTTP 方法、 URI、 请求头和请求参数的接口,通过 body 方法可以获取请求体。
// 将请求体转换为字符串 String string = request.body(String.class);  // 将请求体转换为 List List people = request.body(new ParameterizedTypeReference>() {});  // 获取请求参数 MultiValueMap params = request.params();  
   ServerResponse 提供了访问 HTTP 响应的接口,由于它是不可变的,使用 build 方法来创建。
// 创建状态码为 200 OK 的 JSON 响应 Person person = ... ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);  除了使用 Lambda 表达式,我们也可以像注解方式那样使用处理器类,这样更便于复杂业务逻辑的处理和代码复用。
// 定义处理器类 public class PersonHandler {  	private final PersonRepository repository;  	public PersonHandler(PersonRepository repository) { 		this.repository = repository; 	}  	public ServerResponse listPeople(ServerRequest request) { 		List people = repository.allPeople(); 		return ok().contentType(APPLICATION_JSON).body(people); 	}  	public ServerResponse createPerson(ServerRequest request) throws Exception { 		Person person = request.body(Person.class); 		repository.savePerson(person); 		return ok().build(); 	}  	public ServerResponse getPerson(ServerRequest request) { 		int personId = Integer.parseInt(request.pathVariable("id")); 		Person person = repository.getPerson(personId); 		if (person != null) { 			return ok().contentType(APPLICATION_JSON).body(person); 		} 		else { 			return ServerResponse.notFound().build(); 		} 	}  }  // 在配置类中绑定路由 @Bean public RouterFunction routerFunction(PersonHandler personHandler) {     return route()         .GET("/person", accept(MediaType.APPLICATION_JSON), personHandler::listPeople)         .POST("/person", accept(MediaType.APPLICATION_JSON), personHandler::createPerson)         .GET("/person/{id}", accept(MediaType.APPLICATION_JSON), personHandler::getPerson)         .build();     }    路由用于将请求映射到对应的处理器类上,我们不需要手动编写,可以使用 RouterFunctions.route() 方法创建建造器流式构建路由。建造器提供了 GET、POST 等方法来构建对应的 HTTP 方法路由。除了 HTTP 方法,也可以使用 RequestPredicate (请求谓词)(例如 HTTP 方法、路径、请求头等)来构建更复杂的路由场景。RequestPredicate 还支持逻辑运算组合。
// 使用 RequestPredicates.accept() 匹配请求头 Accept RouterFunction route = RouterFunctions.route() 	.GET("/hello-world", accept(MediaType.TEXT_PLAIN), 		request -> ServerResponse.ok().body("Hello World")).build();   如果一些路由函数共享相同的谓词,例如相同的路径,则可以提取共同的部分,使用嵌套路由。例如,下面的路由都共用 /person 路径。
RouterFunction route = route() 	.path("/person", builder -> builder 		.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson) 		.GET(accept(APPLICATION_JSON), handler::listPeople) 		.POST(handler::createPerson)) 	.build();   尽管共用路径是最常见的,也可以共用其他的,例如请求头 accept。
RouterFunction route = route() 	.path("/person", b1 -> b1 		.nest(accept(APPLICATION_JSON), b2 -> b2 			.GET("/{id}", handler::getPerson) 			.GET(handler::listPeople)) 		.POST(handler::createPerson)) 	.build();   我们可以使用路由构建器的 before、after 和 filter 定义过滤器,对于当前的路由及其嵌套路由都会生效。
RouterFunction route = route() 	.path("/person", b1 -> b1 		.nest(accept(APPLICATION_JSON), b2 -> b2 			.GET("/{id}", handler::getPerson) 			.GET(handler::listPeople) 			.before(request -> ServerRequest.from(request) 				.header("X-RequestHeader", "Value") 				.build())) 		.POST(handler::createPerson)) 	.after((request, response) -> logResponse(response)) 	.build();   路由构建器的 filter 方法是一个 HandlerFilterFunction,接受 ServerRequest 和 HandlerFunction 参数,返回 ServerResponse。方法的第二个参数是过滤器调用链上的下一个处理函数,我们可以继续传递下去或者直接返回。
// 假设一个安全管理器来做权限控制 SecurityManager securityManager = ...  RouterFunction route = route() 	.path("/person", b1 -> b1 		.nest(accept(APPLICATION_JSON), b2 -> b2 			.GET("/{id}", handler::getPerson) 			.GET(handler::listPeople)) 		.POST(handler::createPerson)) 	.filter((request, next) -> { 		if (securityManager.allowAccessTo(request.path())) {             // 传递给下一个过滤器 			return next.handle(request); 		} 		else {             // 返回响应 			return ServerResponse.status(UNAUTHORIZED).build(); 		} 	}) 	.build();   (未完待续)
如果觉得有用,请多多支持,点赞收藏吧!