如何自己构建一个类似springmvc的框架(springmvc原理理解)
构建一个类似springmvc的框架
大致结构:
- DispatcherServlet,接收所有请求,然后进行分发
- 自定义Controller注解,RequestMapping注解
- 设置ViewResolver 视图解析器
大致步骤:
- 把controller和注解进行关联,可以使用url找到对应的方法
- 解析springmvc.xml进行扫包
- 把类和url关联到一起
1.自定义controller注解类
/* 自定义的controller注解类 */ //target的作用是声明该注解可以标记类还是方法还是方法还是属性的,Type代表该注解作用在类上面 //Retention标记该注解什么时候生效,runtime代表运行时生效 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface HainaController { //这其实相当于是一个value属性,用来给controller类设置url String value() default ""; }
可以通过自己设定的注解,来设置controller类,实现在项目中通过url能够访问到这个类
2.自定义requestmapping注解类
//可以用在类上,也可以用在方法上 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface HainaRequestMapping { String value() default ""; }
通过自己设定的注解,这个注解注释的方法,能够在项目中通过url直接调用该方法
3.设置视图解析类
1 /* 2 *视图解析器 3 */ 4 public class HainaViewResolver { 5 6 private String prefix; 7 private String suffix; 8 9 public String jspMapping (String value){ 10 //给视图拼接前缀和后缀 11 return this.prefix + value + this.suffix; 12 } 13 14 public String getPrefix() { 15 return prefix; 16 } 17 18 public void setPrefix(String prefix) { 19 this.prefix = prefix; 20 } 21 22 public String getSuffix() { 23 return suffix; 24 } 25 26 public void setSuffix(String suffix) { 27 this.suffix = suffix; 28 } 29 }
将url中的方法字段传入视图解析器,就可以调用相应的方法
4.自定义分发器DispatcherServlet
1 /** 2 * 自定义的分发器 3 */ 4 public class HainaDispatcherServlet extends HttpServlet { 5 //用来保存url和controller对象的集合 6 private Map<String, Object> controllerMap = new HashMap<>(); 7 //用来保存url和方法之间的映射 8 private Map<String, Method> requestMappingMap = new HashMap<>(); 9 10 private HainaViewResolver resolver = new HainaViewResolver(); 11 12 @Override 13 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 14 doPost(req, resp); 15 } 16 17 @Override 18 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 19 String uri = req.getRequestURI();//获取uri 20 System.out.println(uri); 21 String controllerUri = uri.split("/")[2];//获取controller 22 System.out.println("controller:" + controllerUri); 23 String methodUri = uri.split("/")[3];//获取方法名 24 System.out.println("method:" + methodUri); 25 //通过路径找到对应的controller对象和方法对象 26 Object obj = controllerMap.get(controllerUri); 27 Method method = requestMappingMap.get(methodUri); 28 29 try { 30 //使用反射,调用对应的方法,返回值实际就是页面的名字 31 String result = (String) method.invoke(obj); 32 //把视图名拼接完整 33 String url = resolver.jspMapping(result); 34 //使用请求转发的形式跳转到对应的视图上 35 req.getRequestDispatcher(url).forward(req, resp); 36 } catch (Exception e) { 37 e.printStackTrace(); 38 } 39 } 40 41 //初始化 42 @Override 43 public void init(ServletConfig config) throws ServletException { 44 //1.扫描controller,创建对应的实例对象,存入自定义容器中 45 scanController(config); 46 //2.初始化url和方法映射的容器 47 initRequestMapping(); 48 49 resolver.setPrefix("/"); 50 resolver.setSuffix(".jsp"); 51 } 52 53 //解析springmvc.xml文件,扫描文件中配置的包名中的使用了HainaController注解标注的类 54 private void scanController(ServletConfig config) { 55 SAXReader reader = new SAXReader(); 56 //这里其实是在读取web.xml文件的配置,通过web.xml文件,读取到对应springmvc 57 //文件的名称,然后拼接好完整路径 58 String path = config.getServletContext().getRealPath("") 59 + "\\WEB-INF\\classes\\" 60 + config.getInitParameter("contextConfigLocation"); 61 System.out.println("path:" + path); 62 try { 63 Document root = reader.read(path); 64 Element rootElement = root.getRootElement(); 65 66 Iterator it = rootElement.elementIterator(); 67 while (it.hasNext()) { 68 Element next = (Element) it.next(); 69 if ("component-scan".equals(next.getName())) { 70 //获取要扫描的包名 71 String packageName = next.attributeValue("base-package"); 72 73 //有了包名之后,我们就需要根据包名,找到该包下的所有类名,然后根据类名 74 //创建出对应的class对象 75 List<String> list = getClassNameList(packageName); 76 for (String str : list) { 77 //使用类反射,将类名加载进内存,转换成class类型对象 78 Class clazz = Class.forName(str); 79 //判断是否使用了HainaController注解标注 80 if (clazz.isAnnotationPresent(HainaController.class)) { 81 //获取当前类上标注的注解,获取到之后,我们可以获取该注解中配置的路径 82 HainaController annotation = (HainaController) clazz.getAnnotation(HainaController.class); 83 //路径都用/开头,所以在获取的时候,我们把斜杠过滤掉 84 String value = annotation.value().substring(1); 85 86 //这样,路径以及它对应的类就都有了,我们可以把路径作为key,类的对象作为value,存入map中 87 //方便之后我们使用url来找对应的类和方法 88 controllerMap.put(value, clazz.newInstance()); 89 } 90 } 91 } 92 } 93 } catch (Exception e) { 94 e.printStackTrace(); 95 } 96 System.out.println("controllerMap:" + controllerMap); 97 } 98 99 //获取所有类名 100 public List<String> getClassNameList(String packageName) { 101 String packagePath = packageName.replace(".", "/"); 102 //获取类加载器,通过类加载器去找文件路径 103 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 104 URL url = loader.getResource(packagePath);//获取文件的路径 105 List<String> result = new ArrayList<>(); 106 if (url != null) { 107 File file = new File(url.getPath()); 108 //获取当前包下的所有文件 109 File[] files = file.listFiles(); 110 for (File child : files) { 111 //拼接完整的包名加类名 112 String className = packageName + "." + child.getName() 113 .replace(".class", ""); 114 result.add(className); 115 } 116 } 117 System.out.println(result); 118 return result; 119 } 120 121 //初始化url和方法的映射关系 122 private void initRequestMapping() { 123 //遍历controllerMap,获取里面的所有key 124 for (String str : controllerMap.keySet()) { 125 //通过key,取出对应的值,然后调用getclass方法,把对象转换成class类型 126 //因为只有class类型的对象,才可以获取该类里的所有方法 127 Class<?> clazz = controllerMap.get(str).getClass(); 128 129 Method[] methods = clazz.getMethods(); 130 //遍历每个方法,看看方法有没有被requestmapping注解标注 131 for (Method method : methods) { 132 if (method.isAnnotationPresent(HainaRequestMapping.class)) { 133 //获取当前方法的requestMapping注解对象 134 HainaRequestMapping annotation = method.getAnnotation(HainaRequestMapping.class); 135 System.out.println("annotation:" + annotation); 136 String value = annotation.value().substring(1); 137 //把url和方法对象,添加到map中 138 requestMappingMap.put(value, method); 139 } 140 } 141 } 142 System.out.println("requestMappingMap:" + requestMappingMap); 143 } 144 }
springmvc本质上就是通过一个servlet类来接收所有的请求,并且将这些请求分发到指定注解的controller类中,再调用controller类中指定注解的方法。
5.springmvc.xml文件的配置
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <beans> 3 <component-scan base-package="com.haina.springmvc"></component-scan> 4 5 <bean class="com.haina.springmvc.HainaViewResolver"> 6 <property name="prefix" value="/"></property> 7 <property name="suffix" value=".jsp"></property> 8 </bean> 9 </beans>
6.web.xml文件的配置
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" 5 version="4.0"> 6 7 <servlet> 8 <servlet-name>myDispatcher</servlet-name> 9 <servlet-class>com.haina.springmvc.HainaDispatcherServlet</servlet-class> 10 <init-param> 11 <param-name>contextConfigLocation</param-name> 12 <param-value>springmvc.xml</param-value> 13 </init-param> 14 </servlet> 15 <servlet-mapping> 16 <servlet-name>myDispatcher</servlet-name> 17 <url-pattern>/</url-pattern> 18 </servlet-mapping> 19 </web-app>
7.测试,创建testcontroller类进行测试
1 @HainaController("/haina") 2 public class TestController { 3 4 @HainaRequestMapping("/test") 5 public String abc(){ 6 System.out.println("come in controller"); 7 return "test"; 8 } 9 }
测试结果:
成功访问controller类,并且调用了abc方法