如何自己构建一个类似springmvc的框架(springmvc原理理解)

构建一个类似springmvc的框架

大致结构:

  • DispatcherServlet,接收所有请求,然后进行分发
  • 自定义Controller注解,RequestMapping注解
  • 设置ViewResolver 视图解析器

大致步骤:

  1. 把controller和注解进行关联,可以使用url找到对应的方法
  2. 解析springmvc.xml进行扫包
  3. 把类和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方法

posted @ 2021-03-15 20:24  TidalCoast  阅读(116)  评论(0编辑  收藏  举报