首頁>技術>

對於做Java軟體開發的我們,經常會用到Spring、Mybatis、Hibernate等一些開源框架,有了這些框架的誕生,平時的開發工作量也是變得越來越輕鬆。

最近又看了一下Spring MVC原始碼,接下來自己寫一個簡化版的Spring MVC框架。

框架部分實現為了區分框架部分程式碼和業務部分程式碼,我們將這兩部分分別劃分在不同的包內 com.mars.demo 和 com.mars.framework,以便隨後只掃描業務程式碼。這裡是自己手寫Spring框架,所以不會引入任何Spring專案相關的包。由於是一個Web專案,所有我們需要引入 servlet-api 包,僅供編譯器使用,所有配置 scope 為 provided。

新建一個Servlet

首先新建一個 HttpServlet 的實現類 MarsDispatcherServlet,用來接收請求。

public class MarsDispatcherServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //6. 處理請求 } @Override public void init(ServletConfig config) throws ServletException { }

配置web.xml

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "/file/2019/11/15/20191115181053_7039.jpg.html ><web-app> <display-name>Spring Mvc Education</display-name> <servlet> <servlet-name>marsmvc</servlet-name> <servlet-class>com.mars.framework.servlet.MarsDispatcherServlet</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>marsmvc</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping></web-app>
首先配置了一個 servlet, 名字是 marsmvc, 類全路徑是 com.mars.framework.servlet.MarsDispatcherServlet。設定了初始化引數名和值(這裡的值是整個專案的配置檔案)。配置 load-on-startup, 標記容器是否在啟動的時候就載入這個servlet(例項化並呼叫其init()方法)。配置 servlet-mapping, 將所有請求轉發到這個servlet處理。

配置application.properties

scanPackage=com.mars.demo

這個比較好理解,僅配置了一項內容,意思是要掃描的包,隨後我們會獲取這個值去載入容器。

定義我們常用的註解MarsAutowiredMarsControllerMarsRequestMappingMarsRequestParamMarsService

這裡僅列舉兩個,其他都大同小異,需要原始碼的可以去我的程式碼倉庫fork。

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MarsController { String value() default "";}
@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MarsRequestMapping { String value() default "";}
充實Servlet功能

先列出框架在初始化的時候都要做那些事情

載入配置檔案掃描所有相關聯的類初始化所有相關聯的類,並且將其儲存在IOC容器裡面執行依賴注入(把加了@Autowired註解的欄位賦值)構造HandlerMapping,將URL和Method進行關聯

接下來我們一步步完成上面的操作

 @Override public void init(ServletConfig config) throws ServletException { System.out.println("==================="); //1.載入配置檔案 doLoadConfig(config.getInitParameter("contextConfigLocation")); //2.掃描所有相關聯的類 doScanner(contextConfig.getProperty("scanPackage")); //3.初始化所有相關聯的類,並且將其儲存在IOC容器裡面 doInstance(); //4.執行依賴注入(把加了@Autowired註解的欄位賦值) doAutowired(); //Spring 和核心功能已經完成 IOC、DI //5.構造HandlerMapping,將URL和Method進行關聯 initHandlerMapping(); System.out.println("Mars MVC framework initialized"); }

載入配置檔案

 private Properties contextConfig = new Properties(); private void doLoadConfig(String location) { InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location); try { contextConfig.load(inputStream); } catch (IOException e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }

掃描所有相關聯的類

 private void doScanner(String basePackage) { //獲取要掃描包的url URL url = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\\\.", "/")); File dir = new File(url.getFile()); //遍歷包下面所有檔案 for(File file: dir.listFiles()) { if(file.isDirectory()){ //遞迴掃描 doScanner(basePackage + "." + file.getName()); } else { String className = basePackage + "." + file.getName().replace(".class", ""); classNames.add(className); System.out.println(className); } } }

初始化所有相關聯的類,並且將其儲存在IOC容器裡面

private void doInstance() { if(classNames.isEmpty()) return; for(String className: classNames) { try { Class<?> clazz = Class.forName(className); if(clazz.isAnnotationPresent(MarsController.class)) { Object instance = clazz.newInstance(); String beanName = lowerFirstCase(clazz.getSimpleName()); ioc.put(beanName, instance); } else if (clazz.isAnnotationPresent(MarsService.class)) { MarsService service = clazz.getAnnotation(MarsService.class); //2.優先使用自定義命名 String beanName = service.value(); if("".equals(beanName.trim())) { //1.預設使用類名首字母小寫 beanName = lowerFirstCase(clazz.getSimpleName()); } Object instance = clazz.newInstance(); ioc.put(beanName, instance); //3.自動型別匹配(例如:將實現類賦值給介面) Class<?> [] interfaces = clazz.getInterfaces(); for(Class<?> inter: interfaces) { ioc.put(inter.getName(), instance); } } } catch (Exception e) { e.printStackTrace(); } } } //利用ASCII碼的差值 private String lowerFirstCase(String str) { char[] chars = str.toCharArray(); chars[0] += 32; return String.valueOf(chars); }

執行依賴注入(把加了@Autowired註解的欄位賦值)

private void doAutowired() { if(ioc.isEmpty()) return; for(Map.Entry<String, Object> entry: ioc.entrySet()) { //注入的意思就是把所有的IOC容器中加了@Autowired註解的欄位賦值 //包含私有欄位 Field[] fields = entry.getValue().getClass().getDeclaredFields(); for(Field field : fields) { //判斷是否加了@Autowired註解 if(!field.isAnnotationPresent(MarsAutowired.class)) continue; MarsAutowired autowired = field.getAnnotation(MarsAutowired.class); String beanName = autowired.value(); if("".equals(beanName)) { beanName = field.getType().getName(); } //如果這個欄位是私有欄位的話,那麼要強制訪問 field.setAccessible(true); try { field.set(entry.getValue(), ioc.get(beanName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } }

構造HandlerMapping,將URL和Method進行關聯

private void initHandlerMapping() { if(ioc.isEmpty()) return; for(Map.Entry<String, Object> entry : ioc.entrySet()) { Class<?> clazz = entry.getValue().getClass(); if(!clazz.isAnnotationPresent(MarsController.class)) continue; String baseUrl = ""; if(clazz.isAnnotationPresent(MarsRequestMapping.class)) { MarsRequestMapping requestMapping = clazz.getAnnotation(MarsRequestMapping.class); baseUrl = requestMapping.value(); } Method[] methods = clazz.getMethods(); for(Method method : methods) { if(!method.isAnnotationPresent(MarsRequestMapping.class)) continue; MarsRequestMapping requestMapping = method.getAnnotation(MarsRequestMapping.class); String regex = requestMapping.value(); regex = (baseUrl + regex).replaceAll("/+", "/"); Pattern pattern = Pattern.compile(regex); handlerMapping.add(new Handler(entry.getValue(), method, pattern)); System.out.println("Mapping: " + regex + "," + method.getName()); } } }
編寫業務程式碼

新建一個Controller

@MarsController@MarsRequestMapping("/demo")public class DemoApi { @MarsAutowired private DemoService demoService; @MarsRequestMapping("/query") public void query(HttpServletRequest req, HttpServletResponse resp, @MarsRequestParam("name") String name) { System.out.println("name: " + name); String result = demoService.get(name); try{ resp.getWriter().write(result); } catch (IOException e) { e.printStackTrace(); } } @MarsRequestMapping("/add") public void add(HttpServletRequest req, HttpServletResponse resp, @MarsRequestParam("a") Integer a, @MarsRequestParam("b") Integer b) { try { resp.getWriter().write(String.format("%d+%d=%d", a, b, (a+b))); } catch (IOException e) { e.printStackTrace(); } }}

提供兩個介面,一個通過請求名稱返回響應的介紹內容,另一個將請求的兩個Integer相加並返回。

建立一個Service

public interface DemoService { String get(String name);}@MarsServicepublic class DemoServiceImpl implements DemoService { public String get(String name) { return String.format("My name is %s.", name); }}

新增Jetty外掛

我們的專案執行在Jetty中,所以新增相關外掛以及配置:

瀏覽器訪問: http://localhost:8080/demo/query?name=Mars

瀏覽器訪問:http://localhost:8080/demo/add?a=10&b=20

關注公眾號:JAVA九點半課堂,這裡有一批優秀的程式猿,加入我們,一起探討技術,共同進步!回覆“資料”獲取 2T 行業最新資料!

最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 90行JS程式碼構建屬於你的React