隨著 Spring 的崛起以及其功能的完善,現(xiàn)在可能絕大部分項目的開發(fā)都是使用 Spring(全家桶) 來進行開發(fā),Spring也確實和其名字一樣,是開發(fā)者的春天,Spring 解放了程序員的雙手,而等到 SpringBoot出來之后配置文件大大減少,更是進一步解放了程序員的雙手,但是也正是因為Spring家族產(chǎn)品的強大,使得我們習慣了面向 Spring 開發(fā),那么假如有一天沒有了 Spring,是不是感覺心里一空,可能一下子連最基本的接口都不會寫了,尤其是沒有接觸過Servlet編程的朋友。因為加入沒有了 Spring 等框架,那么我們就需要利用最原生的 Servlet 來自己實現(xiàn)接口路徑的映射,對象也需要自己進行管理。
# Spring 能幫我們做什么
Spring 是為解決企業(yè)級應用開發(fā)的復雜性而設計的一款框架,Spring 的設計理念就是:簡化開發(fā)。
在 Spring 框架中,一切對象都是 bean,所以其通過面向 bean 編程(BOP),結(jié)合其核心思想依賴注入(DI)和面向切面((AOP)編程,Spring 實現(xiàn)了其偉大的簡化開發(fā)的設計理念。
# 控制反轉(zhuǎn)(IOC)
IOC 全稱為:Inversion of Control??刂品崔D(zhuǎn)的基本概念是:不用創(chuàng)建對象,但是需要描述創(chuàng)建對象的方式。
簡單的說我們本來在代碼中創(chuàng)建一個對象是通過 new 關(guān)鍵字,而使用了 Spring 之后,我們不在需要自己去 new 一個對象了,而是直接通過容器里面去取出來,再將其自動注入到我們需要的對象之中,即:依賴注入。
也就說創(chuàng)建對象的控制權(quán)不在我們程序員手上了,全部交由 Spring 進行管理,程序要只需要注入就可以了,所以才稱之為控制反轉(zhuǎn)
# 依賴注入(DI) 依賴注入(Dependency Injection,DI)就是 Spring 為了實現(xiàn)控制反轉(zhuǎn)的一種實現(xiàn)方式,所有有時候我們也將控制反轉(zhuǎn)直接稱之為依賴注入。
# 面向切面編程(AOP) AOP 全稱為:Aspect Oriented Programming。AOP是一種編程思想,其核心構(gòu)造是方面(切面),即將那些影響多個類的公共行為封裝到可重用的模塊中,而使原本的模塊內(nèi)只需關(guān)注自身的個性化行為。
AOP 編程的常用場景有:Authentication(權(quán)限認證)、Auto Caching(自動緩存處理)、Error Handling(統(tǒng)一錯誤處理)、Debugging(調(diào)試信息輸出)、Logging(日志記錄)、Transactions(事務處理)等。
# 利用 Spring 來完成 Hello World 最原生的 Spring 需要較多的配置文件,而 SpringBoot 省略了許多配置,相比較于原始的 Spring 又簡化了不少,在這里我們就以 SpringBoot 為例來完成一個簡單的接口開發(fā)。
1、新建一個 maven 項目,pom 文件中引入依賴(省略了少部分屬性):
2、新建一個 HelloController 類:org.springframework.boot spring-boot-starter-parent 2.4.0 org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-starter-web
packagecom.lonely.wolf.note.springboot.demo; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/hello") public class HelloController { @GetMapping("/demo") public String helloWorld(String name){ return "Hello:" + name; } }3、最后新建一個 SpringBoot 啟動類:
package com.lonely.wolf.note.springboot; import org.springframework.boot.SpringApplication; importorg.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication(scanBasePackages = "com.lonely.wolf.note.springboot") class MySpringBootApplication { public static void main(String[] args) { SpringApplication.run(MySpringBootApplication.class, args); } }4、現(xiàn)在就可以輸入測試路徑:http://localhost:8080/hello/demo?name=雙子孤狼 進行測試,正常輸出:Hello:雙子孤狼。 我們可以看到,利用 SpringBoot 來完成一個簡單的應用開發(fā)非常簡單,可以不需要任何配置完成一個簡單的應用,這是因為 SpringBoot 內(nèi)部已經(jīng)做好了約定(約定優(yōu)于配置思想),包括容器 Tomcat 都被默認集成,所以我們不需要任何配置文件就可以完成一個簡單的 demo 應用。 # 假如沒有了 Spring 通過上面的例子我們可以發(fā)現(xiàn),利用 Spring 來完成一個 Hello World 非常簡單,但是假如沒有了 Spring,我們又該如何完成這樣的一個 Hello World 接口呢? # 基于 Servlet 開發(fā) 在還沒有框架之前,編程式基于原始的 Servlet 進行開發(fā),下面我們就基于原生的 Servlet 來完成一個簡單的接口調(diào)用。 1、pom 文件引入依賴,需要注意的是,package 屬性要設置成 war 包,為了節(jié)省篇幅,這里沒有列出 pom 完整的信息:
2、在 src/main 下面新建文件夾 webapp/WEB-INF,然后在 WEB-INF 下面新建一個 web.xml 文件:war javax.servlet servlet-api 2.4 org.apache.commons commons-lang3 3.7 com.alibaba fastjson 1.2.72
這里面定義了 selvlet 和 servlet-mapping 兩個標簽,這兩個標簽必須一一對應,上面的標簽定義了 servlet 的位置,而下面的 servlet-mapping 文件定義了路徑的映射,這兩個標簽通過 servlet-name 標簽對應。 3、新建一個 HelloServlet 類繼承 HttpServlet:Lonely Wolf Web Application helloServlet com.lonely.wolf.mini.spring.servlet.HelloServlet helloServlet /hello/*
packagecom.lonely.wolf.mini.spring.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; importjava.io.IOException; /** * 原始Servlet接口編寫,一般需要實現(xiàn)GET和POST方法,其他方法可以視具體情況選擇性繼承 */ public class HelloServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); response.getWriter().write("Hello:" + request.getParameter("name")); } }4、執(zhí)行 maven 打包命令,確認成功打包成 war 包:
5、RUN-->Edit Configurations,然后點擊左上角的 + 號,新建一個 Tomcat Server,如果是第一次配置,默認沒有 Tomcat Server 選項,需要點擊底部的 xx more items...:
6、點擊右邊的 Deployment,然后按照下圖依次點擊,最后在彈框內(nèi)找到上面打包好的 war 包文件:
7、選中之后,需要注意的是,下面 Application Context 默認會帶上 war 包名,為了方便,我們需要把它刪掉,即不用上下文路徑,只保留一個根路徑 / (當然上下文也可以保留,但是每次請求都要帶上這一部分), 再選擇 Apply,點擊 OK,即可完成部署:
8、最后我們在瀏覽器輸入請求路徑http://localhost:8080/hello?name=雙子孤狼,即可得到返回:Hello:雙子孤狼。 上面我們就完成了一個簡單的 基于Servlet 的接口開發(fā),可以看到,配置非常麻煩,每增加一個 Servlet 都需要增加對應的配置,所以才會有許多框架的出現(xiàn)來幫我們簡化開發(fā),比如原來很流行的 Struts2 框架,當然現(xiàn)在除了一些比較老的項目,一般我們都很少使用,而更多的是選擇 Spring 框架來進行開發(fā)。 # 模仿 Spring Spring 的源碼體系非常龐大,大部分人對其源碼都敬而遠之。確實,Spring 畢竟經(jīng)過了這么多年的迭代,功能豐富,項目龐大,不是一下子就能看懂的。雖然 Spring 難以理解,但是其最核心的思想仍然是我們上面介紹的幾點,接下來就基于 Spring 最核心的部分來模擬,自己動手實現(xiàn)一個超級迷你版本的 Spring(此版本并不包含 AOP 功能)。 1、pom 依賴和上面保持不變,然后 web.xml 作如下改變,這里會攔截所有的接口 /*,然后多配置了一個參數(shù),這個參數(shù)其實也是為了更形象的模擬 Spring:
2、在 respurces 下面新建一個配置文件 application.properties,用來定義掃描的基本路徑:myDispatcherServlet com.lonely.wolf.mini.spring.v1.MyDispatcherServlet defaultConfig application.properties myDispatcherServlet /*
basePackages=com.lonely.wolf.mini.spring3、創(chuàng)建一些相關(guān)的注解類:
package com.lonely.wolf.mini.spring.annotation; import java.lang.annotation.*; @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WolfAutowired { String value() default ""; } package com.lonely.wolf.mini.spring.annotation; import java.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WolfController { String value() default ""; } package com.lonely.wolf.mini.spring.annotation; import java.lang.annotation.*; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WolfGetMapping { String value() default ""; } package com.lonely.wolf.mini.spring.annotation; importjava.lang.annotation.*; @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WolfRequestParam { String value() default ""; } packagecom.lonely.wolf.mini.spring.annotation; importjava.lang.annotation.*; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface WolfService { String value() default ""; }4、這個時候最核心的邏輯就是 MyDispatcherServlet 類了:
package com.lonely.wolf.mini.spring.v1; import com.lonely.wolf.mini.spring.annotation.*; import com.lonely.wolf.mini.spring.v1.config.MyConfig; importorg.apache.commons.lang3.StringUtils; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URL; importjava.util.*; public class MyDispatcherServlet extends HttpServlet { private MyConfig myConfig = new MyConfig(); private List5、這個 Servlet 相比較于上面的 HelloServlet 多了一個 init 方法,這個方法中主要做了以下幾件事情:classNameList = new ArrayList (); private Map iocContainerMap = new HashMap<>(); privateMap handlerMappingMap=newHashMap<>(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { this.doDispatch(request, response); } catch (Exception e) { e.printStackTrace(); } } private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception{ String requestUrl = this.formatUrl(request.getRequestURI()); HandlerMapping handlerMapping = handlerMappingMap.get(requestUrl); if (null == handlerMapping){ response.getWriter().write("404 Not Found"); return; } //獲取方法中的參數(shù)類型 Class>[] paramTypeArr = handlerMapping.getMethod().getParameterTypes(); Object[]paramArr=newObject[paramTypeArr.length]; for (int i=0;i clazz = paramTypeArr[i]; //參數(shù)只考慮三種類型,其他不考慮 if (clazz == HttpServletRequest.class){ paramArr[i] = request; }else if (clazz == HttpServletResponse.class){ paramArr[i] = response; } else if (clazz == String.class){ Map methodParam = handlerMapping.getMethodParams(); paramArr[i] = request.getParameter(methodParam.get(i)); }else{ System.out.println("暫不支持的參數(shù)類型"); } } //反射調(diào)用controller方法 handlerMapping.getMethod().invoke(handlerMapping.getTarget(), paramArr); } private String formatUrl(String requestUrl) { requestUrl = requestUrl.replaceAll("/+","/"); if (requestUrl.lastIndexOf("/") == requestUrl.length() -1){ requestUrl = requestUrl.substring(0,requestUrl.length() -1); } return requestUrl; } @Override public void init(ServletConfig config) throws ServletException { //1.加載配置文件 try { doLoadConfig(config.getInitParameter("defaultConfig")); } catch (Exception e) { System.out.println("加載配置文件失敗"); return; } //2.根據(jù)獲取到的掃描路徑進行掃描 doScanPacakge(myConfig.getBasePackages()); //3.將掃描到的類進行初始化,并存放到IOC容器 doInitializedClass(); //4.依賴注入 doDependencyInjection() System.out.println("DispatchServlet Init End..." ); } private void doDependencyInjection() { if (iocContainerMap.size() == 0){ return; } //循環(huán)IOC容器中的類 Iterator >iterator=iocContainerMap.entrySet().iterator(); while (iterator.hasNext()){ Map.Entry entry = iterator.next(); Class> clazz = entry.getValue().getClass(); Field[]fields=clazz.getDeclaredFields(); //屬性注入 for (Field field : fields){ //如果屬性有WolfAutowired注解則注入值(暫時不考慮其他注解) if (field.isAnnotationPresent(WolfAutowired.class)){ String value = toLowerFirstLetterCase(field.getType().getSimpleName());//默認bean的value為類名首字母小寫 if (field.getType().isAnnotationPresent(WolfService.class)){ WolfService wolfService = field.getType().getAnnotation(WolfService.class); value = wolfService.value(); } field.setAccessible(true); try { Object target = iocContainerMap.get(beanName); if (null == target){ System.out.println(clazz.getName() + "required bean:" + beanName + ",but we not found it"); } field.set(entry.getValue(),iocContainerMap.get(beanName));//初始化對象,后面注入 } catch (IllegalAccessException e) { e.printStackTrace(); } } } //初始化HanderMapping String requestUrl = ""; //獲取Controller類上的請求路徑 if (clazz.isAnnotationPresent(WolfController.class)){ requestUrl = clazz.getAnnotation(WolfController.class).value(); } //循環(huán)類中的方法,獲取方法上的路徑 Method[] methods = clazz.getMethods(); for (Method method : methods){ //假設只有WolfGetMapping這一種注解 if(!method.isAnnotationPresent(WolfGetMapping.class)){ continue; } WolfGetMapping wolfGetMapping = method.getDeclaredAnnotation(WolfGetMapping.class); requestUrl=requestUrl+"/"+wolfGetMapping.value();//拼成完成的請求路徑 //不考慮正則匹配路徑/xx/* 的情況,只考慮完全匹配的情況 if (handlerMappingMap.containsKey(requestUrl)){ System.out.println("重復路徑"); continue; } Annotation[][]annotationArr=method.getParameterAnnotations();//獲取方法中參數(shù)的注解 Map methodParam = new HashMap<>();//存儲參數(shù)的順序和參數(shù)名 retryParam: for (int i=0;i { try { Field field = myConfig.getClass().getDeclaredField((String)k); field.setAccessible(true); field.set(myConfig,v); } catch (Exception e) { e.printStackTrace(); System.out.println("初始化配置類失敗"); return; } }); } }
初始化配置文件,拿到配置文件中配置的參數(shù)信息(對應方法:doLoadConfig)。
拿到第 1 步加載出來的配置文件,獲取到需要掃描的包路徑,然后將包路徑進行轉(zhuǎn)換成實際的磁盤路徑,并開始遍歷磁盤路徑下的所有 class 文件,最終經(jīng)過轉(zhuǎn)換之后得到掃描路徑下的所有類的全限定類型,存儲到全局變量 classNameList 中(對應方法:doScanPacakge)。
根據(jù)第 2 步中得到的全局變量 classNameList 中的類通過反射進行初始化(需要注意的是只會初始化加了指定注解的類)并將得到的對應關(guān)系存儲到全局變量 iocContainerMap 中(即傳說中的 IOC 容器),其中 key 值為注解中的 value 屬性,如 value 屬性為空,則默認取首字母小寫的類名作為 key 值進行存儲(對應方法:doInitializedClass)。
這一步比較關(guān)鍵,需要對 IOC 容器中的所有類的屬性進行賦值并且需要對 Controller 中的請求路徑進行映射存儲,為了確保最后能順利調(diào)用 Controller 中的方法,還需要將方法的參數(shù)進行存儲 。對屬性進行映射時只會對加了注解的屬性進行映射,映射時會從 IOC 容器中取出第 3 步中已經(jīng)初始化的實例對象進行賦值,最后將請求路徑和 Controller 中方法的映射關(guān)系存入變量 handlerMappingMap,key 值為請求路徑,value 為方法的相關(guān)信息 (對應方法:doDependencyInjection)。
6、存儲請求路徑和方法的映射關(guān)系時,需要用到 HandlerMapping 類來進行存儲:
packagecom.lonely.wolf.mini.spring.v1; import java.lang.reflect.Method; importjava.util.Map; //省略了getter/setter方法 public class HandlerMapping { private String requestUrl; private Object target;//保存方法對應的實例 private Method method;//保存映射的方法 private Map7、初始化完成之后,因為攔截了 /* ,所以調(diào)用任意接口都會進入 MyDispatcherServlet ,而且最終都會執(zhí)行方法 doDispatch,執(zhí)行這個方法時會拿到請求的路徑,然后和全局變量 handlerMappingMap 進行匹配,匹配不上則返回 404,匹配的上則取出必要的參數(shù)進行賦值,最后通過反射調(diào)用到 Controller 中的相關(guān)方法。 8、新建一個 HelloController 和 HelloService 來進行測試:methodParams;//記錄方法參數(shù) }
packagecom.lonely.wolf.mini.spring.controller; import com.lonely.wolf.mini.spring.annotation.WolfAutowired; import com.lonely.wolf.mini.spring.annotation.WolfController; import com.lonely.wolf.mini.spring.annotation.WolfGetMapping; import com.lonely.wolf.mini.spring.annotation.WolfRequestParam; importcom.lonely.wolf.mini.spring.service.HelloService; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @WolfController public class HelloController { @WolfAutowired privateHelloServicehelloService; @WolfGetMapping("/hello") public void query(HttpServletRequest request,HttpServletResponse response, @WolfRequestParam("name") String name) throws IOException { response.setContentType("text/html;charset=utf-8"); response.getWriter().write("Hello:" + name); } } packagecom.lonely.wolf.mini.spring.service; importcom.lonely.wolf.mini.spring.annotation.WolfService; @WolfService(value = "hello_service")//為了演示能否正常取value屬性 public class HelloService { }9、輸入測試路徑:http://localhost:8080////hello?name=雙子孤狼, 進行測試發(fā)現(xiàn)可以正常輸出:Hello:雙子孤狼。 上面這個例子只是一個簡單的演示,通過這個例子只是希望在沒有任何框架的情況下,我們也能知道如何完成一個簡單的應用開發(fā)。例子中很多細節(jié)都沒有進行處理,僅僅只是為了體驗一下 Spring 的核心思想,并了解 Spring 到底幫助我們做了什么,實際上 Spring 能幫我們做的事情遠比這個例子中多得多,Spring 體系龐大,設計優(yōu)雅,經(jīng)過了多年的迭代優(yōu)化,是一款非常值得研究的框架。 # 總結(jié) 本文從介紹 Spring 核心功能開始入手,從如何利用 Spring 完成一個應用開發(fā),講述到假如沒有 Spring 我們該如何基于 Servlet 進行開發(fā),最后再通過一個簡單的例子體驗了 Spring 的核心思想。
-
編程
+關(guān)注
關(guān)注
88文章
3689瀏覽量
95287 -
spring
+關(guān)注
關(guān)注
0文章
340瀏覽量
15096 -
Servlet
+關(guān)注
關(guān)注
0文章
18瀏覽量
8070
原文標題:不用 Spring 居然連最基本的接口都不會寫了!
文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
servlet2.3規(guī)范中文版
Servlet入門----創(chuàng)建第一個自己的Servlet小程序
如何修改Servlet容器的相關(guān)配置呢
如何定制和修改Servlet容器的相關(guān)配置
內(nèi)嵌的Servlet配置如何修改
如何配置嵌入式的Servlet容器?
SpringBoot配置嵌入式Servlet
如何對嵌入式Servlet容器進行配置呢
嵌入式Servlet容器啟動原理
如何配置嵌入式Servlet容器
JSP與Servlet技術(shù)
深入研究Servlet線程安全性問題

如何使用Java ME和Servlet進行移動成績查詢系統(tǒng)的設計實現(xiàn)

嵌入式Servlet容器

評論