看了一個自定義的實現spring mvc的文章,自己敲了一下。原文地址:https://mp.weixin.qq.com/s/36F_fFbGKkRL20DJgX4ahg
Spring mvc流程圖:
請求流程:
⑴ 用戶發送請求至前端控制器DispatcherServlet
⑵ DispatcherServlet收到請求調用HandlerMapping處理器映射器。
⑶ 處理器映射器根據請求url找到具體的處理器,生成處理器對象及處理器攔截器(如果有則生成)一併返回給DispatcherServlet。
⑷ DispatcherServlet通過HandlerAdapter處理器適配器調用處理器
⑸ 執行處理器(Controller,也叫後端控制器)。
⑹ Controller執行完成返回ModelAndView
⑺ HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet
⑻ DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器
⑼ ViewReslover解析後返回具體View
⑽ DispatcherServlet對View進行渲染視圖(即將模型數據填充至視圖中)。
⑾ DispatcherServlet響應用戶。從上面可以看出,DispatcherServlet有接收請求,響應結果,轉發等作用。有了DispatcherServlet之後,可以減少組件之間的耦合度。
DispatcherServlet類結構圖:
主要組件:
protected void initStrategies(ApplicationContext context) {
//用於處理上傳請求。處理方法是將普通的request包裝成 MultipartHttpServletRequest,後者可以直接調用getFile方法獲取File.
initMultipartResolver(context);
//SpringMVC主要有兩個地方用到了Locale:一是ViewResolver視圖解析的時候;二是用到國際化資源或者主題的時候。
initLocaleResolver(context);
//用於解析主題。SpringMVC中一個主題對應 一個properties文件,裏面存放着跟當前主題相關的所有資源、//如圖片、css樣式等。SpringMVC的主題也支持國際化,
initThemeResolver(context);
//用來查找Handler的。
initHandlerMappings(context);
//從名字上看,它就是一個適配器。Servlet需要的處理方法的結構卻是固定的,都是以request和response爲參數的方法。//如何讓固定的Servlet處理方法調用靈活的Handler來進行處理呢?這就是HandlerAdapter要做的事情
initHandlerAdapters(context);
//其它組件都是用來幹活的。在幹活的過程中難免會出現問題,出問題後怎麼辦呢?//這就需要有一個專門的角色對異常情況進行處理,在SpringMVC中就是HandlerExceptionResolver。
initHandlerExceptionResolvers(context);
//有的Handler處理完後並沒有設置View也沒有設置ViewName,這時就需要從request獲取ViewName了,//如何從request中獲取ViewName就是RequestToViewNameTranslator要做的事情了。
initRequestToViewNameTranslator(context);
//ViewResolver用來將String類型的視圖名和Locale解析爲View類型的視圖。//View是用來渲染頁面的,也就是將程序返回的參數填入模板裏,生成html(也可能是其它類型)文件。
initViewResolvers(context);
//用來管理FlashMap的,FlashMap主要用在redirect重定向中傳遞參數。
initFlashMapManager(context);
}
接下來實現mvc
1.新建一個web項目,web.xml裏引入自定義的MyDispatcherServlet類以及配置文件
<?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"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<servlet>
<servlet-name>MySpringMVC</servlet-name>
<servlet-class>com.uiao.servlet.MyDispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>application.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MySpringMVC</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
application.properties裏只配置了掃描路徑
scanPackage=com.uiao.core
2.先來自定義幾個註解模仿Comtroller,RequestMapping,RequestParam註解的功能
/**
* 控制層註解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
String value() default "";
}
/**
* 方法路徑註解
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
String value() default "";
}
/**
* 參數註解
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
String value();
}
3.自定義DispatcherServlet類
首先通過配置掃描需要的類,得到類名後通過反射實例化類並存入IOC容器內(模仿Spring),再維護一個Mapping表記錄請求路徑和類(方法)的對應。加載階段完成之後就可以發起請求了。
/**
* 自定義DispatcherServlet
*/
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> handlerMapping = new HashMap<>();
private Map<String, Object> controllerMapping = new HashMap<>();
@Override
public void init(ServletConfig config) {
//加載配置文件進properties
doloadConfig(config.getInitParameter("contextConfigLocation"));
//掃描用戶包下面所有的類放到classNames裏
doScanner(properties.getProperty("scanPackage"));
//將掃描到的類通過反射實例化,並存放進IOC容器內
doInstance();
//將url和映射存進handlerMapping和controllerMapping
initHandlerMapping();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
try {
doDispatch(req, resp);
} catch (Exception e) {
resp.getWriter().write("500 Server Exception");
}
}
private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (handlerMapping.isEmpty()) {
return;
}
String url = request.getRequestURI();
String contextPath = request.getContextPath();
url = url.replace(contextPath, "").replaceAll("/+", "/");
if (!this.handlerMapping.containsKey(url)) {
response.getWriter().write("404 not found");
}
Method method = this.handlerMapping.get(url);
//獲取方法的參數列表
Class<?>[] parameterTypes = method.getParameterTypes();
//獲取請求的參數列表(只有這一個)
Map<String, String[]> parameterMap = request.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] = request;
continue;
}
if (requestParam.equals("HttpServletResponse")) {
paramValues[i] = response;
continue;
}
if (requestParam.equals("String")) {
for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
String value = Arrays.toString(param.getValue()).replaceAll("[|]", "").replaceAll(",", ",");
paramValues[i] = value;
}
}
}
try {
method.invoke(this.controllerMapping.get(url), paramValues);//第一個參數爲method所對應的實例,在ioc容器中
} catch (Exception e) {
e.printStackTrace();
}
}
private void doloadConfig(String location) {
InputStream inputStream = this.getClass().getResourceAsStream(location);
try {
properties.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void doScanner(String packageName) {
URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll(".", "/"));
File dir = new File(url.getFile());
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
doScanner(packageName + "." + file.getName());
} else {
String className = packageName + "." + file.getName().replaceAll(".class", "");
classNames.add(className);
}
}
}
private void doInstance() {
if (classNames.isEmpty()) {
return;
}
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(MyController.class)) {
ioc.put(toLowerFirstWord(clazz.getSimpleName()), clazz.newInstance());
} else {
continue;
}
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
private void initHandlerMapping() {
if (ioc.isEmpty()) {
return;
}
try {
for (Map.Entry<String, Object> entry : ioc.entrySet()) {
Class<? extends Object> clazz = entry.getValue().getClass();
if (clazz.isAnnotationPresent(MyController.class)) {
continue;
}
String baseUrl = "";
if (clazz.isAnnotationPresent(MyRequestMapping.class))//用在類上或直接用在方法上
{
MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class);
baseUrl = annotation.value();
}
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(MyRequestMapping.class)) {//用在方法上
continue;
}
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
String url = annotation.value();
url = (baseUrl + "/" + url).replace("/+", "/");
handlerMapping.put(url, method);
controllerMapping.put(url, clazz.newInstance());
System.out.println(url + "," + method);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 把字符串的首字母小寫
*
* @param name
* @return
*/
private String toLowerFirstWord(String name) {
char[] charArray = name.toCharArray();
charArray[0] += 32;
return String.valueOf(charArray);
}
}
4.寫個請求測試一下
@MyController
@MyRequestMapping("/test")
public class TestController {
@MyRequestMapping("/testa")
public void testA(HttpServletRequest request, HttpServletResponse response, @MyRequestParam("name") String name) {
System.out.println(name);
try {
response.getWriter().write("dotest methoda success ! param:" + name);
} catch (Exception e) {
e.printStackTrace();
}
}
@MyRequestMapping("/testb")
public void testB(HttpServletRequest request, HttpServletResponse response) {
try {
response.getWriter().write("dotestb method success ! ");
} catch (Exception e) {
e.printStackTrace();
}
}
}