SpringMVC(4.x) 从搭建到放弃(含源码分析)——一
来源:互联网 发布:新版淘宝店图片大小 编辑:程序博客网 时间:2024/05/17 03:09
1. Spring MVC处理流程
DispatcherServlet
-> 处理器映射 -(找到处理器)-> DispatcherServlet
-> 控制器 -(逻辑视图名+Model)-> DispatcherServlet
-> 视图解析器 -(渲染了Model数据的视图View
对象)-> 响应
2. 配置Spring MVC
2.1 搭建项目
2.1.1 maven项目骨架
使用IDEA
File-New Module-Maven(勾选 create from archetype
)-org.apache.maven.archetype:maven-archetype-webapp 然后next到底
使用Maven
$ cd YourWorkspace$ mvn archetype:generate \-DgroupId=com.mywebapp \-DartifactId=mywebapp \-Dversion=1.0.0 \-DarchetypeArtifactId=maven-archetype-webapp \-DarchetypeVersion=RELEASE \-DinteractiveMode=false \ //不需要使用maven的交互模式来创建项目
2.1.2 pom.xml文件
定义常量
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <org.slf4j-version>1.7.12</org.slf4j-version> <org.springframework-version>4.3.9.RELEASE</org.springframework-version></properties>
引入spring MVC
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${org.springframework-version}</version></dependency>
引入thymleaf模板引擎
<dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring4</artifactId> <version>2.1.4.RELEASE</version></dependency>
引入Servlet api和JSP api
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope></dependency><dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.1</version> <scope>provided</scope></dependency><!-- JSTL标签库 用于支持在jsp中使用标签--><dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version></dependency>
引入JSR303 校验库(api 与 实现)
<dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.1.0.Final</version></dependency><!-- 其实和hibernate数据库支持没什么关系 只是validation-api实现--><dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.3.5.Final</version></dependency>
引入日志系统
使用为SLF4J作为api log4j 1.x日志系统为实现
jcl-over-slf4j
为桥接器将使用其他日志系统api(如apache commons-logging)的jar掉入到为SLF4J“陷阱”中slf4j-log4j12
为SLF4J与log4j相连接的接口
此处使用了log4j 1.x版本也是传统使用最多的版本,但是现在log4j已经推出了2.x版本,并把项目进行了重构,增添了log4j对web的支持
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${org.slf4j-version}</version></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${org.slf4j-version}</version> <scope>runtime</scope></dependency><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${org.slf4j-version}</version> <scope>runtime</scope></dependency><dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> <scope>runtime</scope></dependency>
或将log4j的实现改为2.x版本
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.5</version> <scope>runtime</scope></dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.5</version> <scope>runtime</scope></dependency><dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-web</artifactId> <version>2.5</version> <scope>runtime</scope></dependency>
引入单元测试
<!-- JunitTest --><dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope></dependency><!-- 可以不需要启动spring和web上下文来进行测试 --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${org.springframework-version}</version> <scope>test</scope></dependency><!-- mock产生测试对象 --><dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.7.22</version> <scope>test</scope></dependency>
2.1.3 目录结构
项目源文件目录和项目目录, 这两者是不同的
源文件目录是组织源代码的目录结构, 一般如下
-mywebapp--src/main/java //*.java--src/main/resources //*.properties或*.xml等配置文件--src/main/webapp //web资源----WEB-INF //web资源, 但浏览器不能用url方式直接访问到------jsp------html------template------res/css------res/js------res/img------web.xml //可以没有哦!--src/test/java--src/test/resources--target--other dir you like
项目目录是最终发布到tomcat等容器上的目录结构,即maven等工具编译后的目录结构
-mywebapp //所谓的web项目的根目录 webroot--META-INF----MANIFEST.MF //记录build工具打包等相关数据--WEB-INF----classes //项目自身的编译后的src/main/java和src/main/resources的文件 //所谓的classpath的根目录----lib //项目所需的依赖----jsp----html----template----res/css----res/js----web.xml
2.2. 使用javaconfig配置web.xml
2.2.1 配置方法
一般的web项目都是要有web.xml 其作用是作为该web应用的上下文(Servlet Context)所需的配置文件
在Servlet3.0环境中, 容器启动时就会在类路径下查找实现javax.servlet.ServletContainerInitializer
接口的类, Spring提供了这个类的实现org.springframework.web.SpringServletContainerInitializer
, 在这个类中会反过来查找实现了org.springframework.web.WebApplicationInitializer
的类们并将配置任务交给它们来实现.
在Spring3.2中引入了一个便利的简单WebApplicationInitializer
基础实现,
也就是AbstractAnnotationConfigDispatcherServletInitializer
(从名字上看它是一个用annotation配置DispatcherServlet的抽象类),
因此我们只要新建一个自定义配置类实现AbstractAnnotationConfigDispatcherServletInitializer
即可
public class MyDispatcherServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] {RootConfig.class}; //相当于配置dao service bean的spring的xml门 } @Override protected Class<?>[] getServletConfigClasses() { return new Class[] {WebConfig.class}; //相当于只配置controller的spring的xml们 } @Override protected String[] getServletMappings() { return new String[] {"/"}; //DispatcherServlet的处理的url映射 }}
以上相当于
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener><context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param><servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- init-param 其实就是初始化这个bean需要注入的属性--> <!--contextConfigLocation 是--> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/dispatcher-servlet.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported></servlet><servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern></servlet-mapping>
注意<url-pattern>
中/*
和/
的区别 /*
会覆盖掉所有的Servlet包括web容器(如Tomcat)中预定义的Servlet(如处理jsp 静态资源), 可能会造成循环调用情况(dispatcher覆盖处理jsp默认jsp servlet, dispatcher处理页面渲染时转移给默认jsp servlet, 而现在默认servlet就是dispatcher自身, 死循环), 而/
只是覆盖默认servlet(default servlet 该servlet就是处理其他servlet不处理的东西) 比如对于如/test.html(/*.html)的请求, 会自动找处理html的servlet处理, 如果没配置就使用默认的处理html的sevlet, 寻找web根目录下的test.html 记住 servlet不是链式调用, 属于该servlet处理的内容如果找不到, 那么它不会顺着一个链寻找可以处理它的servlet
2.2.2 加载流程
- 容器调用
SpringServletContainerInitialize.onStartup(Set<Class<?>> ServletContext)
- 查找实现
WebApplicationInitializer
的类MyDispatcherServletInitializer
调用onStartup(ServletContext)
- 该方法在抽象父类
AbstractDispatcherServletInitializer
中
public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); this.registerDispatcherServlet(servletContext); }
- 再调用父类的
AbstractContextLoaderInitializer.onStartup(ServletContext)
public void onStartup(ServletContext servletContext) throws ServletException { this.registerContextLoaderListener(servletContext);}protected void registerContextLoaderListener(ServletContext servletContext) { // WebAppcationContext父类是spring的ApplicationContext, 其实就是web版的spring上下文 // 调用子类AbstractAnnotationConfigDispatcherServletInitializer的createRootApplicationContext() WebApplicationContext rootAppContext = this.createRootApplicationContext(); if (rootAppContext != null) { // 创建listener 放入上下文环境 ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); // ContextLoaderListener上下文环境的配置 listener.setContextInitializers(this.getRootApplicationContextInitializers()); servletContext.addListener(listener); } else { this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context"); }}
- 回到
AbstractDispatcherServletInitializer.onStartup(ServletContext)
执行this.registerDispatcherServlet(servletContext)
protected void registerDispatcherServlet(ServletContext servletContext) { //... //调用子类AbstractAnnotationConfigDispatcherServletInitializer的createServletApplicationContext() //创建一个属于DispatcherServlet的上下文环境 WebApplicationContext servletAppContext = this.createServletApplicationContext(); FrameworkServlet dispatcherServlet = this.createDispatcherServlet(servletAppContext); //dispatcherServlet上下文环境的配置 dispatcherServlet.setContextInitializers(this.getServletApplicationContextInitializers()); //在应用上下文中注册dispatcherServlet Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); Assert.notNull(registration, "Failed to register servlet with name '" + servletName + "'.Check if there is another servlet registered under the same name."); registration.setLoadOnStartup(1); registration.addMapping(this.getServletMappings()); registration.setAsyncSupported(this.isAsyncSupported()); //过滤器 Filter[] filters = this.getServletFilters(); if (!ObjectUtils.isEmpty(filters)) { Filter[] var7 = filters; int var8 = filters.length; for(int var9 = 0; var9 < var8; ++var9) { Filter filter = var7[var9]; //在应用上下文中注册过滤器 this.registerServletFilter(servletContext, filter); } } this.customizeRegistration(registration);}protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) { return new DispatcherServlet(servletAppContext);}
- 执行一系列初始化DispatcherServlet的行为
public DispatcherServlet(WebApplicationContext webApplicationContext) { super(webApplicationContext); this.setDispatchOptionsRequest(true);}
在以上流程中初始了两个spring的上下文环境(WebApplicationContext 即ApplicationContext的子), 一个是ContextLoaderListener, 一个是DispatcherServlet(前者是后者的父环境) this.getRootApplicationContextInitializers()
和this.getServletApplicationContextInitializers()
返回的都是用@Configuration
注解的类(配置spring bean的java config类) 一般ContextLoaderListener里配置dao service或其他全局的bean
2.2.3 上下文环境辨析
ServletContext
应用上下文环境, 代表了整个web应用(mywebapp), 由容器(如Tomcat)提供实现, 属于javax.servlet.ServletContext
, 可以说是“最顶层的上下文环境” 通常是用来在其中注册listener servlet filter的(无论是用web.xml 还是java config方式), 它的启动过程是最早的.
它有两个地方可以放key-value
1. set/getInitParameter
即<context-param>
里设置的内容, 用于启动
2. set/getAttribute
用于在webapp各个部分共享, 比如webapp的描述 名称等
添加listener的作用是在servlet启动 webapp还未完全执行前启动一些过程,比如启动spring 上下文
获得ServletContext的方法
1. 在javax.servlet.Filter中直接获取ServletContext context = config.getServletContext();
在HttpServlet中直接获取
this.getServletContext()
在其他地方r通过
HttpSession
获取session.getServletContext();
或通过HttpServletRequest
获取request.getSession().getServletContext();
在thymeleaf模板直接
${application}
(SpEL 模板引擎)或${#servletContext}
(OGNL 原生语言)
ServletContext里添加属性servletContext.setAttribute(name, value)
spring 上下文环境
一般指的就是ContextLoaderListener的WebApplicationContext ContextLoaderListener
的contextInitialized(ServletContextEvent)
被调用
执行它父类ContextLoader.initWebApplicationContext(ServletContext)
时它会在被直接添加到ServletContext
的attribute属性中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
可通过WebApplicationContextUtils.getWebApplicationContext(ServletContext sc)
方法获取到该上下文
dispatcherServlet 上下文
指的是DispatcherServlet的WebApplicationContext
- 由于设置了
registration.setLoadOnStartup(1);
在容器启动完成后就调用servlet的init()
DispatcherServlet
继承FrameworkServlet
继承HttpServletBean
继承HttpServlet
在HttpServletBean
实现了init()
public final void init() throws ServletException { if (this.logger.isDebugEnabled()) { this.logger.debug("Initializing servlet '" + this.getServletName() + "'"); } PropertyValues pvs = new HttpServletBean.ServletConfigPropertyValues(this.getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(this.getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.getEnvironment())); this.initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException var4) { if (this.logger.isErrorEnabled()) { this.logger.error("Failed to set bean properties on servlet '" + this.getServletName() + "'", var4); } throw var4; } } // 调用子类FrameworkServlet的initServletBean() this.initServletBean(); if (this.logger.isDebugEnabled()) { this.logger.debug("Servlet '" + this.getServletName() + "' configured successfully"); }}
- 在
FrameworkServlet
的initServletBean()
中调用了initWebApplicationContext()
将ContextLoaderListener中搞定的spring上下文设置为Dispatcher中上下文的父容器
protected WebApplicationContext initWebApplicationContext() { //... WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); //... cwac.setParent(rootContext); //...}
- SpringMVC(4.x) 从搭建到放弃(含源码分析)——一
- Android项目框架从搭建到放弃(一)
- RxJava1.x从入门到放弃再到RxJava 2.x(一)
- SpringMVC之请求处理源码分析从service到doDispatch(一)
- kotlin——从入门到放弃
- Git从入门到放弃——Git服务器搭建-Linux篇
- Git从入门到放弃——GitHub搭建自己的博客篇
- Vue源码 --- 从入门到放弃
- 手把手教你从最基本的Java工程搭建SpringMVC+SpringDataJPA+Hibernate(含源码下载)
- 手把手教你从最基本的Java工程搭建SpringMVC+SpringDataJPA+Hibernate(含源码下载)
- 傅里叶分析之从入门到放弃
- 自制小四轴:从入门到放弃——基于stm32的小四轴系列(一)
- Kotlin从入门到放弃之基础篇(一)——基本数据类型
- Python从入门到放弃(一):概论
- Docker 从入门到放弃(一)
- Python从入门到放弃(一):概论
- Handler机制从入门到放弃(一)
- Swift3.0从入门到放弃(一)
- 用共享内存和信号量实现的简单的卖票系统
- vector中的resize()函数 VS reserve()函数
- flask 学习成果
- QT SSL QSslSocket: cannot call unresolved function SSLv23_client_method
- 子串和(南阳理工oj-题目44)
- SpringMVC(4.x) 从搭建到放弃(含源码分析)——一
- 直播+产品的商业化探索和思考
- 「端口扫描工具masscan」手把手教你在Ubuntu上安装masscan
- 51Nod 1199 Money out of Thin Air(dfs序+线段树维护区间和)
- PTA-自测-5 Shuffling Machine
- tf.train中的Optimizer相关的函数与功能介绍
- MFC中添加Richedit2.0控件导致程序无法运行的解决方法
- 51nod 最大M子段和
- Oracle里的哈希连接原理