从零到一实现SpringMVC

目标:

  实现SpringMVC的基本功能。主要包括:

  • ChenController 控制类实现
  • ChenService 服务类实现
  • ChenQualifier 自动装配功能的实现
  • ChenRequestMapping 地址映射功能实现
  • ChenRequestParam 请求参数映射功能实现

实现:

思路:

  自定义ChenDispatcherServlet,完成请求分发的功能。大致步骤如下:

  • 首先要根据一个基本包进行扫描,扫描里面的子包以及子包下的类,将全类名添加到List集和中。
  • 其次要把扫描出来的类进行实例化。以全类名为keyclass实例对象为value,放入到hashMap容器中。
  • 依赖注入,把service层的实例注入到controller,即初始化controller层的成员变量。
  • 建立pathmethod的映射关系,保存在hashMap容器中。

一、前期准备

  实现自定义注解,并用自定义注解模拟正常的业务逻辑,实现将用户发送给服务器的数据回写给用户的功能。

1、加入依赖

  本项目SpringMVC是对servlet的封装,所以需要引入servletjar包。源码如下:

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.njust.springmvcchen</groupId>
    <artifactId>springmvcchen</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>springmvcchen Maven Webapp</name>

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

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>3.0-alpha-1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>springmvcchen</finalName>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                    <configuration>
                        <source>8</source>
                        <target>8</target>
                        <!--反射获取方法参数名称-->
                        <compilerArgument>-parameters</compilerArgument>
                    </configuration>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

2、自定义控制层注解

  定义ChenController 类,使得该注解的作用范围在接口或类上 。同时保证注解会在class字节码文件中存在,在运行时可以通过反射获取到。设置value属性值,默认为空。源码如下:

ChenController .java

package com.njust.springmvc.annotation;

import java.lang.annotation.*;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:00
 * @description:
 */
@Target({ElementType.TYPE}) //作用范围:用在接口或类上
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Documented//明该注解将被包含在javadoc中
public @interface ChenController {
    String value() default "";
}


3、自定义服务层注解

  定义ChenService 类,和控制层功能基本一致,定义该注解作用范围在接口或类上 。同时保证 注解会在class字节码文件中存在,在运行时可以通过反射获取到,设置value属性值,默认为空。源码如下:

ChenService .java

package com.njust.springmvc.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:06
 * @description:
 */
@Target({java.lang.annotation.ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenService {
    String value() default "";
}


4、自定义依赖注入注解

  定义ChenQualifier类,和控制层功能基本一致,定义该注解作用范围在字段、枚举的常量上 。同时保证 注解会在class字节码文件中存在,在运行时可以通过反射获取到,设置value属性值,默认为空。源码如下:

ChenQualifier .java

package com.njust.springmvc.annotation;

import java.lang.annotation.*;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:04
 * @description:
 */
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenQualifier {
    String value() default "";

}

5、自定义地址映射注解

  定义ChenRequestMapping 类,和控制层功能基本一致,定义该注解作用范围在字段、枚举的常量以及方法上 。同时保证 注解会在class字节码文件中存在,在运行时可以通过反射获取到,设置value属性值,默认为空。源码如下:

ChenRequestMapping .java

package com.njust.springmvc.annotation;

import java.lang.annotation.*;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:04
 * @description:
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenRequestMapping {
    //路径ChenRequestMapping(value)
    String value() default "";
}


6、自定义请求参数映射注解

  定义ChenRequestParam 类,和控制层功能基本一致,定义该注解作用范围在方法参数上 。同时保证注解会在class字节码文件中存在,在运行时可以通过反射获取到,设置value属性值,默认为空。源码如下:

ChenRequestParam .java

package com.njust.springmvc.annotation;

import java.lang.annotation.*;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:04
 * @description:
 */
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChenRequestParam {
    String value() default "";
}


7、业务服务接口

  定义BaseServiceUse 类,模拟服务的基本操作,此处是查询、新增、更新的功能。源码如下:

BaseServiceUse .java

package com.njust.springmvc.service;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:08
 * @description:
 */
public interface BaseServiceUse {

    String query(String name, String age);

    String insert(String param);

    String update(String param);
}


8、业务服务实现

  定义BaseServiceUseImpl类,该类十分简单,实现BaseServiceUse 接口里面的方法,直接返回字符串数据,模拟从数据库取得的信息。使用自定义注解ChenService,将该类注入到容器中。源码如下:

BaseServiceUseImpl.java

package com.njust.springmvc.service.impl;

import com.njust.springmvc.annotation.ChenService;
import com.njust.springmvc.service.BaseServiceUse;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:08
 * @description:
 */
@ChenService(value = "baseServiceUseImpl")
public class BaseServiceUseImpl implements BaseServiceUse {
    @Override
    public String query(String name, String age) {
        return "{name=" + name + ",age=" + age + "}";
    }

    @Override
    public String insert(String param) {
        return "insert successful.....";
    }

    @Override
    public String update(String param) {
        return "update successful.....";
    }
}


9、业务控制层

  定义BaseControllerUse 类,通过自定义注解ChenQualifierBaseServiceUse自动装配到ChenController实例中。使用自定义注解ChenController,将该类注入到容器中。使用自定义注解ChenRequestMapping,将请求映射到不同的方法实现。其中query()函数,直接将用户发送过来的数据回写给用户,功能十分简单。源码如下:

BaseControllerUse .java

package com.njust.springmvc.controller;

import com.njust.springmvc.annotation.ChenController;
import com.njust.springmvc.annotation.ChenQualifier;
import com.njust.springmvc.annotation.ChenRequestMapping;
import com.njust.springmvc.annotation.ChenRequestParam;
import com.njust.springmvc.service.BaseServiceUse;

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

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:10
 * @description:
 */
@ChenController(value = "baseControllerUse")
//@ChenRequestMapping(value = "/chen")
public class BaseControllerUse {
    @ChenQualifier(value = "baseServiceUseImpl")
    private BaseServiceUse baseServiceUse;

    @ChenRequestMapping(value = "/query")
    public void query(HttpServletRequest request, HttpServletResponse response,
                      @ChenRequestParam(value = "name") String name,
                      @ChenRequestParam(value = "age") String age) {

        try {
            PrintWriter writer = response.getWriter();
            String result = baseServiceUse.query(name, age);
            writer.write(result);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @ChenRequestMapping("/insert")
    public void insert(HttpServletRequest request,
                       HttpServletResponse response) {
        try {
            PrintWriter pw = response.getWriter();
            String result = baseServiceUse.insert("0000");

            pw.write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @ChenRequestMapping("/update")
    public void update(HttpServletRequest request,
                       HttpServletResponse response, String param) {
        try {
            PrintWriter pw = response.getWriter();
            String result = baseServiceUse.update(param);

            pw.write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}


10、通用工具类

  定义CommonUtil 类,实现子类获取等功能。源码如下:

CommonUtil .java

package com.njust.springmvc.utils;


/**
 * @author Chen
 * @version 1.0
 * @date 2020/4/1 14:59
 * @description:
 */
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
public class CommonUtil {

    //获取某个类的实现类
    public static List<Class<?>> getAllAssignedClass(Class<?> cls) throws Exception {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        for (Class<?> c : getClasses(cls)) {
            if (cls.isAssignableFrom(c) && !cls.equals(c)) {
                classes.add(c);
            }
        }
        return classes;
    }

    public static List<Class<?>> getClasses(Class<?> cls) throws Exception {
        String pk = cls.getPackage().getName();
        String path = pk.replace('.', '/');
        ClassLoader classloader = Thread.currentThread().getContextClassLoader();
        URL url = classloader.getResource(path);
        return getClasses(new File(url.getFile()), pk);
    }

    //根据路径获取
    public static List<Class<?>> getClasses(File dir, String pk) throws ClassNotFoundException {
        List<Class<?>> classes = new ArrayList<Class<?>>();
        if (!dir.exists()) {
            return classes;
        }
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {
                classes.addAll(getClasses(f, pk + "." + f.getName()));
            }
            String name = f.getName();
            if (name.endsWith(".class")) {
                classes.add(Class.forName(pk + "." + name.substring(0, name.length() - 6)));
            }
        }
        return classes;
    }

    //动态获取,根据反射,比如获取xx.xx.xx.xx.Action 这个所有的实现类。 xx.xx.xx.xx 表示包名  Action为接口名或者类名
    public static List<Class<?>> getAllActionSubClass(String classPackageAndName) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Field field = null;
        Vector v = null;
        Class<?> cls = null;
        List<Class<?>> allSubclass = new ArrayList<Class<?>>();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class<?> classOfClassLoader = classLoader.getClass();
        cls = Class.forName(classPackageAndName);
        while (classOfClassLoader != ClassLoader.class) {
            classOfClassLoader = classOfClassLoader.getSuperclass();
        }
        field = classOfClassLoader.getDeclaredField("classes");
        field.setAccessible(true);
        v = (Vector) field.get(classLoader);
        for (int i = 0; i < v.size(); ++i) {
            Class<?> c = (Class<?>) v.get(i);
            if (cls.isAssignableFrom(c) && !cls.equals(c)) {
                allSubclass.add((Class<?>) c);
            }
        }
        return allSubclass;
    }
}


}


二、请求分发

  拦截用户请求并分发给响应的控制器进行处理。

1、自定义DispatcherServlet

  自定义DispatcherServlet类,实现根据一个基本包进行扫描,扫描里面的子包以及子包下的类,将全类名添加到List集和中的功能。其次要把扫描出来的类进行实例化。以全类名为keyclass实例对象为value,放入到hashMap容器中。然后实行依赖注入,把service层的实例注入到controller,即初始化controller层的成员变量。最后建立pathmethod的映射关系,保存在hashMap容器中。由于我们的SpringMVC是基于servlet实现的,所以ChenDispatcherServlet要继承HttpServlet。源码如下:

ChenDispatcherServlet.java

package com.njust.springmvc.servlet;

import com.njust.springmvc.annotation.ChenController;
import com.njust.springmvc.annotation.ChenQualifier;
import com.njust.springmvc.annotation.ChenRequestMapping;
import com.njust.springmvc.annotation.ChenService;
import com.njust.springmvc.controller.BaseControllerUse;
import com.njust.springmvc.handlerAdapter.HandlerAdapterService;
import com.sun.xml.internal.ws.util.StringUtils;

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.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 16:16
 * @description:
 */
public class ChenDispatcherServlet extends HttpServlet {



    private static final long serialVersionUID = 1L;

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

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

    Map<String, Object> handlerMap = new HashMap<String, Object>();
    //    将具体的方法和controller关联
    Map<String, Object> handlerControllerMap = new HashMap<String, Object>();

    Properties prop = null;

    private static String HANDLERADAPTER = "jamesHandlerAdapter";


    public void init(ServletConfig config) {
        // 1、我们要根据一个基本包进行扫描,扫描里面的子包以及子包下的类
        scanPackage("com.njust");

        System.out.println("scanPackage(\"com.njust\");");
        for (String className : classNames) {
            System.out.println(className);
        }
        // 2、我们肯定是要把扫描出来的类进行实例化
        instance();

        // 3、依赖注入,把service层的实例注入到controller
        ioc();

        // 4、建立一个path与method的映射关系
        HandlerMapping();
        System.out.println("HandlerMapping();");
        for (Map.Entry<String, Object> entry : handlerMap.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        /*
         * InputStream is = this.getClass()
         * .getResourceAsStream("/config/properties/spring.properties"); prop =
         * new Properties(); try { prop.load(is); } catch (IOException e) {
         * e.printStackTrace(); }
         */
    }

    private void scanPackage(String basePackage) {
        //扫描编译好的类路径下所有的类
        URL url = this.getClass().getClassLoader().getResource("/" + replaceTo(basePackage));

        String fileStr = url.getFile();

        File file = new File(fileStr);
        //拿到所有类com.njust 下的 com.njust.springmvc 文件夹
//        list()方法是返回某个目录下的所有文件和目录的文件名,返回的是String数组
        String[] filesStr = file.list();

        for (String path : filesStr) {
            File filePath = new File(fileStr + path);//扫描com.njust.springmvc下的所有class类

            //递归调用扫描,如果是路径,继续扫描
            if (filePath.isDirectory()) {
                // com.njust.com.njust.springmvc
                scanPackage(basePackage + "." + path);
            } else {
                classNames.add(basePackage + "." + filePath.getName());//如果是class文件则加入List集合(待生成bean)
            }
        }
    }

    private String replaceTo(String basePackage) {
//        英文点号(.)表示任意字符 所以需要转义
        return basePackage.replaceAll("\\.", "/");
    }


    public ChenDispatcherServlet() {
    }

    private void instance() {
        if (classNames.size() <= 0) {
            System.out.println("包扫描失败!");
            return;
        }
        //遍历扫描到的class文件,将需要实例化的类(加了注解的类)进行反射创建对象(像注解就不需要实例化)
        for (String className : classNames) {
            // com.njust.com.njust.springmvc.service.impl.BaseServiceUseImpl.class
            String cn = className.replace(".class", "");

            try {
                Class<?> clazz = Class.forName(cn);//拿到class类,用来实例化
                // 将扫描到的类,获取类名,并判断是否标记了ChenController注解
//                注释ChenController是否在此clazz上。如果在则返回true;不在则返回false。
                if (clazz.isAnnotationPresent(ChenController.class)) {
                    //调用构造方法
                    Object instance = clazz.newInstance();
                    //获取对应的请求路径"/chen"
//                    ChenRequestMapping requestMapping = clazz
//                            .getAnnotation(ChenRequestMapping.class);
//                    String rmvalue = requestMapping.value();//得到"/chen"请求路径
                    //用路径做为key,对应value为实例化对象
//                    beans.put(rmvalue, instance);

//                    注意:此处改进版本是使用全限定名
                    ChenController chenController = clazz.getAnnotation(ChenController.class);

                    if (chenController.value() == null || chenController.value().equals("")) {
                        beans.put(instance.getClass().getName(), instance);
                    } else {
                        beans.put(chenController.value(), instance);
                    }


                } else if (clazz.isAnnotationPresent(ChenService.class)) {
                    //获取当前clazz类的注解(通过这个注解可得到当前service的id)  @com.njust.springmvc.annotation.ChenService(value=baseServiceUseImpl)
                    ChenService service = clazz.getAnnotation(ChenService.class);
                    Object instance = clazz.newInstance();
                    //put(baseServiceUseImpl,instance)

//                    默认使用全限定名
                    if (service.value() == null || service.value().equals("")) {
                        beans.put(instance.getClass().getName(), instance);
                    } else {
                        beans.put(service.value(), instance);
                    }


                } else {
                    continue;
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
     * response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        this.doPost(request, response);
    }

    private void errorDeal(HttpServletResponse response, String message) {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            writer.write(new String(message.getBytes("UTF-8"), StandardCharsets.UTF_8));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
     * response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //获取到请求路径   /springmvcchen/chen/query
        String uri = request.getRequestURI();

        String context = request.getContextPath();
        //将  "/springmvcchen/chen/query"  去掉"/springmvcchen"
        String path = uri.replace(context, "");
        //根据请求路径来获取要执行的方法 
        Method method = (Method) handlerMap.get(path);

        if (method == null) {
            errorDeal(response, "请检查路径是否设置正确!");
            return;
        }

        //拿到控制类
//        BaseControllerUse instance = (BaseControllerUse) beans.get("/" + path.split("/")[1]);
//        BaseControllerUse instance = (BaseControllerUse) beans.get(path);
        Object instance = handlerControllerMap.get(path);

        if (instance == null) {
            errorDeal(response, "controller 请检查参数是否设置正确!");
            return;
        }

        //处理器
        HandlerAdapterService ha = (HandlerAdapterService) beans.get(HANDLERADAPTER);


        /*
         * @RequestMapping("/order")
         * order(@RequestBody String params, @RequestHeader @RequestParam String param1){//参数有多种类型接收方式
         * }
         */


        Object[] args = ha.hand(request, response, method, beans);

        try {
            method.invoke(instance, args);
            // method.invoke(instance, new
            // Object[]{request,response,null});//拿参数
			
			/*如果有多个参数类型,就得这样写了(可用策略模式,省去以下代码)
			 * if(ParamType == HttpServletRequest){
				
			}else if(ParamType == @RquestHeader){
				
			}else
			*用策略模式实现(把粒度控制得更细),新建 ChenHandlerAdapter
			*/


        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }

    private void HandlerMapping() {
        if (beans.entrySet().size() <= 0) {
            System.out.println("没有类的实例化!");
            return;
        }

        for (Map.Entry<String, Object> entry : beans.entrySet()) {
            Object instance = entry.getValue();

            Class<?> clazz = instance.getClass();
            // 拿所有Controller的类
            if (clazz.isAnnotationPresent(ChenController.class)) {
                String classPath = "";

                //防止用户在类上没有写 ChenRequestMapping
                if (clazz.isAnnotationPresent(ChenRequestMapping.class)) {
                    //@com.njust.springmvc.annotation.ChenRequestMapping(value=/chen)
                    ChenRequestMapping requestMapping = clazz
                            .getAnnotation(ChenRequestMapping.class);
                    // 获取Controller类上面的ChenRequestMapping注解里的请求路径
                    classPath = requestMapping.value();
                }
                // 获取控制类里的所有方法
                Method[] methods = clazz.getMethods();
                // 获取方法上的ChenRequestMapping设置的路径,与方法名建立映射关系
                for (Method method : methods) {
                    //判断哪些方法上使用ChenRequestMapping路径注解
                    if (method.isAnnotationPresent(ChenRequestMapping.class)) {
                        //@com.njust.springmvc.annotation.ChenRequestMapping(value=/query)
                        ChenRequestMapping methodrm = method
                                .getAnnotation(ChenRequestMapping.class);
                        String methodPath = methodrm.value();
                        // 把方法上与路径建立映射关系( /Chen/query--->public void com.njust.springmvc.controller.ChenController.query )
                        handlerMap.put(classPath + methodPath, method);
                        //将具体的路径和controller实例关联
                        handlerControllerMap.put(classPath + methodPath, instance);
                    } else {
                        continue;
                    }
                }
            }
        }
    }

    // 初始化IOC容器
    private void ioc() {

        if (beans.entrySet().size() <= 0) {
            System.out.println("没有类的实例化!");
            return;
        }
        //将实例化好的bean遍历,
        for (Map.Entry<String, Object> entry : beans.entrySet()) {
            Object instance = entry.getValue();//获取bean实例

            Class<?> clazz = instance.getClass();//获取类,用来判断类里声明了哪些注解(主要是针对控制类里的判断,比如使用了@Autowired  @Qualifier,对这些注解进行解析)
            //判断该类是否使用了ChenController注解 或ChenService注解,注解类允许注入
            if (clazz.isAnnotationPresent(ChenController.class) || clazz.isAnnotationPresent(ChenService.class)) {
                Field[] fields = clazz.getDeclaredFields();// 拿到类里面的属性
                // 判断是否声明了自动装配(依赖注入)注解,比如@Autrowired @Qualifier
                for (Field field : fields) {
                    if (field.isAnnotationPresent(ChenQualifier.class)) {
                        ChenQualifier qualifier = field.getAnnotation(ChenQualifier.class);
                        //拿到@ChenQualifier("ChenServiceImpl")里的指定要注入的bean名字"ChenServiceImpl"
                        String value = qualifier.value();
//                        通用化,如果不设置值,则使用默认值
                        if (value == null || value.equals("")) {
//                            使用全类名
                            value = field.getType().getName();

                            try {
//                                根据全类名获取class文件
                                Class<?> aClass = Class.forName(value);
//                                判断该类是否是接口
                                if (aClass.isInterface()) {
//                                    查看接口的所有实现类
                                    List<Class<?>> allActionSubClass = CommonUtil.getAllActionSubClass(value);
                                    if (allActionSubClass.size() >= 2 || allActionSubClass.size() <= 0) {
                                        System.exit(0);
                                        throw new RuntimeException("接口没有实现或接口子类有多个实现!");

                                    }

                                    value = (allActionSubClass.get(0).getName());
                                }
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (NoSuchFieldException e) {
                                e.printStackTrace();
                            }
                        }
                        field.setAccessible(true);
                        try {
                            // 从MAP容器中获取"ChenServiceImpl"对应的bean,并注入实例控制层bean,解决依赖注入
//                            给instance对象的field字段赋值 beans.get(value)
                            field.set(instance, beans.get(value));
                        } catch (IllegalArgumentException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    } else {
                        continue;
                    }
                }
            } else {
                continue;
            }
        }
    }


}


2、web.xml

  设置启动容器时刻初始化ChenDispatcherServlet,映射路径为/。源码如下:

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>

  <servlet>
    <servlet-name>ChenDispatcherServlet</servlet-name>
    <servlet-class>com.njust.springmvc.servlet.ChenDispatcherServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>ChenDispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>


3、参数解析

  定义ArgumentResolver接口类,解析方法中的参数,获取数值并返回给调用者。源码如下:

ArgumentResolver .java

package com.njust.springmvc.argumentResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:52
 * @description:
 */
public interface ArgumentResolver {
    public boolean support(Class<?> type, int paramIndex, Method method);

    //参数解析方法
    public Object argumentResolver(HttpServletRequest request,
                                   HttpServletResponse response, Class<?> type,
                                   int paramIndex,//参数索引下坐标,有很多注解,你得知道是哪个参数的注解,每个参数的索引顺序不一样
                                   Method method);
}


4、HttpServletRequest参数解析

  定义HttpServletRequestArgumentResolver 类,实现ArgumentResolver接口。support()主要用来判断传进来的类型是否是ServletRequest类或它的子类。因为这是一个request参数解析类,所以argumentResolver()方法直接返回request参数给调用者。源代码如下:

HttpServletRequestArgumentResolver .java

package com.njust.springmvc.argumentResolver;

import com.njust.springmvc.annotation.ChenService;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:53
 * @description:
 */

@ChenService("httpServletRequestArgumentResolver")
public class HttpServletRequestArgumentResolver implements ArgumentResolver {
    //判断传进来的参数是否为request
    public boolean support(Class<?> type, int paramIndex, Method method) {
//        isAssignableFrom()方法是判断是否为某个类的父类
        /**
         * class1.isAssignableFrom(class2) 判定此 Class1 对象所表示的类或接口与
         * 指定的 Class2 参数所表示的类或接口是否相同,
         * 或是否是其超类或超接口。
         * 如果是则返回 true;否则返回 false。
         */
        return ServletRequest.class.isAssignableFrom(type);
    }
    如果返回的参数是request,则直接返回
    public Object argumentResolver(HttpServletRequest request,
                                   HttpServletResponse response, Class<?> type, int paramIndex,
                                   Method method) {
        return request;
    }

}


5、HttpServletResponse参数解析

  定义HttpServletResponseArgumentResolver类,同HttpServletRequestArgumentResolver类似,实现ArgumentResolver接口。support()主要用来判断传进来的类型是否是ServletResponse类或它的子类。因为这是一个response参数解析类,所以argumentResolver()方法直接返回response参数给调用者。源码如下:

HttpServletResponseArgumentResolver .java

package com.njust.springmvc.argumentResolver;

import com.njust.springmvc.annotation.ChenService;

import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:54
 * @description:
 */
@ChenService("httpServletResponseArgumentResolver")
public class HttpServletResponseArgumentResolver implements ArgumentResolver {
    //判断传进来的参数是否为response
    public boolean support(Class<?> type, int paramIndex, Method method) {
        return ServletResponse.class.isAssignableFrom(type);
    }
    //如果返回的参数是response,则直接返回
    public Object argumentResolver(HttpServletRequest request,
                                   HttpServletResponse response, Class<?> type, int paramIndex,
                                   Method method) {
        return response;
    }

}


6、RequestParam参数解析

  定义RequestParamArgumentResolver 类,同HttpServletRequestArgumentResolver类似,实现ArgumentResolver接口。support()主要用来判断传进来的类型是否加了ChenRequestParam注解或它的子类注解。argumentResolver()方法判断是否使用了ChenRequestParam 注解,如果没有使用,直接返回null,如果使用了,首先判断value是否为空,如果不为空,直接通过request获取参数值并返回,如果为空,则通过java反射机制获取参数名称,然后再通过request获取参数值并返回。这里注意要判断jdk的版本,这个方法只是在JDK1.8以后才支持,同时在maven中一定要配置<compilerArgument>-parameters</compilerArgument>,才能使得该方法生效。源码如下:

RequestParamArgumentResolver .java

package com.njust.springmvc.argumentResolver;

import com.njust.springmvc.annotation.ChenRequestParam;
import com.njust.springmvc.annotation.ChenService;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:54
 * @description:
 */

@ChenService("requestParamArgumentResolver")
//解析声明注解为RequestParam, 获取注解的值
public class RequestParamArgumentResolver implements ArgumentResolver {
    //判断传进来的参数是否为ChenRequestParam
    public boolean support(Class<?> type, int paramIndex, Method method) {

        Annotation[][] an = method.getParameterAnnotations();

        Annotation[] paramAns = an[paramIndex];

        for (Annotation paramAn : paramAns) {
            //判断传进的paramAn.getClass()是不是 ChenRequestParam 类型
            if (ChenRequestParam.class.isAssignableFrom(paramAn.getClass())) {
                return true;
            }
        }
        return false;
    }
    //参数解析,并获取注解的值
    public Object argumentResolver(HttpServletRequest request,
                                   HttpServletResponse response, Class<?> type, int paramIndex,
                                   Method method) {

        Annotation[][] an = method.getParameterAnnotations();

        Annotation[] paramAns = an[paramIndex];

        for (Annotation paramAn : paramAns) {
            if (ChenRequestParam.class.isAssignableFrom(paramAn.getClass())) {
                ChenRequestParam rp = (ChenRequestParam)paramAn;

                String value = rp.value();
                if (value == null || value.equals("")) {
                    //如果是JDK1.8以后,使用反射获取参数名称
                    if (System.getProperty("java.version").contains("1.8.")) {
                        Parameter parameter = method.getParameters()[paramIndex];
                        String name = parameter.getName();
                        value = name;
                    }
                }

                return request.getParameter(value);
            }
        }

        return null;
    }

}

7、请求适配

  定义HandlerAdapterService 类,封装请求处理。源码如下:

HandlerAdapterService .java

package com.njust.springmvc.handlerAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:56
 * @description:
 */
public interface HandlerAdapterService {

    Object[] hand(HttpServletRequest request,//拿request请求里的参数
                         HttpServletResponse response,//
                         Method method,
                         Map<String, Object> beans);
}


8、请求适配实现

  定义JamesHandlerAdapter类,实现根据用户请求的URL和方法,获取到参数的值并返回。源码如下:

JamesHandlerAdapter .java

package com.njust.springmvc.handlerAdapter;

import com.njust.springmvc.annotation.ChenService;
import com.njust.springmvc.argumentResolver.ArgumentResolver;

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

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/29 17:56
 * @description:
 */

@ChenService("jamesHandlerAdapter")
public class JamesHandlerAdapter implements HandlerAdapterService {
    //对method方法里的参数进行处理
    public Object[] hand(HttpServletRequest request,//需要传入request,拿请求的参数
                         HttpServletResponse response, Method method,//执行的方法,可以拿到当前待执行的方法有哪些参数
                         Map<String, Object> beans) {
        //拿到当前待执行的方法有哪些参数类型
        Class<?>[] paramClazzs = method.getParameterTypes();
        //根据参数的个数,new 一个参数的数组,将方法里的所有参数赋值到args来
        Object[] args = new Object[paramClazzs.length];

        //1、要拿到所有实现了ArgumentResolver这个接口的实现类
        Map<String, Object> argumentResolvers = getBeansOfType(beans,
                ArgumentResolver.class);

        int paramIndex = 0;
        int i = 0;
        //对每一个参数进行循环,每个参数都有特殊处理(比如RequestParam的处理类为 RequestParamArgumentResolver )
        for (Class<?> paramClazz : paramClazzs) {
            //哪个参数对应了哪个参数解析类,用策略模式来找
            for (Map.Entry<String, Object> entry : argumentResolvers.entrySet()) {
                ArgumentResolver ar = (ArgumentResolver) entry.getValue();

                if (ar.support(paramClazz, paramIndex, method)) {
                    args[i++] = ar.argumentResolver(request,
                            response,
                            paramClazz,
                            paramIndex,
                            method);
                }
            }
            paramIndex++;
        }

        return args;
    }

    //获取实现了ArgumentResolver接口的所有实例(其实就是每个参数的注解实例)
    private Map<String, Object> getBeansOfType(Map<String, Object> beans,//所有bean
                                               Class<?> intfType) //类型的实例
    {

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

        for (Map.Entry<String, Object> entry : beans.entrySet()) {
            //拿到实例-->反射对象-->它的接口(接口有多实现,所以为数组)
            Class<?>[] intfs = entry.getValue().getClass().getInterfaces();

            if (intfs != null && intfs.length > 0) {
                for (Class<?> intf : intfs) {
                    //接口的类型与传入进来的类型一样,把实例加到resultBeans里来
                    if (intf.isAssignableFrom(intfType)) {
                        resultBeans.put(entry.getKey(), entry.getValue());
                    }
                }
            }
        }

        return resultBeans;
    }

}


三、测试结果

输入url:http://localhost:8080/query?name=1&age=2,结果显示:{name=1,age=2}
在这里插入图片描述

以上结果表明我们的手写SpringMVC已经可以正常工作了。

总结

步骤

  自定义SpringMVC的主要实现步骤如下:

  • 首先我们自定义ChenController ChenService ChenQualifier ChenRequestMapping ChenRequestParam 注解类,保证注解不仅被保存到class文件中,而且jvm加载class文件之后,仍然存在,以便被spring反射出实例对象。
  • 然后自定义ChenDispatcherServlet,完成请求分发的功能。同时在web.xml中设置启动容器时刻初始化ChenDispatcherServlet,映射路径为/ChenDispatcherServlet初始化工作主要包括:
    • 首先要根据一个基本包进行扫描,扫描里面的子包以及子包下的类,将全类名添加到List集和中。
    • 其次要把扫描出来的类进行实例化。以全类名为keyclass实例对象为value,放入到hashMap容器中。
    • 依赖注入,把service层的实例注入到controller,即初始化controller层的成员变量。
    • 建立pathmethod的映射关系,保存在hashMap容器中。
  • 然后doPost方法监听用户请求。具体流程如下:
    • 首先根据请求路径去容器中找到相应的方法,如果没有直接返回,并告知用户没有该路径。
    • 然后根据请求路径去容器中找到对应的控制类,如果没有直接返回,并告知用户没有该路径。
    • 然后根据策略模式获取请求参数的实际值。
    • 最后通过反射机制执行该方法,并响应给用户,完成整个业务处理逻辑。

流程图

服务启动流程图

Created with Raphaël 2.2.0 开始 首先要根据一个基本包进行扫描, 扫描里面的子包以及子包下的类, 将全类名添加到List集和中。 其次要把扫描出来的类进行实例化。 以全类名为key,class实例对象为value, 放入到hashMap容器中。 依赖注入,把service层的实例注入到controller, 即初始化controller层的成员变量。 建立path与method的映射关系,保存在hashMap容器中。 结束

请求响应流程图

Created with Raphaël 2.2.0 开始 获取到请求路径 根据请求路径来获取要执行的方法 method是否为空? 结束 根据请求路径来获取控制类 控制类controller是否为空? 获取HANDLERADAPTER处理器 根据处理器取得参数 通过反射机制执行该方法,并响应给用户 yes no yes no

重点及易错点

1、参数名称获取

               String value = rp.value();
                if (value == null || value.equals("")) {
                    //如果是JDK1.8以后,使用反射获取参数名称
                    if (System.getProperty("java.version").contains("1.8.")) {
                        Parameter parameter = method.getParameters()[paramIndex];
                        String name = parameter.getName();
                        value = name;
                    }
                }

  这段代码处理ChenRequestParam没有设置value()使用默认值的情况,一定要判断JDK的版本,同时在maven中添加如下配置参数。

              <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                    <configuration>
                        <source>8</source>
                        <target>8</target>
                        <!--反射获取方法参数名称-->
                        <compilerArgument>-parameters</compilerArgument>
                    </configuration>
                </plugin>

2、控制反转(IOC),实例化成员变量


                        //拿到@ChenQualifier("ChenServiceImpl")里的指定要注入的bean名字"ChenServiceImpl"
                        String value = qualifier.value();

//                        通用化,如果不设置值,则使用默认值
                        if (value == null || value.equals("")) {
//                            使用全类名
                            value = field.getType().getName();

                            try {
//                                根据全类名获取class文件
                                Class<?> aClass = Class.forName(value);
//                                判断该类是否是接口
                                if (aClass.isInterface()) {
//                                    查看接口的所有实现类
                                    List<Class<?>> allActionSubClass = CommonUtil.getAllActionSubClass(value);
                                    if (allActionSubClass.size() >= 2 || allActionSubClass.size() <= 0) {
                                        System.exit(0);
                                        throw new RuntimeException("接口没有实现或接口子类有多个实现!");

                                    }

                                    value = (allActionSubClass.get(0).getName());
                                }
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (NoSuchFieldException e) {
                                e.printStackTrace();
                            }
                        }

  上面的代码是处理@ChenQualifier没有设置value()使用默认值的情况。如果是正常的类,则直接使用全类名作为key去容器中找实例对象。如果是接口,则需要注意判断接口是否实现,或有多个实现。如果有多个实现可以任意选择一个实例或抛出异常。spring官方是直接抛出异常,这里我们也是抛出异常。

3、容器key命名

  我查阅相关资料发现,有一些作者将类的名称作为key,而不是全类名,当框架中有多个模块的时候难免会有类名冲突的问题。所以我建议还是使用全类名,上面的代码实现也是全类名。

4、小结

  注解默认值确实需要考虑太多的东西,同时框架也需要更多的业务逻辑判断,所以在编程实践中能明确的就明确,提高程序的运行效率。
  写小轮子太烧脑了,有问题欢迎各位读者批评指正。

点个赞再走呗!欢迎留言哦!

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值