SpringMVC框架对于Java后端程序员来说再熟悉不过了,但是也只是熟悉而已,用多了就熟了,但是有多少人去了解过他的原理,其实很多人会说,我会用不就行了吗,了解原理有什么用了,但当你了解之后才知道有很多巧妙的设计在里面。如果不看Spring的源码,你将会失去一次和大师学习的机会,它的代码规范,设计思想很值得学习。会用,你永远只是一个码农,深入原理你才能成为大师。
废话不多说,我们进入今天的正题,用过springMVC的人,应该都知道SpringMVC以DispatcherServlet为核心,负责协调和组织不同组件以完成请求处理并返回响应的工作,如果连这个都不知道,那你真该好好的反思下自己了,好好的补补课了。所以想要实现自己的SpringMVC框架,需要从DispatcherServlet开始:
先上一张springMVC的原理图吧(网上找的)
参照上面的图,说下springMVC的工作流程
1、用户发送请求至前端控制器DispatcherServlet
2、 DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3、处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4、 DispatcherServlet通过HandlerAdapter处理器适配器调用处理器
5、执行处理器(Controller,也叫后端控制器)。
6、Controller执行完成返回ModelAndView
7、 HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8、DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9、ViewReslover解析后返回具体View
10、DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
11、DispatcherServlet响应用户。
上面只是我个人的看法,但是实际上是不是这样的呢,读读源码就知道了,源码在哪里,从哪里看起,前面说了,核心在DispatcherServlet,废话跳过,直接上图,本文是手写系列,所以这里就不深入的讲了,看完了这篇博客,大家在反过手来看这个应该就容易多了。
我这里只实现自己的@Controller、@RequestMapping、@RequestParam注解起作用,其余SpringMVC功能照葫芦画瓢即可。
目录结构如下
下面到上源码部分了:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
String value() default "";
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
String value();
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestParam {
String value();
}
@MyController
@MyRequestMapping("/test")
public class TestController {
@MyRequestMapping("/hello")
public String Hello(String name) {
return "hello: " + name;
}
}
@SuppressWarnings("serial")
public class MyDispatcherServlet extends HttpServlet {
private Properties properties = new Properties();
private List<String> classNames = new ArrayList<>();
private Map<String, Object> ioc = new HashMap<>();
private Map<String, Method> handMapping = new HashMap<>();
private Map<String, Object> controllerMapping = new HashMap<>();
@Override
public void init(ServletConfig config) throws ServletException {
// 加载配置文件
try {
load(config);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 初始化相关类
doScanner(properties.getProperty("scanPackage"));
// 实例化
doInstance();
// 初始化handMapping
initHandMapping();
}
private void load(ServletConfig config) throws Exception {
String location = config.getInitParameter("contextConfigLocation");
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);
properties.load(inputStream);
}
private void doScanner(String scanPackage) {
URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replace(".", "/"));
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
// 递归读取包
doScanner(scanPackage + "." + file.getName());
} else {
String className = scanPackage + "." + file.getName().replace(".class", "");
classNames.add(className);
}
}
}
private void doInstance() {
if (classNames == null || classNames.size() == 0)
return;
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(MyController.class)) {
ioc.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private String toLowerFirstWord(String name) {
char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
private void initHandMapping() {
if (classNames != null && classNames.size() != 0) {
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
String controllUrl = "";
if (clazz.isAnnotationPresent(MyController.class)) {
if (clazz.isAnnotationPresent(MyRequestMapping.class)) {
MyRequestMapping myRequestMapping = clazz.getAnnotation(MyRequestMapping.class);
controllUrl = myRequestMapping.value();
}
Method[] methods = clazz.getMethods();
if (methods != null && methods.length != 0) {
for (Method method : methods) {
if (method.isAnnotationPresent(MyRequestMapping.class)) {
String methodUrl = method.getAnnotation(MyRequestMapping.class).value();
String url = (controllUrl + "/" + methodUrl).replaceAll("/+", "/");
handMapping.put(url, method);
controllerMapping.put(url, ioc.get(toLowerFirstWord(clazz.getSimpleName())));
}
}
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
try {
doDispatch(req, resp);
} catch (Exception e) {
// TODO Auto-generated catch block
resp.getWriter().write(e.getMessage());
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
if (handMapping != null && !handMapping.isEmpty()) {
String url = req.getRequestURI();
String contextPath = req.getContextPath();
url = url.replace(contextPath, "").replaceAll("/+", "/");
if (!this.handMapping.containsKey(url)) {
resp.getWriter().write("404 NOT FOUND!");
return;
} else {
Method method = handMapping.get(url);
Object instance = controllerMapping.get(url);
// req.getp
String retValue = method.invoke(instance, getMethodParams(method, req, resp)).toString();
resp.getWriter().write(retValue);
}
}
}
private Object[] getMethodParams(Method method, HttpServletRequest req, HttpServletResponse resp) {
// 获取方法的参数列表
Class<?>[] parameterTypes = method.getParameterTypes();
// 获取请求的参数
Map<String, String[]> parameterMap = req.getParameterMap();
// 保存参数值
Object[] paramValues = new Object[parameterTypes.length];
// 方法的参数列表
for (int i = 0; i < parameterTypes.length; i++) {
// 根据参数名称,做某些处理
String requestParam = parameterTypes[i].getSimpleName();
if (requestParam.equals("HttpServletRequest")) {
// 参数类型已明确,这边强转类型
paramValues[i] = req;
continue;
}
if (requestParam.equals("HttpServletResponse")) {
paramValues[i] = resp;
continue;
}
if (requestParam.equals("String")) {
for (Entry<String, String[]> param : parameterMap.entrySet()) {
String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
paramValues[i] = value;
}
}
}
return paramValues;
}
}
application.properties
application.properties
pom.xml
<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 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>springmvc.hand</groupId>
<artifactId>springmvc-hand</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>springmvc-hand Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>springmvc-hand</finalName>
</build>
</project>
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>com.max.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
到这里代码就贴完了,源代码已经上传到我的github上,感兴趣的可以从上面下载: