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() {
}
}