Github源码下载地址:https://github.com/chenxingxing6/springmvc
CSDN源码下载地址:https://download.csdn.net/download/m0_37499059/11783232
一、前言
SpringMVC是Spring框架的一个模块,是基于mvc的webframework模块。mvc是一种设计模式,即model-view-controller,mvc在b/s系统下的应用如下图所示。
SpringMvc原理图:
二、手写SpringMvc
代码下载Github:https://github.com/chenxingxing6/springmvc
我们所有的注解都自己定义,并对注解进行解析处理。通过写这个SpringMvc框架,我们可以大致掌握SpringMvc的实现思路,用户请求怎么进来的,怎么通过Mapping映射到具体需要执行的方法上,并如何对结果进行处理(Json数据格式,视图)。
需要依赖的包:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<optional>true</optional>
<scope>provided</scope> #应用服务器,比如tomcat都有这个jar包
</dependency>
GitHub里面对进行了更新,完善了更多的功能,具体看Github.
2.1项目结构
2.2登陆测试Demo
访问地址:http://localhost:8080/test/view?path=login
@RequestMapping("/login")
public MyModeAndView login(@RequestParam("name") String name, @RequestParam("pwd") String pwd){
MyModeAndView modeAndView = new MyModeAndView();
modeAndView.setViewName("index");
modeAndView.addObject("name", name);
return modeAndView;
}
<html>
<head>
<title>登陆</title>
</head>
<body>
<div>
<h1>欢迎登陆</h1>
<form action="/test/login" method="get">
<div>
<label>用户名:</label>
<input name="name" type="text"/>
</div>
<div>
<label>用户名:</label>
<input name="pwd" type="password"/>
</div>
<div>
<label></label>
<input type="submit" value="提交"/>
</div>
</form>
</div>
</body>
</html>
<html>
<head>
<title>Title</title>
</head>
<body>
<div>
<h1>手写SpringMvc</h1>
<h3 style="color: blue;">欢迎 ${name} 你使用本系统....</h3>
</div>
</body>
</html>
2.3核心代码
package org.springframework.servlet;
import com.alibaba.fastjson.JSON;
import org.springframework.annotation.*;
import org.springframework.core.*;
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.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/**
* @Author: cxx
* @Date: 2019/8/27 22:45
*/
public class MyDispatcherServlet extends HttpServlet{
// 存放配置信息
Properties props = null;
// 所有的类名
private List<String> classNames = null;
// 实例化对象
Map<String, Object> ioc = null;
// Handler
List<Handler> handlers;
// 默认适配器
IHandlerAdapter defaultHandlerAdapter = null;
public MyDispatcherServlet(){
props = new Properties();
classNames = new ArrayList<>();
ioc = new ConcurrentHashMap<>();
handlers = new ArrayList<>();
defaultHandlerAdapter = new DefaultHandlerAdapter();
}
// 初始化
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("------ My mvc is init start...... ------");
// 1.加载配置文件
doLoadConfig(config.getInitParameter("contextConfigLocation"));
System.out.println("------ 1.加载配置文件成功-doLoadConfig() ------");
// 2.根据配置文件扫描所有相关类
doScanner(props.getProperty("scanPackage"));
System.out.println("------ 2.扫描所有相关类-ddoScanner() ------");
// 3.初始化所有相关类的实例,并将放入IOC容器中
doInstance();
System.out.println("------ 3.实例化成功-doInstance() ------");
// 4.实现DI
doAutowried();
System.out.println("------ 4.依赖注入成功-doAutowried() ------");
// 5.初始化HandlerMapping
initHandlerMapping();
System.out.println("------ 5.HandlerMapping初始化成功-initHandlerMapping() ------");
System.out.println("------ My mvc is init end...... ------");
}
public void doLoadConfig(String location){
String configName = location.split(":")[1];
InputStream is = this.getClass().getClassLoader().getResourceAsStream(configName);
try {
props.load(is);
}catch (Exception e){
e.printStackTrace();
}finally {
if (is != null){
try {
is.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
}
public void doScanner(String packageName){
// 进行递归扫描
URL url = this.getClass().getClassLoader().getResource("/" + packageName.replace(".", "/"));
File classDir = new File(url.getFile());
for (File file : classDir.listFiles()) {
if (file.isDirectory()){
doScanner(packageName + "." + file.getName());
}else {
String className = packageName + "." + file.getName().replace(".class", "");
classNames.add(className);
}
}
}
/**
* IOC容器规则 key-value
* 1.key默认用类名小写字段,否则优先使用用户自定义名字
* 2.如果是接口,用接口的类型作为key
*/
public void doInstance(){
if (classNames.isEmpty()){
return;
}
// 利用反射,将扫描的className进行初始化
try {
for (String className : classNames) {
Class clazz = Class.forName(className);
// 进行Bean实例化,初始化IOC
if (clazz.isAnnotationPresent(Controller.class)){
String beanName = lowerFirstCase(clazz.getSimpleName());
ioc.put(beanName, clazz.newInstance());
}else if (clazz.isAnnotationPresent(Service.class)){
Service service = (Service) clazz.getAnnotation(Service.class);
String beanName = service.value();
if ("".equals(beanName.trim())){
beanName = lowerFirstCase(clazz.getSimpleName());
}
Object instance = clazz.newInstance();
ioc.put(beanName, instance);
// 接口也需要注入,接口类型作为key
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> i : interfaces) {
ioc.put(i.getName(), instance);
}
}else {
continue;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
public void doAutowried(){
if (ioc.isEmpty()){
return;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
// 获取到所有字段,不管什么类型,都强制注入
Field[] field = entry.getValue().getClass().getDeclaredFields();
for (Field f : field) {
if (f.isAnnotationPresent(Autowired.class)){
Autowired autowired = f.getAnnotation(Autowired.class);
String beanName = autowired.value().trim();
if ("".equals(beanName)){
// com.demo.service.ITestService
beanName = f.getType().getName();
}
// 不管愿不愿意,都需要强吻
f.setAccessible(true);
try {
// 例如:TestController -> TestService
f.set(entry.getValue(), ioc.get(beanName));
}catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
}
}
public void initHandlerMapping(){
if (ioc.isEmpty()){
return;
}
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class clazz = entry.getValue().getClass();
if (!clazz.isAnnotationPresent(Controller.class)){
continue;
}
String baseUrl = "";
if (clazz.isAnnotationPresent(RequestMapping.class)){
RequestMapping requestMapping = (RequestMapping)clazz.getAnnotation(RequestMapping.class);
baseUrl = requestMapping.value();
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(RequestMapping.class)){
continue;
}
RequestMapping requestMapping = (RequestMapping)method.getAnnotation(RequestMapping.class);
String regex = ("/" + baseUrl + requestMapping.value()).replaceAll("/+", "/");
Pattern pattern = Pattern.compile(regex);
handlers.add(new Handler(pattern, entry.getValue(), method));
System.out.println("------ Mapping: " + regex + ", method:" + method);
}
}
}
// 6.运行阶段,等待请求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req, resp);
String url = req.getRequestURI();
String contextpath= req.getContextPath();
url = url.replace(contextpath, "").replaceAll("/+", "/");
System.out.println("进行请求....url:" + url);
}catch (FileNotFoundException e){
resp.getWriter().write("404 Not Found");
return;
}catch (Exception e){
resp.getWriter().write("500 error \r\n\n" + Arrays.toString(e.getStackTrace()));
return;
}
}
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception{
boolean jsonResult = false;
// 获取适配器
IHandlerAdapter handlerAdapter = defaultHandlerAdapter;
Handler handler = handlerAdapter.getHandler(req, handlers);
if (handler == null){
throw new FileNotFoundException();
}
Object[] paramValues = handlerAdapter.hand(req, resp, handlers);
Method method = handler.method;
Object controller = handler.controller;
String beanName = lowerFirstCase(method.getDeclaringClass().getSimpleName());
//如果controller或这个方法有UVResponseBody修饰,返回json
if (controller.getClass().isAnnotationPresent(ResponseBody.class) || method.isAnnotationPresent(ResponseBody.class)){
jsonResult = true;
}
Object object = method.invoke(ioc.get(beanName), paramValues);
if (jsonResult && object !=null){
resp.getWriter().write(JSON.toJSONString(object));
}else {
// 返回视图
doResolveView(object, req, resp);
}
}
public void doResolveView(Object object, HttpServletRequest req, HttpServletResponse resp) throws Exception{
// 视图前缀
String prefix = props.getProperty("view.prefix");
// 视图后缀
String suffix = props.getProperty("view.suffix");
MyModeAndView modeAndView = null;
if (object instanceof MyModeAndView){
modeAndView = (MyModeAndView) object;
}else {
modeAndView = new MyModeAndView(object.toString());
}
DefaultViewResolver viewResolver = new DefaultViewResolver(prefix, suffix);
viewResolver.resolve(modeAndView, req, resp);
}
/**
* 首字母小写
* @param old
* @return
*/
private static String lowerFirstCase(String old){
char [] chars = old.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
三、总结
服务启动会去读取web.xml配置文件,然后执行MyDispatcherServlet的init()方法,初始化一些配置,IOC,依赖注入,RequestMapping等,然后对发送进来的请求进行解析,通过反射方式调用到具体Controller的某个方法,然后对执行的结果进行处理。
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<display-name>DispatcherServlet</display-name>
<servlet-class>org.springframework.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
添加过滤器对编码进行设置:
<!-- 过滤器 -->
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
package org.springframework.web.filter;
import javax.servlet.*;
import java.io.IOException;
/**
* @Author: cxx
* @Date: 2019/9/15 23:56
*/
public class CharacterEncodingFilter implements Filter{
// 编码格式
private String encoding;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("------ filter init .......");
encoding = filterConfig.getInitParameter("encoding");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("------ 过滤器处理编码问题......" + encoding);
servletRequest.setCharacterEncoding(encoding);
servletResponse.setCharacterEncoding(encoding);
servletResponse.setContentType("text/html");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}