动手实现SpringMvc

    Spring的IOC是要加载所有的类,并实例化对象。我们要在什么时候进行加载呢?

想了想,MySpringMvc有一个MyDispatcherServlet,Servlet可以设置为在tomcat服务器启动时进行实例化,而Servlet实例化会调用Servlet的init方法。我们不妨将加载类的起点放在init方法中,这样tomcat服务器启动时,所有的类都已加载并且实例化。很是方便。

一、定义注解

    因为我们只有SpringMvc,所以我们只需要定义MyController和MyRequestMapping两个注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
    String value() default "";
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
    String value() default "";
}

    因为MyController只能放在类上,所以定义为Type,而MyRequestMapping可以放在类上也可以放在方法上,所以定义如上。

二、实现MyDispatcherServlet

    1、实现MyDispatcherServlet之前先进行配置,为了方便,我将basePackage定义在web.xml中。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
<servlet>
  <servlet-name>MySpringMvc</servlet-name>
  <servlet-class>com.MySpringMvc.Servlet.MyDispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>application.properties</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>MySpringMvc</servlet-name>
  <url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>

    其实application.properties只有basePackage一项,你也可以直接写在web.xml。无所谓

   2、MyDispatcherServlet的属性如下

//读取配置文件
private Properties properties = new Properties();
//存放类的全路径名
private List<String> classNames = new ArrayList<>();
//类名与实例的映射
private Map<String,Object> ioc = new HashMap<>();
//url和方法的映射,表明是哪个方法
private Map<String,Method> handlerMapping = new HashMap<>();
//url和实例映射,表明是哪个Conyroller
private Map<String,Object> controllerMap = new HashMap<>();

    3、编写init方法

    刚才也说了,init方法要完成类的扫描,类的实例化

@Override
public void init(ServletConfig config) throws ServletException {
    //读取web.xml的参数,加载配置文件
    doLoadConfig(config.getInitParameter("contextConfigLocation"));
    //初始化所有相关联的类,扫描用户设定的包下面所有类
    doScanner(properties.getProperty("scanPackage"));
    //拿到扫描的类,通过反射实例化,放到iocmap    doInstance();
    //初始化HandlerMapping(将urlmethod对应)
    initHandlerMapping();
}

    4、doLoadConfig方法

这个方法主要用于加载配置文件,获取basePackage。实现如下

private void doLoadConfig(String location){
    //web.xml中的contextConfigLocation对应value值的文件加载到流里面
    InputStream is = this.getClass().getClassLoader()
            .getResourceAsStream(location);
    try {
        //使用Properties文件加载文件里的内容
        properties.load(is);
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        if (is != null){
            try {
                is.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

    5、doScanner方法

这个方法便是扫描包下的类,获取每个类的全路径名

public void doScanner(String packageName){
    //url所有的.替换为/
    URL url = this.getClass().getClassLoader()
            .getResource("/"+packageName.replaceAll("\\.","/"));
    File dir = new File(url.getFile());
    for (File file : dir.listFiles()){
        //如果是文件夹,递归扫描
        if (file.isDirectory()){
            doScanner(packageName+"."+file.getName());
        }else {
            String className = packageName+"."+file.getName().replace(".class","");
            this.classNames.add(className);
        }
    }
}

    6、doInstance方法

这个方法就是实例化所有带有MyController注解的类

private void doInstance(){
    if (classNames.isEmpty()){
        return;
    }
    for (String name : classNames){
        try {
            //实例化注解为@MyController
            System.out.println(name);
            Class<?> clazz = Class.forName(name);
            if (clazz.isAnnotationPresent(MyController.class)){
                //首字母小写存入
                ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());
            }else {
                continue;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
private String toLowerFirstWord(String name){
    char[] chars = name.toCharArray();
    chars[0]+=32;
    return String.valueOf(chars);
}

    7、initHandlerMapping方法

这个方法用来完成url与方法的映射以及url和Controller的映射

private void initHandlerMapping(){
    if (ioc.isEmpty()){
        return;
    }
    try {
        for (Map.Entry<String,Object> entry : ioc.entrySet()){
            Class<? extends Object> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(MyController.class)){
                continue;
            }
            String baseUrl = "";
            //如果Controller类也有路径,将此路径加上
            if (clazz.isAnnotationPresent(MyRequestMapping.class)){
                MyRequestMapping annocation = clazz.getAnnotation(MyRequestMapping.class);
                baseUrl = annocation.value();
            }
            Method[] methods = clazz.getMethods();
            for (Method method : methods){
                if (!method.isAnnotationPresent(MyRequestMapping.class)){
                    continue;
                }
                MyRequestMapping annocation = method.getAnnotation(MyRequestMapping.class);
                String url = annocation.value();
                url = (baseUrl+"/"+url).replaceAll("/+","/");
                handlerMapping.put(url,method);
                controllerMap.put(url,entry.getValue());
                System.out.println(url+","+method);
            }
        }
    }catch (Exception e){
        e.printStackTrace();
    }
}

    8、进行请求分发

MyDispatcherServlet拦截所有的请求,并为每一个请求进行分发相应的handler

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

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    try {
        //处理请求
        doDispatch(req,resp);
    }catch (Exception e){
        resp.getWriter().write("500!!!Server Exception");
    }
}

private void doDispatch(HttpServletRequest request,HttpServletResponse response) throws Exception{
    if (handlerMapping.isEmpty()){
        return;
    }
    String uri = request.getRequestURI();
    String contextPath = request.getContextPath();
    uri = uri.replace(contextPath,"").replaceAll("/+","/");
    if (!this.handlerMapping.containsKey(uri)){
        response.getWriter().write("404 Not Found");
        return;
    }
    Method method = this.handlerMapping.get(uri);
    //获取方法参数列表
    Class<?>[] parameterTypes = method.getParameterTypes();
    //获取请求的参数
    Map<String, String[]> parameterMap = request.getParameterMap();
    //保存参数值
    Object[] paramsValues = new Object[parameterTypes.length];
    //方法的参数列表
    for (int i=0;i<parameterTypes.length;i++){
        //根据参数名,进行处理
        String requestParam = parameterTypes[i].getSimpleName();
        if (requestParam.equals("HttpServletRequest")){
            //参数类型已明确
            paramsValues[i] = request;
            continue;
        }
        if (requestParam.equals("HttpServletResponse")){
            paramsValues[i] = response;
        }
        if (requestParam.equals("String")){
            for (Map.Entry<String, String[]> param : parameterMap.entrySet()){
                String value = Arrays.toString(param.getValue())
                        .replaceAll("\\[|\\]","")
                        .replaceAll(",\\s",",");
                paramsValues[i] = value;
            }
        }
    }
    //利用反射调用方法
    try {
        method.invoke(this.controllerMap.get(uri),paramsValues);
    }catch (Exception e){
        e.printStackTrace();
    }
}

    好了,上述实现之后,我们的MySpringMvc就已经实现完成了。下面我们测试一下吧

定义TestController

@MyController
@MyRequestMapping("/test")
public class TestController {
    @MyRequestMapping("/doTest")
    public void test1(HttpServletRequest request, HttpServletResponse response,
                      @MyRequestParam("param") String param){
        System.out.println(param);
        try {
            response.getWriter().write("doTest method success : param:"+param);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @MyRequestMapping("/doTest2")
    public void test2(HttpServletRequest request,HttpServletResponse response){
        try {
            response.getWriter().println("doTest2 success");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

启动tomcat,在地址栏输入http://localhost:8080/MySpringMvc/test/doTest?param=123。结果如下


再次输入http://localhost:8080/MySpringMvc/test/doTest2。结果如下


输入一个不存在的url:http://localhost:8080/MySpringMvc/test/doTest3。结果如下


好了,大功告成

    

猜你喜欢

转载自blog.csdn.net/yanghan1222/article/details/80304613