该文章主要是分析Springmvc启动的流程(配置阶段、初始化阶段和运行阶段),可以让自己对spring框架有更深一层的理解。对框架比较感兴趣的朋友都可以了解阅读下,对于我所描述的内容有错误的还望能不吝指出。
对于springmvc中的整个流程我个人把他分为这几个阶段,包括个人手写的spring也是参照此按阶段实现:
1.配置阶段
根据web.xml ,先定义DispatcherServlet并且定义该sevlet传入的参数和路径。
2.初始化阶段
初始化阶段中又可以分为IOC、DI和MVC阶段:
(1)IOC:初始化配置文件和IOC容器,扫描配置的包下的类,通过反射机制将需要实例化的类放入IOC容器,既将带有spring注解的类进行实例化后存放到 IOC 容器中。IOC容器的实质就是一个集合;
(2)DI:DI阶段(其实就是依赖注入)。对需要赋值的实例属性进行赋值(一般较多都是处理带有注解的@Autowrized的属性)
(3)MVC:构造出HandlerMapping集合,主要作用就是用于存放对外公开的API和Method之间的关系,一个API一般会对应一个可执行的Method.
3.运行阶段
运行阶段中,当接受到一个url后,会到HandleMapping集合中,找到对应Method、通过反射机制去执行invoker,再返回结果给调用方。
这样就大体完成了springmvc整个运行阶段,所描述的都仅为个人观点,如果有误请在评论中指出。
其整体流程可以参照下图:
接下来就来尝试手写一个类似springmvc的框架了,这个手写的过程还是相当有成就感的!
1.创建一个空的JavaWeb工程,引入依赖,其实因为我们是要手写spring,所以基本不需要什么外部的依赖工具,只需要导入servlet-api即可,如下:
<dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> |
2.根据上述的流程描述,接下来就是对web.xml进行配置:
<servlet> <servlet-name>appServlet</servlet-name> <servlet-class>com.wangcw.cwframework.sevlet.CwDispatcherServlet</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>appServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> |
对于配置中的CwDispatcherServlet其实就是个人自定义一个作用与spring中DispatcherServlet相同的Servlet,此处先创建一个空的CwDispatcherServlet,继承 javax.servlet.http.HttpServlet即可,具体实现后面会描述。
此处因为是手写spring的部分功能,所以配置也不用写太多,此处仅拿一个包扫描的配置(scanPackage),各位少侠可自行拓展。
CwDispatcherServlet中初始化的配置文件application.properties内容如下:
scanPackage=com.wangcw
3.相信spring中又一部分注解都是大家比较熟悉的,接下来我们先从这几个注解着手吧。(此处就不指出各个注解的作用了,相信百度上已经很多了)
spring注解 | 自定义注解 |
@Controller | @CwController |
@Autowired | @CwAutowired |
@RequestMapping | @CwRequestMapping |
@RequestParam | @CwRequestParam |
@Service | @CwService |
然后实现下各个自定义的注解,直接贴代码:
/* |
4.创建一个简单的控制层和业务层交互 Demo,加上自定的注解,具体注解的功能,后面赘述。
Controller.java
@CwController
Service.java
public interface Service {
@CwService |
5.上面的controller层和service层已经把我们上述的自定注解都使用上去了,接下来我们开始手写spring的核心功能了,也就是实现CwDispatcherServlet.java这个HttpServlet的子类。
首先需要重写父类中的init方法,因为我们要在Init过程中实现出跟spring一样的效果。
理一理init()过程中都需要做哪些事情呢?整理了一下init()中主要需要以下几步操作
@Override
|
第一步很简单,在类中定义一个Properties实例,用于存放Servlet初始化的配置文件。导入配置代码略过,IO常规读写即可。
private Properties contextConfig = new Properties();
第二步通过上面获取到的配置,取到需要扫描的包路径,然后在根据路径找到对应文件夹,做一个递归扫描即可。将扫描到的文件名去除后缀,保存到一个集合中,那么该集合就存放了包下所有类的类名。
String scanFileDir = contextConfig.getProperty("scanPackage");
-
/* 用于存放扫描到的类 */
private List<String> classNames = new ArrayList<String>();
/*扫描获取到对应的class名,便于后面反射使用*/
String className = scanPackage + "." + file.getName().replace(".class", "");
classNames.add(className);
第三步就是IOC阶段,简而言之就是对上面集合中所有的类进行遍历,并且创建一个IOC容器,将带有@CwController和@CwService的类置于容器内。
-
/* 创建一个IOC容器 */
-
private Map<String, Object> IOC = new HashMap<String, Object>();
for (String classNme : classNames){ if( 对加了 @CwController 注解的类进行初始化){ /* 对于初始化的类还需要放入IOC容器, 对于存入IOC的实例,key值是有一定规则的,默认类名首字母小写;*/ /* toLowerFirstCase是自定义的一个工具方法,用于将传入的字符串首字母小写 */ String beanName = toLowerFirstCase(clazz.getSimpleName()); IOC.put(beanName, clazz.newInstance()); } else if (对加了 @CwService 注解的类进行初始化){ /* 对于存入IOC的实例,key值是有一定规则的,而Service层的规则相对上面更复杂一些,因为注解可以有自定义实例名,并且可能是接口实现类 */ IOC.put(beanName, instance); } else { //对于扫描到的没有注解的类,忽略初始化行为 continue; } } |
第四步是DI操作,将IOC容器中需要赋值的实例属性进行赋值,即带有Autowired注解的实例属性。伪代码如下:
-
/*遍历IOC中的所有实例*/
for(Map.Entry<String, Object> entry : IOC.entrySet()){
/* 使用getDeclaredFields暴力反射 */
Field [] fields = entry.getValue().getClass().getDeclaredFields();
for (Field field : fields){
/*1.判断属性是否有加注解@CwAutowried.对于有注解的属性才需要赋值*/
....
/*属性授权*/
field.setAccessible(true);
field.set(entry.getValue(), IOC.get(beanName));
}
}
第五步要处理Controller层的Method与请求url的匹配关系,让请求能准确的请求到对应的url。篇幅问题,此处还是上传伪代码。
/* 创建HandlerMapping存放url,method的匹配关系 其中类Handler是我自己定义的一个利用正则去匹配url和method, 只要用户传入url,Handler就可以响应出其对应的method*/ private List<Handler> handlerMapping = new ArrayList<Handler>(); /* 遍历IOC容器 */ for (Map.Entry<String, Object> entry : IOC.entrySet()){ Class<?> clazz = entry.getValue().getClass(); /* 只对带有CwController注解的类进行处理 */ 定义一个url,由带有CwController的实例类上的@CwRequestMapping注解的值和Method上@CwRequestMapping注解的值组成 /* (1).判断类上是否有CwRequestMapping注解 ,进行拼接 url*/ /* (2).遍历实例下每个Method,并且需要判断该方法是否有【 @CwRequestMapping 】注解,拼接url*/ /* 最后将匹配关系以正则的形式,放到HandlerMapping集合中 */ String regex = (url); Pattern pattern = Pattern.compile(regex); handlerMapping.add(new Handler(pattern,method)); } |
到这里就基本完成了springmvc的初始化阶段,之后的工作就是重写一下CwDispatcherServlet.java父类的doGet()/doPost()方法。根据request中的URI和参数来执行对应的Method,并且响应结果。
-
/* 利用反射执行其所匹配的方法 */
-
handler.method.invoke(handler.controller, paramValues);
到此整个步骤就完成了,此时可以愉快的启动项目,并访问对应的url进行测试了。
根据上面Controller定义的方法可以知道其匹配的url为 : /demo/query 和 /demo/add,并且有使用@CwRequestParam注解定义了其各个参数的名称。
测试结果如下:
http://localhost:8080/spring/demo/query?name=James
http://localhost:8080/spring/demo/add?a=222222&b=444444
再来测试个url,是controller中没有声明出@CwRequestMapping注解的,看看结果。
http://localhost:8080/spring/demo/testNoUrl
---------------------
作者:WangCw的夏天
来源:CSDN
原文:http://blog.csdn.net/qq_33404395/article/details/80645205