内容主要参考自《Spring源码深度解析》一书,算是读书笔记或是原书的补充。进入正文后可能会引来各种不适,毕竟阅读源码是件极其痛苦的事情。
本文主要涉及书中第十一章的部分,依照书中内容以及个人理解对Spring源码进行了注释,详见Github仓库:https://github.com/MrSorrow/spring-framework
本文作为正式研究SpringMVC的预热文章,主要目的是为了搭建SpringMVC源码的测试工程。为什么要单独的作为一篇文章,因为发现想要测试SpringMVC的源码和之前的工程搭建还是不太一样的,笔者也调试了很久才终于搭建成功。
本文主要对应书中的第一部分,为了演示SpringMVC的使用案例,完全是为了复现书上的测试过程,过程中出现很多问题,记录下来以便参考。
首先我们准备新建一个专门测试SpringMVC的 module,不再使用之前文章测试模块 spring-mytest,新建的模块名称我们定义为 spring-mymvc。具体步骤见图中红框。
新建完毕后可以看到,一个新的模块就创建好了。
在 build.gradle 配置文件中添加SpringMVC的依赖,配置文件如下。
plugins {
id 'java'
id 'war'
}
group 'org.springframework'
version '5.1.0.BUILD-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile(project(":spring-context"))
compile(project(":spring-context-support")) // freemarker需要,不加可能报错
compile(project(":spring-webmvc"))
providedCompile 'javax.servlet:servlet-api:2.5'
compile 'javax.servlet.jsp.jstl:jstl:1.2'
compile 'org.apache.taglibs:taglibs-standard-spec:1.2.5'
compile 'org.apache.taglibs:taglibs-standard-impl:1.2.5'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
依赖关系图如下。
打开项目的 Project Structure 窗口,打开左侧的 Facets 和 Artifacts 两个窗口,删除之前创建好的内容,我们手动亲自创建一遍。
先进入 Facets 窗口,点击 + 号,选择 Web,然后会让我们选择模块。选中我们创建的 spring-mymvc-main,点击 OK。
新建成功后,会出现一个 Web 选项卡,点击选中,右侧会显示 web.xml 的位置。注意查看路径,IDEA默认的路径是在 .idea 目录下的,而我们最终是要在自己的测试模块的 webapp/WEB-INF/ 下的,所以要进行修改。
点击两个路径右侧的绿色铅笔进行修改。如果没有 webapp/WEB-INF/ 目录则自行新建,最后路径末尾别忘记 web.xml,这样IDEA就会在 webapp/WEB-INF/ 目录下帮我们新建一个 web.xml。
然后修改 Web Resource Path 为 webapp 目录即可。
创建完 Facets,随后创建 Artifacts。
选中我们的 spring-mymvc-main 模块,结果如下。之后我们解决问题都是在这个地方进行调整。
回到工程目录,此时 webapp/WEB-INF/ 目录下就存在 web.xml 文件了。修改文件内容,至于何种含义,相信会使用Spring的人不用多说。
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:spring-config.xmlparam-value>
context-param>
<servlet>
<servlet-name>mvc-testservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc-config.xmlparam-value>
init-param>
<load-on-startup>1load-on-startup>
servlet>
<servlet-mapping>
<servlet-name>mvc-testservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
web-app>
在 resources 目录下创建配置文件,分别是Spring配置文件 spring-config.xml,还有SpringMVC配置文件 springmvc-config.xml。
spring-config.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
bean>
beans>
springmvc-config.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="simpleUrlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/userlist">userControllerprop>
props>
property>
bean>
<bean id="userController" class="guo.ping.mvctest.controller.UserController" />
beans>
这里我们用用户类作为示例。
public class User {
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
/** setter and getter */
}
编写用户控制器 UserController
类。
public class UserController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("hahha");
List<User> userList = new ArrayList<>();
User user1 = new User("张三", 12);
User user2 = new User("李四", 21);
userList.add(user1);
userList.add(user2);
return new ModelAndView("userlist", "users", userList);
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
This is SpringMVC demo page
创建完成后,整个目录结构为:
新建 Run/Debug Configurations,选择本地 Tomcat 服务器。
新建 Deployment,点击 Apply,创建成功。
点击运行,发现没有启动成功,查看报错信息。server窗口报错信息如下:
再次查看Tomcat日志报错,发现输出找不到 org.springframework.web.context.ContextLoaderListener
类。
想想不可能啊,明明我们在 gradle 中导入了依赖,配置文件也没有报错,我们决定去打包的 war 中一看究竟。进入 out 目录,我们惊讶的发现,打包内容根本不包含Spring的任何类,不仅如此,我们自己的配置文件也没有打包进去,怪不得找不到 ContextLoaderListener
类。
那该怎么解决呢,我们的目的是把Spring的 java 文件也要打进包,还要把我们配置文件打入包中。
再次打开 Project Structure 窗口,查看红框中的内容,可以发现这里可以管理需要打包进 war 的内容。
选中 classes 行,点击 + 号,选择 Directory Content,添加上我们模块的 resources 目录。
结果如下:
从右侧的 Available Elements
中选择 spring-webmvc_main,右键选择 Put into Output Root。
结果如下:
这下感觉有点像那么回事情了。再次点击运行按钮,发现依然报错。
16-Nov-2018 22:37:43.372 严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.startup.ContextConfig.processAnnotationsJar Unable to process Jar entry [module-info.class] from Jar [file:/E:/spring_src/spring-framework/out/artifacts/spring_mymvc_main_war_exploded/WEB-INF/lib/javax.json.bind-api-1.0.jar] for annotations
······
16-Nov-2018 22:37:45.030 严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.startup.ContextConfig.processAnnotationsJar Unable to process Jar entry [module-info.class] from Jar [file:/E:/spring_src/spring-framework/out/artifacts/spring_mymvc_main_war_exploded/WEB-INF/lib/money-api-1.0.3.jar] for annotations
······
16-Nov-2018 22:37:45.137 严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.startup.ContextConfig.processAnnotationsJar Unable to process Jar entry [module-info.class] from Jar [file:/E:/spring_src/spring-framework/out/artifacts/spring_mymvc_main_war_exploded/WEB-INF/lib/jaxws-api-2.3.0.jar] for annotations
·····
16-Nov-2018 22:37:45.164 严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.startup.ContextConfig.processAnnotationsJar Unable to process Jar entry [module-info.class] from Jar [file:/E:/spring_src/spring-framework/out/artifacts/spring_mymvc_main_war_exploded/WEB-INF/lib/javax.xml.soap-api-1.4.0.jar] for annotations
······
16-Nov-2018 22:37:45.175 严重 [RMI TCP Connection(3)-127.0.0.1] org.apache.catalina.startup.ContextConfig.processAnnotationsJar Unable to process Jar entry [module-info.class] from Jar [file:/E:/spring_src/spring-framework/out/artifacts/spring_mymvc_main_war_exploded/WEB-INF/lib/jaxb-api-2.3.0.jar] for annotations
这次报的错好像是部分 jar 包引起的,没办法,把报错的 jar 包都删了。
好了,再次运行。果然不出所料,还是报错,这次报的又不一样了。报错的原因,表明不能加载 ContextLoader.properties 文件。
那么这个文件哪来的呢,报错中显示是 org/springframework/web/context/ContextLoader.properties,其实这是 spring-web 模块中的配置文件。
我们再去 war 包中瞅一眼,发现确实没有。有了我们自己配置文件的打包经验,看来我们只要将 ContextLoader.properties 文件加入即可。
再次运行。发现其实还需要将 spring-webmvc 模块中的 DispatcherServlet.properties 文件加入。
其实这时候已经可以发现 UserController
已经拦截到用户请求了,但是我们还要将 jstl 相关jar包加入。加入好了,这下终于成功了。