前言
Sping的生態圈已經非常大了,很多時候對Spring的理解都是在會用的階段,想要理解其設計思想卻無從下手。前些天看了某某學院的關於Spring學習的相關視頻,有幾篇講到手寫Spring源碼,感覺有些地方還是說的挺好的,讓博主對Spring的理解又多了一些,於是在業余時間也按照視頻講解實現一遍SpringIOC、DI、MVC的設計思想,加深鞏固記憶,也便於初學者理解,我們不用重復造輪子,但得知道輪子怎么造。
源碼放在文章末尾哦~
開發工具
環境:jdk8 + IDEA + maven
jar包:javax.servlet-2.5
實現步驟
視頻中這張圖畫得很好,我們按照這張圖來概括一下實現步驟
1.配置階段:即配置web.xml和application.properties,以及相關自定義注解;
2.初始化階段:初始化Ioc容器,將掃描到的類實例化交給IoC容器管理,然后注入實例的各屬性,接着將請求路徑與執行方法的映射加載到HandlerMapping中;
3.運行階段:由HandlerMapping根據請求路徑將請求分發到指定方法,返回執行結果。
附上自己的項目結構供參考
具體實現
配置階段
application.properties 配置掃描路徑,這里為了方便,所以沒有使用xml文件去解析
scanPackage=com.wqfrw
web.xml 配置Servlet和application文件的路徑
<servlet> <servlet-name>mymvc</servlet-name> <servlet-class>com.framework.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>mymvc</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
pom.xml 引入servlet.jar
<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> </dependency>
相關自定義注解
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyAutowired { String value() default ""; } @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() default ""; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyService { String value() default ""; }
初始化階段
這部分的操作才是最重要的,注釋我都詳細的寫在代碼中
MyDispatcherServlet
public class MyDispatcherServlet extends HttpServlet { //IoC容器 private Map<String, Object> ioc = new HashMap<>(); //加載配置文件對象 private Properties contextConfig = new Properties(); //掃描到的類名帶包路徑 private List<String> classNameList = new ArrayList<>(); //url與方法的映射 也就是請求分發器 private Map<String, Method> handlerMapping = new HashMap<>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { try { doDispatch(req, resp); } catch (Exception e) { e.printStackTrace(); resp.getWriter().write("500 Exception Detail:" + Arrays.toString(e.getStackTrace())); } } @Override public void init(ServletConfig config) throws ServletException { //加載配置文件 拿到需要掃描的包路徑 doLoadConfig(config.getInitParameter("contextConfigLocation")); //掃描相關的類 doScanner(contextConfig.getProperty("scanPackage")); //實例化Bean到IoC容器 doInstance(); //依賴注入(DI) doAutowired(); //初始化HandlerMapping url和對應方法的鍵值對 doInitHandlerMapping(); //初始化完成 System.out.println("MySpring framework is init!"); } /** * 功能描述: 接收到瀏覽器的請求,執行方法 * * @創建人: 我恰芙蓉王 * @創建時間: 2020年07月28日 20:54:04 * @param req * @param resp * @return: void **/ private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { String url = req.getRequestURI(); String contextPath = req.getContextPath(); url = url.replaceAll(contextPath,""); //根據請求路徑如果沒有找到對應的方法則拋出404錯誤 if (!handlerMapping.containsKey(url)) { resp.getWriter().write("404 Not Found!"); return; } Map<String,String[]> params = req.getParameterMap(); Method method = handlerMapping.get(url); String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName()); //方法調用 method.invoke(ioc.get(beanName),new Object[]{req,resp,params.get("name")[0]}); } /** * 功能描述: 初始化HandlerMapping * * @創建人: 我恰芙蓉王 * @創建時間: 2020年07月28日 20:56:09 * @param * @return: void **/ private void doInitHandlerMapping() { ioc.forEach((k, v) -> { Class<?> clazz = v.getClass(); //加了MyController注解的類才操作 if (clazz.isAnnotationPresent(MyController.class)) { String baseUrl = ""; //如果類上面加了MyRequestMapping注解,則需要拿到url進行拼接 if (clazz.isAnnotationPresent(MyRequestMapping.class)) { MyRequestMapping annotation = clazz.getAnnotation(MyRequestMapping.class); baseUrl = annotation.value(); } //獲取所有public修飾的方法 Method[] methods = clazz.getMethods(); //過濾拿到所有MyRequestMapping注解的方法,put到handlerMapping中 String finalBaseUrl = baseUrl; Stream.of(methods) .filter(m -> m.isAnnotationPresent(MyRequestMapping.class)) .forEach(m -> { MyRequestMapping annotation = m.getAnnotation(MyRequestMapping.class); String url = (finalBaseUrl + annotation.value()).replaceAll("/+", "/"); handlerMapping.put(url, m); }); } }); } /** * 功能描述: 依賴注入(DI) * * @創建人: 我恰芙蓉王 * @創建時間: 2020年07月28日 20:55:57 * @param * @return: void **/ private void doAutowired() { //循環IoC容器中所管理的對象 注入屬性 ioc.forEach((k, v) -> { //拿到bean所有的字段 包括private、public、protected、default Field[] Fields = v.getClass().getDeclaredFields(); //過濾拿到所有加了MyAutowired注解的字段並循環注入 Stream.of(Fields) .filter(f -> f.isAnnotationPresent(MyAutowired.class)) .forEach(f -> { MyAutowired annotation = f.getAnnotation(MyAutowired.class); String beanName = annotation.value().trim(); if ("".equals(beanName)) { beanName = f.getType().getName(); } //強制訪問 f.setAccessible(true); try { //賦值 f.set(v, ioc.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } }); }); } /** * 功能描述: 實例化bean至IoC容器 * * @創建人: 我恰芙蓉王 * @創建時間: 2020年07月28日 20:55:39 * @param * @return: void **/ private void doInstance() { classNameList.forEach(v -> { try { Class<?> clazz = Class.forName(v); //只初始化加了MyController注解和MyService注解的類 if (clazz.isAnnotationPresent(MyController.class)) { String beanName = toLowerFirstCase(clazz.getSimpleName()); ioc.put(beanName, clazz.newInstance()); } else if (clazz.isAnnotationPresent(MyService.class)) { // 1.默認首字母小寫 String beanName = toLowerFirstCase(clazz.getSimpleName()); // 2.自定義beanName MyService myService = clazz.getAnnotation(MyService.class); if (!"".equals(myService.value())) { beanName = myService.value(); } // 3.如果是接口 必須創建實現類的實例 for (Class<?> i : clazz.getInterfaces()) { if (ioc.containsKey(i)) { throw new Exception("This beanName is exists!"); } beanName = i.getName(); } //將實例化的對象放入IoC容器中 ioc.put(beanName, clazz.newInstance()); } } catch (Exception e) { e.printStackTrace(); } }); } /** * 功能描述: 掃描相關的類 加入到classNameList * * @創建人: 我恰芙蓉王 * @創建時間: 2020年07月28日 20:55:10 * @param scanPackage * @return: void **/ private void doScanner(String scanPackage) { //獲取根目錄 拿到com.wqfry替換成/com/wqfrw URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/")); File classFile = new File(url.getFile()); for (File file : classFile.listFiles()) { //如果file是文件夾 則遞歸調用 if (file.isDirectory()) { doScanner(scanPackage + "." + file.getName()); } else { //如果非class文件 則跳過 if (!file.getName().endsWith(".class")) { continue; } String className = (scanPackage + "." + file.getName()).replace(".class", ""); //類名+包路徑放入到類名集合中 方便后續實例化 classNameList.add(className); } } } /** * 功能描述: 加載配置文件 * * @創建人: 我恰芙蓉王 * @創建時間: 2020年07月28日 20:54:57 * @param contextConfigLocation * @return: void **/ private void doLoadConfig(String contextConfigLocation) { InputStream resource = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation); try { //加載配置文件 contextConfig.load(resource); } catch (IOException e) { e.printStackTrace(); } finally { //關閉文件流 if (resource != null) { try { resource.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 首字母轉小寫 * * @param className * @return */ private String toLowerFirstCase(String className) { char[] chars = className.toCharArray(); chars[0] += 32; return String.valueOf(chars); } }
運行階段
部分代碼在上面,這里我貼出Controller與Server的代碼
TestController
@MyController @MyRequestMapping("/test") public class TestController { @MyAutowired private ITestService testService; @MyRequestMapping("/query") public void query(HttpServletRequest req, HttpServletResponse resp, @MyRequestParam("name") String name){ String result = testService.query(name); try { resp.getWriter().write(result); } catch (IOException e) { e.printStackTrace(); } } @MyRequestMapping("/add") public void add(HttpServletRequest req, HttpServletResponse resp, @MyRequestParam("name") String name){ System.out.println("add"); } @MyRequestMapping("/remove") public void remove(HttpServletRequest req, HttpServletResponse resp, @MyRequestParam("name") String name){ System.out.println("remove"); } }
ITestService
public interface ITestService { String query(String name); }
TestServiceImpl
@MyService public class TestServiceImpl implements ITestService { @Override public String query(String name) { return "hello " + name + "!"; } }
實際調用
總結
以上只是簡略實現了Spring的核心思想,真正的Spring當然要比此復雜許多,但是學習都是由淺至深的,希望大家不僅會用工具,並且都能知道為什么要這樣用。
附上原視頻鏈接:https://live.gupaoedu.com/watch/1284875
代碼已經提交至Git : https://github.com/wqfrw/HandWritingSpringV1.0