跳至主要內容

SpringMVC

soulballad微服务SpringCloud NetfilxSpringCloud约 1662 字大约 6 分钟

Spring Web MVC

MVC 概念

  • M:Model
  • V:View
  • C:Controller

SpringMVC

1569919131314

  • Controller:springmvc 中 Controller 由两部分组成

    • 前端控制器(Front Controller): 指DispatcherServlet
    • 应用控制器(App Controller):使用@Controller修饰或者实现Controller接口的类
  • Context:springmvc 中 Context 由两部分组成

    • Root WebApplicationContext:它由 ServletContextListener -> ContextLoadListener 来加载

      1569919490599

    • Servlet WebApplicationContext:它由 DispatcherServlet 来加载

    二者的关系是:Root WebApplicationContext处理底层,Servlet WebApplicationContext处理上层,二者之间有一个层次关系。

  • Services:使用 @Service 修饰的类

  • Repositories:使用 @Repository 修饰的类

处理映射

Servlet 处理方式

使用 web.xml 配置映射或者 @WebServlet注解的方式

@WebServlet("/IndexServlet")
//@WebServlet("/") 匹配当前你路径,不包含子路径
//@WebServlet("/*") 匹配所有路径,包含子路惊
//@WebServlet("*.jsp") 匹配以jsp结尾文件
public class IndexServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public IndexServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		response.getWriter().append("Served at: ").append(request.getContextPath());
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// TODO Auto-generated method stub
		doGet(request, response);
	}
}

ContextPath: /servlet-demo (在tomcat的server.xml文件中)

1569921111556

完整路径 = ContextPath + servlet Path = /servlet-demo/IndexServlet

1569921187300

SpringMvc 处理方式

@RestController // @Controller + @ResponseBody
public class RestDemoController {

    @GetMapping
    public String index() {
        return "hello world";
    }
}

1569923224277

Web URI组装

RequestURI = @RestController.value() + @RequestMapping.value()/@GetMapping.value()

当前 ServletContextPath = "" or "/"

这里没有配置任何路径,springboot也可以启动起来,要归功于 DispatcherServlet 的自动装配。

1569921930347

DispatcherSerlet 自动装配:DispatcherServletAutoConfiguration

1569921993374

Web Mvc 配置

Sprig Boot 允许通过 application.properties 去定义一些配置,配置外部化

Spring Web Mvc 的配置Bean: WebMvcProperties

WebMvcProperties 配置前缀: spring.mvc

1569922251265

在 application.properties 中进行配置:

1569922345144

Web Uri 映射处理

HandlerMapping 寻找 Request URI,匹配 Handler。

Handler 是处理的方法,是一种实例。

Handler 操作

  • 整体流程:Request -> Handler -> 执行结果 -> 返回(REST)-> 普通文本
  • 请求处理映射: RequestMappingHandlerMapping -> @RequestMapping Handler Mapping

Handler 是什么?

拦截器:HandlerInterceptor 可以理解 Handler 到底是什么。

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }

    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

HandlerInterceptor 处理顺序:

  • preHandle (true) -> handle执行(Method#invoke)-> postHandle -> afterCompletition
  • preHandle(false) -> end;preHandle 返回失败,直接结束,不执行后面流程。

自定义拦截器,DefaultHandlerInterceptor:

  1. 实现 HandlerInterceptor 接口,重写方法

    public class DefaultHandlerInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    		// 输出当前拦截器
            System.out.println(handler.toString());
            return true;
        }
    }
    
  2. 将拦截器注册到 registry

    实现 WebMvcConfigureAdapter,重写 addInterceptors 方法

    @SpringBootApplication
    public class SpringWebmvcApplication extends WebMvcConfigurerAdapter {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringWebmvcApplication.class, args);
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            // 把拦截器注册到 registry
            registry.addInterceptor(new DefaultHandlerInterceptor());
        }
    }
    
  3. 查看结果

    1569923193987

异常处理

Servlet 处理方式

  1. 配置 web.xml 错误页面

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd ">
    
        <servlet>
            <servlet-name>pageNotFoundServlet</servlet-name>
            <servlet-class>com.gupao.servlet.PageNotFoundServlet</servlet-class>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>pageNotFoundServlet</servlet-name>
            <url-pattern>/404.html</url-pattern>
        </servlet-mapping>
    
        <error-page>
            <error-code>404</error-code>
            <location>/404.html</location>
        </error-page>
    </web-app>
    
  2. 自定义错误处理servlet

    public class PageNotFoundServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
            resp.setContentType("text/html;charset=UTF-8");
            resp.setCharacterEncoding("UTF-8");
    
            PrintWriter writer = resp.getWriter();
            writer.write("页面找不到");
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            this.doGet(req, resp);
        }
    }
    
  3. 浏览器访问

    1569925428126

    原因是,当前 context-path 是 “servlet-demo”,配置的错误处理页面只能处理 servlet-demo 路径下的 404错误,无法处理其他 context-path 下的 404错误,所以还是提示 404。

  • 优点:统一处理,业界标准
  • 不足:灵活度不够,只能定义 web.xml文件里面

SpringMvc 处理方式

使用 @ControllerAdvice + @ExceptionHandler 注解的方式

  1. 配置异常请求

    @GetMapping("/npe")
    public String npe() {
        throw new NullPointerException("故意抛出异常!");
    }
    
  2. 设置拦截切面

    @RestControllerAdvice("com.gupao.microservice.springwebmvc")
    public class RestControllerAdviser {
    
    	// 拦截额空指针异常
        @ExceptionHandler(NullPointerException.class)
        public Object handleNPE(Throwable throwable) {
            return throwable.getMessage();
        }
    }
    
  3. 浏览器访问

    1569930831181

  • @ExceptionHandler
    • 优点:易于理解,尤其是全局异常处理
    • 不足:很难完全掌握所有的异常类型
  • @RestControllerAdvice = @ControllerAdvice + @ResponseBody
  • @ControllerAdvice 专门拦截(AOP) @Controller

SpringBoot处理方式

  1. 实现 ErrorPageRegistrar 接口,重写registerErrorPages方法

  2. 注册ErrorPage对象

    @SpringBootApplication
    public class SpringWebmvcApplication extends WebMvcConfigurerAdapter implements ErrorPageRegistrar {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringWebmvcApplication.class, args);
        }
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new DefaultHandlerInterceptor());
        }
    
        // 新增一个404错误页面处理处理
        @Override
        public void registerErrorPages(ErrorPageRegistry registry) {
            registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"));
        }
    }
    
  3. 实现 ErrorPage 对象中的Path 路径Web服务

    @RestController
    public class PageNotFoundController {
    
        @GetMapping("/404.html")
        public Object pageNotFound(HttpServletRequest request, HttpStatus status, Throwable throwable) {
    
            Map<String, Object> errorMap = new HashMap<>();
    		// 获取错误码
            errorMap.put("statusCode", request.getAttribute("javax.servlet.error.status_code"));
            // 获取请求的 uri
            errorMap.put("requestUri", request.getAttribute("javax.servlet.error.request_uri"));
            System.err.println(errorMap.toString());
            return errorMap;
        }
    }
    
  4. 浏览器访问

    1569930180299

  • 状态码:比较通用,不需要理解Spring WebMVC 异常体系
  • 不足:页面处理的路径必须固定

视图技术

View

render 方法

public interface View {
	// ...
	void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
    
}

处理页面渲染的逻辑,例如:Velocity、JSP、Thymeleaf 官方推荐)

ViewResolver

View Resolver = 页面 + 解析器 -> resolveViewName 寻找合适/对应 View 对象

处理流程:

  • 请求返回 View

    RequestURI-> RequestMappingHandlerMapping -> HandleMethod -> return "viewName"

    完整的 viewName = prefix + "viewName" + suffix

  • 渲染 View

    ViewResolver -> View -> render -> HTML

Spring Boot 解析完整的页面路径:

​ spring.view.prefix + HandlerMethod return + spring.view.suffix

ContentNegotiationViewResolver

用于协调多个ViewResolver:JSP、Velocity、Thymeleaf

  • 当所有的ViewResover 配置完成时,他们的 order 默认值一样,所以先来先服务(List)

  • 当他们定义自己的 order,通过 order 来倒序排列

Thymeleaf

自动装配类:ThymeleafAutoConfiguration

外部配置类:ThymeleafProperties

1569932793997

  • 配置项前缀:spring.thymeleaf

  • 模板寻找前缀:spring.thymeleaf.prefix

  • 模板寻找后缀:spring.thymeleaf.suffix

代码示例:/thymeleaf/index.htm

  • prefix: /thymeleaf/
  • return value : index
  • suffix: .htm

Thymeleaf 使用

  1. 引入 thymeleaf starter

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
        <version>2.1.8.RELEASE</version>
    </dependency>
    
  2. 定义 indexController

    @Controller
    public class IndexController {
    
        @RequestMapping
        public String index() {
            return "index";
        }
    }
    
  3. 配置前缀、后缀

    spring.thymeleaf.prefix=classpath:/thymeleaf/
    spring.thymeleaf.suffix=.htm
    
  4. 编写页面

    1569934400542

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <title>首页</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    </head>
    <body>
    <p th:text="#{home.welcome}">Welcome to our grocery store!</p>
    </body>
    </html>
    
  5. 浏览器访问

    1569933020009

源码分析

org.springframework.web.servlet.view.AbstractCachingViewResolver#resolveViewName

1569933194849

国际化

•Locale/LocaleContext

•LocaleContextHolder

•LocaleResolver/LocaleContextResolver

SpringBoot 配置

  1. application.properties 配置

    spring.messages.basename=META-INF/locale/messages
    
  2. message.properties & message_zh_CN.properties

    # message.properties
    home.welcome=Hello, World
    # message_zh_CN.properties
    home.welcome=欢迎光临
    

    1569934186554

  3. 浏览器访问

    1569934212970

源码分析

MessageSourceProperties

org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration#messageSource

使用 ResourceBundleMessageSource 来加载配置

1569934341548

上次编辑于:
贡献者: soulballad