手寫一個簡單版的SpringMVC
阿新 • • 發佈:2020-08-24
一 寫在前面
這是自己實現一個簡單的具有SpringMVC功能的小Demo,主要實現效果是;
自己定義的實現效果是通過瀏覽器地址傳一個name引數,列印“my name is”+name引數。不使用SpringMVC,自己定義部分註解,實現DispatcherServlet核心功能,通過這個demo可以加深自己對原始碼的理解。
先看一下實現效果:
(傳入了引數時)
(沒有傳入引數時)
二DispatcherServlet流程
- 載入配置檔案
- 掃描所有相關類
- 初始化所有相關的類
- 自動注入
- 初始化HandlerMapping
- 等待請求
三 程式碼回顧
1.首先來看一下Pom檔案的依賴:
<dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>View Codecommons-lang3</artifactId> <version>3.10</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> </dependencies>
依賴比較少,沒有spring的依賴,主要就是一個servlet的。
2. 配置檔案:
2.1.application.properties檔案:
scanPackage=com.qunar.framework.demoView Code
這是說明要掃描的位置。
2.2. web.xml檔案:
<!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>MySpringMVC</display-name> <servlet> <servlet-name>mvc</servlet-name> <servlet-class>com.qunar.framework.webmvc.DispatcherServlet</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>mvc</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
3. 下面是整個工程的目錄結構:
4. 自定義註解:
@Controller:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Controller { String value() default ""; }View Code
@Service:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Service { String value() default ""; }View Code
@AutoWired:
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { String value() default ""; }View Code
@RequestMapping:
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { String value() default ""; }View Code
@RequestParam:
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam { String value() default ""; }View Code
5.自己封裝的Handler:
public class Handler { protected Object controller; protected Method method; protected Pattern pattern; protected Map<String,Integer> paramIndexMap; public Handler(Object controller, Method method, Pattern pattern) { this.controller = controller; this.method = method; this.pattern = pattern; this.paramIndexMap = new HashMap<>(); putParamIndexMapping(method); } private void putParamIndexMapping(Method method) { //獲取方法中加了註解的引數 Annotation[][] annotations = method.getParameterAnnotations(); for (int i =0; i < annotations.length;i++){ for (Annotation annotation : annotations[i]){ if (annotation instanceof RequestParam){ String paramName = ((RequestParam) annotation).value(); if (!StringUtils.isBlank(paramName)){ paramIndexMap.put(paramName,i); } } } } //獲取方法中的我request和response的引數 Class<?>[] paramTypes = method.getParameterTypes(); for (int i = 0; i < paramTypes.length; i++){ Class<?> paramType = paramTypes[i]; if (paramType == HttpServletRequest.class || paramType == HttpServletResponse.class){ paramIndexMap.put(paramType.getName(),i); } } } }View Code
6. 自己封裝的DispatcherServlet:
@Slf4j public class DispatcherServlet extends HttpServlet { private static final long serialVersionUID = 1L; private Properties contextConfig = new Properties(); private List<String> classNames = new ArrayList<>(); private Map<String, Object> iocMap = new HashMap<>(); private List<Handler> handlerMapping = new ArrayList<>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { //等待請求 try { doDispatch(req, resp); } catch (Exception exception) { resp.getWriter().write("500 Exception"); log.error("500 Exception. Cause: {}", exception.getMessage()); exception.printStackTrace(); } } private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { Handler handler = getHandler(req); if (handler == null) { //沒有匹配上,404 log.info("404 Not Found"); resp.getWriter().write("404 Not Found"); return; } //獲取引數列表 Class<?>[] parameterTypes = handler.method.getParameterTypes(); //儲存所有需要自動賦值的引數值 Object[] parameterValues = new Object[parameterTypes.length]; Map<String, String[]> parameterMap = req.getParameterMap(); for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { String value = Arrays.toString(entry.getValue()).replaceAll("\\[|\\]", "").replaceAll("/+", "/"); log.info(value); //如果找到了匹配的值,就填充 if (!handler.paramIndexMap.containsKey(entry.getKey())) { continue; } Integer index = handler.paramIndexMap.get(entry.getKey()); parameterValues[index] = convert(parameterTypes[index], value); } //設定方法中的request物件和response物件 Integer reqIndex = handler.paramIndexMap.get(HttpServletRequest.class.getName()); Integer respIndex = handler.paramIndexMap.get(HttpServletResponse.class.getName()); parameterValues[reqIndex] = req; parameterValues[respIndex] = resp; handler.method.invoke(handler.controller, parameterValues); } private Object convert(Class<?> parameterType, String value) { if (parameterType == Integer.class) { return Integer.valueOf(value); } return value; } private Handler getHandler(HttpServletRequest req) { if (handlerMapping.isEmpty()) { return null; } String requestURI = req.getRequestURI(); String contextPath = req.getContextPath(); requestURI = requestURI.replace(contextPath, "").replaceAll("/+", "/"); for (Handler handler : handlerMapping) { Matcher matcher = handler.pattern.matcher(requestURI); if (!matcher.matches()) { continue; } return handler; } return null; } @Override public void init(ServletConfig config) { //從這裡開始啟動: //載入配置檔案 loadConfig(config.getInitParameter("contextConfigLocation")); //掃描相關類 doScanner(contextConfig.getProperty("scanPackage")); //初始化相關類 try { doInstance(); } catch (Exception exception) { log.error("Execute doInstance method fail."); exception.printStackTrace(); } //自動注入 doAutowired(); //初始化HandlerMapping initHandlerMapping(); } private void initHandlerMapping() { if (iocMap.isEmpty()) { return; } for (Map.Entry<String, Object> entry : iocMap.entrySet()) { Class<?> clazz = entry.getValue().getClass(); if (!clazz.isAnnotationPresent(Controller.class)) { continue; } String baseUrl = ""; if (clazz.isAnnotationPresent(RequestMapping.class)) { RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); baseUrl = requestMapping.value(); } //掃描所有的公共方法 for (Method method : clazz.getMethods()) { if (!method.isAnnotationPresent(RequestMapping.class)) { continue; } RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); String regex = ("/" + baseUrl + requestMapping.value()).replaceAll("/+", "/"); Pattern pattern = Pattern.compile(regex); handlerMapping.add(new Handler(entry.getValue(), method, pattern)); log.info("Mapping: {}.{}", regex, method); } } } private void doAutowired() { if (iocMap.isEmpty()) { return; } //迴圈所有的類,對需要自動賦值的屬性進行賦值 for (Map.Entry<String, Object> entry : iocMap.entrySet()) { Field[] fields = entry.getValue().getClass().getDeclaredFields(); for (Field field : fields) { if (!field.isAnnotationPresent(Autowired.class)) { continue; } Autowired autowired = field.getAnnotation(Autowired.class); String beanName = autowired.value(); if (beanName != null) { beanName = beanName.trim(); } if (StringUtils.isBlank(beanName)) { beanName = field.getType().getName(); } field.setAccessible(true); try { field.set(entry.getValue(), iocMap.get(beanName)); } catch (IllegalAccessException e) { log.error("AutoWired fail,beanName: {}", beanName); e.printStackTrace(); continue; } } } } private void doInstance() throws Exception { if (classNames.isEmpty()) { return; } for (String className : classNames) { Class<?> clazz = Class.forName(className); //如果自定義了名字,就優先使用自己的名字,否則預設是小寫(這裡就不預設首字母為小寫了 if (clazz.isAnnotationPresent(Controller.class)) { Controller controller = clazz.getAnnotation(Controller.class); String beanName = controller.value(); if (StringUtils.isBlank(beanName)) { beanName = clazz.getName().toLowerCase(); } Object instance = clazz.newInstance(); iocMap.put(beanName, instance); } else if (clazz.isAnnotationPresent(Service.class)) { Service service = clazz.getAnnotation(Service.class); String beanName = service.value(); if (StringUtils.isBlank(beanName)) { beanName = clazz.getName().toLowerCase(); } Object instance = clazz.newInstance(); iocMap.put(beanName, instance); //根據介面型別來賦值 for (Class<?> clazzInterface : clazz.getInterfaces()) { iocMap.put(clazzInterface.getName(), instance); } } else { continue; } } } private void doScanner(String scanPackage) { URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/")); File classDir = new File(url.getFile()); for (File file : classDir.listFiles()) { if (file.isDirectory()) { doScanner(scanPackage + "." + file.getName()); } else { String className = scanPackage + "." + file.getName().replace(".class", ""); classNames.add(className); } } } private void loadConfig(String location) { InputStream inputStream = this.getClass().getResourceAsStream(location); try { contextConfig.load(inputStream); } catch (IOException e) { log.error("Load fail, location: {}", location); e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { log.error("Close fail, inputStream: {}", inputStream); e.printStackTrace(); } } } } }View Code
這個類就是最核心的類,它做了SpringMVC的事情。
7.下面是驗證自己SpringMVC是否可用的時候了,自己寫了service和controller:
7.1 service:
public class DemoServiceImpl implements IDemoService { @Override public String get(String name) { return "my name is " + name; } }View Code
7.2 controller:
@Controller @RequestMapping("/demo") @Slf4j public class DemoController { @Autowired IDemoService service; @RequestMapping("/get") public void get(HttpServletRequest req, HttpServletResponse resp, @RequestParam("name") String name) { String res = service.get(name); try { resp.setContentType("text/html;charset=UTF-8"); resp.getWriter().println(res); } catch (IOException e) { log.info(e.getMessage()); e.printStackTrace(); } } }View Code
再結合開頭貼出來的圖片,驗證了自己的這個SpringMVC是可以使用的。
四 最後
這裡只要實現了SpringMVC最簡單的功能而已。這只是一個加深自己對SpringMVC的mapping對映流程的理解而已,真正的SpringMVC當然遠不止如此簡單。
Demo的github地址:https://github.com/Happy-Ape/Spring