spring mvc的运行流程如下图所示
在这里我们只实现基本功能,对流程做一定的简化。我们需要以下几个步骤:
加载配置文件
获取所有的class名称
获取需要注入的实例
缓存方法映射
注入bean
首先,我们需要新建一个web工程,并创建相应注解,以及添加依赖,工程目录如下所示:
由于是手写自己的mvc demo,pom文件中只需要添加servlet的依赖
<dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency>
第一步,加载配置文件,首先在resource目录下新建application.properties文件,并创建自己的servlet,配置web.xml
application.properties文件
#设置扫描根目录,此处为了简化,直接使用properties文件
baseScanPackage=com.study.mvc
自定义的servlet
... // DispatcherServlet类继承HttpServlet,并覆写doGet、doPost方法 public class DispatcherServlet extends HttpServlet { private static final String Context_CONFIG_LOCATON = "contextConfigLocation"; private Properties properties = new Properties(); //缓存读取到的class private Set<String> classNames = new HashSet<>(); //缓存className与class实例的对应关系 private Map<String, Object> iocMap = new HashMap<>(); //缓存url与controller的映射关系 private Map<String, Object> controllerMap = new HashMap<>(); //缓存url与其对应方法的映射关系 private Map<String, Method> handMappingMap = new HashMap<>(); @Override public void init(ServletConfig servletConfig) throws ServletException { String configPath = servletConfig.getInitParameter(Context_CONFIG_LOCATON); try { //加载配置文件 doLoadConfig(configPath); //获取所有class名称 String baseScanPackage = properties.getProperty("baseScanPackage"); doLoadClassNames(baseScanPackage); //获取需要注入的实例 doLoadIocMap(); //方法映射 doHandMapping(); //注入bean doIoc(); } catch (Exception e) { throw new RuntimeException(e); } super.init(); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doDispatcher(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doDispatcher(req, resp); } //实际调用的路由方法 private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) { String url = req.getRequestURI(); System.out.println(url); Method method = handMappingMap.get(url); Object controller = controllerMap.get(url); try { Parameter[] parameters = method.getParameters(); if (parameters != null && parameters.length > 0) { Object[] args = new Object[parameters.length]; //根据requestParam封装参数 for (int i = 0; i < parameters.length; i++) { if (parameters[i].isAnnotationPresent(RequestParam.class)) { RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class); args[i] = req.getParameter(requestParam.value()); continue; } args[i] = null; } method.invoke(controller, args); } else { method.invoke(controller); } resp.getWriter().print("success"); } catch (Exception e) { e.printStackTrace(); } } ... }
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> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>com.study.mvc.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>application.properties</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app>
从classPath下加载配置文件并缓存配置到properties中
private void doLoadConfig(String configPath) throws IOException { InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(configPath); properties.load(inputStream); }
第二步,根据读取到的basePackage获取到package下所有类
private void doLoadClassNames(String basePackage) throws FileNotFoundException { String scanPackage = basePackage.replaceAll("\\.", "/"); URL url = this.getClass().getClassLoader().getResource(String.format("/%s", scanPackage)); if (url == null) { throw new FileNotFoundException(); } File dir = new File(url.getFile()); for (File file : Optional.ofNullable(dir.listFiles()).orElse(new File[]{})) { if (file.isDirectory()) { //如果是目录则递归获取class doLoadClassNames(basePackage + "." + file.getName()); } else { if (file.getName().endsWith("class")) { String className = basePackage + "." + file.getName().replace(".class", ""); classNames.add(className); System.out.println(String.format("load class:%s", className)); } } } }
第三步:获取需要注入的实例,并放入iocMap,在此只处理类上含有@Service或者@Controler的类
private void doLoadIocMap() throws ClassNotFoundException, IllegalAccessException, InstantiationException { for (String className : classNames) { Class clazz = Class.forName(className); //加载含有Controller或者Service注解的bean if (clazz.isAnnotationPresent(Controller.class) || clazz.isAnnotationPresent(Service.class)) { iocMap.put(toLowerCaseFirstName(clazz.getSimpleName()), clazz.newInstance()); } } }
第四步:缓存url和method的对应关系到handMappingMap中,首先找到含有@Controller注解的类,然后读取@RequestMapping的value进行拼接
private void doHandMapping() { iocMap.forEach((className, instance) -> { Class clazz = instance.getClass(); String baseUrl = ""; if (clazz.isAnnotationPresent(Controller.class)) { if (clazz.isAnnotationPresent(RequestMapping.class)) { RequestMapping requestMapping = (RequestMapping) clazz.getAnnotation(RequestMapping.class); baseUrl = requestMapping.value(); } Method[] methods = clazz.getDeclaredMethods(); //拼接@RequestMapping伤的url及其对应的method for (Method method : methods) { if (method.isAnnotationPresent(RequestMapping.class)) { RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); String subUrl = requestMapping.value(); handMappingMap.put(baseUrl + subUrl, method); controllerMap.put(baseUrl + subUrl, instance); } } } }); }
第五步:在controller中注入所需的bean
private void doIoc() { iocMap.forEach((className, instance) -> { Class clazz = instance.getClass(); if (clazz.isAnnotationPresent(Controller.class)) { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(Autowired.class)) { String filedName = field.getName(); //修改field的access属性 field.setAccessible(true); try { field.set(instance, iocMap.get(filedName)); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } }); }
接下来,就可以进行测试了,首先创建DemoController和DemoService,如下所示:
@Controller @RequestMapping("/demo") public class DemoController { @Autowired private DemoService demoService; @RequestMapping("/get") public void demo(@RequestParam("name") String name) { System.out.println(name); demoService.Demo(name); } }
@Service public class DemoService { public void Demo(String name) { System.out.println("=======" + name + "===="); } }
在浏览器访问http://localhost:8080/demo/get?name=li,后台成功打印出name