作為java程序員而晒,項目中使用到的主流框架多多少少和spring有關(guān)聯(lián),在面試的過程難免會問一些spring springmvc spring boot的東西螃壤,比如設(shè)計模式的使用抗果、 怎么實現(xiàn)springioc?怎么實現(xiàn)springmvc諸如此類的問題,今天我們就來探尋spring mvc的實現(xiàn)奸晴,然后自己實現(xiàn)一個簡單的spring mvc
一.了解spring mvc的基本運行流程
ps:?網(wǎng)上一大堆關(guān)于springmvc的詳細講解冤馏,在這里就不累贅了
小結(jié):spring mvc的核心是DispatcherServlet,DispatcherServlet繼承于HttpServlet寄啼,可以說spring mvc是基于Servlet的一個實現(xiàn)逮光,DispatcherServlet負責(zé)協(xié)調(diào)和組織不同組件以完成請求處理并返回響應(yīng)的工作,實現(xiàn)了MVC模式辕录。
二.?梳理簡單SpringMVC的設(shè)計思路
1. 初始化容器?
1.1?讀取配置文件
1.1.1.加載配置文件信息到DispatcherServlet
1.2??根據(jù)配置掃描包睦霎、初始化容器和組件
1.2.1.根據(jù)配置信息遞歸掃描包
1.2.2.把包下的類實例化 并且掃描注解
1.2.3.根據(jù)類的方法和注解,初始化HandlerMapping
2. 處理業(yè)務(wù)請求
2.1?處理請求業(yè)務(wù)
2.2.1 首先拿到請求URI?
? ???2.2.2 根據(jù)URI走诞,在HandlerMapping中查找和URI對應(yīng)的Handler
? ? ? ? 2.2.3 根據(jù)Handler里面的method中的參數(shù)名稱和http中的請求參數(shù)匹配,填充method參數(shù)蛤高,反射調(diào)用
三.?沒時間解釋了蚣旱,快上車
ps :環(huán)境基于maven idea tomat(端口8080) servlet
1.搭建一個基本web項目,并導(dǎo)入idea配置servlet 和javassist pom依賴 如下
創(chuàng)建命令: mvn archetype:generate -DgroupId=com.adminkk -DartifactId=adminkk-mvc -DpackageName=com.adminkk -Dversion=1.0
?pom依賴
? 4.0.0? com.adminkk? adminkk-mvc? 1.0-SNAPSHOT? ? ? ? ? ? ? ? ? ? org.apache.maven.plugins? ? ? ? maven-compiler-plugin? ? ? ? ? ? ? ? ? 8? ? ? ? ? 8? ? ? ? ? ? ? ? ? ? ? war? adminkk-mvc? http://maven.apache.org</url>? ? UTF-8? ? ? ? ? ? ? junit? ? ? junit? ? ? 3.8.1? ? ? test? ? ? ? ? ? ? ? ? ? ? javax.servlet? ? ? ? javax.servlet-api? ? ? ? 3.0.1? ? ? ? provided? ? ? ? ? ? ? ? ? ? asm? ? ? asm? ? ? 3.3.1? ? ? ? ? ? ? ? ? org.javassist? ? ? javassist? ? ? 3.23.1-GA? ? ?
2.創(chuàng)建mvc的注解?Controller?RequestMapping 和統(tǒng)一異常處理類戴陡、方法參數(shù)工具類ParameterNameUtils ?
package com.adminkk.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)public@interface Controller {
? ? publicString value()default"";
? ? publicString description()default"";
}
package com.adminkk.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)public@interface RequestMapping {
? ? publicString value()default"";
? ? publicString method()default"";
? ? publicString description()default"";
}
package com.adminkk.exception;
public? final? class MvcException extends RuntimeException{
? ? public MvcException() {
? ? ? ? super();
? ? }
? ? public MvcException(String message) {
? ? ? ? super(message);
? ? }
}
package com.adminkk.tools;importjavassist.*;import javassist.bytecode.CodeAttribute;import javassist.bytecode.LocalVariableAttribute;import javassist.bytecode.MethodInfo;import java.lang.reflect.Method;publicfinalclass ParameterNameUtils {
? ? publicfinalstaticString[] getParameterNamesByJavassist(finalClass clazz,final Method method) {
? ? ? ? ClassPool pool = ClassPool.getDefault();
? ? ? ? try {
? ? ? ? ? ? CtClass ctClass = pool.get(clazz.getName());
? ? ? ? ? ? CtMethod ctMethod = ctClass.getDeclaredMethod(method.getName());
? ? ? ? ? ? // 使用javassist的反射方法的參數(shù)名MethodInfo methodInfo = ctMethod.getMethodInfo();
? ? ? ? ? ? CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
? ? ? ? ? ? LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute
? ? ? ? ? ? ? ? ? ? .getAttribute(LocalVariableAttribute.tag);
? ? ? ? ? ? if(attr !=null) {
? ? ? ? ? ? ? ? String[] rtv =new String[ctMethod.getParameterTypes().length];
? ? ? ? ? ? ? ? intlen = ctMethod.getParameterTypes().length;
? ? ? ? ? ? ? ? // 非靜態(tài)的成員函數(shù)的第一個參數(shù)是thisintpos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
? ? ? ? ? ? ? ? for(inti = 0; i < len; i++) {
? ? ? ? ? ? ? ? ? ? rtv[i] = attr.variableName(i + pos);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? return rtv;
? ? ? ? ? ? }
? ? ? ? } catch (NotFoundException e) {
? ? ? ? ? ? System.out.println("獲取異常"+ e.getMessage());
? ? ? ? }
? ? ? ? returnnewString[0];
? ? }
}
3.創(chuàng)建?HandlerMapping類 主要是兩個方法? doInit初始化?doService處理請求 相關(guān)代碼如下
package com.adminkk.handler;import com.adminkk.scan.FileScaner;import com.adminkk.scan.Scaner;import com.adminkk.scan.XmlScaner;import com.adminkk.tools.ParameterNameUtils;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.PrintWriter;import java.lang.reflect.Method;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;publicfinalclass HandlerMapping {
? ? privatestaticfinalMap handlerMapping =newHashMap();
? ? privatestaticfinalList scaners =newArrayList<>(2);
? ? static {
? ? ? ? scaners.add(new XmlScaner());
? ? ? ? scaners.add(new FileScaner());
? ? }
? ? publicstaticvoidscanPackage(String scanUrl)throws RuntimeException, IllegalAccessException, InstantiationException, ClassNotFoundException {
? ? ? ? for (Scaner scaner : scaners) {
? ? ? ? ? ? scaner.doScane(scanUrl);
? ? ? ? }
? ? }
? ? publicstaticvoiddoInit(String scanUrl)throws IllegalAccessException, ClassNotFoundException, InstantiationException {
? ? ? ? scanPackage(scanUrl);
? ? }
? ? publicstaticvoid doService(HttpServletRequest request, HttpServletResponse response) {
? ? ? ? String requestURI = request.getRequestURI();
? ? ? ? System.out.println("請求地址是="+ requestURI);
? ? ? ? Handler handler = handlerMapping.get(requestURI);
? ? ? ? if(handler ==null){
? ? ? ? ? ? System.out.println("請求地址是="+ requestURI+" 沒有配置改路徑");
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? Method method = handler.getMethod();
? ? ? ? Object instance = handler.getInstance();
? ? ? ? response.setCharacterEncoding("UTF-8");
? ? ? ? //response.setContentType("application/json; charset=utf-8");PrintWriter writer =null;
? ? ? ? try {
? ? ? ? ? ? //這里是簡單的解析 可以像springmvc那樣解析處理Map parameterMap = request.getParameterMap();
? ? ? ? ? ? String[] parameters = ParameterNameUtils.getParameterNamesByJavassist(instance.getClass(),method);
? ? ? ? ? ? Object[]? parameter =new Object[parameters.length];
? ? ? ? ? ? if(parameters !=null&& parameters.length > 0){
? ? ? ? ? ? ? ? for(inti = 0; i < parameters.length; i++) {
? ? ? ? ? ? ? ? ? ? finalString simpleName = parameters[i];
? ? ? ? ? ? ? ? ? ? StringBuilder parameterSb =new? StringBuilder();
? ? ? ? ? ? ? ? ? ? finalString[] parameterStr = parameterMap.get(simpleName);
? ? ? ? ? ? ? ? ? ? if(parameterStr !=null){
? ? ? ? ? ? ? ? ? ? ? ? for(intj = 0; j < parameterStr.length; j++) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? parameterSb.append(parameterStr[j]);
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? parameter[i] = parameterSb.toString();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? writer = response.getWriter();
? ? ? ? ? ? String result = (String) method.invoke(instance,parameter);
? ? ? ? ? ? writer.print(result);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? System.out.println("請求地址是="+ requestURI+" 執(zhí)行異常");
? ? ? ? ? ? writer.print("業(yè)務(wù)執(zhí)行異常");
? ? ? ? }finally {
? ? ? ? ? ? writer.flush();
? ? ? ? ? ? writer.close();
? ? ? ? }
? ? }
? ? publicstatic Handler addHandlerMapping(String url,Handler handler) {
? ? ? ? return handlerMapping.put(url,handler);
? ? }
? ? publicstatic Handler getHandlerMapping(String url) {
? ? ? ? return handlerMapping.get(url);
? ? }
}
?掃描包
package com.adminkk.scan;publicinterface Scaner {
? ? voiddoScane(String scanUrl)throws IllegalAccessException, InstantiationException, ClassNotFoundException;
}
package com.adminkk.scan;
import com.adminkk.exception.MvcException;
import com.adminkk.factory.BeanPostProcessor;
import com.adminkk.factory.MvcBeanPostProcessor;
import com.adminkk.factory.ServiceBeanPostProcessor;
import com.adminkk.handler.HandlerMapping;
import javassist.ClassClassPath;
import javassist.ClassPool;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public final? class FileScaner implements Scaner{
public FileScaner() {
}
public static final List beanPostProcessorList = new ArrayList<>();
static {
beanPostProcessorList.add(new MvcBeanPostProcessor());
beanPostProcessorList.add(new ServiceBeanPostProcessor());
}
@Override
public void doScane(String scanUrl) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
if(scanUrl == null || scanUrl.length() == 0){
throw new MvcException("容器基礎(chǔ)掃描路徑為空塞绿,請檢查參數(shù)配置");
}
String baseUrl = HandlerMapping.class.getResource("/").getPath();
String codeUrl = scanUrl.replaceAll("\\.", "/");
String path =? baseUrl + codeUrl;
File file = new File(path);
if(file == null || !file.exists()){
throw new MvcException("找不到對應(yīng)掃描路徑,請檢查參數(shù)配置");
}
recursionRedFile(scanUrl,file);
}
//遞歸讀取文件
private? void recursionRedFile(String scanUrl,File file) throws MvcException, ClassNotFoundException, IllegalAccessException, InstantiationException {
if(!file.exists()){
return;
}
//讀取java文件
if(file.isFile()){
String beanName = scanUrl.replaceAll(".class","");
Class forName = Class.forName(beanName);
//放到Javassist容器里面
ClassPool pool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(forName);
pool.insertClassPath(classPath);
if(forName.isAnnotation() || forName.isEnum() || forName.isInterface() ){
return;
}
Object newInstance = forName.newInstance();
//前置執(zhí)行
for (int i = 0; i < beanPostProcessorList.size() ; i++) {
BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i);
beanPostProcessor.postProcessBeforeInitialization(newInstance,beanName);
}
//后置執(zhí)行
for (int i = beanPostProcessorList.size()-1; i > 0? ; i++) {
BeanPostProcessor beanPostProcessor = beanPostProcessorList.get(i);
beanPostProcessor.postProcessAfterInitialization(newInstance,beanName);
}
return;
}
//文件夾下面的文件都遞歸處理
if(file.isDirectory()){
File[] files = file.listFiles();
if(files != null && files.length >0){
for (int i = 0; i < files.length; i++) {
File targetFile = files[i];
recursionRedFile(scanUrl+"."+targetFile.getName(),targetFile);
}
}
}
}
}
package com.adminkk.scan;
public final class XmlScaner implements Scaner{
? ? public XmlScaner() {
? ? }
? ? @Override
? ? public void doScane(String scanUrl) {
? ? ? ? //可自行擴展
? ? }
}
掃描bean
package com.adminkk.factory;import com.adminkk.exception.MvcException;publicinterface BeanPostProcessor {
? ? Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException;
? ? Object postProcessAfterInitialization(Object object, String beanName) throws MvcException;
}
package com.adminkk.factory;import com.adminkk.annotation.Controller;import com.adminkk.annotation.RequestMapping;import com.adminkk.exception.MvcException;import com.adminkk.handler.Handler;import com.adminkk.handler.HandlerMapping;import java.lang.reflect.Method;publicclassMvcBeanPostProcessorimplements BeanPostProcessor{
? ? //掃描Controller業(yè)務(wù)? ? @Override
? ? publicObject postProcessBeforeInitialization(Object object, String beanName)throws MvcException {
? ? ? ? Class objectClass = object.getClass();
? ? ? ? if(objectClass.getAnnotation(Controller.class) !=null){
? ? ? ? ? ? RequestMapping calssRequestMappingAnnotation = objectClass.getAnnotation(RequestMapping.class);
? ? ? ? ? ? StringBuilder urlSb =new StringBuilder();
? ? ? ? ? ? if(calssRequestMappingAnnotation !=null){
? ? ? ? ? ? ? ? urlSb.append(calssRequestMappingAnnotation.value());
? ? ? ? ? ? }
? ? ? ? ? ? Method[] methods = objectClass.getMethods();
? ? ? ? ? ? if(methods !=null&& methods.length > 0 ){
? ? ? ? ? ? ? ? for(inti = 0; i < methods.length; i++) {
? ? ? ? ? ? ? ? ? ? Method method = methods[i];
? ? ? ? ? ? ? ? ? ? RequestMapping methodAnnotation = method.getAnnotation(RequestMapping.class);
? ? ? ? ? ? ? ? ? ? if(methodAnnotation !=null){
? ? ? ? ? ? ? ? ? ? ? ? String methodValue = methodAnnotation.value();
? ? ? ? ? ? ? ? ? ? ? ? String url =new StringBuilder().append(urlSb).append(methodValue).toString();
? ? ? ? ? ? ? ? ? ? ? ? Handler handler = HandlerMapping.getHandlerMapping(url);
? ? ? ? ? ? ? ? ? ? ? ? if(handler ==null){
? ? ? ? ? ? ? ? ? ? ? ? ? ? handler =new Handler();
? ? ? ? ? ? ? ? ? ? ? ? ? ? handler.setMethod(method);
? ? ? ? ? ? ? ? ? ? ? ? ? ? handler.setInstance(object);
? ? ? ? ? ? ? ? ? ? ? ? ? ? HandlerMapping.addHandlerMapping(url,handler);
? ? ? ? ? ? ? ? ? ? ? ? }else {
? ? ? ? ? ? ? ? ? ? ? ? ? ? thrownewMvcException("請求路徑"+ url + "已經(jīng)存在容器中");
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return object;
? ? }
? ? @Override
? ? publicObject postProcessAfterInitialization(Object object, String beanName)throws MvcException {
? ? ? ? returnnull;
? ? }
}
package com.adminkk.factory;
import com.adminkk.exception.MvcException;
public class ServiceBeanPostProcessor implements BeanPostProcessor {
? ? @Override
? ? public Object postProcessBeforeInitialization(Object object, String beanName) throws MvcException {
? ? ? ? //可自行擴展
? ? ? ? return null;
? ? }
? ? @Override
? ? public Object postProcessAfterInitialization(Object object, String beanName) throws MvcException {
? ? ? ? //可自行擴展
? ? ? ? return null;
? ? }
}
5.創(chuàng)建?DispatcherServlet
package com.adminkk.servlet;
import com.adminkk.handler.HandlerMapping;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "DispatcherServlet",loadOnStartup=1,urlPatterns={"/"})
public final? class DispatcherServlet extends HttpServlet {
? ? public static final String BASE_SCAN_URL = "com.adminkk";
? ? //初始化容器
? ? @Override
? ? public void init() throws ServletException {
? ? ? ? doInit();
? ? }
? ? //處理業(yè)務(wù)請求
? ? @Override
? ? protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
? ? ? ? doService(req,resp);
? ? }
? ? private void doService(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
? ? ? ? try {
? ? ? ? ? ? HandlerMapping.doService(req,resp);
? ? ? ? }catch (Exception e){
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? throw new ServletException(e.getMessage());
? ? ? ? }
? ? }
? ? private void doInit() throws ServletException {
? ? ? ? try {? ?
? ? ? ? ? ? HandlerMapping.doInit(this.BASE_SCAN_URL);
? ? ? ? }catch (Exception e){
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? throw new ServletException(e.getMessage());
? ? ? ? }
? ? }
}
好了恤批,目前為止我們就寫好了簡版的springmvc 下面開始測試
package com.adminkk.controller;
import com.adminkk.annotation.Controller;
import com.adminkk.annotation.RequestMapping;
@Controller
@RequestMapping("/mvc")
public class MvcController {
? ? @RequestMapping("/index")
? ? public String index(){
? ? ? ? return? "adminkk-mvc system is running";
? ? }
? ? @RequestMapping("/arg")
? ? public String parameter(String argOne, String argTwo){
? ? ? ? return? "argOne = " + argOne + " argTwo = " + argTwo;
? ? }
}
訪問地址?http://localhost:8080/mvc/index
訪問地址: http://localhost:8080/mvc/arg?argOne=argOne&argTwo=argTwo
總結(jié):整體實現(xiàn)簡單的springmvc异吻,設(shè)計上還可以擴展更多,難點在于method 獲取方法上的參數(shù)名稱,由于jdk1.8以前是不支持的诀浪,需要借用第三方工具 比如 asm?javassist黑科技工具包來幫助實現(xiàn)棋返,spring-core使用的是LocalVariableTableParameterNameDiscoverer底層是調(diào)用asm,我們這里使用的是javassist。延用這套思路還可以和spring項目結(jié)合雷猪,寫一個 基于spring的springmvc項目
源代碼 : https://gitee.com/chenchenche/mvc
寫博客不容易睛竣,希望大家多多提建議?
下一篇預(yù)告? ? ?跟我一起造輪子 手寫分布式IM系統(tǒng)(上)