纯手写SpringMVC逻辑代码

项目地址
SpringMVC_03
觉得博主还可以给个Star

项目目录
在这里插入图片描述

pom.xml

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.0</version>
</dependency>

动手写代码
首先,看过前面的文章(SpringMVC源码分析------关键源码分析),都应该知道SpringMVC的容器注册,还有前端访问请求的处理了。
那下面我们开始来一步一步的实现SpringMVC的逻辑

  1. 容器注册
    前面可能没有提及到容器的注册,在这里补充一下吧。
    SpringMVC的核心分发器DispatcherServlet继承自HttpServletBean,HttpServletBean重写了Servlet的init方法
    那么我们就使用HttpServletBean继承HttpServlet来重写init()方法。
    创建HttpServletBean.java并继承HttpServlet,我们再看到源码中的HttpServletBean的init()方法。
    在这里插入图片描述调用了initServletBean()方法,那么我们也调用这个方法。并且可以发现在HttpServletBean中initServletBean()只是个模板方法,我们一样模仿
    在这里插入图片描述HttpServletBean.java
package com.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

/**
 * @author 龙小虬
 * @date 2021/3/15 10:20
 */
public class HttpServletBean extends HttpServlet {
    
    

    @Override
    public void init() throws ServletException {
    
    
        initServletBean();
    }

    protected void initServletBean() {
    
    
    }

}

再来寻找,谁重写了initServletBean()方法,
在这里插入图片描述这里可以看到是FrameworkServlet重写了此方法,我们进去看看,他重写的方法主要做的什么。
在这里插入图片描述进去看initWebApplicationContext()方法
在这里插入图片描述他调用了onRefresh()方法,并且这个方法在本类中是个抽象方法
在这里插入图片描述那么我们创建FrameworkServlet,java并加入如下代码

package com.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 龙小虬
 * @date 2021/3/15 10:28
 */
public class FrameworkServlet extends HttpServletBean{
    
    
    @Override
    protected void initServletBean() {
    
    
        onRefresh();
    }

    protected void onRefresh() {
    
    
    }
}

我们可以发现,这个方法被DispatcherServlet重写。
在这里插入图片描述那么我们进入去看看
可以看到onRefresh()调用了initStrategies(),而initStrategies()就在下面,并且有一大堆的initxxx(),但是我们之前在上一篇文章提到过RequestMappingHandlerMapping,这个和下面的HandlerMappings很相似吧,其实这个就是信息注册。
在这里插入图片描述那么我们就可以自己创建DispatcherServlet.java

public class DispatcherServlet extends FrameworkServlet{
    
    
    @Override
    protected void onRefresh() {
    
    
        initStrategies();
    }

    protected void initStrategies() {
    
    
        initHandlerMappings();
    }

    private void initHandlerMappings() {
    
    
        // 初始化容器
        System.out.println(">>>初始化initHandlerMappings对象<<<");
    }
}

好了,容器创建的步骤就出来了,我们再去看看容器是利用什么方法进行创建的。
我们利用SpringMVC源码分析------关键源码分析的代码来测试查看,在DispatcherServlet的initHandlerMappings()方法之中打开断点调试。
在这里插入图片描述debug可以看到。在handlerMappings中有注册我们的url数据。并且数据来自RequestMappingHandlerMapping类。
在这里插入图片描述那么我们就利用RequestMappingHandlerMapping这个类来进行数据注入,将url,method存放(因为项目只是demo,所以拦截器什么的,在此处已忽略)

那么我们怎么去识别他是否为接口类呢?当然是使用注解去识别。我们一般使用的注解有:@ComponentScan、@RequestMapping、@Controller。我们先创建这三个注解。

package com.annotation;

import java.lang.annotation.*;

/**
 * @author 龙小虬
 * @date 2021/3/15 10:41
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({
    
    ElementType.TYPE})
@Documented
public @interface ComponentScan {
    
    
    String value();
}
package com.annotation;

import java.lang.annotation.*;

/**
 * @author 龙小虬
 * @date 2021/3/15 10:43
 */
@Target({
    
    ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    
    
    String value();
}
package com.annotation;

import java.lang.annotation.*;

/**
 * @author 龙小虬
 * @date 2021/3/15 10:39
 */
@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
    
    
}

我们现在创建RequestMappingHandlerMapping.java,并进行逻辑处理,将url,method注入。并且实现查询功能,以url为key在map集合中查询。

package com.web;

import com.annotation.ComponentScan;
import com.annotation.Controller;
import com.annotation.RequestMapping;
import com.config.SpringMVCConfig;
import com.method.HandlerMethod;
import com.utils.ReflexUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author 龙小虬
 * @date 2021/3/15 11:30
 */
public class RequestMappingHandlerMapping {
    
    

    private final Map<String, HandlerMethod> registryMapping = new HashMap<String, HandlerMethod>();

    // 初始化mvc容器
    public void registryMapping(){
    
    
        // 1.获取@ComponentScan注解的类名
        ComponentScan declaredAnnotation = SpringMVCConfig.class.getDeclaredAnnotation(ComponentScan.class);
        // 若没有就跳出
        if(declaredAnnotation == null){
    
    
            return;
        }
        String springmvcPackage = declaredAnnotation.value();
        // 有注解但没有value跳出
        if(StringUtils.isEmpty(springmvcPackage)){
    
    
            return;
        }
        // 2.使用java反射机制获取类上加有@Controller注解的类
        Set<Class<?>> classes = ReflexUtils.getClasses(springmvcPackage);
        // 3.遍历每一个类 查找类上是否加有RequestMapping注解
        for (Class<?> c: classes) {
    
    
            // 查找类上的注解
            Controller controller = c.getDeclaredAnnotation(Controller.class);
            // 若没有就查看下一个类
            if(controller == null){
    
    
                continue;
            }
            // 获取含有@Controller的类的所有方法
            Method[] declaredMethods = c.getDeclaredMethods();
            for (Method m : declaredMethods) {
    
    
                RequestMapping requestMapping = m.getDeclaredAnnotation(RequestMapping.class);
                // 若方法上含有@RequestMapping就获取其value
                if(requestMapping != null){
    
    
                    String url = requestMapping.value();
                    // 获取value并且将value和对象 put到registryMapping 对象必须实例化
                    registryMapping.put(url,new HandlerMethod(m,newInstance(c)));
                }
            }
        }
    }

    /**
     * 在registryMapping中通过url查找相应的对象和方法
     * @param url
     * @return
     */
    public HandlerMethod getHandler(String url) {
    
    
        return registryMapping.get(url);
    }

    private Object newInstance(Class classInfo) {
    
    
        try {
    
    
            Object value = classInfo.newInstance();
            return value;
        } catch (Exception e) {
    
    
            return null;
        }
    }
}

在上面的类中有使用一个工具类,java反射工具类ReflexUtils。

ReflexUtils.java

package com.utils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author 龙小虬
 * @date 2021/3/15 10:45
 */
public class ReflexUtils {
    
    

    /**
     * 从包package中获取所有的Class
     *
     * @param pack
     * @return
     */
    public static Set<Class<?>> getClasses(String pack) {
    
    

        // 第一个class类的集合
        Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
        // 是否循环迭代
        boolean recursive = true;
        // 获取包的名字 并进行替换
        String packageName = pack;
        String packageDirName = packageName.replace('.', '/');
        // 定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
    
    
            dirs = Thread.currentThread().getContextClassLoader().getResources(
                    packageDirName);
            // 循环迭代下去
            while (dirs.hasMoreElements()) {
    
    
                // 获取下一个元素
                URL url = dirs.nextElement();
                // 得到协议的名称
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
    
    
                    System.err.println("file类型的扫描");
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath,
                            recursive, classes);
                } else if ("jar".equals(protocol)) {
    
    
                    // 如果是jar包文件
                    // 定义一个JarFile
                    System.err.println("jar类型的扫描");
                    JarFile jar;
                    try {
    
    
                        // 获取jar
                        jar = ((JarURLConnection) url.openConnection())
                                .getJarFile();
                        // 从此jar包 得到一个枚举类
                        Enumeration<JarEntry> entries = jar.entries();
                        // 同样的进行循环迭代
                        while (entries.hasMoreElements()) {
    
    
                            // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            // 如果是以/开头的
                            if (name.charAt(0) == '/') {
    
    
                                // 获取后面的字符串
                                name = name.substring(1);
                            }
                            // 如果前半部分和定义的包名相同
                            if (name.startsWith(packageDirName)) {
    
    
                                int idx = name.lastIndexOf('/');
                                // 如果以"/"结尾 是一个包
                                if (idx != -1) {
    
    
                                    // 获取包名 把"/"替换成"."
                                    packageName = name.substring(0, idx)
                                            .replace('/', '.');
                                }
                                // 如果可以迭代下去 并且是一个包
                                if ((idx != -1) || recursive) {
    
    
                                    // 如果是一个.class文件 而且不是目录
                                    if (name.endsWith(".class")
                                            && !entry.isDirectory()) {
    
    
                                        // 去掉后面的".class" 获取真正的类名
                                        String className = name.substring(
                                                packageName.length() + 1, name
                                                        .length() - 6);
                                        try {
    
    
                                            // 添加到classes
                                            classes.add(Class
                                                    .forName(packageName + '.'
                                                            + className));
                                        } catch (ClassNotFoundException e) {
    
    
                                            // log
                                            // .error("添加用户自定义视图类错误 找不到此类的.class文件");
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
    
    
                        // log.error("在扫描用户定义视图时从jar包获取文件出错");
                        e.printStackTrace();
                    }
                }
            }
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }

        return classes;
    }
    /**
     * 以文件的形式来获取包下的所有Class
     *
     * @param packageName
     * @param packagePath
     * @param recursive
     * @param classes
     */
    public static void findAndAddClassesInPackageByFile(String packageName,
                                                        String packagePath, final boolean recursive, Set<Class<?>> classes) {
    
    
        // 获取此包的目录 建立一个File
        File dir = new File(packagePath);
        // 如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
    
    
            // log.warn("用户定义包名 " + packageName + " 下没有任何文件");
            return;
        }
        // 如果存在 就获取包下的所有文件 包括目录
        File[] dirfiles = dir.listFiles(new FileFilter() {
    
    
            // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            public boolean accept(File file) {
    
    
                return (recursive && file.isDirectory())
                        || (file.getName().endsWith(".class"));
            }
        });
        // 循环所有文件
        for (File file : dirfiles) {
    
    
            // 如果是目录 则继续扫描
            if (file.isDirectory()) {
    
    
                findAndAddClassesInPackageByFile(packageName + "."
                                + file.getName(), file.getAbsolutePath(), recursive,
                        classes);
            } else {
    
    
                // 如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0,
                        file.getName().length() - 6);
                try {
    
    
                    // 添加到集合中去
                    //classes.add(Class.forName(packageName + '.' + className));
                    //这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
                    classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
                } catch (ClassNotFoundException e) {
    
    
                    // log.error("添加用户自定义视图类错误 找不到此类的.class文件");
                    e.printStackTrace();
                }
            }
        }
    }
}

现在我们写完了扫描的逻辑处理,但是扫包范围、Controller都没有。我们先创建一个扫包管理的config。
SpringMVCConfig.java

package com.config;

import com.annotation.ComponentScan;

/**
 * @author 龙小虬
 * @date 2021/3/15 10:47
 */
@ComponentScan("com.controller")
public class SpringMVCConfig {
    
    
}

MyController.java

package com.controller;

import com.annotation.Controller;
import com.annotation.RequestMapping;

/**
 * @author 龙小虬
 * @date 2021/3/15 10:42
 */
@Controller
public class MyController {
    
    

    @RequestMapping("/pay")
    public String test(){
    
    
        return "test";
    }
}

好了,现在我们来进行前端访问的逻辑处理。
我们都知道DispatcherServlet是前端控制器。所有逻辑都在这里。而且在SpringMVC源码分析------关键源码分析我们也提到过。他是先经过FrameworkServlet重写了HttpServlet的service(),然后在调用doService()。DispatcherServlet再重写doService(),之后调用doDispatch()。那么我们需要先完善FrameworkServlet。加入代码之后:
FrameworkServlet.java

package com.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 龙小虬
 * @date 2021/3/15 10:28
 */
public class FrameworkServlet extends HttpServletBean{
    
    
    @Override
    protected void initServletBean() {
    
    
        onRefresh();
    }

    protected void onRefresh() {
    
    
    }

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

    protected void doService(HttpServletRequest req, HttpServletResponse resp) {
    
    
    }
}

DispatcherServlet.java增加代码。重写doService方法,并调用doDispatch()。初始化容器

package com.servlet;

import com.method.HandlerMethod;
import com.view.ModelAndView;
import com.web.RequestMappingHandlerMapping;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 龙小虬
 * @date 2021/3/15 10:32
 */
public class DispatcherServlet extends FrameworkServlet{
    
    

    RequestMappingHandlerMapping requestMappingHandlerMapping;

    public DispatcherServlet() {
    
    
        requestMappingHandlerMapping = new RequestMappingHandlerMapping();
    }


    @Override
    protected void onRefresh() {
    
    
        initStrategies();
    }

    protected void initStrategies() {
    
    
        initHandlerMappings();
    }

    private void initHandlerMappings() {
    
    
        // 初始化容器
        System.out.println(">>>初始化initHandlerMappings对象<<<");
        requestMappingHandlerMapping.registryMapping();
    }

    @Override
    protected void doService(HttpServletRequest req, HttpServletResponse resp) {
    
    
        try {
    
    
            doDispatch(req,resp);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    
    
    }
 }

在之前文章详细的了解到了doDispatch()进行的逻辑处理(这里只进行简单的处理,所以只有几步)

  1. 获取url
  2. 查找url
  3. 执行目标方法
  4. 渲染页面

首先获取urlString url = req.getRequestURI();
再来查找url是否存在HandlerExecutionChain handler = getHandler(url);
源码中getHandler()方法分离了。

public HandlerExecutionChain getHandler(String url) {
    
    
    HandlerMethod handler = requestMappingHandlerMapping.getHandler(url);
    if(handler == null){
    
    
        return null;
    }
    return new HandlerExecutionChain(handler);
}

在查找url的时候获取的具体handler,是利用进行了包装,并且使用了反射执行目标方法,我们先写出HandlerExecutionChain.java

package com.servlet;

import com.method.HandlerMethod;
import com.view.ModelAndView;

import java.lang.reflect.Method;

/**
 * @author 龙小虬
 * @date 2021/3/15 14:32
 */
public class HandlerExecutionChain {
    
    
    HandlerMethod handlerMethod;

    public HandlerExecutionChain(HandlerMethod handlerMethod) {
    
    
        this.handlerMethod = handlerMethod;
    }

    public ModelAndView handler() throws Exception{
    
    
        Method method = handlerMethod.getMethod();
        Object bean = handlerMethod.getBean();
        // 因为我们的contrller只用了string,所以直接强转了
        String invoke = (String)method.invoke(bean, null);
        ModelAndView modelAndView = new ModelAndView(invoke);
        return modelAndView;
    }
}

ModelAndView.java

package com.view;

/**
 * @author 龙小虬
 * @date 2021/3/15 14:47
 */
public class ModelAndView {
    
    

    private String view;

    public void setView(String view) {
    
    
        this.view = view;
    }

    public String getView() {
    
    
        return view;
    }

    public ModelAndView(String view) {
    
    
        this.view = view;
    }
}

在我们没有找到url的情况下,源码中的是直接调用了noHandlerFound()方法,那么我们就直接使用他的,删除日志打印即可。

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response){
    
    
    try{
    
    
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        response.getWriter().print("没有查找到该请求");
    }catch (Exception e){
    
    

    }
}

页面渲染我们直接利用getRequestDispatcher。

private void render(ModelAndView mv, HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
    String view = mv.getView();
    req.getRequestDispatcher("/WEB-INF/view/" + view + ".jsp").forward(req, resp);
}

所以最后我们的doDispatch()方法就写完了。

private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
    
    
     // 对请求进行解析
     // 1.获取请求的url
     String url = req.getRequestURI();
     // 2.查找请求的url是否存在 获取具体的handler
     HandlerExecutionChain handler = getHandler(url);
     if(handler == null){
    
    
         noHandlerFound(req,resp);
         return;
     }
     // 3.执行对应的目标方法
     ModelAndView mv = handler.handler();
     // 4.渲染页面
     render(mv,req,resp);
 }

目前关于逻辑处理就全部写好了,但是,应该会发现,我们之前使用SpringMVC的时候,需要将DispatcherServlet注入容器内。我们现在没有xml,怎么进行注入呢?在之前SpringMVC源码分析------基础知识(二)中提到过,不使用xml怎么进行启动。
我们直接继承ServletContainerInitializer,在使用@HandlesTypes来定义感兴趣的类。之后利用反射的方法执行感兴趣的类中的onStartup()方法
创建SpringServletContainerInitializer.java

package com.web;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import java.lang.reflect.Method;
import java.util.Set;

/**
 * @author 龙小虬
 * @date 2021/3/15 11:06
 */
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    
    

    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
    
    
        for (Class<?> classInfo : c) {
    
    
            try {
    
    
                // 使用Java反射技术执行onStartup方法
                Object object = classInfo.newInstance();
                Method onStartup = classInfo.getMethod("onStartup", ServletContext.class);
                onStartup.invoke(object, ctx);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }

        }
    }
}

WebApplicationInitializer.java

package com.web;

import javax.servlet.ServletContext;

/**
 * @author 龙小虬
 * @date 2021/3/15 11:08
 */
public interface WebApplicationInitializer {
    
    
    void onStartup(ServletContext servletContext);
}

创建AbstractDispatcherServletInitializer.java并实现接口WebApplicationInitializer

package com.web.impl;

import com.servlet.DispatcherServlet;
import com.web.WebApplicationInitializer;

import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;

/**
 * @author 龙小虬
 * @date 2021/3/15 11:10
 */
public class AbstractDispatcherServletInitializer implements WebApplicationInitializer {
    
    

    @Override
    public void onStartup(ServletContext servletContext) {
    
    
        ServletRegistration.Dynamic dynamic = servletContext.addServlet("dispatcherServlet", new DispatcherServlet());
        dynamic.addMapping("/");
        System.out.println("反射启动onStartup()方法");
    }
}

这个时候代码就直接写完了,但是有问题。少了一个很死板的配置
在这里插入图片描述是的,就是这个玩意。文件中配置com.web.SpringServletContainerInitializer
然后再创建test.jsp,注意是在view下。
test.jsp

<html>
<body>
<h2>test</h2>
</body>
</html>

猜你喜欢

转载自blog.csdn.net/weixin_43911969/article/details/114926643
今日推荐