为了模拟SpringMVC,首先了解一下正常SpringMVC的启动流程和它的一些配置
详细的应用参考《java框架复习——springmvc》
SpringMVC启动
引入依赖
springmvc依赖
<dependencies>
<!--
springmvc的依赖
其中包含了core和context的依赖
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
</dependencies>
包含了core和context依赖,不需要我们单独引入
配置SpringMVC
这里是用的xml的方式配置的
- web.xml
- spring-mvc.xml
模拟SpringMVC
引入依赖
只需要dom4j和servlet的依赖
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
这里还需要一个tomcat容器,这里使用的tomcat插件
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
这里如果启动的tomcat请参考《tomcat插件式启动》
tomcat配置
我们知道在配置springmvc需要配置一个DispatcherServlet,我们如果要模拟一个SpringMVC,那也就需要自己写一个这样的Servlet。
当然首先得再tomcat中配置一个Servlet
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>zddServlet</servlet-name>
<servlet-class>com.zdd.servlet.ZddServlet</servlet-class>
<init-param>
<param-name>XmlPath</param-name>
<param-value>zdd-mvc.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>zddServlet</servlet-name>
<url-pattern>/*.do</url-pattern>
</servlet-mapping>
</web-app>
mvc配置
同样我们需要一个mvc得配置,用来配置扫描的路径和访问路径的前缀和后缀
<beans>
<compentScan package="com"/>
<view prefix = "/page/" suffix=".html">
</view>
</beans>
Servlet拦截器
springmvc的核心就是这个Servlet拦截器,它负责拦截访问的请求,并执行真正的Controller的逻辑或者将请求转发到指定的路径。
package com.zdd.servlet;
import com.zdd.annotation.Controller;
import com.zdd.annotation.RequestMapping;
import com.zdd.annotation.ResponseBody;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
public class ZddServlet extends HttpServlet {
private static String COMPENT_SCAN_ELEMENT_PACKAGE_NAME= "package";
private static String COMPENT_SCAN_ELEMENT_NAME = "compentScan";
private static String XML_PATH_LOCAL= "xmlPath";
private static String prefix = "";
private static String suffix = "";
private static String projectPath = ZddServlet.class.getResource("/").getPath();
private static Map<String, Method> methodMap = new HashMap<>();
/**
* init主要做得事情:
* 加载配置文件 web.xml 加载spring mvc.xml
扫描整个项目 根据配置文件给定的目录来扫描
扫描所有加了@Controller注解的类
当扫描到加了@Controller注解的类之后遍历里面所有的方法
拿到方法对象之后 解析方法上面是否加了@RequestMapping注解
定义一个Map集合 吧@RequstMapping的Value 与方法对象绑定起来
Map<String,Method>
* @param config
* @throws ServletException
*/
@Override
public void init(ServletConfig config) throws ServletException {
//解析xml 解析web.xml 解析lubanMvc.xml
// lubanMvc.xml
//initParameter 就是用户在web.xml里面指定的配置文件的地址 :lubanMvc.xml
// \\ =\ // d:\\\\ = d:\\ d: =
//projectPath需要进行url转义 空格 会变成%20
// URLEncoder;
// URLDecoder;
projectPath = projectPath.replaceAll("%20"," ");
String initParameter = config.getInitParameter(XML_PATH_LOCAL);
//解析xml文件 file:xml 文件对象
File file = new File(projectPath + "//" + initParameter);
Document prase = prase(file);
Element rootElement = prase.getRootElement();
Element view = rootElement.element("view");
prefix = view.attribute("prefix").getValue();
suffix = view.attribute("suffix").getValue();
//
Element compentScanEle = rootElement.element(COMPENT_SCAN_ELEMENT_NAME);
// value:com
String value = compentScanEle.attribute(COMPENT_SCAN_ELEMENT_PACKAGE_NAME).getValue();
scanProjectByPath(projectPath+"\\"+value);
// element.element("A")
//扫描项目
// super.init(config);
}
public void scanProjectByPath(String path){
File file =new File(path);
//递归解析项目所有文件
scanFile(file);
}
public void scanFile(File file){
//递归解析项目
if (file.isDirectory()){
for (File file1 : file.listFiles()) {
scanFile(file1);
}
}else{
//如果不是文件夹
//D://project//com//TestContrller.class
//D://project//com//controller//TestController.class
//com.controller.TestController
String filePath = file.getPath();
String suffix =filePath.substring(filePath.lastIndexOf("."));
if (suffix.equals(".class")){
String classPath = filePath.replace(new File(projectPath).getPath()+"\\","");
classPath = classPath.replaceAll("\\\\",".");
String className = classPath.substring(0,classPath.lastIndexOf("."));
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Controller.class)) {
RequestMapping classRequestMapping = clazz.getAnnotation(RequestMapping.class);
String classRequestMappingUrl = "";
if (classRequestMapping!=null){
classRequestMappingUrl = classRequestMapping.value();
}
for (Method method : clazz.getDeclaredMethods()) {
if (!method.isSynthetic()) {
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
if (annotation != null) {
String methodRequsetMappingUrl = "";
methodRequsetMappingUrl = annotation.value();
System.out.println("类:"+clazz.getName()+"的"+method.getName()+"方法被映射到了"+classRequestMappingUrl+methodRequsetMappingUrl+"上面");
methodMap.put(classRequestMappingUrl+methodRequsetMappingUrl,method);
}
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//Class.forName
}
}
}
/**
*
* @param file :你的xml文件对象
* @return
*/
public Document prase(File file){
SAXReader saxReader = new SAXReader();
try {
return saxReader.read(file);
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
/**
* 执行的时候做的事情:
* 拿到请求URI去map里面get
* 给参数赋值并调用方法
* 拿到方法返回值做视图跳转和消息返回
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//拿到请求的URI
String requestURI = req.getRequestURI();
Method method = methodMap.get(requestURI);
if (method!=null){
//jdk8以前 直接拿参数名称 拿不到
Parameter[] parameters = method.getParameters();
Object[] objects = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String name = parameter.getName();
Class type = parameter.getType();
if (type.equals(String.class)){
objects[i] = req.getParameter(name);
}else if(type.equals(HttpServletRequest.class)){
objects[i] = req;
}else if(type.equals(HttpServletResponse.class)){
objects[i] = resp;
}else{
try {
Object o = type.newInstance();
// type.getDeclaredConstructor().newInstance()
for (Field field : type.getDeclaredFields()) {
field.setAccessible(true);
String fieldName = field.getName();
field.set(o,req.getParameter(fieldName));
}
objects[i] = o;
} catch (Exception e) {
e.printStackTrace();
}
}
}
try {
Object o= null;
o = method.getDeclaringClass().newInstance();
Object invoke = method.invoke(o, objects);
// 判断返回值是否是Void
if (!method.getReturnType().equals(Void.class)){
ResponseBody annotation = method.getAnnotation(ResponseBody.class);
if (annotation!=null){
//提供接口来做这个事情
resp.getWriter().write(String.valueOf(invoke));
}else {
// /page/index.html page/index.html
req.getRequestDispatcher(prefix+String.valueOf(invoke)+suffix).forward(req,resp);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}else {
resp.setStatus(404);
}
// super.doPost(req, resp);
}
}
其中为了模拟springmvc中的注解,这里同样创建了几个和springmvc相同的注解,@Controller、@RequestMapping和@ResponseBody,发挥的作用也基本和springmvc中相同,不过只有几个基本的功能。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Controller {
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface RequestMapping {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ResponseBody {
String value();
}
这个servlet的工作流程分为两个阶段,一个是初始化阶段,也就是再init方法的工作,一个是请求访问阶段,就是再doPost和doGet方法中处理请求。
- 初始化阶段
- 读取web.xml中的配置属性,找到配置mvc的xml文件,读取其中的配置信息。
- 首先找到需要扫描的路径,读取其中的class文件,找到需要映射的方法(就是加了@RequestMapping注解的),并且将方法和它的映射路径存储在一个map中。
- 读取xml文件的其他配置,比如访问请求路径的前缀和后缀,同样缓存在一个变量中。
- 请求阶段
- 接收到请求的路径,通过这个路径从刚刚缓存的map中拿到对应的方法对象,如果能找到就执行此方法,注意这里需要处理方法中的参数,为了获得参数名需要jdk的版本是1.8以上才能得到参数名。
- 对于找到映射的请求,执行完方法后对于方法返回的结果有两种情况,根据注解一种是将结果直接输出到页面上,一种是将请求转发到返回的路径上。
- 对于没有找到对应映射的请求直接返回404。
Controller
测试用的一个Controller
@Controller
public class TestController {
@RequestMapping("/test.do")
@ResponseBody
public String Test(){
return "hello";
}
}
访问效果
转发请求
@RequestMapping("/hello.do")
public String hello(){
return "hello";
}
效果