手写springMVC

先让大家看一下我的目录结构
这里写图片描述
第一步: 先创建一个Maven项目,引入servlet-api依赖,因为springMVC底层就是用到servlet的

<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>3.0-alpha-1</version>
      <!--打包时不要打包进去-->
      <scope>provided</scope>
    </dependency>

第二步: 创建注解,例如@Autwired、@Controller等注解,这里你可以自定义命名,例如我就不要叫@Autwired,我就要叫@MyAutwired也行。
@Autwired

package com.cvc.nelson.annaotation;

import java.lang.annotation.*;

/**
 * @auther xiehuaxin
 * @create 2018-08-22 10:38
 * @todo
 */
//因为Autowired一般是用在类的成员变量上,这里的ElementType.FIELD的意思是这个注解用在类的成员变量上
@Target(ElementType.FIELD)
//表示在运行时可以通过反射获取相应的注解
@Retention(RetentionPolicy.RUNTIME)
//表示:当我们的注解包含在javadoc中,在public @interface EnjoyAutowired{}上声明@Inherited,EnjoyAutowired注解就可以被继承
@Documented
public @interface Autowired {
    //@Autowired有时候会带参数,例如@Autowired("orderService"),下面这一行的意思是,在使用EnjoyAutowired注解时,可以在后面带参数
    String value() default "";
}

@Controller

package com.cvc.nelson.annaotation;

import java.lang.annotation.*;

/**
 * @auther xiehuaxin
 * @create 2018-08-22 10:31
 * @todo
 */
//因为Controller一般作用在类上,这里的ElementType.TYPE的意思是这个注解用在类上
@Target(ElementType.TYPE)
@Documented
//在运行时可以通过反射获取相应的注解
@Retention(RetentionPolicy.RUNTIME)

public @interface Controller {
    String value() default "";
}

@RequestMapping

package com.cvc.nelson.annaotation;

import java.lang.annotation.*;

/**
 * @auther xiehuaxin
 * @create 2018-08-22 10:39
 * @todo
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String value() default "";
}

@RequestParam

package com.cvc.nelson.annaotation;

import java.lang.annotation.*;

/**
 * @auther xiehuaxin
 * @create 2018-08-22 10:41
 * @todo
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    String value() default "";
}

@Service

package com.cvc.nelson.annaotation;

import java.lang.annotation.*;

/**
 * @auther xiehuaxin
 * @create 2018-08-22 10:36
 * @todo
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
    String value() default "";
}

第三步:编写Controller、service、serviceImpl、web.xml,先把这些架子搭好,架子搭好后还没有实现springMVC的功能,这里我们先把我们平时写的业务代码先写好先,第四步就开始实现springMVC的底层逻辑。
TestController.java

package com.cvc.nelson.controller;

import com.cvc.nelson.annaotation.Autowired;
import com.cvc.nelson.annaotation.Controller;
import com.cvc.nelson.annaotation.RequestMapping;
import com.cvc.nelson.annaotation.RequestParam;
import com.cvc.nelson.service.TestService;

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

/**
 * @auther xiehuaxin
 * @create 2018-08-22 10:42
 * @todo
 */
@Controller
@RequestMapping("/test")
public class TestController {

    @Autowired("TestServiceImpl")
    TestService testService;

    @RequestMapping("/query")
    public void queryData(HttpServletRequest request, HttpServletResponse response, @RequestParam("userName") String userName, @RequestParam("age") String age){

        String result = testService.query(userName,age);
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
            writer.write(result);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            writer.close();
        }

    }
}

接口TestService

package com.cvc.nelson.service;

/**
 * @auther xiehuaxin
 * @create 2018-08-22 10:43
 * @todo
 */
public interface TestService {

    public String query(String userName, String age);
}

TestServiceImpl.java

package com.cvc.nelson.service.impl;

import com.cvc.nelson.annaotation.Service;
import com.cvc.nelson.service.TestService;

/**
 * @auther xiehuaxin
 * @create 2018-08-22 10:43
 * @todo
 */
@Service("TestServiceImpl")
public class TestServiceImpl implements TestService{


    public String query(String userName, String age) {
        return "模拟到数据库查询到数据: user_name = " + userName + "     age = " + age;
    }
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>com.cvc.nelson.servlet.DispatcherServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <!--这里是拦截所有的请求,都去执行DispatcherServlet的doPost方法-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

重头戏来了=======>DispatcherServlet.java
首先让DispatcherServlet这个类继承HttpServlet,然后重写它的doGet和doPost方法
然后从init这个方法开始看。

package com.cvc.nelson.servlet;

import com.cvc.nelson.annaotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @auther xiehuaxin
 * @create 2018-08-22 11:05
 * @todo
 */
public class DispatcherServlet extends HttpServlet {

    //用于存放扫描出来的所有全类名,如:com.cvc.nelson.controller.TestController.class,反射的时候需要用到
    List<String> classNames = new ArrayList<String>();
    //用于存放反射生成的bean,相当于IOC容器
    Map<String,Object> beans = new HashMap<String, Object>();
    //用于存放请求路径与方法的映射,例如请求接口http://39.108.66.150:8080/write-springmvc/test/query接口,会把它映射到对应的方法
    Map<String,Object> handlerMap = new HashMap<String, Object>();


    @Override
    public void init(ServletConfig config){

        //1.先扫描出包下的所有类名
        scanPackage("com.cvc");

        //2.根据扫描到的类名创建bean
        doInstance();

        //3.根据bean进行依赖注入
        doIoc();

        //4.请求路径与方法之间的映射
        buildUrlMapping();
    }

    /**
     * 建立请求与方法之间的对应关系
     * 例如我请求http://39.108.66.150:8080/write-springmvc/test/query 接口时,到底是执行哪个controller的哪个方法
     */
    private void buildUrlMapping() {
        if(beans.entrySet().size() <= 0) {
            System.out.println("没有一个实例化类......");
            return;
        }
        for(Map.Entry<String,Object> entity : beans.entrySet()) {
            Object obj = entity.getValue();
            Class<?> clazz = obj.getClass();
            //映射关系都在Controller中
            if(clazz.isAnnotationPresent(Controller.class)) {
                //类上的@RequestMapping
                RequestMapping classMapping = clazz.getAnnotation(RequestMapping.class);
                String classPath = classMapping.value();
                /**
                 * 一个类可能有多个方法,每个方法上都可能有@RequestMapping
                 */
                Method[] methods = clazz.getMethods();
                for(Method method : methods) {
                    //有@RequestMapping注解的方法才处理
                    if(method.isAnnotationPresent(RequestMapping.class)) {
                        RequestMapping methodMapping = method.getAnnotation(RequestMapping.class);
                        String methodPath = methodMapping.value();
                        handlerMap.put(classPath + methodPath,method);
                    }else {
                        continue;
                    }
                }
            }
        }
    }

    /**
     * 根据注解进行依赖注入
     */
    private void doIoc() {
        if(beans.entrySet().size() <= 0) {
            System.out.println("没有一个实例化类......");
            return;
        }
        for(Map.Entry<String,Object> entity : beans.entrySet()) {
            Object instance = entity.getValue();
            Class<?> clazz = instance.getClass();
            //因为这里没有用的mybatis,所以只会在controller中注入service
            if(clazz.isAnnotationPresent(Controller.class)) {
                //1.@Autowired注解是用在类的成员变量上的,所以要先拿到成员变量先
                Field[] fields = clazz.getDeclaredFields();
                for(Field field : fields) {
                    //2.成员变量里又可能包含一些非bean类型的成员变量,所以要判断有@Autowired注解的才要注入
                    if(field.isAnnotationPresent(Autowired.class)) {
                        Autowired autowired = field.getAnnotation(Autowired.class);
                        //3.拿到上面定义的用于存放beans的Map的key,根据key去map中找到对应的实体
                        String key = autowired.value();
                        Object obj = beans.get(key);
                        //4.由于在被@Autowired修饰的成员变量一般是private的,所有要打开它的权限
                        field.setAccessible(true);
                        try {
                            //5.注入
                            field.set(instance,obj);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    /**
     * 为扫描到的类创建实例化对象
     */
    private void doInstance() {
        if(classNames.size() <= 0) {
            System.out.println("没有扫描到类.......");
            return;
        }
        for(String className : classNames) {
            try {
                String cn = className.replace(".class","");
                Class<?> clazz = Class.forName(cn);
                if(clazz.isAnnotationPresent(Controller.class)) {
                    //这一句要放在里面,我的包目录结构里,除了有类(class),还有注解@Autowired等,否则会报错
                    Object instance = clazz.newInstance();
                    //这里注意不要写成Controller controller = clazz.getAnnotation(Controller.class),因为Controller的key是@RequestMapping的value
                    RequestMapping controllerRequestMapping = clazz.getAnnotation(RequestMapping.class);
                    beans.put(controllerRequestMapping.value(),instance);
                }else if (clazz.isAnnotationPresent(Service.class)) {
                    Object instance = clazz.newInstance();
                    Service service = clazz.getAnnotation(Service.class);
                    beans.put(service.value(),instance);
                }else {
                    continue;
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 扫描basePackage包,获取到这个包以及这个包的子包中的所有类
     * @param basePackage
     */
    private void scanPackage(String basePackage) {
        URL url = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.","/"));
//        URL url = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.","/"));
        String fileStr = url.getPath();
        File file = new File(fileStr);
        String[] files = file.list();
        for(String path : files) {
            File filePath = new File((fileStr + path));
            if(filePath.isDirectory()) {
                scanPackage(basePackage + "." + path);
            }else {
                classNames.add(basePackage + "." + filePath.getName());
            }
        }
    }

    @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 {
        //1.先获取到请求的路径,获取到的值例如:write-springmvc/test/query
        String uri = req.getRequestURI();
        /**
         * 2.截取,只留下Controller的@RequestMapping + 方法的@RequestMapping部分
         */
        //获取的的值如:write-springmvc
        String context = req.getContextPath();
        //只留下 /test/query
        String path = uri.replace(context,"");

        //3.然后以path作为key,去handlerMap中找到对应的方法对象
        Method method = (Method) handlerMap.get(path);

        //4.把参数封装到参数数组中(这一步在spingMVC中是以策略模式实现的,由于我不是很懂策略模式,所以这里用自己的方法达到相同的效果)
        Object[] args = hand(req,resp,method);

        //5.获取到Controller实例
        String pathStr = "/" + path.split("/")[1];
        Object instance = beans.get(pathStr);

        //6.调用方法
        try {
            method.invoke(instance,args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 把参数封装到参数数组中
     * @param request
     * @param response
     * @param method
     * @return
     */
    private static Object[] hand(HttpServletRequest request, HttpServletResponse response,Method method) {
        //拿到当前执行的方法有哪些参数
        Class<?>[] paramClazzs = method.getParameterTypes();
        //根据参数的个数new一个参数数组,将方法里的所有参数赋值到args来
        Object[] args = new Object[paramClazzs.length];

        int args_i = 0;
        int index = 0;
        for(Class<?> paramClazz : paramClazzs) {
            if(ServletRequest.class.isAssignableFrom(paramClazz)) {
                args[args_i ++] = request;
            }
            if(ServletResponse.class.isAssignableFrom(paramClazz)) {
                args[args_i ++] = response;
            }
            /**
             * 从0-3判断有没有RequestParam注解,很明显paramClazz为0和1时,不是,
             * 当为2和3时为@RequestParam,需要解析
             * [@com.enjoy.nelson.annotation.EnjoyRequestParam(value=name)]
             */
            Annotation[] paramAns = method.getParameterAnnotations()[index];
            if(paramAns.length > 0) {
                for(Annotation paramAn : paramAns) {
                    if(RequestParam.class.isAssignableFrom(paramAn.getClass())) {
                        RequestParam rp = (RequestParam) paramAn;
                        //找到注解里的name和age
                        args[args_i ++] = request.getParameter(rp.value());
                    }
                }
            }
            index ++;
        }
        return args;
    }
}

最后运行结果截图
这里写图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值