认识 Spring MVC
DispatcherServlet
Spring MVC 中的常用注解
Spring 的应用程序上下文
关于上下文常用的接口及其实现
- @SpringBootApplication
- @Slf4j
- public class ContextHierarchyDemoApplication implements ApplicationRunner {
-
- public static void main(String[] args) {
- SpringApplication.run(ContextHierarchyDemoApplication.class, args);
- }
-
- @Override
- public void run(ApplicationArguments args) throws Exception {
- ApplicationContext fooContext = new AnnotationConfigApplicationContext(FooConfig.class);
- ClassPathXmlApplicationContext barContext = new ClassPathXmlApplicationContext(
- new String[] {"applicationContext.xml"}, fooContext);
- TestBean bean = fooContext.getBean("testBeanX", TestBean.class);
- bean.hello();
-
- log.info("=============");
-
- bean = barContext.getBean("testBeanX", TestBean.class);
- bean.hello();
-
- bean = barContext.getBean("testBeanY", TestBean.class);
- bean.hello();
- }
- }
FooConfig.java:父上下文(parent application context)。
applicationContext.xml:子上下文(child application context)。
FooConfig.java 中定义两个 testBean,分别为 testBeanX(foo) 和 testBeanY(foo)。 applicationContext.xml 定义了一个 testBeanX(bar)。
委托机制:在自己的 context 中找不到 bean,会委托父 context 查找该 bean。 ---------- 代码解释: fooContext.getBean("testBeanX"),在父上下文查找 testBeanX,命中直接返回 testBeanX(foo)。 barContext.getBean("testBeanX"),在子上下文查找 testBeanX,命中直接返回 testBeanX(bar)。 barContext.getBean("testBeanY"),在子上下文查找 testBeanY,未命中;委托父上下文查找,命中,返回 testBeanY(foo)。
---------- 场景一: 父上下文开启 @EnableAspectJAutoProxy 的支持 子上下文未开启 <aop: aspectj-autoproxy /> 切面 fooAspect 在 FooConfig.java 定义(父上下文增强) 输出结果: testBeanX(foo) 和 testBeanY(foo) 均被增强。 testBeanX(bar) 未被增强。 结论: 在父上下文开启了增强,父的 bean 均被增强,而子的 bean 未被增强。
---------- 场景二: 父上下文开启 @EnableAspectJAutoProxy 的支持 子上下文开启 <aop: aspectj-autoproxy /> 切面 fooAspect 在 applicationContext.xml 定义(子上下文增加) 输出结果: testBeanX(foo) 和 testBeanY(foo) 未被增强。 testBeanX(bar) 被增强。
结论: 在子上下文开启增强,父的 bean 未被增强,子的 bean 被增强。 ---------- 根据场景一和场景二的结果,有结论:“各个 context 相互独立,每个 context 的 aop 增强只对本 context 的 bean 生效”。如果想将切面配置成通用的,对父和子上下文的 bean 均支持增强,则: 1. 切面 fooAspect 定义在父上下文。 2. 父上下文和子上下文,均要开启 aop 的增加,即 @EnableAspectJAutoProxy 或<aop: aspectj-autoproxy /> 的支持。
Spring MVC的请求处理流程
一个请求的大致处理流程
绑定一些 Attribute
处理 Mulitipart
Hadnler 处理
处理返回的 Model,呈现视图
- public class DispatcherServlet extends FrameworkServlet
- |
- public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
- |
- public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware
- |
- public abstract class javax.servlet.http.HttpServlet extends GenericServlet
- |
- public abstract class javax.servlet.GenericServlet implements Servlet, ServletConfig,
- java.io.Serializable
- |
- public interface javax.servlet.Servlet
1. DispatcherServlet.java: doService()开始处理请求;
2. DispatcherServlet.java: getHandler() 遍历几种handler
3. AbstractHandlerMapping.java: getHandler() 获取处理类的实例
4. AbstractHandlerMapping.java: getHandler() 检查是否为跨域请求(检查代码: request.getHeader("Origin") != null;)
5. AbstractHandlerMethodMapping.java: lookupHandlerMethod()->实际是在addMatchingMappings()方法里获取请求url对应的Controller全限定类路径及返回参数及全限定方法名. (例如(因代码过长,所以折行.): public java.util.List<xyz.suancaiyu.complexcontrollerdemo.model.Coffee> xyz.suancaiyu.complexcontrollerdemo.controller.CoffeeController.getAll() )
6. DispatcherServlet.java: doService()实际调用处理程序:mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
7. DispatcherServlet.java: doService()处理完成,并返回.
定义映射关系
@Controller
@RequestMapping
一些快捷方式
定义处理方法
- @Controller
- @RequestMapping("/coffee")
- public class CoffeeController {
- @Autowired
- private CoffeeService coffeeService;
-
- @GetMapping(path = "/", params = "!name")
- @ResponseBody
- public List<Coffee> getAll() {
- return coffeeService.getAllCoffee();
- }
-
- @RequestMapping(path = "/{id}", method = RequestMethod.GET,
- produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
- @ResponseBody
- public Coffee getById(@PathVariable Long id) {
- Coffee coffee = coffeeService.getCoffee(id);
- return coffee;
- }
-
- @GetMapping(path = "/", params = "name")
- @ResponseBody
- public Coffee getByName(@RequestParam String name) {
- return coffeeService.getCoffee(name);
- }
- }
定义类型转换
自己实现 WebMvcConfigurer
- @Component
- public class MoneyFormatter implements Formatter<Money> {
- /**
- * 处理 CNY 10.00 / 10.00 形式的字符串
- * 校验不太严密,仅作演示
- */
- @Override
- public Money parse(String text, Locale locale) throws ParseException {
- if (NumberUtils.isParsable(text)) {
- return Money.of(CurrencyUnit.of("CNY"), NumberUtils.createBigDecimal(text));
- } else if (StringUtils.isNotEmpty(text)) {
- String[] split = StringUtils.split(text, " ");
- if (split != null && split.length == 2 && NumberUtils.isParsable(split[1])) {
- return Money.of(CurrencyUnit.of(split[0]),
- NumberUtils.createBigDecimal(split[1]));
- } else {
- throw new ParseException(text, 0);
- }
- }
- throw new ParseException(text, 0);
- }
-
- @Override
- public String print(Money money, Locale locale) {
- if (money == null) {
- return null;
- }
- return money.getCurrencyUnit().getCode() + " " + money.getAmount();
- }
- }
定义校验
- @Getter
- @Setter
- @ToString
- public class NewCoffeeRequest {
- @NotEmpty
- private String name;
- @NotNull
- private Money price;
- }
-
- @PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
- @ResponseBody
- @ResponseStatus(HttpStatus.CREATED)
- public Coffee addCoffee(@Valid NewCoffeeRequest newCoffee,
- BindingResult result) {
- if (result.hasErrors()) {
- // 这里先简单处理一下,后续讲到异常处理时会改
- log.warn("Binding Errors: {}", result);
- return null;
- }
- return coffeeService.saveCoffee(newCoffee.getName(), newCoffee.getPrice());
- }
Multipart 上传
- @PostMapping(path = "/", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
- @ResponseBody
- @ResponseStatus(HttpStatus.CREATED)
- public List<Coffee> batchAddCoffee(@RequestParam("file") MultipartFile file) {
- List<Coffee> coffees = new ArrayList<>();
- if (!file.isEmpty()) {
- BufferedReader reader = null;
- try {
- reader = new BufferedReader(
- new InputStreamReader(file.getInputStream()));
- String str;
- while ((str = reader.readLine()) != null) {
- String[] arr = StringUtils.split(str, " ");
- if (arr != null && arr.length == 2) {
- coffees.add(coffeeService.saveCoffee(arr[0],
- Money.of(CurrencyUnit.of("CNY"),
- NumberUtils.createBigDecimal(arr[1]))));
- }
- }
- } catch (IOException e) {
- log.error("exception", e);
- } finally {
- IOUtils.closeQuietly(reader);
- }
- }
- return coffees;
- }
视图解析的实现基础
ViewResolver 与 View 接口
DispatcherServlet 中的视图解析逻辑
DispatcherServlet 中的视图解析逻辑
使用 @ResponseBoyd 的情况
重定向
两种不同的重定向逻辑
Spring MVC 支持的视图
支持的视图列表
Spring Boot 对 Jackson的支持