Spring笔记(03) SpringMVC底层实现模拟

SpringMVC平时用得多,但其底层原理并没具体了解。整个HTTP请求过程,有几个重要的类或方法。

DispatcherServlet接收请求

Controller注解

RequestMapping注解

ViewResolver视图解释器

现在用代码手动实现这些类或注解,更好的了解整个过程。

1、pom文件依赖

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
      <scope>provided</scope>
    </dependency>

    <!-- dom4j -->
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6.1</version>
    </dependency>

2、创建注解 MyController,用来模拟注解 Controller

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyController {

    //该注解模拟Controller注解

    String value() default "";

    //@interface关键字用来定义注解
    //注解「注解」的注解被称作 元注解
    //注解和接口类似,内部可以定义常量和方法,
    //但是方法有限制,不能有参数,返回值只能是基本类型、字符串、Class、枚举、数组,可以包含默认值
    //元注解有四种:
    //1、@Target
    //  -ElementType.PACKAGE 作用与包,-ElementType.TYPE 作用于 类、接口、注解、枚举,
    //  -ElementType.METHOD 作用于方法 ,-ElementType.FIELD 作用于属性
}

3、创建 MyRequestMapping 注解,模拟 RequestMapping

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface MyRequestMapping {
    //该注解模拟RequestMapping,作用于类和方法
    String value() default "";
}

4、创建 MyDispatcherServlet ,模拟 DispatcherServlet

public class MyDispatcherServlet extends HttpServlet {

    //模拟ioc容器,用来存储 Controller 对象
    private Map<String, Object> iocContainer = new HashMap<>();

    private Map<String, Method> handlerMapping = new HashMap<>();

    //自定视图解析器
    private MyViewResolver myViewResolver;

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        super.init();
        //扫描Controller,识别 MyController 注解的类,创建实例对象,并保存到 map 中
        scanController(servletConfig);
        initHandlerMapping();
    }


}

5、扫描 Controller,识别 MyController 注解的类,创建实例对象,并保存到 map 中

    /**
     * 扫描 controller
     * @param servletConfig
     */
    public void scanController(ServletConfig servletConfig){
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read("./src/main/resources/spring-mvc.xml");
            Element root = document.getRootElement();
            Iterator iterator = root.elementIterator();
            while (iterator.hasNext()){
                Element ele = (Element) iterator.next();
                if(ele.getName().equals("component-scan")){
                    String packageName = ele.attributeValue("base-package");
                    //获取包下面所有类名,这个 类名 字符串是拼接了前面包名的
                    List<String> list = getClassNames(packageName);
                    for(String str:list){
                        Class clazz = Class.forName(str);
                        //判断类上是否有MyController注解
                        if(clazz.isAnnotationPresent(MyController.class)){
                            //获取Controller类上 MyRequestMapping 注解,如果没有 MyRequestMapping 注解呢?
                            MyRequestMapping requestMapping = (MyRequestMapping) clazz.getAnnotation(MyRequestMapping.class);
                            //获取MyRequestMapping注解的值
                            String value = requestMapping.value().substring(1);
                            iocContainer.put(value,clazz.newInstance());
                        }
                    }
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }

6、将 Controller类 中 Method 与其 MyRequestMapping 注解关联,然后保存到 handlerMapping 中

    /**
     * 将 Controller类 中 Method 与其 MyRequestMapping 注解关联,然后保存到 handlerMapping 中
     */
    private void initHandlerMapping() {
        //首先遍历保存的Controller,也就是加了MyController注解的类
        for(String key : iocContainer.keySet()){
            //获取到当前类
            Class clazz = iocContainer.get(key).getClass();
            //获取类中所有方法
            Method[] methods = clazz.getMethods();
            for(Method method : methods){
                //判断方法上是否有 MyRequestMapping 注解
                if(method.isAnnotationPresent(MyRequestMapping.class)){
                    //如果有,获取该注解
                    MyRequestMapping myRequestMapping = method.getAnnotation(MyRequestMapping.class);
                    //获取该注解的值
                    String value = myRequestMapping.value().substring(1);
                    //保存该方法
                    handlerMapping.put(value, method);
                }
            }
        }
    }

7、创建自定义的视图解释器

public class MyViewResolver {
    private String prefix;
    private String suffix;

    public String getPrefix() {
        return prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public void setSuffix(String suffix) {
        this.suffix = suffix;
    }
}

8、spring-mvc.xml文件内容

  <context:component-scan base-package="com.guozh.mvc"/>
    <!-- 配置视图解析器 -->
    <bean class="com.guozh.mvc.MyViewResolver">
        <property name="prefix" value="/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

9、实例化自定义的视图解释器,主要用来接收 method 的返回值,然后返回物理视图,完成页面跳转

    /**
     * 实例化自定义的视图解释器,主要用来接收 method 的返回值,然后返回物理视图,完成页面跳转
     */
    public void initViewResolver(){
        //实例化某个对象,如同通过IoC获取对象,底层代码实现是一样的
        SAXReader saxReader = new SAXReader();
        try {
            //读取 spring-mvc 文件
            Document document = saxReader.read("./src/main/resources/spring-mvc.xml");
            Element root = document.getRootElement();
            Iterator iterator = root.elementIterator();
            if(iterator.hasNext()){
                Element element = (Element) iterator.next();
                if(element.getName().equals("bean")){
                    String className = element.attributeValue("class");
                    Class clazz = Class.forName(className);
                    Object object = clazz.newInstance();
                    Method prefixMethod = clazz.getMethod("setPrefix", String.class);
                    Method suffixMethod = clazz.getMethod("setSuffix",String.class);
                    //xml bean 中的属性
                    Iterator beanIter = element.elementIterator();
                    //获取 property 值
                    Map<String,String> propertyMap = new HashMap<String,String>();
                    while (beanIter.hasNext()){
                        Element beanEle = (Element) beanIter.next();
                        String name = beanEle.attributeValue("name");
                        String value = beanEle.attributeValue("value");
                        propertyMap.put(name, value);
                    }
                    for(String str : propertyMap.keySet()){
                        if(str.equals("prefix")){
                            prefixMethod.invoke(object, propertyMap.get(str));
                        }
                        if(str.equals("suffix")){
                            suffixMethod.invoke(object, propertyMap.get(str));
                        }
                    }
                    myViewResolver = (MyViewResolver) object;
                }
            }

        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

以上代码笔记内容来自付费专栏:案例上手 Spring 全家桶

PS:并没有透露关键内容,纯粹是零碎笔记。如果侵犯版权,请联系我。

本文由老郭种树原创,转载请注明:https://guozh.net/springmvc-demo/

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注