Spring 源码分析(一)—— 手写Spring初体验 V1

导读:本篇文章讲解 Spring 源码分析(一)—— 手写Spring初体验 V1,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

简陋Spring运行流程

在手写spring之前我们可以从下图大致了解一个简陋版spring运行的流程。需要注意的是这个简陋版本没有包含AOP功能。

img

项目搭建

直接创建一个Maven项目,然后逐步添加代码。以下为我的工程目录。
在这里插入图片描述

Pom文件

包含 jetty插件,帮助我们直接运行程序。webdefault.xml为jetty所需要的文件。

<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
    <artifactId>spring-source-1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <servlet.api.version>2.4</servlet.api.version>
    </properties>

    <dependencies>

        <!-- requied start -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>${servlet.api.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- requied end -->

    </dependencies>

    <build>
        <finalName>${artifactId}</finalName>
        <resources>
            <resource>
                <directory>${basedir}/src/main/resources</directory>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
            <resource>
                <directory>${basedir}/src/main/java</directory>
                <excludes>
                    <exclude>**/*.java</exclude>
                    <exclude>**/*.class</exclude>
                </excludes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <encoding>UTF-8</encoding>
                    <compilerArguments>
                        <verbose />
                        <bootclasspath>${java.home}/lib/rt.jar</bootclasspath>
                    </compilerArguments>
                </configuration>
            </plugin>

            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.5</version>
                <executions>
                    <execution>
                        <id>copy-resources</id>
                        <!-- here the phase you need -->
                        <phase>validate</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <encoding>UTF-8</encoding>
                            <outputDirectory>${basedir}/target/classes</outputDirectory>
                            <resources>
                                <resource>
                                    <directory>src/main/resources</directory>
                                    <includes>
                                        <include>**/*.*</include>
                                    </includes>
                                    <filtering>true</filtering>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.mortbay.jetty</groupId>
                <artifactId>maven-jetty-plugin</artifactId>
                <version>6.1.26</version>
                <configuration>
                    <webDefaultXml>src/main/resources/webdefault.xml</webDefaultXml>
                    <contextPath>/</contextPath>
                    <connectors>
                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                            <port>8081</port>
                        </connector>
                    </connectors>
                    <scanIntervalSeconds>0</scanIntervalSeconds>
                    <scanTargetPatterns>
                        <scanTargetPattern>
                            <directory>src/main/webapp</directory>
                            <includes>
                                <include>**/*.xml</include>
                                <include>**/*.properties</include>
                            </includes>
                        </scanTargetPattern>
                    </scanTargetPatterns>
                    <systemProperties>
                        <systemProperty>
                            <name>
                                javax.xml.parsers.DocumentBuilderFactory
                            </name>
                            <value>
                                com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
                            </value>
                        </systemProperty>
                        <systemProperty>
                            <name>
                                javax.xml.parsers.SAXParserFactory
                            </name>
                            <value>
                                com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
                            </value>
                        </systemProperty>
                        <systemProperty>
                            <name>
                                javax.xml.transform.TransformerFactory
                            </name>
                            <value>
                                com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
                            </value>
                        </systemProperty>
                        <systemProperty>
                            <name>org.eclipse.jetty.util.URI.charset</name>
                            <value>UTF-8</value>
                        </systemProperty>
                    </systemProperties>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <archive>
                        <addMavenDescriptor>false</addMavenDescriptor>
                    </archive>
                    <webResources>
                        <resource>
                            <!-- this is relative to the pom.xml directory -->
                            <directory>src/main/resources/</directory>
                            <targetPath>WEB-INF/classes</targetPath>
                            <includes>
                                <include>**/*.*</include>
                            </includes>
                            <!-- <excludes>
                                <exclude>**/local</exclude>
                                <exclude>**/test</exclude>
                                <exclude>**/product</exclude>
                            </excludes> -->
                            <filtering>true</filtering>
                        </resource>
                        <resource>
                            <!-- this is relative to the pom.xml directory -->
                            <directory>src/main/resources</directory>
                            <targetPath>WEB-INF/classes</targetPath>
                            <filtering>true</filtering>
                        </resource>
                    </webResources>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.zeroturnaround</groupId>
                <artifactId>javarebel-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>generate-rebel-xml</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <version>1.0.5</version>
            </plugin>

        </plugins>
        <pluginManagement>
            <plugins>
                <!--This plugin's configuration is used to store Eclipse m2e settings
                    only. It has no influence on the Maven build itself. -->
                <plugin>
                    <groupId>org.eclipse.m2e</groupId>
                    <artifactId>lifecycle-mapping</artifactId>
                    <version>1.0.0</version>
                    <configuration>
                        <lifecycleMappingMetadata>
                            <pluginExecutions>
                                <pluginExecution>
                                    <pluginExecutionFilter>
                                        <groupId>
                                            org.zeroturnaround
                                        </groupId>
                                        <artifactId>
                                            javarebel-maven-plugin
                                        </artifactId>
                                        <versionRange>
                                            [1.0.5,)
                                        </versionRange>
                                        <goals>
                                            <goal>generate</goal>
                                        </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                        <ignore></ignore>
                                    </action>
                                </pluginExecution>
                            </pluginExecutions>
                        </lifecycleMappingMetadata>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

</project>
<?xml version="1.0" encoding="ISO-8859-1"?>

<!-- ===================================================================== -->
<!-- This file contains the default descriptor for web applications.       -->
<!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -->
<!-- The intent of this descriptor is to include jetty specific or common  -->
<!-- configuration for all webapps.   If a context has a webdefault.xml    -->
<!-- descriptor, it is applied before the contexts own web.xml file        -->
<!--                                                                       -->
<!-- A context may be assigned a default descriptor by:                    -->
<!--  + Calling WebApplicationContext.setDefaultsDescriptor                -->
<!--  + Passed an arg to addWebApplications                                -->
<!--                                                                       -->
<!-- This file is used both as the resource within the jetty.jar (which is -->
<!-- used as the default if no explicit defaults descriptor is set) and it -->
<!-- is copied to the etc directory of the Jetty distro and explicitly     -->
<!-- by the jetty.xml file.                                                -->
<!--                                                                       -->
<!-- ===================================================================== -->
<web-app 
   xmlns="http://java.sun.com/xml/ns/javaee" 
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
   metadata-complete="true"
   version="2.5"> 

  <description>
    Default web.xml file.  
    This file is applied to a Web application before it's own WEB_INF/web.xml file
  </description>


  <!-- ==================================================================== -->
  <!-- Context params to control Session Cookies                            -->
  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
  <!-- UNCOMMENT TO ACTIVATE
  <context-param>
    <param-name>org.mortbay.jetty.servlet.SessionDomain</param-name>
    <param-value>127.0.0.1</param-value>
  </context-param>

  <context-param>
    <param-name>org.mortbay.jetty.servlet.SessionPath</param-name>
    <param-value>/</param-value>
  </context-param>

  <context-param>
    <param-name>org.mortbay.jetty.servlet.MaxAge</param-name>
    <param-value>-1</param-value>
  </context-param>
  -->

  <context-param>
    <param-name>org.mortbay.jetty.webapp.NoTLDJarPattern</param-name>
    <param-value>start.jar|ant-.*\.jar|dojo-.*\.jar|jetty-.*\.jar|jsp-api-.*\.jar|junit-.*\.jar|servlet-api-.*\.jar|dnsns\.jar|rt\.jar|jsse\.jar|tools\.jar|sunpkcs11\.jar|sunjce_provider\.jar|xerces.*\.jar</param-value>
  </context-param>
            


  <!-- ==================================================================== -->
  <!-- The default servlet.                                                 -->
  <!-- This servlet, normally mapped to /, provides the handling for static -->
  <!-- content, OPTIONS and TRACE methods for the context.                  -->
  <!-- The following initParameters are supported:                          -->
  <!--                                                                      -->
  <!--   acceptRanges     If true, range requests and responses are         -->
  <!--                    supported                                         -->
  <!--                                                                      -->
  <!--   dirAllowed       If true, directory listings are returned if no    -->
  <!--                    welcome file is found. Else 403 Forbidden.        -->
  <!--                                                                      -->
  <!--   welcomeServlets  If true, attempt to dispatch to welcome files     -->
  <!--                    that are servlets, if no matching static          --> 
  <!--                    resources can be found.                           -->
  <!--                                                                      -->
  <!--   redirectWelcome  If true, redirect welcome file requests           -->
  <!--                    else use request dispatcher forwards              -->
  <!--                                                                      -->
  <!--   gzip             If set to true, then static content will be served--> 
  <!--                    as gzip content encoded if a matching resource is -->
  <!--                    found ending with ".gz"                           -->
  <!--                                                                      -->
  <!--   resoureBase      Can be set to replace the context resource base   -->
  <!--                                                                      -->
  <!--   relativeResourceBase                                               -->
  <!--                    Set with a pathname relative to the base of the   -->
  <!--                    servlet context root. Useful for only serving     -->
  <!--                    static content from only specific subdirectories. -->
  <!--                                                                      -->
  <!--   useFileMappedBuffer                                                -->
  <!--                    If set to true (the default), a  memory mapped    -->
  <!--                    file buffer will be used to serve static content  -->
  <!--                    when using an NIO connector. Setting this value   -->
  <!--                    to false means that a direct buffer will be used  -->
  <!--                    instead. If you are having trouble with Windows   -->
  <!--                    file locking, set this to false.                  -->
  <!--                                                                      -->
  <!--  cacheControl      If set, all static content will have this value   -->
  <!--                    set as the cache-control header.                  -->
  <!--                                                                      -->
  <!--  maxCacheSize      Maximum size of the static resource cache         -->
  <!--                                                                      -->
  <!--  maxCachedFileSize Maximum size of any single file in the cache      -->
  <!--                                                                      -->
  <!--  maxCachedFiles    Maximum number of files in the cache              -->
  <!--                                                                      -->
  <!--  cacheType         "nio", "bio" or "both" to determine the type(s)   -->
  <!--                    of resource cache. A bio cached buffer may be used-->
  <!--                    by nio but is not as efficient as a nio buffer.   -->
  <!--                    An nio cached buffer may not be used by bio.      -->
  <!--                                                                      -->
  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
  <servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.mortbay.jetty.servlet.DefaultServlet</servlet-class>
    <init-param>
      <param-name>acceptRanges</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>dirAllowed</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>welcomeServlets</param-name>
      <param-value>false</param-value>
    </init-param>
    <init-param>
      <param-name>redirectWelcome</param-name>
      <param-value>false</param-value>
    </init-param>
    <init-param>
      <param-name>maxCacheSize</param-name>
      <param-value>256000000</param-value>
    </init-param>
    <init-param>
      <param-name>maxCachedFileSize</param-name>
      <param-value>10000000</param-value>
    </init-param>
    <init-param>
      <param-name>maxCachedFiles</param-name>
      <param-value>1000</param-value>
    </init-param>
    <init-param>
      <param-name>cacheType</param-name>
      <param-value>both</param-value>
    </init-param>
    <init-param>
      <param-name>gzip</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>useFileMappedBuffer</param-name>
      <param-value>false</param-value>
    </init-param>  
    <!--
    <init-param>
      <param-name>cacheControl</param-name>
      <param-value>max-age=3600,public</param-value>
    </init-param>
    -->
    <load-on-startup>0</load-on-startup>
  </servlet> 

  <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
  

  <!-- ==================================================================== -->
  <!-- JSP Servlet                                                          -->
  <!-- This is the jasper JSP servlet from the jakarta project              -->
  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
  <!-- The JSP page compiler and execution servlet, which is the mechanism  -->
  <!-- used by Glassfish to support JSP pages.  Traditionally, this servlet -->
  <!-- is mapped to URL patterh "*.jsp".  This servlet supports the         -->
  <!-- following initialization parameters (default values are in square    -->
  <!-- brackets):                                                           -->
  <!--                                                                      -->
  <!--   checkInterval       If development is false and reloading is true, -->
  <!--                       background compiles are enabled. checkInterval -->
  <!--                       is the time in seconds between checks to see   -->
  <!--                       if a JSP page needs to be recompiled. [300]    -->
  <!--                                                                      -->
  <!--   compiler            Which compiler Ant should use to compile JSP   -->
  <!--                       pages.  See the Ant documenation for more      -->
  <!--                       information. [javac]                           -->
  <!--                                                                      -->
  <!--   classdebuginfo      Should the class file be compiled with         -->
  <!--                       debugging information?  [true]                 -->
  <!--                                                                      -->
  <!--   classpath           What class path should I use while compiling   -->
  <!--                       generated servlets?  [Created dynamically      -->
  <!--                       based on the current web application]          -->
  <!--                       Set to ? to make the container explicitly set  -->
  <!--                       this parameter.                                -->
  <!--                                                                      -->
  <!--   development         Is Jasper used in development mode (will check -->
  <!--                       for JSP modification on every access)?  [true] -->
  <!--                                                                      -->
  <!--   enablePooling       Determines whether tag handler pooling is      -->
  <!--                       enabled  [true]                                -->
  <!--                                                                      -->
  <!--   fork                Tell Ant to fork compiles of JSP pages so that -->
  <!--                       a separate JVM is used for JSP page compiles   -->
  <!--                       from the one Tomcat is running in. [true]      -->
  <!--                                                                      -->
  <!--   ieClassId           The class-id value to be sent to Internet      -->
  <!--                       Explorer when using <jsp:plugin> tags.         -->
  <!--                       [clsid:8AD9C840-044E-11D1-B3E9-00805F499D93]   -->
  <!--                                                                      -->
  <!--   javaEncoding        Java file encoding to use for generating java  -->
  <!--                       source files. [UTF-8]                          -->
  <!--                                                                      -->
  <!--   keepgenerated       Should we keep the generated Java source code  -->
  <!--                       for each page instead of deleting it? [true]   -->
  <!--                                                                      -->
  <!--   logVerbosityLevel   The level of detailed messages to be produced  -->
  <!--                       by this servlet.  Increasing levels cause the  -->
  <!--                       generation of more messages.  Valid values are -->
  <!--                       FATAL, ERROR, WARNING, INFORMATION, and DEBUG. -->
  <!--                       [WARNING]                                      -->
  <!--                                                                      -->
  <!--   mappedfile          Should we generate static content with one     -->
  <!--                       print statement per input line, to ease        -->
  <!--                       debugging?  [false]                            -->
  <!--                                                                      -->
  <!--                                                                      -->
  <!--   reloading           Should Jasper check for modified JSPs?  [true] -->
  <!--                                                                      -->
  <!--   suppressSmap        Should the generation of SMAP info for JSR45   -->
  <!--                       debugging be suppressed?  [false]              -->
  <!--                                                                      -->
  <!--   dumpSmap            Should the SMAP info for JSR45 debugging be    -->
  <!--                       dumped to a file? [false]                      -->
  <!--                       False if suppressSmap is true                  -->
  <!--                                                                      -->
  <!--   scratchdir          What scratch directory should we use when      -->
  <!--                       compiling JSP pages?  [default work directory  -->
  <!--                       for the current web application]               -->
  <!--                                                                      -->
  <!--   tagpoolMaxSize      The maximum tag handler pool size  [5]         -->
  <!--                                                                      -->
  <!--   xpoweredBy          Determines whether X-Powered-By response       -->
  <!--                       header is added by generated servlet  [false]  -->
  <!--                                                                      -->
  <!-- If you wish to use Jikes to compile JSP pages:                       -->
  <!--   Set the init parameter "compiler" to "jikes".  Define              -->
  <!--   the property "-Dbuild.compiler.emacs=true" when starting Jetty     -->
  <!--   to cause Jikes to emit error messages in a format compatible with  -->
  <!--   Jasper.                                                            -->
  <!--   If you get an error reporting that jikes can't use UTF-8 encoding, -->
  <!--   try setting the init parameter "javaEncoding" to "ISO-8859-1".     -->
  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
  <servlet id="jsp">
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
    <init-param>
        <param-name>logVerbosityLevel</param-name>
        <param-value>DEBUG</param-value>
    </init-param>
    <init-param>
        <param-name>fork</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>xpoweredBy</param-name>
        <param-value>false</param-value>
    </init-param>
    <!--  
    <init-param>
        <param-name>classpath</param-name>
        <param-value>?</param-value>
    </init-param>
    -->
    <load-on-startup>0</load-on-startup>
  </servlet>

  <servlet-mapping> 
    <servlet-name>jsp</servlet-name> 
    <url-pattern>*.jsp</url-pattern> 
    <url-pattern>*.jspf</url-pattern>
    <url-pattern>*.jspx</url-pattern>
    <url-pattern>*.xsp</url-pattern>
    <url-pattern>*.JSP</url-pattern> 
    <url-pattern>*.JSPF</url-pattern>
    <url-pattern>*.JSPX</url-pattern>
    <url-pattern>*.XSP</url-pattern>
  </servlet-mapping>
  
  <!-- ==================================================================== -->
  <!-- Dynamic Servlet Invoker.                                             -->
  <!-- This servlet invokes anonymous servlets that have not been defined   -->
  <!-- in the web.xml or by other means. The first element of the pathInfo  -->
  <!-- of a request passed to the envoker is treated as a servlet name for  -->
  <!-- an existing servlet, or as a class name of a new servlet.            -->
  <!-- This servlet is normally mapped to /servlet/*                        -->
  <!-- This servlet support the following initParams:                       -->
  <!--                                                                      -->
  <!--  nonContextServlets       If false, the invoker can only load        -->
  <!--                           servlets from the contexts classloader.    -->
  <!--                           This is false by default and setting this  -->
  <!--                           to true may have security implications.    -->
  <!--                                                                      -->
  <!--  verbose                  If true, log dynamic loads                 -->
  <!--                                                                      -->
  <!--  *                        All other parameters are copied to the     -->
  <!--                           each dynamic servlet as init parameters    -->
  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
  <!-- Uncomment for dynamic invocation
  <servlet>
    <servlet-name>invoker</servlet-name>
    <servlet-class>org.mortbay.jetty.servlet.Invoker</servlet-class>
    <init-param>
      <param-name>verbose</param-name>
      <param-value>false</param-value>
    </init-param>
    <init-param>
      <param-name>nonContextServlets</param-name>
      <param-value>false</param-value>
    </init-param>
    <init-param>
      <param-name>dynamicParam</param-name>
      <param-value>anyValue</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
  </servlet>

  <servlet-mapping> <servlet-name>invoker</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
  -->



  <!-- ==================================================================== -->
  <session-config>
    <session-timeout>30</session-timeout>
  </session-config>

  <!-- ==================================================================== -->
  <!-- Default MIME mappings                                                -->
  <!-- The default MIME mappings are provided by the mime.properties        -->
  <!-- resource in the org.mortbay.jetty.jar file.  Additional or modified  -->
  <!-- mappings may be specified here                                       -->
  <!-- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -->
  <!-- UNCOMMENT TO ACTIVATE
  <mime-mapping>
    <extension>mysuffix</extension>
    <mime-type>mymime/type</mime-type>
  </mime-mapping>
  -->

  <!-- ==================================================================== -->
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <!-- ==================================================================== -->
  <locale-encoding-mapping-list>
    <locale-encoding-mapping><locale>ar</locale><encoding>ISO-8859-6</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>be</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>bg</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>ca</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>cs</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>da</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>de</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>el</locale><encoding>ISO-8859-7</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>en</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>es</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>et</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>fi</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>fr</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>hr</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>hu</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>is</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>it</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>iw</locale><encoding>ISO-8859-8</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>ja</locale><encoding>Shift_JIS</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>ko</locale><encoding>EUC-KR</encoding></locale-encoding-mapping>     
    <locale-encoding-mapping><locale>lt</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>lv</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>mk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>nl</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>no</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>pl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>pt</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>ro</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>ru</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>sh</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>sk</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>sl</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>sq</locale><encoding>ISO-8859-2</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>sr</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>sv</locale><encoding>ISO-8859-1</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>tr</locale><encoding>ISO-8859-9</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>uk</locale><encoding>ISO-8859-5</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>zh</locale><encoding>GB2312</encoding></locale-encoding-mapping>
    <locale-encoding-mapping><locale>zh_TW</locale><encoding>Big5</encoding></locale-encoding-mapping>   
  </locale-encoding-mapping-list>
  
  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Disable TRACE</web-resource-name>
      <url-pattern>/</url-pattern>
      <http-method>TRACE</http-method>
    </web-resource-collection>
    <auth-constraint/>
  </security-constraint>
  
</web-app>

相关配置文件

application

scanPackage=com.spring.test.demo

web.xml

web容器的项目都是从读取web.xml开始;其中DemoDispatchServlet是我们模拟spring的核心功能类。

<?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/j2ee" xmlns:javaee="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/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         version="2.4">
    <display-name>Web Application</display-name>
    <servlet>
        <servlet-name>gpmvc</servlet-name>
        <servlet-class>com.spring.test.mvc.servlet.v1.DemoDispatchServlet</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>gpmvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

自定义注解

模拟spring的常用注解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DemoAutowired {
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DemoController {
    String value() default "";
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DemoRequestMapping {
    String value() default "";
}
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DemoRequestParam {
    String value() default "";
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DemoService {
    String value() default "";
}

业务逻辑类

就是常规的spring操作,直接复制即可。

public interface IDemoService {
   
   String get(String name);
   
}
//我这边是类名和注解名重复了,可以自行替换
@com.spring.test.mvc.annotation.DemoService
public class DemoService implements IDemoService {

    public String get(String name) {
        return "My name is " + name + ",from service.";
    }

}
@DemoController
@DemoRequestMapping("/demo")
public class DemoAction {

    @DemoAutowired
    private IDemoService demoService;

    @DemoRequestMapping("/query")
    public void query(HttpServletRequest req, HttpServletResponse resp,
                      @DemoRequestParam("name") String name) {
//    String result = demoService.get(name);
        String result = "My name is " + name;
        try {
            resp.getWriter().write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @DemoRequestMapping("/add")
    public void add(HttpServletRequest req, HttpServletResponse resp,
                    @DemoRequestParam("a") Integer a, @DemoRequestParam("b") Integer b) {
        try {
            resp.getWriter().write(a + "+" + b + "=" + (a + b));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @DemoRequestMapping("/sub")
    public void sub(HttpServletRequest req, HttpServletResponse resp,
                    @DemoRequestParam("a") Double a, @DemoRequestParam("b") Double b) {
        try {
            resp.getWriter().write(a + "-" + b + "=" + (a - b));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @DemoRequestMapping("/remove")
    public String remove(@DemoRequestParam("id") Integer id) {
        return "" + id;
    }

}

编写核心业务

当我们构建完项目之后,我们便可以开始编写我们的核心servlet了。

初始类

我们首先创建一个初始类,根据流程将大致框架搭好,然后开始编写代码。

public class DemoDispatchServlet extends HttpServlet {

    //保存用户篇配置好的配置文件
    private Properties contextConfig = new Properties();

    //缓存从包路径下扫描的全类名
    private List<String> classNames = new ArrayList<String>();

    //保存所有扫描的类的实例
    private Map<String, Object> ioc = new HashMap<String, Object>();

    //保存controller 中 URL和Method的对应关系
    private Map<String, Method> handlerMapping = new HashMap<String, Method>();

    @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.根据url委派给具体的调用方法
       
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件
        //参数为web.xml中定义的键值名
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //2.扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));

        //============IOC功能===============
        //3.初始IOC容器,将扫描到的类进行实例化,缓存到IOC容器中
        doInstance();

        //============DI功能===============
        //4.完成依赖注入
        doAutowired();

        //============MVC功能===============
        //5.初始化HandleMapping
        doInitHandleMapping();

        System.out.println("Spring framework is init.");

    }

    private void doInitHandleMapping() {
        
    }

    private void doAutowired() {

    }

    private void doInstance() {

    }

    // 扫描 classpath下符合包路劲规则所有的class文件
    private void doScanner(String scanPackage) {
       
    }

    //根据contextConfigLocation的路径去classpath下找到的对应的文件
    private void doLoadConfig(String contextConfigLocation) {
       
    }
}

加载配置文件

在初始化过程中,我们的第一步就是获取配置文件;

//根据contextConfigLocation的路径去classpath下找到的对应的文件
private void doLoadConfig(String contextConfigLocation) {
    InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

    try {
        contextConfig.load(is);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (null != is) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

扫描相关的类

扫描配置类中路径下的所有文件;首先需要注意配置类中的文件路径和我们正常的路径不同,需要将其 * . * 替换为/。

如果扫描到文件夹则继续往该路径下扫描;

如果不是class文件则跳过;

将符合规则的文件添加到集合中。

// 扫描 classpath下符合包路劲规则所有的class文件
private void doScanner(String scanPackage) {
    URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
    File classPath = new File(url.getFile());

    for (File file : classPath.listFiles()) {
        if (file.isDirectory()) {
            doScanner(scanPackage + "." + file.getName());
        } else {
            // 不是class文件则跳过
            if (!file.getName().endsWith(".class")) {
                continue;
            }
            // 包名.类型  比如: com.demo.DemoAction
            String className = (scanPackage + "." + file.getName().replace(".class", ""));

            // 实例化 需要用到class.forName(className);
            classNames.add(className);
        }
    }
}

将扫描的类放入IOC容器中

扫描到文件之后,我们就需要将其实例化并存至我们的IOC容器中。

流程:

  • controller注解的类:

    1.判断这个类有没有使用controller注解

    2.将类名的首字母小写,通过位操作实现

    3.将该类实例化后添加至IOC容器中

  • service注解的类:

    1.判断这个类有没有使用service注解

    2.将类名的首字母小写,通过位操作实现

    3.获取别名,因为注解具有默认值,所以不需要进行判空操作

    4.实例化类并存入IOC容器中

    5.如果是接口则初始化他的实现类,多个实现类则报错提示。

  • 其他:跳过

private void doInstance() {
    if (classNames.isEmpty()) {
        return;
    }

    try {
        for (String className : classNames) {
            Class<?> clazz = Class.forName(className);

            if (clazz.isAnnotationPresent(DemoController.class)) {
                String beanName = toLowerFirstCase(clazz.getSimpleName());
                Object instance = clazz.newInstance();
                ioc.put(beanName, instance);
            } else if (clazz.isAnnotationPresent(DemoService.class)) {
                // 1.默认类名首字母小写
                String beanName = toLowerFirstCase(clazz.getSimpleName());

                // 2.如果多个包下出现了相同的类名,优先使用别名(自定义命名)
                DemoService demoService = clazz.getAnnotation(DemoService.class);
                if (!"".equals(demoService.value())) {
                    beanName = demoService.value();
                }

                Object instance = clazz.newInstance();

                ioc.put(beanName, instance);

                // 3.如果是接口 ,只能初始化他的实现类
                for (Class<?> i : clazz.getInterfaces()) {
                    if (ioc.containsKey(i.getName())) {
                        throw new Exception("The" + i.getName() + "is exists! please use aliens");
                    }
                    ioc.put(i.getName(), instance);
                }

            } else {
                continue;
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }

}

private String toLowerFirstCase(String simpleName) {
    char[] chars = simpleName.toCharArray();
    chars[0] += 32;  //利用ascll码,大小写字母之间相差32
    return String.valueOf(chars);
}

依赖注入

1.遍历IOC容器中的每个实例

2.通过反射获取类中的所有字段

3.判断该字段有无使用auto wired注解

4.如果使用了则获取该注解的值,如果为默认值,则获取该字段的类型名使用

5.将IOC容器中对应该beanName的实例注入到该字段中

private void doAutowired() {

    if (ioc.isEmpty()) {
        return;
    }

    for (Map.Entry<String, Object> entry : ioc.entrySet()) {
        //忽略字段的修饰符
        for (Field field : entry.getValue().getClass().getDeclaredFields()) {
            if (!field.isAnnotationPresent(DemoAutowired.class)) {
                continue;
            }

            DemoAutowired autowired = field.getAnnotation(DemoAutowired.class);
            String beanName = autowired.value().trim();
            if ("".equals(beanName)) {
                beanName = field.getType().getName();
            }

            //强制访问
            field.setAccessible(true);
            try {
                // 相当于 demoAction.demoService = ioc.get("com.demo.service.IDemoService");
                field.set(entry.getValue(), ioc.get(beanName));
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }
}

初始化HandlerMapping

该部分主要是对url路径及相对应的方法进行映射。

首先遍历IOC容器,没有使用controller的则跳过。

然后处理controller中添加的requestmapping,把其作为baseUrl。

然后开始处理类中的publi方法,如果没有使用requestMapping注解则跳过;

使用该注解的则对url地址进行相关处理后添加进Map中以供使用。

private void doInitHandleMapping() {
    if (ioc.isEmpty()) {
        return;
    }

    for (Map.Entry<String, Object> entry : ioc.entrySet()) {
        Class<?> clazz = entry.getValue().getClass();

        if (!clazz.isAnnotationPresent(DemoController.class)) {
            continue;
        }

        //处理controller类添加地址注解
        String baseUrl = "";
        if (clazz.isAnnotationPresent(DemoRequestMapping.class)) {
            DemoRequestMapping requestMapping = clazz.getAnnotation(DemoRequestMapping.class);
            baseUrl = requestMapping.value();
        }

        //只迭代public方法
        for (Method method : clazz.getMethods()) {
            if (!method.isAnnotationPresent(DemoRequestMapping.class)) {
                continue;
            }

            DemoRequestMapping requestMapping = method.getAnnotation(DemoRequestMapping.class);
            // /demo/query 使用正则表达处理 多个斜杠问题
            String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
            handlerMapping.put(url, method);
            System.out.println("Mapped : " + url + "--->" + method);

        }
    }
}

根据url委派给具体的调用方法

这一步就是我们通过地址访问后触发的方法。

1.首先获取URL地址;这边是将地址中的根地址去除,方便根据映射找到对应的方法。

2.首先判断映射关系中是否包含该路径

3.然后将形参的位置和参数名字建立映射关系,缓存下来

4.根据参数位置匹配参数名字,从url中取到参数名字对应的值

5.以上两步都需要对request及response两个类进行相关的处理

6.使用反射调用相关方法

@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.根据url委派给具体的调用方法
    try {
        doDispatcher(req, resp);
    } catch (Exception e) {
        e.printStackTrace();
        resp.getWriter().write("500 Exception,Detail: " + Arrays.toString(e.getStackTrace()));
    }
}

private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    String url = req.getRequestURI();
    String contextPath = req.getContextPath();
    url = url.replaceAll(contextPath, "").replaceAll("/+", "/");

    if (!this.handlerMapping.containsKey(url)) {
        resp.getWriter().write("404 Not Found!");
        return;
    }

    Method method = this.handlerMapping.get(url);

    // 1.先把形参的位置和参数名字建立映射关系,缓存下来
    Map<String, Integer> paramIndexMapping = new HashMap<String, Integer>();
    // 多个参数, 每个参数后面有多个值
    Annotation[][] pa = method.getParameterAnnotations();
    for (int i = 0; i < pa.length; i++) {
        for (Annotation annotation : pa[i]) {
            if (annotation instanceof DemoRequestParam) {
                String paramName = ((DemoRequestParam) annotation).value();
                if (!"".equals(paramName.trim())) {
                    paramIndexMapping.put(paramName, i);
                }
            }
        }
    }

    Class<?>[] paramTypes = method.getParameterTypes();
    for (int i = 0; i < paramTypes.length; i++) {
        Class<?> type = paramTypes[i];
        if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
            paramIndexMapping.put(type.getName(), i);
        }
    }

    // 2.根据参数位置匹配参数名字,从url中取到参数名字对应的值
    Object[] paramValues = new Object[paramTypes.length];

    //http://localhost/demo/query?name=Tom&name=Tomcat&name=Mic
    Map<String, String[]> params = req.getParameterMap();
    for (Map.Entry<String, String[]> param : params.entrySet()) {
        String value = Arrays.toString(param.getValue())
                .replaceAll("\\[|\\]", "")
                .replaceAll("\\s", "");

        if (!paramIndexMapping.containsKey(param.getKey())) {
            continue;
        }

        int index = paramIndexMapping.get(param.getKey());

        //设计到类型强制转换
        paramValues[index] = value;
    }

    if(paramIndexMapping.containsKey(HttpServletRequest.class.getName())){
        int index = paramIndexMapping.get(HttpServletRequest.class.getName());
        paramValues[index] = req;
    }

    if(paramIndexMapping.containsKey(HttpServletResponse.class.getName())){
        int index = paramIndexMapping.get(HttpServletResponse.class.getName());
        paramValues[index] = resp;
    }


    String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
    // 3.组成动态参数列表 ,传给反射调用。
    method.invoke(ioc.get(beanName), paramValues);
    //method.invoke(ioc.get(beanName), new Object[]{req, resp, params.get("name")[0]});
}

运行测试

这边是使用的jeety直接启动的项目。

按照如下方式运行即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VcLK1E1l-1634366749995)(C:\Users\14297\AppData\Roaming\Typora\typora-user-images\image-20211016142920015.png)]

在这里插入图片描述

打开浏览器访问控制器中的路径即可发现成功。

xx

优化

以上便是一个简陋版Spring的编写过程,基本功能已经实现,但代码的优雅程度还不人意,在处理URL及方法映射的时候还可以继续优化。以下直接放优化后的全部代码,有相关注解可以自行查看。

public class DemoDispatcherServlet extends HttpServlet {

    //保存application.properties配置文件中的内容
    private Properties contextConfig = new Properties();

    //保存扫描的所有的类名
    private List<String> classNames = new ArrayList<String>();

    //为了简化程序,暂时不考虑ConcurrentHashMap
    private Map<String, Object> ioc = new HashMap<String, Object>();

    //保存url和Method的对应关系
//    private Map<String,Method> handlerMapping = new HashMap<String,Method>();

    //用Map的话,key,只能是url
    //Handler 本身的功能就是把url和method对应关系,已经具备了Map的功能
    //根据设计原则:单一职责,最少知道原则,帮助我们更好的理解
    private List<Handler> handlerMapping = new ArrayList<Handler>();

    @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、调用,运行阶段
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Exection,Detail : " + Arrays.toString(e.getStackTrace()));
        }


    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {

        Handler handler = getHandler(req);
        if (handler == null) {
//        if(!this.handlerMapping.containsKey(url)){
            resp.getWriter().write("404 Not Found!!!");
            return;
        }

        //获得方法的形参列表
        Class<?>[] paramTypes = handler.getParamTypes();

        Object[] paramValues = new Object[paramTypes.length];

        Map<String, String[]> params = req.getParameterMap();
        for (Map.Entry<String, String[]> parm : params.entrySet()) {
            String value = Arrays.toString(parm.getValue()).replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", ",");

            if (!handler.paramIndexMapping.containsKey(parm.getKey())) {
                continue;
            }

            int index = handler.paramIndexMapping.get(parm.getKey());
            paramValues[index] = convert(paramTypes[index], value);
        }

        if (handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
            int reqIndex = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[reqIndex] = req;
        }

        if (handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
            int respIndex = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[respIndex] = resp;
        }

        Object returnValue = handler.method.invoke(handler.controller, paramValues);
        if (returnValue == null || returnValue instanceof Void) {
            return;
        }
        resp.getWriter().write(returnValue.toString());
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) {
            return null;
        }
        //绝对路径
        String url = req.getRequestURI();
        //处理成相对路径
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");


        for (Handler handler : this.handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if (!matcher.matches()) {
                continue;
            }
            return handler;
        }
        return null;
    }

    //url传过来的参数都是String类型的,HTTP是基于字符串协议
    //只需要把String转换为任意类型就好
    private Object convert(Class<?> type, String value) {
        //如果是int
        if (Integer.class == type) {
            return Integer.valueOf(value);
        } else if (Double.class == type) {
            return Double.valueOf(value);
        }
        //如果还有double或者其他类型,继续加if
        //这时候,我们应该想到策略模式了
        //在这里暂时不实现,希望小伙伴自己来实现
        return value;
    }


    //初始化阶段
    @Override
    public void init(ServletConfig config) throws ServletException {

        //1、加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //2、扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));

        //3、初始化扫描到的类,并且将它们放入到ICO容器之中
        doInstance();

        //4、完成依赖注入
        doAutowired();

        //5、初始化HandlerMapping
        initHandlerMapping();

        System.out.println("Spring framework is init.");

    }


    //初始化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(DemoController.class)) {
                continue;
            }


            //保存写在类上面的@GPRequestMapping("/demo")
            String baseUrl = "";
            if (clazz.isAnnotationPresent(DemoRequestMapping.class)) {
                DemoRequestMapping requestMapping = clazz.getAnnotation(DemoRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            //默认获取所有的public方法
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(DemoRequestMapping.class)) {
                    continue;
                }

                DemoRequestMapping requestMapping = method.getAnnotation(DemoRequestMapping.class);
                //优化
                // //demo///query
                String regex = ("/" + baseUrl + "/" + requestMapping.value())
                        .replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                this.handlerMapping.add(new Handler(pattern, entry.getValue(), method));
//                handlerMapping.put(url,method);
                System.out.println("Mapped :" + pattern + "," + method);

            }


        }


    }

    //自动依赖注入
    private void doAutowired() {
        if (ioc.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            //Declared 所有的,特定的 字段,包括private/protected/default
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(DemoAutowired.class)) {
                    continue;
                }
                DemoAutowired autowired = field.getAnnotation(DemoAutowired.class);

                //如果用户没有自定义beanName,默认就根据类型注入
                //这个地方省去了对类名首字母小写的情况的判断
                String beanName = autowired.value().trim();
                if ("".equals(beanName)) {
                    //获得接口的类型,作为key待会拿这个key到ioc容器中去取值
                    beanName = field.getType().getName();
                }

                field.setAccessible(true);

                try {
                    //用反射机制,动态给字段赋值
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }


            }

        }


    }

    private void doInstance() {
        //初始化,为DI做准备
        if (classNames.isEmpty()) {
            return;
        }

        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);

                if (clazz.isAnnotationPresent(DemoController.class)) {
                    Object instance = clazz.newInstance();
                    //Spring默认类名首字母小写
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);
                } else if (clazz.isAnnotationPresent(DemoService.class)) {
                    //1、自定义的beanName
                    DemoService service = clazz.getAnnotation(DemoService.class);
                    String beanName = service.value();
                    //2、默认类名首字母小写
                    if ("".equals(beanName.trim())) {
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }

                    Object instance = clazz.newInstance();
                    ioc.put(beanName, instance);
                    //3、根据类型自动赋值,投机取巧的方式
                    for (Class<?> i : clazz.getInterfaces()) {
                        if (ioc.containsKey(i.getName())) {
                            throw new Exception("The “" + i.getName() + "” is exists!!");
                        }
                        //把接口的类型直接当成key了
                        ioc.put(i.getName(), instance);
                    }
                } else {
                    continue;
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }


    //扫描出相关的类
    private void doScanner(String scanPackage) {
        //转换为文件路径,实际上就是把.替换为/就OK了
        //classpath
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classPath = new File(url.getFile());
        for (File file : classPath.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                classNames.add(className);
            }
        }
    }


    //加载配置文件
    private void doLoadConfig(String contextConfigLocation) {
        InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(fis);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != fis) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    //保存一个url和一个Method的关系
    public class Handler {
        //必须把url放到HandlerMapping才好理解
        private Pattern pattern;  //正则
        private Method method;
        private Object controller;
        private Class<?>[] paramTypes;

        public Pattern getPattern() {
            return pattern;
        }

        public Method getMethod() {
            return method;
        }

        public Object getController() {
            return controller;
        }

        public Class<?>[] getParamTypes() {
            return paramTypes;
        }

        //形参列表
        //参数的名字作为key,参数的顺序,位置作为值
        private Map<String, Integer> paramIndexMapping;

        public Handler(Pattern pattern, Object controller, Method method) {
            this.pattern = pattern;
            this.method = method;
            this.controller = controller;

            paramTypes = method.getParameterTypes();

            paramIndexMapping = new HashMap<String, Integer>();
            putParamIndexMapping(method);
        }

        private void putParamIndexMapping(Method method) {

            //提取方法中加了注解的参数
            //把方法上的注解拿到,得到的是一个二维数组
            //因为一个参数可以有多个注解,而一个方法又有多个参数
            Annotation[][] pa = method.getParameterAnnotations();
            for (int i = 0; i < pa.length; i++) {
                for (Annotation a : pa[i]) {
                    if (a instanceof DemoRequestParam) {
                        String paramName = ((DemoRequestParam) a).value();
                        if (!"".equals(paramName.trim())) {
                            paramIndexMapping.put(paramName, i);
                        }
                    }
                }
            }

            //提取方法中的request和response参数
            Class<?>[] paramsTypes = method.getParameterTypes();
            for (int i = 0; i < paramsTypes.length; i++) {
                Class<?> type = paramsTypes[i];
                if (type == HttpServletRequest.class ||
                        type == HttpServletResponse.class) {
                    paramIndexMapping.put(type.getName(), i);
                }
            }

        }
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/16857.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!