简陋Spring运行流程
在手写spring之前我们可以从下图大致了解一个简陋版spring运行的流程。需要注意的是这个简陋版本没有包含AOP功能。
项目搭建
直接创建一个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直接启动的项目。
按照如下方式运行即可。
打开浏览器访问控制器中的路径即可发现成功。
优化
以上便是一个简陋版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