整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Spring面試題

          Spring面試題

          pring Bean實例化流程

          在 Spring 框架中,Bean 的實例化流程通常包括以下步驟:

          1. 加載配置文件:Spring 容器會加載配置文件,包括 XML 配置文件、注解配置等,用于定義 Bean 的信息。
          2. 解析配置文件:Spring 容器會解析配置文件,識別出配置中定義的 Bean,以及它們之間的依賴關系。
          3. 實例化 Bean:Spring 容器根據配置文件中的信息,實例化每個 Bean。這通常是通過調用 Bean 的構造方法來創建 Bean 對象的過程。
          4. 設置 Bean 的屬性:一旦 Bean 對象被實例化,Spring 容器會根據配置文件中的屬性設置,通過調用相應的 setter 方法來設置 Bean 的屬性。
          5. Bean 的初始化:如果配置了初始化方法(例如通過 init-method 屬性),Spring 容器會在 Bean 的所有屬性設置完成后調用初始化方法。
          6. Bean 的使用:一旦所有 Bean 實例化、屬性設置和初始化都完成,Bean 就可以被應用程序使用了。
          7. Bean 的銷毀:如果配置了銷毀方法(例如通過 destroy-method 屬性),Spring 容器在應用程序關閉時會調用 Bean 的銷毀方法進行清理工作。

          這是 Spring Bean 的一般實例化流程,具體的實現細節可能會根據不同的配置方式和 Bean 類型而有所不同。希望這個回答能夠幫助你理解 Spring Bean 的實例化過程。

          Bean生命周期

          在 Spring 框架中,Bean 的生命周期可以分為以下階段:

          1. 實例化階段
          2. Spring 容器根據配置信息實例化 Bean 對象,通常是通過調用 Bean 的構造方法來創建實例。
          3. 屬性設置階段
          4. Spring 容器會注入 Bean 的屬性,可以通過構造函數注入、Setter 方法注入或字段注入等方式來設置 Bean 的屬性。
          5. 初始化階段
          6. 如果 Bean 實現了 InitializingBean 接口,或者在配置文件中通過 init-method 指定了初始化方法,Spring 容器會在 Bean 的所有屬性設置完成后調用初始化方法進行一些初始化操作。
          7. Bean 可用階段
          8. 在初始化完成后,Bean 就可以被應用程序使用了,可以執行各種業務邏輯。
          9. 銷毀階段
          10. 當應用程序關閉時,Spring 容器會調用 Bean 的銷毀方法,可以通過實現 DisposableBean 接口或在配置文件中通過 destroy-method 指定銷毀方法來進行清理操作。

          在整個生命周期中,Spring 容器負責管理 Bean 的創建、初始化和銷毀等過程,開發人員可以通過配置文件或注解來定義 Bean 的生命周期行為,以滿足不同的需求。

          總的來說,Spring Bean 的生命周期可以概括為實例化、屬性設置、初始化、可用和銷毀等階段。希望這個回答對你有幫助。


          Bean的完整生命周期經歷了各種方法調用,這些方法可以劃分為以下幾類

          • Bean自身的方法: 這個包括了Bean本身調用的方法和通過配置文件中<bean>的init-method和destroy-method指定的方法
          • Bean級生命周期接口方法: 這個包括了BeanNameAware、BeanFactoryAware、ApplicationContextAware;當然也包括InitializingBean和DiposableBean這些接口的方法(可以被@PostConstruct和@PreDestroy注解替代)
          • 容器級生命周期接口方法: 這個包括了InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 這兩個接口實現,一般稱它們的實現類為“后處理器”。
          • 工廠后處理器接口方法: 這個包括了AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer等等非常有用的工廠后處理器接口的方法。工廠后處理器也是容器級的。在應用上下文裝配配置文件之后立即調用。

          具體而言,流程如下

          • 如果 BeanFactoryPostProcessor 和 Bean 關聯, 則調用postProcessBeanFactory方法.(即首先嘗試從Bean工廠中獲取Bean)
          • 如果 InstantiationAwareBeanPostProcessor 和 Bean 關聯,則調用postProcessBeforeInstantiation方法
          • 根據配置情況調用 Bean 構造方法實例化 Bean
          • 利用依賴注入完成 Bean 中所有屬性值的配置注入
          • 如果 InstantiationAwareBeanPostProcessor 和 Bean 關聯,則調用postProcessAfterInstantiation方法和postProcessProperties
          • 調用xxxAware接口 (上圖只是給了幾個例子)
          • 第一類Aware接口
            • 如果 Bean 實現了 BeanNameAware 接口,則 Spring 調用 Bean 的 setBeanName() 方法傳入當前 Bean 的 id 值。
            • 如果 Bean 實現了 BeanClassLoaderAware 接口,則 Spring 調用 setBeanClassLoader() 方法傳入classLoader的引用。
            • 如果 Bean 實現了 BeanFactoryAware 接口,則 Spring 調用 setBeanFactory() 方法傳入當前工廠實例的引用。
          • 第二類Aware接口
          • 如果 Bean 實現了 EnvironmentAware 接口,則 Spring 調用 setEnvironment() 方法傳入當前 Environment 實例的引用。
          • 如果 Bean 實現了 EmbeddedValueResolverAware 接口,則 Spring 調用 setEmbeddedValueResolver() 方法傳入當前 StringValueResolver 實例的引用。
          • 如果 Bean 實現了 ApplicationContextAware 接口,則 Spring 調用 setApplicationContext() 方法傳入當前 ApplicationContext 實例的引用。
          • ...
          • 如果 BeanPostProcessor 和 Bean 關聯,則 Spring 將調用該接口的預初始化方法 postProcessBeforeInitialzation() 對 Bean 進行加工操作,此處非常重要,Spring 的 AOP 就是利用它實現的。
          • 如果 Bean 實現了 InitializingBean 接口,則 Spring 將調用 afterPropertiesSet() 方法。(或者有執行@PostConstruct注解的方法)
          • 如果在配置文件中通過 init-method 屬性指定了初始化方法,則調用該初始化方法。
          • 如果 BeanPostProcessor 和 Bean 關聯,則 Spring 將調用該接口的初始化方法 postProcessAfterInitialization()。此時,Bean 已經可以被應用系統使用了。
          • 如果在 <bean> 中指定了該 Bean 的作用范圍為 scope="singleton",則將該 Bean 放入 Spring IoC 的緩存池中,將觸發 Spring 對該 Bean 的生命周期管理;如果在 <bean> 中指定了該 Bean 的作用范圍為 scope="prototype",則將該 Bean 交給調用者,調用者管理該 Bean 的生命周期,Spring 不再管理該 Bean。
          • 如果 Bean 實現了 DisposableBean 接口,則 Spring 會調用 destory() 方法將 Spring 中的 Bean 銷毀;(或者有執行@PreDestroy注解的方法)
          • 如果在配置文件中通過 destory-method 屬性指定了 Bean 的銷毀方法,則 Spring 將調用該方法對 Bean 進行銷毀。


          Spring啟動流程

          https://blog.csdn.net/dreambyday/article/details/125473596

          Spring 框架在啟動過程中會執行一系列步驟來初始化應用程序上下文并準備好處理請求。以下是 Spring 啟動的主要流程:

          1. 加載配置文件:Spring 應用程序啟動時會加載配置文件,包括 XML 配置文件、注解配置類等。
          2. 創建應用程序上下文:Spring 容器在啟動時會創建應用程序上下文,該上下文包含了應用程序中所有 Bean 的定義、依賴關系和配置信息。
          3. 掃描組件:Spring 容器會掃描應用程序中的組件,包括注解標記的 Bean、配置類等,并將它們注冊到應用程序上下文中。
          4. 實例化 Bean:Spring 容器根據配置信息實例化 Bean 對象,可以通過構造函數注入、Setter 方法注入或字段注入等方式來設置 Bean 的屬性。
          5. 依賴注入:Spring 容器會自動解析 Bean 之間的依賴關系,并將依賴的 Bean 注入到目標 Bean 中。
          6. 執行 Bean 的生命周期回調:如果 Bean 實現了特定的接口(如 InitializingBean 和 DisposableBean),Spring 容器會在適當的時機調用 Bean 的初始化方法和銷毀方法。
          7. 執行自定義初始化方法:如果在配置文件中通過 @PostConstruct 注解或 init-method 指定了初始化方法,Spring 容器會在實例化和依賴注入完成后調用這些方法。
          8. 應用程序準備就緒:當所有 Bean 實例化、依賴注入和初始化完成后,應用程序就處于可用狀態,可以處理請求和執行業務邏輯。
          9. 處理請求:Spring 應用程序啟動后,可以接收和處理來自客戶端的請求,調用相應的 Bean 處理請求并返回結果。

          總的來說,Spring 啟動流程包括加載配置文件、創建應用程序上下文、注冊 Bean、依賴注入、執行生命周期回調等步驟,最終使應用程序準備好接收和處理請求。

          AOP

          核心概念

          1. Jointpoint(連接點):具體的切面點點抽象概念,可以是在字段、方法上,Spring中具體表現形式是PointCut(切入點),僅作用在方法上。
          2. Advice(通知): 在連接點進行的具體操作,如何進行增強處理的,分為前置、后置、異常、最終、環繞五種情況。
          3. 目標對象:被AOP框架進行增強處理的對象,也被稱為被增強的對象。
          4. AOP代理:AOP框架創建的對象,簡單的說,代理就是對目標對象的加強。Spring中的AOP代理可以是JDK動態代理,也可以是CGLIB代理。
          5. Weaving(織入):將增強處理添加到目標對象中,創建一個被增強的對象的過程

          總結為一句話就是:在目標對象(target object)的某些方法(jointpoint)添加不同種類的操作(通知、增強操處理),最后通過某些方法(weaving、織入操作)實現一個新的代理目標對象。


          原理

          Spring AOP是基于動態代理的,如果要代理的對象實現了某個接口,那么Spring AOP就會使用JDK動態代理去創建代理對象;而對于沒有實現接口的對象,就無法使用JDK動態代理,轉而使用CGlib動態代理生成一個被代理對象的子類來作為代理。

          當然也可以使用AspectJ,Spring AOP中已經集成了AspectJ,AspectJ應該算得上是Java生態系統中最完整的AOP框架了。使用AOP之后我們可以把一些通用功能抽象出來,在需要用到的地方直接使用即可,這樣可以大大簡化代碼量。我們需要增加新功能也方便,提高了系統的擴展性。日志功能、事務管理和權限管理等場景都用到了AOP。


          實現 AOP 的技術,主要分為兩大類:

          • 靜態代理指使用 AOP 框架提供的命令進行編譯,從而在編譯階段就可生成 AOP 代理類,因此也稱為編譯時增強;
          • 編譯時編織(特殊編譯器實現)
          • 類加載時編織(特殊的類加載器實現)。
          • 動態代理在運行時在內存中“臨時”生成 AOP 動態代理類,因此也被稱為運行時增強。
          • JDK 動態代理
            • JDK Proxy 是 Java 語言自帶的功能,無需通過加載第三方類實現;
            • Java 對 JDK Proxy 提供了穩定的支持,并且會持續的升級和更新,Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
            • JDK Proxy 是通過攔截器加反射的方式實現的;
            • JDK Proxy 只能代理實現接口的類;
            • JDK Proxy 實現和調用起來比較簡單;
          • CGLIB
          • CGLib 是第三方提供的工具,基于 ASM 實現的,性能比較高;
          • CGLib 無需通過接口來實現,它是針對類實現代理,主要是對指定的類生成一個子類,它是通過實現子類的方式來完成調用的。


          使用場景

          1. 日志打印
          2. 共同業務處理
          3. 監控
          4. 事務


          Spring中的bean的作用域有哪些?

          1. singleton:唯一bean實例,Spring中的bean默認都是單例的。
          2. prototype:每次請求都會創建一個新的bean實例。
          3. request:每一次HTTP請求都會產生一個新的bean,該bean僅在當前HTTP request內有效。
          4. session:每一次HTTP請求都會產生一個新的bean,該bean僅在當前HTTP session內有效。
          5. global-session:全局session作用域,僅僅在基于Portlet的Web應用中才有意義,Spring5中已經沒有了。Portlet是能夠生成語義代碼(例如HTML)片段的小型Java Web插件。它們基于Portlet容器,可以像Servlet一樣處理HTTP請求。但是與Servlet不同,每個Portlet都有不同的會話。


          Spring mvc工作原理

          流程說明:

          1.客戶端(瀏覽器)發送請求,直接請求到DispatcherServlet。

          2.DispatcherServlet根據請求信息調用HandlerMapping,解析請求對應的Handler。

          3.解析到對應的Handler(也就是我們平常說的Controller控制器)。

          4.HandlerAdapter會根據Handler來調用真正的處理器來處理請求和執行相對應的業務邏輯。

          5.處理器處理完業務后,會返回一個ModelAndView對象,Model是返回的數據對象,View是邏輯上的View。

          6.ViewResolver會根據邏輯View去查找實際的View。

          7.DispatcherServlet把返回的Model傳給View(視圖渲染)。

          8.把View返回給請求者(瀏覽器)。


          如何自定義Spring Boot Starter?

          • 實現功能
          • 添加Properties
          @Data@ConfigurationProperties(prefix="com.pdai")
          public class DemoProperties {
              private String version;
              private String name;
          }
          • 添加AutoConfiguration
          @Configuration@EnableConfigurationProperties(DemoProperties.class)
          public class DemoAutoConfiguration {
              @Bean
              public com.pdai.demo.module.DemoModule demoModule(DemoProperties properties){
                  com.pdai.demo.module.DemoModule demoModule=new com.pdai.demo.module.DemoModule();
                  demoModule.setName(properties.getName());
                  demoModule.setVersion(properties.getVersion());return demoModule;
              }
          }
          • 添加spring.factory

          在META-INF下創建spring.factory文件

          org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
          com.pdai.demospringbootstarter.DemoAutoConfiguration
          • install

          了應對在SpringBoot中的高并發及優化訪問速度,我們一般會把頁面上的數據查詢出來,然后放到redis中進行緩存。減少數據庫的壓力。

          在SpringBoot中一般使用

          thymeleafViewResolver.getTemplateEngine().process("goodlist", ctx);

          進行頁面的渲染,而這個ctx就是SpringWebContext對象,我們一般進行如下獲取:

          SpringWebContext swc=new SpringWebContext(request,response,request.getServletContext(),request.getLocale(),model.asMap(),applicationContext);

          在SpringBoot 1.X的版本中以上代碼可以使用。但在SpringBoot 2.0中,就無法找到SpringWebContext了。那應該如何去解決這個問題呢?

          說一下我的思路,.process方法中ctx所在參數所需要的類型為接口IContext



          image

          也就是需要有實現了IContext的類就可以了,然后進入IContext接口找所有的實現類



          image

          然后看到WebContext似乎有些像上面所用的SpringWebContext。即做出如下改變,完美實現了thymeleaf的頁面渲染。

          WebContext ctx=new WebContext(request, response, request.getServletContext(), request.getLocale(), model.asMap());

          html=thymeleafViewResolver.getTemplateEngine().process("goodlist", ctx);

          在SpringBoot 2.0中使用上述代碼,可以完全替代。

          (當然在下不才,暫時只找到了這種辦法,在網絡上也沒找到對應的比較不錯的策略。所以分享出來,以備分享出來,幫助遇到此問題的程序員們。如果大家有什么更好的處理辦法可以一起互相交流哦)

          目前我正在搞基于SpringBoot、Redis、消息隊列的秒殺小項目,主要還是為了梳理如何解決高并發的問題過程。

          GitHub:https://github.com/iquanzhan/SecKillShop

          歡迎點擊Start哦

          所用技術

          1.后端:SpringBoot、JSR303、MyBatis

          2.前端:Thymeleaf、BootStrap、Jquery

          3.中間件:RabbitMQ、Redis、Druid

          Spring框架對于Java后端程序員來說再熟悉不過了,以前只知道它用的反射實現的,但了解之后才知道有很多巧妙的設計在里面。如果不看Spring的源碼,你將會失去一次和大師學習的機會:它的代碼規范,設計思想很值得學習。我們程序員大部分人都是野路子,不懂什么叫代碼規范。寫了一個月的代碼,最后還得其他老司機花3天時間重構,相信大部分老司機都很頭疼看新手的代碼。

          廢話不多說,我們進入今天的正題,在Web應用程序設計中,MVC模式已經被廣泛使用。SpringMVC以DispatcherServlet為核心,負責協調和組織不同組件以完成請求處理并返回響應的工作,實現了MVC模式。想要實現自己的SpringMVC框架,需要從以下幾點入手:

          一、了解SpringMVC運行流程及九大組件

          二、梳理自己的SpringMVC的設計思路

          三、實現自己的SpringMVC框架

          一、了解SpringMVC運行流程及九大組件

          1、SpringMVC的運行流程

          ⑴ 用戶發送請求至前端控制器DispatcherServlet

          ⑵ DispatcherServlet收到請求調用HandlerMapping處理器映射器。

          ⑶ 處理器映射器根據請求url找到具體的處理器,生成處理器對象及處理器攔截器(如果有則生成)一并返回給DispatcherServlet。

          ⑷ DispatcherServlet通過HandlerAdapter處理器適配器調用處理器

          ⑸ 執行處理器(Controller,也叫后端控制器)。

          ⑹ Controller執行完成返回ModelAndView

          ⑺ HandlerAdapter將controller執行結果ModelAndView返回給DispatcherServlet

          ⑻ DispatcherServlet將ModelAndView傳給ViewReslover視圖解析器

          ⑼ ViewReslover解析后返回具體View

          ⑽ DispatcherServlet對View進行渲染視圖(即將模型數據填充至視圖中)。

          ⑾ DispatcherServlet響應用戶。

          從上面可以看出,DispatcherServlet有接收請求,響應結果,轉發等作用。有了DispatcherServlet之后,可以減少組件之間的耦合度。

          2、SpringMVC的九大組件(ref:【SpringMVC】9大組件概覽)

          protected void initStrategies(ApplicationContext context) {	//用于處理上傳請求。處理方法是將普通的request包裝成MultipartHttpServletRequest,后者可以直接調用getFile方法獲取File.
          initMultipartResolver(context);	//SpringMVC主要有兩個地方用到了Locale:一是ViewResolver視圖解析的時候;二是用到國際化資源或者主題的時候。
          initLocaleResolver(context); 
          //用于解析主題。SpringMVC中一個主題對應一個properties文件,里面存放著跟當前主題相關的所有資源、
          //如圖片、css樣式等。SpringMVC的主題也支持國際化, 
          initThemeResolver(context);	//用來查找Handler的。
          initHandlerMappings(context);	//從名字上看,它就是一個適配器。Servlet需要的處理方法的結構卻是固定的,都是以request和response為參數的方法。
          //如何讓固定的Servlet處理方法調用靈活的Handler來進行處理呢?這就是HandlerAdapter要做的事情
          initHandlerAdapters(context);	//其它組件都是用來干活的。在干活的過程中難免會出現問題,出問題后怎么辦呢?
          //這就需要有一個專門的角色對異常情況進行處理,在SpringMVC中就是HandlerExceptionResolver。
          initHandlerExceptionResolvers(context);	//有的Handler處理完后并沒有設置View也沒有設置ViewName,這時就需要從request獲取ViewName了,
          //如何從request中獲取ViewName就是RequestToViewNameTranslator要做的事情了。
          initRequestToViewNameTranslator(context);	//ViewResolver用來將String類型的視圖名和Locale解析為View類型的視圖。
          //View是用來渲染頁面的,也就是將程序返回的參數填入模板里,生成html(也可能是其它類型)文件。
          initViewResolvers(context);	//用來管理FlashMap的,FlashMap主要用在redirect重定向中傳遞參數。
          initFlashMapManager(context); 
          }
          

          二、梳理SpringMVC的設計思路

          本文只實現自己的@Controller、@RequestMapping、@RequestParam注解起作用,其余SpringMVC功能讀者可以嘗試自己實現。

          1、讀取配置

          從圖中可以看出,SpringMVC本質上是一個Servlet,這個 Servlet 繼承自 HttpServlet。FrameworkServlet負責初始化SpringMVC的容器,并將Spring容器設置為父容器。因為本文只是實現SpringMVC,對于Spring容器不做過多講解(有興趣同學可以看看我另一篇文章:向spring大佬低頭--大量源碼流出解析)。

          為了讀取web.xml中的配置,我們用到ServletConfig這個類,它代表當前Servlet在web.xml中的配置信息。通過web.xml中加載我們自己寫的MyDispatcherServlet和讀取配置文件。

          2、初始化階段

          在前面我們提到DispatcherServlet的initStrategies方法會初始化9大組件,但是這里將實現一些SpringMVC的最基本的組件而不是全部,按順序包括:

          • 加載配置文件
          • 掃描用戶配置包下面所有的類
          • 拿到掃描到的類,通過反射機制,實例化。并且放到ioc容器中(Map的鍵值對 beanName-bean) beanName默認是首字母小寫
          • 初始化HandlerMapping,這里其實就是把url和method對應起來放在一個k-v的Map中,在運行階段取出

          3、運行階段

          每一次請求將會調用doGet或doPost方法,所以統一運行階段都放在doDispatch方法里處理,它會根據url請求去HandlerMapping中匹配到對應的Method,然后利用反射機制調用Controller中的url對應的方法,并得到結果返回。按順序包括以下功能:

          • 異常的攔截
          • 獲取請求傳入的參數并處理參數
          • 通過初始化好的handlerMapping中拿出url對應的方法名,反射調用

          三、實現自己的SpringMVC框架

          工程文件及目錄:

          首先,新建一個maven項目,在pom.xml中導入以下依賴:

          <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
           <modelVersion>4.0.0</modelVersion>
           <groupId>com.liugh</groupId>
           <artifactId>liughMVC</artifactId>
           <version>0.0.1-SNAPSHOT</version>
           <packaging>war</packaging>
           
          <properties>
          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
          <maven.compiler.source>1.8</maven.compiler.source>
          <maven.compiler.target>1.8</maven.compiler.target>
          <java.version>1.8</java.version>
          </properties>
          <dependencies>
           <dependency>
           	 <groupId>javax.servlet</groupId> 
           <artifactId>javax.servlet-api</artifactId> 
           <version>3.0.1</version> 
           <scope>provided</scope>
          </dependency>
           </dependencies></project>
          

          接著,我們在WEB-INF下創建一個web.xml,如下配置:

          <?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
          version="3.0">
          <servlet>
          <servlet-name>MySpringMVC</servlet-name>
          <servlet-class>com.liugh.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>MySpringMVC</servlet-name>
          <url-pattern>/*</url-pattern>
          </servlet-mapping></web-app>
          

          application.properties文件中只是配置要掃描的包到SpringMVC容器中。

          scanPackage=com.liugh.core
          

          創建自己的Controller注解,它只能標注在類上面:

          package com.liugh.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyController { /**
           * 表示給controller注冊別名
           * @return
           */
           String value() default "";
          }
          

          RequestMapping注解,可以在類和方法上:

          package com.liugh.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyRequestMapping { /**
           * 表示訪問該方法的url
           * @return
           */
           String value() default "";
          }
          

          RequestParam注解,只能注解在參數上

          package com.liugh.annotation;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.PARAMETER)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MyRequestParam { /**
           * 表示參數的別名,必填
           * @return
           */
           String value();
          }
          

          然后創建MyDispatcherServlet這個類,去繼承HttpServlet,重寫init方法、doGet、doPost方法,以及加上我們第二步分析時要實現的功能:

          package com.liugh.servlet;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Method;import java.net.URL;import java.util.ArrayList;import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.Map.Entry;import java.util.Properties;import javax.servlet.ServletConfig;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.liugh.annotation.MyController;import com.liugh.annotation.MyRequestMapping;public class MyDispatcherServlet extends HttpServlet{ 
          private Properties properties=new Properties();	
          private List<String> classNames=new ArrayList<>();	
          private Map<String, Object> ioc=new HashMap<>();	
          private Map<String, Method> handlerMapping=new HashMap<>();	
          private Map<String, Object> controllerMap=new HashMap<>();	
          @Override
          public void init(ServletConfig config) throws ServletException {	
          //1.加載配置文件
          doLoadConfig(config.getInitParameter("contextConfigLocation"));	
          //2.初始化所有相關聯的類,掃描用戶設定的包下面所有的類
          doScanner(properties.getProperty("scanPackage"));	
          //3.拿到掃描到的類,通過反射機制,實例化,并且放到ioc容器中(k-v beanName-bean) beanName默認是首字母小寫
          doInstance();	
          //4.初始化HandlerMapping(將url和method對應上)
          initHandlerMapping();
          }	
          @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 { try {	//處理請求
          doDispatch(req,resp);
          } catch (Exception e) {
          resp.getWriter().write("500!! Server Exception");
          }
          }	
          private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception { if(handlerMapping.isEmpty()){	return;
          }
          String url=req.getRequestURI();
          String contextPath=req.getContextPath();
          url=url.replace(contextPath, "").replaceAll("/+", "/");	
          if(!this.handlerMapping.containsKey(url)){
          resp.getWriter().write("404 NOT FOUND!");	return;
          }
          Method method=this.handlerMapping.get(url);	
          //獲取方法的參數列表
          Class<?>[] parameterTypes=method.getParameterTypes();	
          //獲取請求的參數
          Map<String, String[]> parameterMap=req.getParameterMap();	
          //保存參數值
          Object [] paramValues=new Object[parameterTypes.length];	
          //方法的參數列表
           for (int i=0; i<parameterTypes.length; i++){ 
           //根據參數名稱,做某些處理 
           String requestParam=parameterTypes[i].getSimpleName(); 
           
           
           if (requestParam.equals("HttpServletRequest")){ 
           //參數類型已明確,這邊強轉類型 
           	paramValues[i]=req; continue; 
           } 
           if (requestParam.equals("HttpServletResponse")){ 
           	paramValues[i]=resp; continue; 
           } if(requestParam.equals("String")){ for (Entry<String, String[]> param : parameterMap.entrySet()) {
           	String value=Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
           	paramValues[i]=value;
           	}
           }
           } 
          //利用反射機制來調用
          try {
          method.invoke(this.controllerMap.get(url), paramValues);//第一個參數是method所對應的實例 在ioc容器中
          } catch (Exception e) {
          e.printStackTrace();
          }
          }	private void doLoadConfig(String location){	//把web.xml中的contextConfigLocation對應value值的文件加載到流里面
          InputStream resourceAsStream=this.getClass().getClassLoader().getResourceAsStream(location); try {	//用Properties文件加載文件里的內容
          properties.load(resourceAsStream);
          } catch (IOException e) {
          e.printStackTrace();
          }finally {	//關流
          if(null!=resourceAsStream){	try {
          resourceAsStream.close();
          } catch (IOException e) {
          e.printStackTrace();
          }
          }
          }
          }	
          private void doScanner(String packageName) {	//把所有的.替換成/
          URL url=this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.", "/"));
          File dir=new File(url.getFile());	for (File file : dir.listFiles()) { if(file.isDirectory()){	//遞歸讀取包
          doScanner(packageName+"."+file.getName());
          }else{
          String className=packageName +"." +file.getName().replace(".class", "");
          classNames.add(className);
          }
          }
          }	
          private void doInstance() {	if (classNames.isEmpty()) {	return;
          }	
          for (String className : classNames) {	try {	//把類搞出來,反射來實例化(只有加@MyController需要實例化)
          Class<?> clazz=Class.forName(className); if(clazz.isAnnotationPresent(MyController.class)){
          ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());
          }else{	continue;
          }
          } catch (Exception e) {
          e.printStackTrace();	continue;
          }
          }
          }	private void initHandlerMapping(){	if(ioc.isEmpty()){	return;
          }	try {	for (Entry<String, Object> entry: ioc.entrySet()) {
          Class<? extends Object> clazz=entry.getValue().getClass(); if(!clazz.isAnnotationPresent(MyController.class)){	continue;
          }	
          //拼url時,是controller頭的url拼上方法上的url
          String baseUrl="";	if(clazz.isAnnotationPresent(MyRequestMapping.class)){
          MyRequestMapping annotation=clazz.getAnnotation(MyRequestMapping.class);
          baseUrl=annotation.value();
          }
          Method[] methods=clazz.getMethods();	for (Method method : methods) { if(!method.isAnnotationPresent(MyRequestMapping.class)){	continue;
          }
          MyRequestMapping annotation=method.getAnnotation(MyRequestMapping.class);
          String url=annotation.value();
          url=(baseUrl+"/"+url).replaceAll("/+", "/");
          handlerMapping.put(url,method);
          controllerMap.put(url,clazz.newInstance());
          System.out.println(url+","+method);
          }
          }
          } catch (Exception e) {
          e.printStackTrace();
          }
          }	/**
           * 把字符串的首字母小寫
           * @param name
           * @return
           */
          private String toLowerFirstWord(String name){ char[] charArray=name.toCharArray();
          charArray[0] +=32;	return String.valueOf(charArray);
          }
          }
          

          這里我們就開發完了自己的SpringMVC,現在我們測試一下:

          package com.liugh.core.controller;import java.io.IOException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import com.liugh.annotation.MyController;import com.liugh.annotation.MyRequestMapping;import com.liugh.annotation.MyRequestParam;@MyController@MyRequestMapping("/test")public class TestController { 
           @MyRequestMapping("/doTest") public void test1(HttpServletRequest request, HttpServletResponse response,
           	@MyRequestParam("param") String param){
           	System.out.println(param);	 try {
           response.getWriter().write( "doTest method success! param:"+param);
           } catch (IOException e) {
           e.printStackTrace();
           }
           }	 
           
           @MyRequestMapping("/doTest2") public void test2(HttpServletRequest request, HttpServletResponse response){ try {
           response.getWriter().println("doTest2 method success!");
           } catch (IOException e) {
           e.printStackTrace();
           }
           }
          }
          

          訪問http://localhost:8080/liughMVC/test/doTest?param=liugh如下:

          訪問一個不存在的試試:

          到這里我們就大功告成了!

          我自己在騰訊課堂上也有講過一堂手寫springMVC的直播分享,有感興趣的可以來看看

          https://pan.baidu.com/s/17v3syshIGQWjCHL0yi73Cg

          1,3分鐘讀懂Spring核心源碼;

          2,SpringMVC與Spring框架關系;

          3,SpringMVC的所有注解定義實戰;

          4,手寫SpringMVC框架實戰;

          5,Tomcat加載進行測試實戰;

          6,互動答疑。

          如果感興趣的話可以來找我獲取其他的資料 想學習提升自己的私信我【JAVA架構】獲取往期Java高級架構資料、源碼、筆記、視頻。Dubbo、Redis、設計模式、Netty、zookeeper、Spring cloud、分布式、高并發等架構技術 記得轉發下哦

          來自同事James的一篇文章


          主站蜘蛛池模板: 无码丰满熟妇一区二区| 精品人妻少妇一区二区| 国产精久久一区二区三区| 无码精品视频一区二区三区| 国产日韩综合一区二区性色AV| 国产在线无码一区二区三区视频| 国产乱码精品一区二区三区麻豆| 亚洲综合无码精品一区二区三区| 日韩一区二区三区在线精品| 国模极品一区二区三区| 99久久精品午夜一区二区| 久久国产精品最新一区| 夜色福利一区二区三区| 久久久久久人妻一区二区三区 | 怡红院一区二区在线观看| 日本韩国黄色一区二区三区| 久久99精品国产一区二区三区| 日韩在线一区二区| 国产另类ts人妖一区二区三区 | 中文无码一区二区不卡αv| 日本夜爽爽一区二区三区| 日本内射精品一区二区视频| 国产一区二区精品久久岳√| 久久久久99人妻一区二区三区| 国产色欲AV一区二区三区| 久久一区不卡中文字幕| 免费在线视频一区| 中文字幕无线码一区二区| 精品久久久久一区二区三区 | 能在线观看的一区二区三区| 人体内射精一区二区三区| 一区二区三区无码高清| 亚洲一区二区三区高清在线观看| 久久99久久无码毛片一区二区| 无码人妻精品一区二区三区久久久 | 国产欧美一区二区精品仙草咪| 国产综合精品一区二区三区| 色综合视频一区二区三区| 亚洲综合av一区二区三区不卡| 国产成人精品一区二区三区免费| 午夜无码一区二区三区在线观看 |