前幾章咱們已經分析了 Spring 的源碼而且手動實現了一個 IOC/DI 容器。java
這章咱們在本身實現的 Spring 框架的基礎上實現一個 SpringMVC 框架。git
咱們本身實現的 Spring 框架的快速入口:Spring 框架 裏面的 MySpring 是咱們 IOC/DI 框架的源碼。github
下面咱們在這個基礎上實現一個 MVC 框架。首先 @Controller,@Service,@Repository 註解已經在咱們本身的 Spring 框架裏面定義了,能夠實現將被這些註解標記的類交給 Spring 管理,而且實現了依賴注入。web
由於咱們這個 MVC 框架要依賴 IOC/DI 容器,因此咱們在 pom 文件裏要將本身的 Spring 框架引入進來。spring
首先咱們定義兩個 MVC 專用的註解,RequestMapping 用來作 url 匹配,RequestParam 作參數轉換:瀏覽器
package com.paul.annotation; import java.lang.annotation.*; @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestMapping { String value() default ""; }
package com.paul.annotation; import java.lang.annotation.*; @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam { String value() default ""; }
咱們知道 SpringMVC 的核心是 DispatcherServlet,用來作核心路由控制,咱們也定義這樣一個類,而且在初始化方法裏初始化一個 IOC/DI 容器,看過前面文章的同窗應該知道,初始化容器後咱們已經將 Bean 放到容器中並且完成了依賴注入。架構
package com.paul.servlet; import com.paul.annotation.RequestMapping; import com.paul.annotation.RequestParam; import org.springframework.ioc.annotation.Controller; import org.springframework.ioc.bean.AnnotationApplicationContext; import org.springframework.ioc.bean.ApplicationContext; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; //實現 servlet,和咱們之前使用 servlet 同樣。 public class DispatcherServlet extends HttpServlet { // 完整路徑和 方法的 mapping private Map<String,Object> handleMapping = new ConcurrentHashMap<>(); // 類路徑和controller 的 mapping private Map<String,Object> controllerMapping = new ConcurrentHashMap<>(); private Map<String,Object> beanFactory = new ConcurrentHashMap<>(); @Override public void init(ServletConfig config) throws ServletException { super.init(config); //實例化 IOC 容器 ApplicationContext ctx = new AnnotationApplicationContext("applicationContext.xml"); this.beanFactory = ((AnnotationApplicationContext) ctx).beanFactory; //上一步已經完成了 Controller,service,respostry,autowired 等註解的掃描和注入 //遍歷容器,將 requestmapping 註解的路徑和對應的方法以及 contoller 實例對應起來 for(Map.Entry<String,Object> entry:beanFactory.entrySet()){ Object instance = entry.getValue(); Class<?> clazz = instance.getClass(); if(clazz.isAnnotationPresent(Controller.class)){ RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); String classPath = requestMapping.value(); Method[] methods = clazz.getMethods(); for(Method method:methods){ if(method.isAnnotationPresent(RequestMapping.class)){ RequestMapping requestMapping2 = method.getAnnotation(RequestMapping.class); String methodPath = requestMapping2.value(); String requestPath = classPath + methodPath; handleMapping.put(requestPath,method); controllerMapping.put(requestPath,instance); }else{ continue; } } }else{ continue; } } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } //根據上一步獲取到的 mapping,根據 url 找到對應的 controller 和方法去執行。 @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String uri = req.getRequestURI(); // /paul-mvc/com.paul.controller/method-com.paul.controller String context = req.getContextPath(); // /paul-vmc String path = uri.replace(context,""); // /com.paul.controller/method-com.paul.controller Method m = (Method) handleMapping.get(path); //從容器裏拿到controller 實例 Object instance = controllerMapping.get(path); Object[] args = handle(req,resp,m); for (Object a:args){ System.out.println("Object:"+a); } try { m.invoke(instance,args); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } private static Object[] handle(HttpServletRequest req, HttpServletResponse resp,Method method){ //拿到當前執行的方法有哪些參數 Class<?>[] paramClazzs = method.getParameterTypes(); //根據參數的個數,new 一個參數的數據 Object[] args = new Object[paramClazzs.length]; int args_i = 0; int index = 0; for(Class<?> paramClazz:paramClazzs){ if(ServletRequest.class.isAssignableFrom(paramClazz)){ args[args_i++] = req; } if(ServletResponse.class.isAssignableFrom(paramClazz)){ args[args_i++] = resp; } //判斷requestParam 註解 Annotation[] paramAns = method.getParameterAnnotations()[index]; if(paramAns.length > 0){ System.out.println("my"); for(Annotation paramAn:paramAns){ if(RequestParam.class.isAssignableFrom(paramAn.getClass())){ System.out.println("13mj"); RequestParam rp = (RequestParam) paramAn; args[args_i++] = req.getParameter(rp.value()); } } } index ++; } return args; } }
新建一個 WebDemo,Maven web 項目。mvc
先來看咱們須要測試的 Controller 和 Service。app
@Controller @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/query") public void get(HttpServletRequest request, HttpServletResponse response, @RequestParam("name") String name,@RequestParam("age") String age){ try { PrintWriter pw = response.getWriter(); String res = userService.query(name,age); pw.write(res); } catch (IOException e) { e.printStackTrace(); } } }
import org.springframework.ioc.annotation.Service; @Service("userService") public class UserServiceImpl implements UserService { @Override public String query(String name, String age) { return "name="+name+"age="+age; } }
在 resources 目錄咱們須要寫一個名字爲 applicationContext 的配置文件來指明掃描包路徑。框架
<?xml version="1.0" encoding="UTF-8"?> <beans> <package-scan component-scan="com.paul.demo" /> </beans>
在 web.xml 中配置咱們本身的 DispatcherServlet。
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class>com.paul.servlet.DispatcherServlet</servlet-class> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
在瀏覽器中測試結果:
結果和咱們想的同樣。
整個項目的源碼在:源碼,但願你們多 star 一塊兒改進。