手動實現 SpringMVC

前幾章咱們已經分析了 Spring 的源碼而且手動實現了一個 IOC/DI 容器。java

這章咱們在本身實現的 Spring 框架的基礎上實現一個 SpringMVC 框架。git

咱們本身實現的 Spring 框架的快速入口:Spring 框架 裏面的 MySpring 是咱們 IOC/DI 框架的源碼。github

下面咱們在這個基礎上實現一個 MVC 框架。首先 @Controller,@Service,@Repository 註解已經在咱們本身的 Spring 框架裏面定義了,能夠實現將被這些註解標記的類交給 Spring 管理,而且實現了依賴注入。web

先看總體架構


由於咱們這個 MVC 框架要依賴 IOC/DI 容器,因此咱們在 pom 文件裏要將本身的 Spring 框架引入進來。spring

實現 MVC 的總體功能

首先咱們定義兩個 MVC 專用的註解,RequestMapping 用來作 url 匹配,RequestParam 作參數轉換:瀏覽器

package com.paul.annotation;

import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
    String value() default "";
}
package com.paul.annotation;

import java.lang.annotation.*;

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

咱們知道 SpringMVC 的核心是 DispatcherServlet,用來作核心路由控制,咱們也定義這樣一個類,而且在初始化方法裏初始化一個 IOC/DI 容器,看過前面文章的同窗應該知道,初始化容器後咱們已經將 Bean 放到容器中並且完成了依賴注入。架構

package com.paul.servlet;

import com.paul.annotation.RequestMapping;
import com.paul.annotation.RequestParam;
import org.springframework.ioc.annotation.Controller;
import org.springframework.ioc.bean.AnnotationApplicationContext;
import org.springframework.ioc.bean.ApplicationContext;

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.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

//實現 servlet,和咱們之前使用 servlet 同樣。
public class DispatcherServlet extends HttpServlet {

    // 完整路徑和 方法的 mapping
    private Map<String,Object> handleMapping = new ConcurrentHashMap<>();

    // 類路徑和controller 的 mapping
    private Map<String,Object> controllerMapping = new ConcurrentHashMap<>();
    
    private Map<String,Object> beanFactory = new ConcurrentHashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        //實例化 IOC 容器
        ApplicationContext ctx = new AnnotationApplicationContext("applicationContext.xml");
        this.beanFactory = ((AnnotationApplicationContext) ctx).beanFactory;
        //上一步已經完成了 Controller,service,respostry,autowired 等註解的掃描和注入
        //遍歷容器,將 requestmapping 註解的路徑和對應的方法以及 contoller 實例對應起來

        for(Map.Entry<String,Object> entry:beanFactory.entrySet()){
            Object instance = entry.getValue();
            Class<?> clazz = instance.getClass();
            if(clazz.isAnnotationPresent(Controller.class)){
                RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class);
                String classPath = requestMapping.value();
                Method[] methods = clazz.getMethods();
                for(Method method:methods){
                    if(method.isAnnotationPresent(RequestMapping.class)){
                        RequestMapping requestMapping2 = method.getAnnotation(RequestMapping.class);
                        String methodPath = requestMapping2.value();
                        String requestPath = classPath + methodPath;
                        handleMapping.put(requestPath,method);
                        controllerMapping.put(requestPath,instance);
                    }else{
                        continue;
                    }

                }
            }else{
                continue;
            }
        }
    }


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

    //根據上一步獲取到的 mapping,根據 url 找到對應的 controller 和方法去執行。
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String uri = req.getRequestURI();   //   /paul-mvc/com.paul.controller/method-com.paul.controller
        String context = req.getContextPath();  // /paul-vmc
        String path = uri.replace(context,"");  // /com.paul.controller/method-com.paul.controller
        Method m = (Method) handleMapping.get(path);

        //從容器裏拿到controller 實例
        Object instance = controllerMapping.get(path);

        Object[] args =  handle(req,resp,m);
        for (Object a:args){
            System.out.println("Object:"+a);
        }

        try {
            m.invoke(instance,args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


    }


    private static Object[] handle(HttpServletRequest req, HttpServletResponse resp,Method method){
        //拿到當前執行的方法有哪些參數
        Class<?>[] paramClazzs = method.getParameterTypes();
        //根據參數的個數,new 一個參數的數據
        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++] = req;
            }
            if(ServletResponse.class.isAssignableFrom(paramClazz)){
                args[args_i++] = resp;
            }

            //判斷requestParam  註解
            Annotation[] paramAns = method.getParameterAnnotations()[index];
            if(paramAns.length > 0){
                System.out.println("my");
                for(Annotation paramAn:paramAns){
                    if(RequestParam.class.isAssignableFrom(paramAn.getClass())){
                        System.out.println("13mj");
                        RequestParam rp = (RequestParam) paramAn;
                        args[args_i++] = req.getParameter(rp.value());
                    }
                }
            }
            index ++;
        }
        return  args;
    }

}

測試代碼

新建一個 WebDemo,Maven web 項目。mvc

先來看咱們須要測試的 Controller 和 Service。app

@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/query")
    public void get(HttpServletRequest request, HttpServletResponse response, @RequestParam("name") String name,@RequestParam("age") String age){
        try {
            PrintWriter pw = response.getWriter();
            String res = userService.query(name,age);
            pw.write(res);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
import org.springframework.ioc.annotation.Service;

@Service("userService")
public class UserServiceImpl implements UserService {
    @Override
    public String query(String name, String age) {
        return "name="+name+"age="+age;
    }
}

在 resources 目錄咱們須要寫一個名字爲 applicationContext 的配置文件來指明掃描包路徑。框架

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <package-scan component-scan="com.paul.demo" />
</beans>

在 web.xml 中配置咱們本身的 DispatcherServlet。

<!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>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>com.paul.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

在瀏覽器中測試結果:

結果和咱們想的同樣。

整個項目的源碼在:源碼,但願你們多 star 一塊兒改進。

相關文章
相關標籤/搜索