1. 前言
前两天看到一篇关于手动实现简单的Spring和SpringMVC框架的文章,自己动手跟着做了一遍来加深对Spring框架的理解。
原文地址《自己实现spring核心功能》
2. 分析
SpringMVC的关键是请求到servlet后怎么去做映射和处理。首先来看一看dispatherServlet的基本流程
这里先给个简易处理流程
3. 创建项目
3.1 创建项目
首先创建一个maven项目,代码结构如下:
- annotaiton包:存放自定义注解类
- controller包:存放控制器类,对映SpringMVC项目中的controller包
- model包:存放实体类
- service包:存放service类
- servlet包:存放自定义servlet类
3.2 添加jar包依赖
<dependency> |
本项目需要使用到两个jar包javax.servlet-api
包:用来启动核心代码和处理请求fastjson
包:用来处理json类型的数据
4. 项目实现
4.1 创建自定义注解类
创建MyController注解
|
创建MyService注解
|
创建MyAutoWrited注解
|
创建MyRequestMapping注解
|
创建MyComponent注解
|
4.2 配置web.xml
需要配置<servlet>
和<servlet-mapping>
2个标签
<servlet>
中需要
- 指定servlet名称
- 指定处理请求的前端控制器类
- 设置初始化配置文件路径
完整web.xml文件如下:
|
4.3 建立测试接口
为了测试依赖注入,所以加了1个controller类、2个接口和2个实现类
HomeController.java
|
实体类GetUserInfo.java
public class GetUserInfo { |
IStudentService
接口
public interface IStudentService { |
IStudentService
接口的实现类
|
IHomeService
接口
public interface IHomeService { |
IHomeService
接口的实现类
|
4.4 初始化MyDispatcherServlet类
MyDispatcherServlet类专门处理servlet请求,匹配到对应的方法执行后返回
这里我们创建一个叫MyDispatcherServlet的类,它继承HttpServlet类,并且重写HttpServlet的init(),doGet(),doPost()这3个方法
public class MyDispatcherServlet extends HttpServlet { |
4.4.1 初始化分析
初始化工作是框架完成依赖注入的关键,我们在MyDispatcherServlet
类的init()
方法中,实现如下业务逻辑,就能将spring功能给初始化了,就可以使用依赖注入了
public void init(ServletConfig config) { |
4.4.2 加载配置
String contextConfigLocation = config.getInitParameter("contextConfigLocation"); |
这里会获取到web.xml中init-param节点中的值
<init-param> |
具体指向的是resources文件下的application.properties配置文件,里面只有一行配置
scanner.package=com.myspring.demo |
这是指定了需要扫描的包路径
loadConfig()
方法是将application.properties
文件中的包路径信息加载到properties
对象中
/** |
4.4.3 扫描要加载的类
定义一个成员变量beanNames
列表用来存放扫描到的需要加载类的路径
private List<String> beanNames = new ArrayList<String>(); |
从properties对象中获取刚刚加载的要扫描的包路径信息
//获取要扫描的包地址 |
就是要扫描图中红色方框中的包
定义doScanner
方法,用递归方式扫描要加载的类
/** |
此时可打断点调试看是否能正确扫描到类信息,如图:
4.4.4 实例化要加载的类
我们已经得到了这些定义好的类的名称列表,现在我们需要一个个实例化,并且保存在ioc容器当中。
先定义个装载类的容器,使用HashMap就能做到
private Map<String, Object> ioc = new HashMap<>(); |
创建doInstance()
方法进行实例化
/** |
其中firstLowerCase(String str)
方法将类名的首字母小写后存入ioc
/** |
只要提供类的完全限定名,通过Class.forName
静态方法,就能将类信息加载到内存中并且返回Class 对象,通过反射来实例化,见第10行代码。
通过循环beanNames
集合,来实例化需要加载每个类,并将实例化后的对象装入HashMap中(目前需要实例化的类只有包含MyController
和MyService
注解的类)
实例化完成后,ioc容器中的数据如下:
图片中可以看出,hashMap的key都是小写,value已经是对象了。红框表示类中的字段属性,此时可以看到,虽然类已经被实例化了,但是属性还是null,需要进行接下来的依赖注入。
4.4.5 进行依赖注入,给属性赋值
定义一个无参的方法doAutoWrite()
进行依赖注入
private void doAutoWrited() { |
这个方法是通过循环遍历ioc里面的实体,反射找出字段,看看是否有需要注入的标记MyAutoWrited
,如果加了标记,就反射给字段赋值,类型从ioc容器中获取。
4.4.6 加载映射地址
定义一个HashMap
对象urlMapping
用来存放url和方法之间的映射
private Map<String, Method> urlMapping = new HashMap<>(); |
映射地址的作用是根据请求的url匹配method方法
新建doRequestMapping()
方法加载映射地址;
private void doRequestMapping() { |
这里其实就是根据对象反射获取到MyRequestMapping上面的value值@MyRequestMapping("/sayHi")
取到的就是/sayHi
4.5 请求转发和参数解析
在浏览器发起请求localhost:8080/home/sayHi,请求会到达MyDispatherServlet类,由于是GET请求
最终会走到doDispatcherServlet
方法里面处理请求
|
定义doDispatcherServlet
方法处理请求
private void doDispatcherServlet(HttpServletRequest req, HttpServletResponse resp) throws IOException, InvocationTargetException, IllegalAccessException { |
post请求参数处理
处理json格式参数
private Object[] getRequestParam(String json, Method method) { |
处理form参数
private Object[] getRequestParam(Map<String, String[]> map, Method method) { |
5. 结果测试
用浏览器访问/home/sayHi发现可以正常访问
用postman测试form请求可以获取数据
用postman测试json请求也可以获取数据