網上不少關於springmvc的分析,但不多有手動實現的,這裏經過一個簡單的demo實現mvc基本功能,僅供學習參考。java
咱們先簡單瞭解下springmvc請求流程,以下圖:
從圖上得知,最早處理請求的是dispatcherServlet,它接受請求並查詢獲得攔截器鏈HandlerExecutionChain,HandlerAdapter通過適配器調用具體的處理器(Controller).web
查看源碼能夠到spring-webmvc.jar中,org.springframework.web.servlet/DispatcherServlet.class,其中方法doDispatch()完成了一個請求到返回數據的完整操做.spring
下面咱們開始動手了,先建立一個javaweb工程,寫一個Servlet,以下:瀏覽器
@WebServlet(urlPatterns = "/*", loadOnStartup = 1) public class DispatcherServlet extends HttpServlet { private List<String> clzList = new ArrayList<>(); private Map<String, Object> beansMap = new HashMap<>(); private Map<String, Object> urlMapping = new HashMap<>(); @Override public void init() throws ServletException { try { //掃描配置 這裏是包名 scanPackages("com.iti.smvc"); //實例化對象 doInstance(); //創建對象之間的依賴ioc ioc(); //創建url到controller的映射 doMapping(); } catch (Exception e) { e.printStackTrace(); } } }
注:servlet3.0再也不須要web.xml, 這裏將成員變量寫入servlet並非很好的實現,會致使線程不安全
這個servlet要作一些初始化的工做,如:
1.掃描包名,將class對象裝入clzList列表
2.遍歷clzList列表,實例化有Controller和Service標註的類
3.依賴注入,將service注入到controller
4.創建url與controller中方法url的mapping關係安全
咱們依次來實現他們:
下面的是掃描配置方法mvc
private void scanPackages(String packageUrl) { String fileUrl = getClass().getClassLoader().getResource("/"+packageUrl.replaceAll("\\.", "/")).getFile(); File scanfile = new File(fileUrl); String[] fileList = scanfile.list(); for (String fileName: fileList) { File file = new File(fileUrl+fileName); if (file.isDirectory()) { scanPackages(packageUrl + "." + fileName);; } else { clzList.add(packageUrl + "." + fileName.replace(".class", "")); } } }
接着是對象實例化:app
private void doInstance() throws Exception{ if (clzList.size()>0) { for (String clzName : clzList) { Class<?> clzClass = Class.forName(clzName); if (clzClass.isAnnotationPresent(Controller.class)) { //for controller RequestMapping rm = clzClass.getAnnotation(RequestMapping.class); beansMap.put(rm.value(), clzClass.newInstance()); } else if (clzClass.isAnnotationPresent(Service.class)) { Service service = clzClass.getAnnotation(Service.class); beansMap.put(service.value(), clzClass.newInstance()); } } } }
接着是依賴注入,實際上是反射:ide
private void ioc() throws Exception{ if (beansMap.size()>0) { for (Map.Entry<String, Object> entry : beansMap.entrySet()) { Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Autowired.class)) { Qualifier anno = field.getAnnotation(Qualifier.class); field.set(entry.getValue(), beansMap.get(anno.value())); } } } } }
最後是創建請求url與controller的mapping關係,以下:學習
private void doMapping() { if (beansMap.size()>0) { for (Map.Entry<String, Object> entry: beansMap.entrySet()) { Class<? extends Object> obj = entry.getValue().getClass(); if (obj.isAnnotationPresent(Controller.class)){ RequestMapping rm = obj.getAnnotation(RequestMapping.class); Method[] methods = obj.getMethods(); for (Method method: methods) { if (method.isAnnotationPresent(RequestMapping.class)) { RequestMapping anno = method.getAnnotation(RequestMapping.class); urlMapping.put(rm.value() + anno.value(), method); } } } } } }
還要把註解建立下:測試
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { String value() default ""; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Controller { String value() default ""; } @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Qualifier { String value() default ""; } @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestMapping { String value() default ""; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Service { String value() default ""; }
此時就能夠啓動服務完成初始化工做了
下面咱們要建立一個sevice方法來接收url請求
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String requestURI = req.getRequestURI(); String contextPath = req.getContextPath(); //獲得請求地址 String path = requestURI.replace(contextPath, ""); Method method = (Method)urlMapping.get(path); if (method != null) { try { Object obj = method.invoke(beansMap.get("/"+path.split("/")[1])); resp.getOutputStream().write(obj.toString().getBytes()); } catch (Exception e) { e.printStackTrace(); } } }
上面是獲得請求url,而後從urlMapping獲得要調用的method,再經過反射調用,最後經過outputStream輸出到瀏覽器上。
下面編寫一個controller和service測試下
controller代碼以下:
@Controller @RequestMapping("/hello") public class HelloController { @Autowired @Qualifier("helloservice") Helloservice helloservice; @RequestMapping("/sayHello") public String sayHello() { //System.out.println(helloservice.hello()); return "controller"; } }
service代碼以下:
@Service("helloservice") public class Helloservice { public String hello() { return "hello"; } }
請求url:http://localhost:8080/hello/sayHello學習交流,歡迎加羣:64691032