Spring MVC的 架构模式
MVC 架构模式
在 UI 相关的开发领域,通过控制实现模型与视图解耦
M 模型:实体、业务逻辑
V 视图:用户接口(Web、桌面、移动端)
C 控制器:Servlet、Action、Controller
MVC 架构模式相关的:MVVM(移动端、Vue、React)
问题域(通用的,与语言无关,都需要面对的)
MVC / MVVM : 模型与视图的解耦
ORM - OOP :语言 & RDBMS 之间的映射
IoC / DI : 依赖注入
Java EE 技术规范中 Web 相关技术
Servlet : 通过继承 HttpServlet定义大量的 Servlet
JSP/JSTL
Filter:对特定路径的请求执行前置或后置的过滤(加入功能)
Listener: 对应用程序(全局)、用户会话、HTTP 请求的生命周期或特定时刻进行监听,注册回调函数
启动顺序:
Filter 早于 Servlet
关于 Filter 和Listener的具体作用:(接下来用代码演示)
工程目录:
AuthFilter.java
package com.newer.mvc.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
/**
* Servlet Filter implementation class AuthListener
*/
@WebFilter(value = {"/admin/*","/api/*","/other"})
public class AuthFilter implements Filter {
/**
* Default constructor.
*/
public AuthFilter() {
System.out.println("AuthListener 创建");
}
/**
* @see Filter#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
/**
* @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req=(HttpServletRequest) request;
System.out.println("鉴权"+req.getRequestURI());
chain.doFilter(request, response);
}
/**
* @see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
// TODO Auto-generated method stub
}
}
EncodingFilter.java
package com.newer.mvc.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 过滤器
*/
@WebFilter("/*")
public class EncodingFilter implements Filter {
/**
* Default constructor.
*/
public EncodingFilter() {
System.out.println("创建EncodingFilter");
}
/**
* @see Filter#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
/**
* @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
*/
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 执行前
// 应用场景:编码设置,鉴权,图片加水印。。。
HttpServletRequest req=(HttpServletRequest) request;
HttpServletResponse res=(HttpServletResponse) response;
req.setCharacterEncoding("UTF-8");
res.setCharacterEncoding("UTF-8");
String path=req.getRequestURI();
System.out.println("EncodingFilter doFilter:"+path);
if(path.equals("/mvc/admin")) {
// 鉴权
System.out.println("需要鉴权");
}
// 执行后续的正常流程
chain.doFilter(request, response);
// 执行后
// 如果是图片
if(res.getContentType().equals("image/*")){
System.out.println("加水印");
}
}
/**
* @see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
System.out.println("EncodingFilter 初始化。。。。。。");
}
}
AppContextListener.java
package com.newer.mvc.web.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* 监听器
* 不是你去调用的
* 该监听器关注的事件发生时,回调特定的方法
*
* @author Admin
*
*/
@WebListener
public class AppContextListener implements ServletContextListener {
/**
* Default constructor.
*/
public AppContextListener() {
System.out.println("ServletContextListener 创建");
}
/**
* @see ServletContextListener#contextDestroyed(ServletContextEvent)
*/
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContextListener 准备销毁。。。");
System.out.println("ServletContextListener 释放资源,如关闭数据库的连接池,");
System.out.println("ServletContextListener 销毁完毕。。。");
}
/**
* @see ServletContextListener#contextInitialized(ServletContextEvent)
*/
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContextListener 初始化。。。");
System.out.println("ServletContextListener 加载资源,如创建数据库的连接池,装配依赖的资源");
}
}
RequestListener.java
package com.newer.mvc.web.listener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpServletRequest;
/**
*监听一个Http请求的生命周期
*
*/
@WebListener
public class RequestListener implements ServletRequestListener {
/**
* Default constructor.
*/
public RequestListener() {
System.out.println("RequestListener");
}
/**
* @see ServletRequestListener#requestDestroyed(ServletRequestEvent)
*/
public void requestDestroyed(ServletRequestEvent sre) {
HttpServletRequest req=(HttpServletRequest) sre.getServletRequest();
System.out.printf("RequestListener:销毁为%s使用过的数据库连接\n",req.getRequestURI());
}
/**
* @see ServletRequestListener#requestInitialized(ServletRequestEvent)
*/
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest req=(HttpServletRequest) sre.getServletRequest();
System.out.printf("RequestListener:为%s从数据库连接池 创建一个数据库连接\n",req.getRequestURI());
}
}
程序运行后,控制台输出为:
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: Server.服务器版本: Apache Tomcat/9.0.34
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 服务器构建: Apr 3 2020 12:02:52 UTC
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 服务器版本号(:9.0.34.0
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: OS Name: Windows 10
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: OS.版本: 10.0
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 架构: amd64
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: Java 环境变量: C:\Program Files\Java\jdk-14
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: JVM 版本: 14+36-1461
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: JVM.供应商: Oracle Corporation
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: CATALINA_BASE:[D:\springbootproject\.metadata\.plugins\org.eclipse.wst.server.core\tmp0]
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: CATALINA_HOME: D:\Spring Boot\apache-tomcat-9.0.34
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:[-Dcatalina.base=D:\springbootproject\.metadata\.plugins\org.eclipse.wst.server.core\tmp0]
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:[-Dcatalina.home=D:\Spring Boot\apache-tomcat-9.0.34]
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:[-Dwtp.deploy=D:\springbootproject\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps]
4月 17, 2020 5:25:56 下午 org.apache.catalina.startup.VersionLoggerListener log
信息: 命令行参数:[-Dfile.encoding=UTF-8]
4月 17, 2020 5:25:56 下午 org.apache.catalina.core.AprLifecycleListener lifecycleEvent
信息: The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [C:\Program Files\Java\jdk-14\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:/Program Files/Java/jdk-14/bin/server;C:/Program Files/Java/jdk-14/bin;C:\Program Files\Java\jdk-14\bin;C:\Program Files\Java\jdk1.8.0_131\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;D:\mysql\mysql-8.0.18-winx64\bin;C:\WINDOWS\System32\OpenSSH\;C:\Users\Admin;C:\Program Files\MySQL\MySQL Server 6.0\bin;C:\Program Files\nodejs\;C:\Users\Admin\AppData\Local\Microsoft\WindowsApps;;C:\Users\Admin\AppData\Roaming\npm;D:\Spring Boot\sts-4.5.1.RELEASE\sts-4.5.1.RELEASE;;.]
4月 17, 2020 5:25:57 下午 org.apache.coyote.AbstractProtocol init
信息: 初始化协议处理器 ["http-nio-8080"]
4月 17, 2020 5:25:57 下午 org.apache.catalina.startup.Catalina load
信息: 服务器在[1,382]毫秒内初始化
4月 17, 2020 5:25:57 下午 org.apache.catalina.core.StandardService startInternal
信息: Starting service [Catalina]
4月 17, 2020 5:25:57 下午 org.apache.catalina.core.StandardEngine startInternal
信息: 正在启动 Servlet 引擎:[Apache Tomcat/9.0.34]
ServletContextListener 创建
RequestListener
ServletContextListener 初始化。。。
ServletContextListener 加载资源,如创建数据库的连接池,装配依赖的资源
AuthListener 创建
创建EncodingFilter
EncodingFilter 初始化。。。。。。
DispatcherServlet
4月 17, 2020 5:25:58 下午 org.apache.coyote.AbstractProtocol start
信息: 开始协议处理句柄["http-nio-8080"]
4月 17, 2020 5:25:58 下午 org.apache.catalina.startup.Catalina start
信息: Server startup in [899] milliseconds
RequestListener:为/mvc/从数据库连接池 创建一个数据库连接
EncodingFilter doFilter:/mvc/
GET:/mvc/
RequestListener:销毁为/mvc/使用过的数据库连接
RequestListener:为/mvc/staff从数据库连接池 创建一个数据库连接
EncodingFilter doFilter:/mvc/staff
staff
GET:/mvc/staff
RequestListener:销毁为/mvc/staff使用过的数据库连接
RequestListener:为/mvc/dept从数据库连接池 创建一个数据库连接
EncodingFilter doFilter:/mvc/dept
dept
GET:/mvc/dept
RequestListener:销毁为/mvc/dept使用过的数据库连接
RequestListener:为/mvc/other从数据库连接池 创建一个数据库连接
鉴权/mvc/other
EncodingFilter doFilter:/mvc/other
GET:/mvc/other
RequestListener:销毁为/mvc/other使用过的数据库连接
以上就是 Filter 和Listener的具体作用。
Spring MVC
DispatcherServlet(分发器): 拦截所有请求
HandlerMapping:映射请求 url 到控制器中的特定方法名
ViewResolver:解析控制器返回的视图名
ViewResolver:根据视图类型具体视图分发
DispatcherServlet: 返回特定视图
Handler:处理一个 HTTP 请求(HTTP 方法加 URL)的控制器中的特定方法
HandlerInterceptor:把 HTTP 请求头中数据拦截封装成了控制器方法中的请求参数、路径参数、或是对象
两种创建和注册DispatcherServlet的方式:
// 创建和注册 DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
// 预实例化和初始化
registration.setLoadOnStartup(1);
// 路径映射:接收 `*` 所有请求
registration.addMapping("/app/*");
<!-- 创建和注册 DispatcherServlet -->
<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>
Spring Web MVC 启动过程
为了更好的了解 Spring Web MVC 启动过程,接下来用代码进行演示:
工程目录:
pom.xml(工程所需的依赖)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.newer</groupId>
<artifactId>smvc</artifactId>
<version>0.1</version>
<name>smvc</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties(配置)
logging.level.web=debug
spring.http.log-request-details=true
SmvcApplication.java(这个工程会自动生成)
package com.newer.smvc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SmvcApplication {
public static void main(String[] args) {
SpringApplication.run(SmvcApplication.class, args);
}
}
HomeController.java
package com.newer.smvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
// 视图名
return "index.html";
}
@ResponseBody
@GetMapping("/hello")
public String hello() {
// 数据或资源
return "hello";
}
}
工程运行,控制台输出:
接下来分析 Spring Web MVC 启动过程
1.在 ContextLoaderListener监听器中初始化 WebApplicationContext
2. 初始化 characterEncodingFilter 过滤器
3. 初始化 dispatcherServlet 前端控制器
4. 初始化线程池
5. 初始化 RequestMappingHandlerAdapter处理器适配器
6. 初始化 RequestMappingHandlerMapping处理器映射
DispatcherServlet : GET "/", parameters={}
RequestMappingHandlerMapping : Mapped to com.newer.smvc.HomeController#home()
控制器方法返回 `@ResponseBody` 则不进行 `ViewResolver` 视图的解析
o.s.web.servlet.DispatcherServlet : GET "/", parameters={}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.newer.smvc.HomeController#home()
o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8]
o.s.w.servlet.view.InternalResourceView : View name 'index.html', model {}
o.s.w.servlet.view.InternalResourceView : Forwarding to [index.html]
o.s.web.servlet.DispatcherServlet : "FORWARD" dispatch for GET "/index.html", parameters={}
o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler ["classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/", "/"]
o.s.web.servlet.DispatcherServlet : Exiting from "FORWARD" dispatch, status 200
o.s.web.servlet.DispatcherServlet : Completed 200 OK
关于Spring MVC的 架构模式就到这里结束了,重要的是理解,理解,理解!!!有问题的小伙伴,欢迎留言!!!