SpringMVC之手写MVC框架

了解SpringMVC的执行流程以及应用后,我们可以自己自定义一个MVC框架

一、手写MVC框架

1.1 自定义MVC框架分析步骤

  1. tomcat加载web.xml,前端控制器DispatcherServlet加载指定的配置文件springmvc.xml
  2. 进行包扫描,扫描注解@Controller,@Service,@Autowired,@RequestMapping
  3. 初始化ioc容器,bean的初始化,以及维护依赖关系
  4. SpringMVC相关组件的初始化,建立url和Handler方法之间的映射关系-HandlerMapping(处理器映射器)
  5. 等待请求,处理请求

1.2 环境准备

  • pom.xml
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.lagou.edu</groupId>
  <artifactId>springMVC</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>springMVC Maven Webapp</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <!--servlet 依赖-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      	<!--编译插件定义编译细节-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>11</source>
          <target>11</target>
          <encoding>utf-8</encoding>
          <!--告诉编译器,编译的时候记录下形参的真实名称-->
          <compilerArgs>
            <arg>-parameters</arg>
          </compilerArgs>
        </configuration>
      </plugin>
      <!--tomcat7 插件-->
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <port>8080</port>
          <path>/</path>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
  • LagouAutowried注解
package com.lagou.edu.mvcframework.Annotaions;

import java.lang.annotation.*;

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouAutowried {

    String value() default "";
}

  • LagouController注解
package com.lagou.edu.mvcframework.Annotaions;

import java.lang.annotation.*;

@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouController {

    String value() default "";
}

  • LagouRequestMapping注解
package com.lagou.edu.mvcframework.Annotaions;

import java.lang.annotation.*;

@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouRequestMapping {

    String value() default "";
}

  • LagouService注解
package com.lagou.edu.mvcframework.Annotaions;

import java.lang.annotation.*;

@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LagouService {

    String value() default "";
}
  • springmvc.properties配置文件
scanPackage=com.lagou.edu.demo
  • 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>lagoumvc</servlet-name>
        <servlet-class>com.lagou.edu.mvcframework.servlet.LgDispatcherServlet</servlet-class>
        <!--加载配置文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>springmvc.properties</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <!--配置进入前端控制器的请求-->
        <servlet-name>lagoumvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

1.3 自定义DispatcherServlet编写

先编写一些空壳方法,然后我们再一点点的填充里面的实现

package com.lagou.edu.mvcframework.servlet;

import com.lagou.edu.mvcframework.Annotaions.LagouController;
import com.lagou.edu.mvcframework.Annotaions.LagouService;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * 自定义前端控制器
 */
public class LgDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();

    private List<String> classNames = new ArrayList<>();

    private Map<String, Object> ioc = new HashMap<>();

    /**
     * 初始化
     *
     * @param config
     * @throws ServletException
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        //1. 加载配置文件
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        //2. 扫描相关的类,扫描注解
        doScan(properties.getProperty("scanPackage"));

        //3. 初始化ioc容器,基于注解的形式
        doInstance();

        //4. 维护bean之间的依赖关系
        doAutoWired();

        //5. 构造一个HandlerMapping处理器适配器,将配置好的url和handler方法建立映射
        initHandlerMapping();

        System.out.println("lagoumvc 初始化完成 ==============");

    }

    /**
     * 构造一个HandlerMapping处理器适配器
     */
    private void initHandlerMapping() {

    }

    /**
     * 依赖注入
     */
    private void doAutoWired() {

    }

    /**
     * 实例化ioc容器
     */
    private void doInstance() {
    }

    /**
     * 扫描包
     *
     * @param scanPackage
     */
    private void doScan(String scanPackage) {
    }

    /**
     * 加载配置文件
     *
     * @param contextConfigLocation
     */
    private void doLoadConfig(String contextConfigLocation) {

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    /**
     * 处理具体业务逻辑
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    }
}
  • doScan方法
    通过配置文件拿到的文件路径,访问目录下所有文件,以.class结尾的获取全限定类名存储起来
    /**
     * 扫描包
     *
     * @param scanPackage
     */
    private void doScan(String scanPackage) {
        //获取到路径
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");
        File pack = new File(scanPackagePath);
        File[] files = pack.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                //递归
                doScan(scanPackage + "." + file.getName());
            } else if (file.getName().endsWith(".class")) {
                //class文件 获取到类的全限定类名,存储起来
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }
    }
  • doInstance方法
    通过全限定类名进行实例化,获取其类中注解,Controller以类名首字母小写放入容器,Service则以类名和类型放入容器方便匹配
    /**
     * 实例化ioc容器
     */
    private void doInstance() {
        if (classNames.isEmpty()) {
            return;
        }
        for (String className : classNames) {
            try {
                //通过全限定类名获取字节码文件,并实例化
                Class<?> aClass = Class.forName(className);
                Object obj = aClass.newInstance();
                if (aClass.isAnnotationPresent(LagouController.class)) {
                    //controller,简单点只取类名的首字母小写作为id,存入ioc容器中
                    String simpleName = aClass.getSimpleName();
                    //首字母小写
                    simpleName = lowerFirst(simpleName);
                    ioc.put(simpleName, obj);
                }else if (aClass.isAnnotationPresent(LagouService.class)){
                    //service
                    LagouService annotation = aClass.getAnnotation(LagouService.class);
                    //获取注解的值
                    String value = annotation.value();
                    if (!"".equals(value.trim())) {
                        //注解值作为id
                        ioc.put(value, obj);
                    }else {
                        //类名首字母小写作为id
                        value = lowerFirst(aClass.getSimpleName());
                        ioc.put(value, obj);
                    }

                    //service往往是有接口的,还需根据接口名注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        ioc.put(anInterface.getName(), obj);
                    }
                }else {
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 首字母转小写
     *
     * @param str
     * @return
     */
    private String lowerFirst(String str) {
        char[] chars = str.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }
  • doAutoWired
    遍历容器中所有对象,获取属性值,以及属性值的注解,进行属性赋值,完成依赖维护
    /**
     * 依赖注入
     */
    private void doAutoWired() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            Class<?> aClass = value.getClass();
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (!declaredField.isAnnotationPresent(LagouAutowried.class)) {
                    //没有注解
                    continue;
                }
                LagouAutowried annotation = declaredField.getAnnotation(LagouAutowried.class);
                //注解上的值
                String annValue = annotation.value();
                if ("".equals(annValue.trim())) {
                    //没有配置注解值,根据类型获取名称
                    annValue = declaredField.getType().getName();
                }
                //暴力反射
                declaredField.setAccessible(true);
                //给属性赋值
                try {
                    declaredField.set(value, ioc.get(annValue));
                    ioc.put(key, value);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • initHandlerMapping
    同样的对容器进行遍历,获取类上的url和方法上的url,方法中需要的信息比较多我们创建一个handler来存储method相关信息
/**
     * 构造一个HandlerMapping处理器适配器
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {
            return;
        }
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Object value = entry.getValue();
            Class<?> aClass = value.getClass();
            //没有controller注解
            if (!aClass.isAnnotationPresent(LagouController.class)) {
                continue;
            }

            String baseUrl = "";
            if (aClass.isAnnotationPresent(LagouRequestMapping.class)) {
                //获取到controller上的url
                baseUrl = aClass.getAnnotation(LagouRequestMapping.class).value();
            }

            //获取handler方法
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                if (!declaredMethod.isAnnotationPresent(LagouRequestMapping.class)) {
                    //没有注解不进行处理
                    continue;
                }
                LagouRequestMapping annotation = declaredMethod.getAnnotation(LagouRequestMapping.class);
                String methodUrl = annotation.value();
                //最终拼接的url
                String url = baseUrl + methodUrl;
                //把method所有信息和url封装成一个Handler
                Handler handler = new Handler(value, declaredMethod, Pattern.compile(url));

                //计算方法的参数位置信息
                Parameter[] parameters = declaredMethod.getParameters();
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    Class<?> type = parameter.getType();
                    if (type == HttpServletRequest.class || type == HttpServletResponse.class) {
                        //原生api,参数名称直接写类名
                        handler.getParamIndexMapping().put(type.getSimpleName(), i);
                    } else {
                        //参数名
                        handler.getParamIndexMapping().put(parameter.getName(), i);
                    }
                }

                //建立起url和method的映射关系,并存储起来
                handlerMapping.add(handler);
            }
        }
    }
package com.lagou.edu.mvcframework.pojo;


import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

public class Handler {

    /**
     * 实例化的Controller
     */
    private Object controller;

    /**
     * Controller中的方法
     */
    private Method method;

    /**
     * spring中url是支持正则的,方便到时候匹配url
     */
    private Pattern pattern;

    /**
     * 参数顺序,是为了进行参数绑定,key是参数名,value代表是第几个参数 <name,2>
     */
    private Map<String, Integer> paramIndexMapping;

    public Handler(Object controller, Method method, Pattern pattern) {
        this.controller = controller;
        this.method = method;
        this.pattern = pattern;
        this.paramIndexMapping = new HashMap<>();
    }

    public Object getController() {
        return controller;
    }

    public void setController(Object controller) {
        this.controller = controller;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    public Pattern getPattern() {
        return pattern;
    }

    public void setPattern(Pattern pattern) {
        this.pattern = pattern;
    }

    public Map<String, Integer> getParamIndexMapping() {
        return paramIndexMapping;
    }

    public void setParamIndexMapping(Map<String, Integer> paramIndexMapping) {
        this.paramIndexMapping = paramIndexMapping;
    }
}
  • doPost
    这里也是执行业务方法的地方
 /**
     * 处理具体业务逻辑
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取handler
        Handler handler = getHandler(req);
        //handler为空,直接404
        resp.setHeader("Content-type", "text/html;charset=UTF-8");

        if (handler == null) {
            resp.getWriter().write("404 not found");
            return;
        }
        //获取需要执行的handler方法
        Method method = handler.getMethod();
        //获取执行方法的参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        //创建一个长度一致的数组,存放执行方法需要的参数
        Object[] parameterList = new Object[parameterTypes.length];

        //获取请求中的所有参数
        Map<String, String[]> parameterMap = req.getParameterMap();

        //之前存好的参数与位置映射
        Map<String, Integer> paramIndexMapping = handler.getParamIndexMapping();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            //将多个参数通过逗号拼接在一起
            String value = String.join(",", entry.getValue());
            if (paramIndexMapping.containsKey(entry.getKey())) {
                //获取位置关系映射,并赋值
                Integer index = paramIndexMapping.get(entry.getKey());
                parameterList[index] = value;
            }
        }


        //HttpServletRequest和HttpServletResponse单独处理
        int requestIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        parameterList[requestIndex] = req;

        int responseIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        parameterList[responseIndex] = resp;

        try {
            //执行方法
            Object invoke = method.invoke(handler.getController(), parameterList);
            resp.getWriter().write(invoke.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据请求url从HandlerMapping获取handler
     *
     * @param req
     * @return
     */
    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) {
            return null;
        }
        String url = req.getRequestURI();
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(url);
            if (matcher.matches()) {
                return handler;
            }
        }
        return null;
    }

总结: 这里的难点主要是在url和handler方法之间的映射,需要考虑的东西较多.参数之间的处理较为麻烦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值