从0开始仿写一个SpringMvc框架(1)

本文章只是仿写SpringMvc框架的第一篇,后续会增加Aop、HandlerInterceptor、Valid、扫描xml文件等功能。

首先说下仿写SpringMvc(mini)需要实现的几个核心功能:实现Ioc容器(Beanfactory),扫描注解解析出需要处理的类(controller、bean、RequestMapping、AutoWired注解等),实现SpringMvc核心的DispatcherServlet以及Handler等。

本项目基于idea开发

1、建立项目,新建一个maven工程导入相关依赖,主要是tomcat的依赖和配置打成jar包后程序的入口

 
    <dependencies>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
            <version>8.5.34</version>
        </dependency>

    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.2</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                        implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.dy.minispringMvc.MiniSpringMvcApp</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

注意:mainClass是你自己的main方法所在类,这里主要是为了配置jar包的程序入口。

打包时候点击左上角的Maven然后点击install即可,打完后的包可以直接运行。

2、整合tomcat服务器,这里**注意 TomcatServer.this.tomcat.getServer().await();**开启tomcat服务等待。不然运行的时候刷一下就过去了。

package com.dy.minispringMvc.core;

import com.dy.minispringMvc.Servlet.CustomDispatcherServlet;
import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.startup.Tomcat;

public class TomcatServer {
    private Tomcat tomcat;
    private String[] args;

    public TomcatServer(String[] args) {
        this.args = args;
    }

    //把自定义servlet注册到tomcat容器当中
    public void startServer() throws LifecycleException {
        tomcat = new Tomcat();
        tomcat.setPort(7788);
        tomcat.start();
        //创建一个Context对象
        Context context = new StandardContext();
        context.setPath("");
        context.addLifecycleListener(new Tomcat.FixContextListener());
        
        //实例化我们自己的CustomDispatcherServlet
        CustomDispatcherServlet servlet = new CustomDispatcherServlet();
        
        //注册我们自己的CustomDispatcherServlet到tomcat
        tomcat.addServlet(context, "servlet", servlet).setAsyncSupported(true);
        
        //设置映射,这里是7788端口下所有路径都会到我们自己的CustomDispatcherServlet 
        context.addServletMappingDecoded("/", "servlet");
        tomcat.getHost().addChild(context);

        TomcatServer.this.tomcat.getServer().await();

    }


}

下面是自己实现的CustomDispatcherServlet类,实现Servlet的service方法即可


public class CustomDispatcherServlet implements Servlet {
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    public ServletConfig getServletConfig() {
        return null;
    }

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        servletResponse.getWriter().println("hello SpringMc");
    }

    public String getServletInfo() {
        return null;
    }

    public void destroy() {

    }
}

直接运行,然后可以看到tomcat打印信息,证明tomcat整合完成
在这里插入图片描述在这里插入图片描述
浏览器访问7788端口下任意路径看到如上图,证明第一步整合tomcat容器成功。

2、创建核心类CustomClassLoader,这个类的目的很简单,就是拿到基于当前main方法所在类的所有Class。


/**
 * 自定义类加载 目的就是拿到当前jar包下所有java.lang.Class
 * <p>
 * 类的加载3个阶段
 * <p>
 * 通过一个类的全限定名来获取定义此类的二进制字节流
 * 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
 * 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
 * <p>
 */
public class CustomClassLoader {
    public static List<Class<?>> loadeClass(String packageName) throws IOException, ClassNotFoundException {
        List<Class<?>> classList = new ArrayList<Class<?>>();
        String path = packageName.replace(".", "/");
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        Enumeration<URL> resources = contextClassLoader.getResources(path);
        while (resources.hasMoreElements()) {
            URL url = resources.nextElement();//file:/C:/Users/root/Desktop/study/mini_springMvc/target/classes/com/dy/minispringMvc
            //只对jar包进行处理
            if (url.getProtocol().contains("jar")) {
                JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                String jarPath = jarURLConnection.getJarFile().getName();//拿到jar包绝对路径  C:\Users\root\Desktop\study\mini_springMvc\target\mini_springMvc-1.0-SNAPSHOT-shaded.jar
                classList.addAll(getClassFromJar(jarPath, path));
            } else {
                //其他暂时不做处理
            }
        }
        return classList;
    }

    private static List<Class<?>> getClassFromJar(String jarPath, String path) throws IOException, ClassNotFoundException {
        List<Class<?>> classList = new ArrayList<Class<?>>();
        JarFile jarFile = new JarFile(jarPath);
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            String name = jarEntry.getName();//com/dy/minispringMvc/core/CustomClassLoader.class  这个jar包下的资源路径
            //如果是class文件,并且是我们建的class,非依赖
            if (name.startsWith(path) && name.endsWith(".class")) {
                //去掉.class后缀
                String className = name.replace("/", ".").substring(0, name.length() - 6);//com.dy.minispringMvc.core.CustomClassLoader
                classList.add(Class.forName(className));
            }
        }
        return classList;
    }
}

代码很简单就是通过类的全限定名称,拿到当前jar包下所有java.lang.Class对象,只要知道java.lang.Class是什么就行,代码上方注解已经有说明。

我们拿到所有java.lang.Class对象就可以为所欲为了。

3、创建框架基础容器,创建CustomBeanfactory,这里需要说一下控制反转(IOC)和依赖注入(DI)的区别,控制反转通俗来说就是对Bean的控制交给框架去管理,是一种思想。而依赖注入则是一种手段,让Bean的创建去依赖容器,从容器当中拿Bean。好处就是只需要new一次,如果需要就去容器当中拿即可。

首先来看几个自定义注解:
注意:controller也是一个特殊的Bean

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

}

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

}

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

}

定义好了注解我们才能知道哪些类需要交给容器管理
CustomBeanfactory:


public class CustomBeanfactory {
    private static Map<Class<?>, Object> beanMap = new ConcurrentHashMap<Class<?>, Object>();

    public static Object getBean(Class<?> cla) {
        return beanMap.get(cla);
    }

    public static void ininBean(List<Class<?>> classes) throws InstantiationException, IllegalAccessException {
        List<Class<?>> classList = new ArrayList<Class<?>>(classes);
        while (!classList.isEmpty() && classList.size() > 0) {
            //记录一下需要扫描的总数
            int size = classList.size();

            for (int i = 0; i < classList.size(); i++) {
                if (crateBean(classList.get(i))) {
                    classList.remove(i);
                }
            }
            //如果出现相互依赖的情况,则抛出一个异常
            if (size == classList.size()) {
                throw new RuntimeException("创建失败,暂未解决相互依赖问题");
            }
        }
    }

    private static boolean crateBean(Class<?> cla) throws IllegalAccessException, InstantiationException {
        //如果是不需要处理的类,则直接删除
        if (!cla.isAnnotationPresent(CustomBean.class) && !cla.isAnnotationPresent(CustomController.class)) {
            return true;
        }
        Object obj = cla.newInstance();
        Field[] declaredFields = cla.getDeclaredFields();
        //设置需要创建的对象的属性
        for (Field field : declaredFields) {
            //如果是CustomAutoWired注解修饰的属性,则从我们的BeanFactory取,取不到则返回false,因为while循环,会在后面循环创建成功
            //找当前的依赖,当依赖创建好以后,直接从beanMap取即可。
            if (field.isAnnotationPresent(CustomAutoWired.class)) {
                Class<?> fieldType = field.getType();
                Object fieldObj = beanMap.get(fieldType);
                if (fieldObj == null) {
                    return false;
                }
                //将取到的依赖set到需要创建的bean中
                //大坑 这里必须设置为true,不然不能进行set
                field.setAccessible(true);
                field.set(obj, fieldObj);
            }
        }
        beanMap.put(cla, obj);
        return true;
    }
}

4、创建Mvc框架的核心Handle和HandlerManager管理类

同样的先看几个简单注解
注意:这两个注解和上面注解不同的是CustomRequestMapping用于方法上的注解,CustomResqusetParam注解是用于属性上的注解。其中CustomResqusetParam的value属性是参数的key值,目的是为了方便用req.getParameter(“key”)取到请求参数,CustomRequestMapping的value则是映射的UrL

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CustomRequestMapping {
    String value();
}

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface CustomResqusetParam {
    String value();
}

继续使用我们CustomClassLoader.loadeClass(packName)方法拿到的所有Class对象为所欲为。

HandlerManager类,这个类主要是扫描出所有CustomController注解的类,把他变成一个Handler

**
 * 创建所有Url和handler的映射
 */
public class HandlerManager {
    private static Map<String, HandlerMapping> handlerMappingMap = new ConcurrentHashMap<String, HandlerMapping>();

    public static HandlerMapping getHandler(String url) {
        return handlerMappingMap.get(url);
    }

    public static void resolveMappingHandler(List<Class<?>> classList) {
        for (Class<?> cls : classList) {
            if (cls.isAnnotationPresent(CustomController.class)) {
                parseHandlerFromController(cls);
            }
        }
    }

    private static void parseHandlerFromController(Class<?> cla) {
        Method[] methods = cla.getMethods();
        for (Method method : methods) {
            if (!method.isAnnotationPresent(CustomRequestMapping.class)) {
                continue;
            }
            String url = method.getDeclaredAnnotation(CustomRequestMapping.class).value();
            List<String> parmasKey = new ArrayList<>();
            for (Parameter parameter : method.getParameters()) {
                if (!parameter.isAnnotationPresent(CustomResqusetParam.class)) {
                    continue;
                }
                parmasKey.add(parameter.getDeclaredAnnotation(CustomResqusetParam.class).value());
            }
            parmasKey.toArray();
            String[] args = parmasKey.toArray(new String[parmasKey.size()]);
            HandlerMapping handlerMapping = new HandlerMapping(url, method, cla, args);
            handlerMappingMap.put(url, handlerMapping);
        }

    }

}

HandlerMapping 类

public class HandlerMapping {
    private String url;
    private Method method;
    private Class<?> controller;
    //请求参数的key
    private String[] paramKey;

    private String[] paramValue;

    public HandlerMapping(String url, Method method, Class<?> controller, String[] args) {
        this.url = url;
        this.method = method;
        this.controller = controller;
        this.paramKey = args;
    }

    public boolean handler(HttpServletRequest req, HttpServletResponse res) throws InvocationTargetException, IllegalAccessException, IOException {
        paramValue=new String[paramKey.length];
        for (int i = 0; i < paramKey.length; i++) {
            paramValue[i] = req.getParameter(paramKey[i]);
        }
        Object bean = CustomBeanfactory.getBean(controller);
        Object response = method.invoke(bean, paramValue);
        res.getWriter().println(response.toString());
        return true;
    }
}

最后,改造一下我们的CustomDispatcherServlet类的service方法,让这个类完成他的目的,分发

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        HttpServletRequest httpServletRequest= (HttpServletRequest) servletRequest;
        HandlerMapping handler = HandlerManager.getHandler(httpServletRequest.getRequestURI());
       if (handler!=null){
           HttpServletResponse httpServletResponse= (HttpServletResponse) servletResponse;
           try {
               handler.handler(httpServletRequest,httpServletResponse);
           } catch (InvocationTargetException e) {
               e.printStackTrace();
           } catch (IllegalAccessException e) {
               e.printStackTrace();
           }
       }
    }

下面是main方法代码:


public class MiniSpringMvcApp {
    public static void main(String[] args) throws LifecycleException, IOException {
        System.out.println("打包");
        System.out.println(MiniSpringMvcApp.class.getPackage().getName());
        try {
            List<Class<?>> classList = CustomClassLoader.loadeClass(MiniSpringMvcApp.class.getPackage().getName());
            CustomBeanfactory.ininBean(classList);
            HandlerManager.resolveMappingHandler(classList);
        } catch (Exception e) {
            e.printStackTrace();
        }

        TomcatServer tomCatServer=new TomcatServer(args);
        tomCatServer.startServer();
    }
}

注意:测试时一定要打成jar包后测试,注解使用方法和SpringMvc框架一样,请自行测试

本文章只是仿写SpringMvc框架的第一篇,后续会增加Aop、HandlerInterceptor、Valid、扫描xml文件等功能。

gitHup地址:https://github.com/dengyu123456/mini_springMvc.git

原创文章 3 获赞 7 访问量 1057

猜你喜欢

转载自blog.csdn.net/qq_30765051/article/details/102493962