一、创建Maven工程

创建出来的工程结构如下
java以及resources文件夹为后面新增
二、创建对应的目录结构
其中,cn.spectrumrpc.springmvc为手写的springmvc代码所在,cn.spectrumrpc.test为测试手写mvc的测试代码
annotation包下存放自定义注解,包括@Controller,@Service,@RequestMapping等
context包下存放自定义的一个简单的Spring容器,用来存储@Controller,@Service这些Bean
handler包下存放自定义的Handler,用来保存请求url与Controller的映射关系
servlet包下存放DispatcherServlet,类比于SpringMVC中的中央控制器
controller包下存放测试的Controller类
service包下存放测试的Service类
三、创建自定义注解/mvc配置文件/controller等
3.1 自定义注解
由于自定义注解相对较多,且没啥变化,这里不贴代码,详见最后的工程代码
3.2 mvc配置文件
配置扫描Controller&Service包
<?xml version="1.0" encoding="UTF-8"?><beans><component-scan base-package="com.spectrumrpc.test.controller,com.spectrumrpc.test.service"/></beans>
3.3 Controller
@Controllerpublic class UserController {@Autowired("userService")private UserService userService;@RequestMapping("/hello")public String hello() {return userService.hello();}}
3.4 Service
public interface UserService {String hello();}@Service(value = "userService")public class UserServiceImpl implements UserService {@Overridepublic String hello() {return "hello,my-mvc";}}
四、创建DispatcherServlet,将其注册到web.xml中
web.xml
<!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Application</display-name><servlet><servlet-name>DispatcherServlet</servlet-name><servlet-class>cn.spectrumrpc.springmvc.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:springmvc.xml</param-value></init-param><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>DispatcherServlet</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>
五、创建Web容器,保存springmvc的配置文件路径
public class WebApplicationContext {private String configLocation;public WebApplicationContext() {}public WebApplicationContext(String configLocation) {this.configLocation = configLocation;}}
六、刷新Web容器,将Controller&Service扫描进入到容器中
首先,需要读取springmvc的配置文件,此处通过Dom4J,读取XML配置,获取component-scan对应的节点中的base-package属性(即,这个方法返回的是[com.spectrumrpc.test.controller,com.spectrumrpc.test.service])
XML解析工具
public class XmlParser {public static String getBasePackage(String xml){try {SAXReader saxReader=new SAXReader();InputStream inputStream = XmlParser.class.getClassLoader().getResourceAsStream(xml);//XML文档对象Document document = saxReader.read(inputStream);Element rootElement = document.getRootElement();Element componentScan = rootElement.element("component-scan");Attribute attribute = componentScan.attribute("base-package");String basePackage = attribute.getText();return basePackage;} catch (DocumentException e) {e.printStackTrace();} finally {}return "";}}
将读取到的包路径,进行扫描,保存到容器中
其中,包含如下几步
- 循环所有的包路径,将所在包以及子包下的类,通过反射进行初始化,保存到集合中
- 进行Controller类的属性赋值,将Controller中包含@Autowired的属性,从ioc容器中取出赋值。
- 这几部操作完成之后,此时的beans集合中,即包含了所有的Controller以及Service。 ```java package cn.spectrumrpc.springmvc.context;
import cn.spectrumrpc.springmvc.annotation.Autowired; import cn.spectrumrpc.springmvc.annotation.Controller; import cn.spectrumrpc.springmvc.annotation.Service; import cn.spectrumrpc.springmvc.xml.XmlParser;
import java.io.File; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap; import java.util.Map; public class WebApplicationContext {
private String configLocation;private Map<String, Object> beans = new HashMap<>();public WebApplicationContext() {}public Map<String, Object> getBeans() {return beans;}public void setBeans(Map<String, Object> beans) {this.beans = beans;}public WebApplicationContext(String configLocation) {this.configLocation = configLocation;}public void onRefresh() {//String basePackage = XmlParser.getBasePackage(configLocation.split(":")[1]);String[] packages = basePackage.split(",");for (String pack : packages) {// 扫描包下的类,并通过反射进行初始化scanAndInit(pack);}// 将Controller中的Service属性,从ioc中取出,设置进去propertiesSet();}private void propertiesSet() {try {for (String beanName : beans.keySet()) {Object o = beans.get(beanName);Field[] declaredFields = o.getClass().getDeclaredFields();for (Field declaredField : declaredFields) {if (declaredField.isAnnotationPresent(Autowired.class)) {Autowired autowired = declaredField.getAnnotation(Autowired.class);String value = autowired.value();declaredField.setAccessible(true);declaredField.set(o, beans.get(value));}}}} catch (IllegalAccessException e) {e.printStackTrace();}}private void scanAndInit(String pack) {System.out.println("pack = " + pack);URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.", "/"));String path = url.getFile();File dir = new File(path);for (File f : dir.listFiles()) {if (f.isDirectory()) {//当前是一个文件目录,递归进行初始化scanAndInit(pack + "." + f.getName());} else {//文件目录下文件 获取全路径String className = pack + "." + f.getName().replaceAll(".class", "");System.out.println("className = " + className);try {Class<?> clazz = Class.forName(className);// Controller的名字,默认采用,类名首字母小写,放入容器。Service采用注解配置的valueif (clazz.isAnnotationPresent(Controller.class)) {//控制层 beanString beanName = clazz.getSimpleName().substring(0, 1).toLowerCase() + clazz.getSimpleName().substring(1);beans.put(beanName, clazz.newInstance());} else if (clazz.isAnnotationPresent(Service.class)) {//Service层 beanService serviceAn = clazz.getAnnotation(Service.class);String beanName = serviceAn.value();beans.put(beanName, clazz.newInstance());}} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {e.printStackTrace();}}}}
}
<a name="8fOY4"></a># 七、在DispatcherServlet中进行容器的初始化,以及处理器映射器的初始化在Servlet初始化的时候,进行ioc容器的创建,以及刷新,将Controller以及Service保存到容器中然后。```java@Overridepublic void init() throws ServletException {// 1. 获取 web.xml中配置的init-paramString contextConfigLocation = this.getServletConfig().getInitParameter("contextConfigLocation");// 2. 让ioc容器去加载这个配置文件this.webApplicationContext = new WebApplicationContext(contextConfigLocation);// 3. 刷新容器webApplicationContext.onRefresh();// 4. 保存url与Controller,Method的映射关系initHandlerMappings();}
对应的组件保存到容器中之后,将进行处理器关系的映射。
private void initHandlerMappings() {// 从ioc容器中取出所有的组件Map<String, Object> beans = webApplicationContext.getBeans();for (String key : beans.keySet()) {Object bean = beans.get(key);Method[] declaredMethods = bean.getClass().getDeclaredMethods();// 判断这些组件是否包含了@RequestMapping注解,如果包含了,说明对应请求for (Method declaredMethod : declaredMethods) {if (declaredMethod.isAnnotationPresent(RequestMapping.class)) {// 然后通过Handler保存这些映射关系,让如集合中RequestMapping requestMapping = declaredMethod.getAnnotation(RequestMapping.class);String value = requestMapping.value();MyHandler myHandler = new MyHandler(value, bean, declaredMethod);handlers.add(myHandler);}}}}
当接收到请求时,通过请求路径与Handler中的路径进行比较,找出对应的Handler,如果找不到,则返回404,如果查找到了,则通过反射,调用对应的Method,完成一次请求。最后,通过函数的返回值,以及ResponseBody注解,来判断这个是一个跳转视图的请求,还是一次json前后端分离的请求,通过不同的格式,相应不同的数据。
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {MyHandler handler = getHandler(req);if (handler == null) {resp.getWriter().print("404 NOT FOUNT");} else {try {Method method = handler.getMethod();Object invoke = method.invoke(handler.getController());if (method.isAnnotationPresent(ResponseBody.class)) {if (invoke instanceof String) {resp.getWriter().write(invoke.toString());} else {resp.setContentType("application/json;charset=utf-8");resp.getWriter().write(JSONObject.toJSONString(invoke));}} else {if (invoke.toString().startsWith("redirect:")) {resp.sendRedirect(invoke.toString() + ".jsp");} else {req.getRequestDispatcher(invoke.toString().replaceFirst("forward:", "") + ".jsp").forward(req, resp);}}} catch (IllegalAccessException | IOException | InvocationTargetException | ServletException e) {e.printStackTrace();}}}private MyHandler getHandler(HttpServletRequest request) {String requestURI = request.getRequestURI();for (MyHandler handler : handlers) {if (handler.getUrl().equals(requestURI)) {return handler;}}return null;}
当然,此mvc源码,并未处理controller中的参数问题,
那么,接下来,我们就看看,参数如何进行处理
首先,都知道,通过method.getParameters(),可以获取到这个方法中的所有参数,在通过getName可以获取到参数的名称,但是,如果在默认情况下,获取到的是,arg0,arg1这种东西,根本没用。
在这种情况下,需要开启idea的一个功能,让其可以获取到真实的参数名称,详细步骤如下
在Settings下,添加 -parameters选项,即可获取到真实的参数名
然后,通过req.getParameter(name),根据真实的参数名称,从request中获取到请求参数,再通过反射调用即可。
for (int i = 0; i < parameters.length; i++) {String name = parameters[i].getName();System.out.println("name = " + name);String param = req.getParameter(name);params[i] = param;}Object invoke = method.invoke(handler.getController(), params);



