手写一个简易的类springMVC

2019-04-08  本文已影响0人  JaJIng

首先了解一下SpringBean的生命周期:


937513-20160507202024015-234747937.png

工程目录结构如图所示:


jjmvc.PNG

注解类JJAutowired:

package jj.mvc.anonation;

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

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

注解类JJController:

package jj.mvc.anonation;

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

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


注解类JJRequestMapping:

package jj.mvc.anonation;

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

@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.TYPE,ElementType.METHOD}) 
public @interface JJRequestMapping {
    String value() default "";
}


注解类JJService:

package jj.mvc.anonation;

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

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

controller实现,我们的请求派发入口:

package jj.mvc.controller;

import jj.mvc.anonation.JJAutowired;
import jj.mvc.anonation.JJController;
import jj.mvc.anonation.JJRequestMapping;
import jj.mvc.anonation.JJService;

@JJController
@JJRequestMapping("jj/mv/controller")
public class BaseController {
    
    @JJAutowired
    private MyService myservice;
    
    @JJRequestMapping("/test1")
     public String test1(String name) {
        return "hello "+name;    
     }
     
}

简单的service接口和实现:

package jj.mvc.service;

public interface MyService {
    String doService(String name);
}

下面实现mvc核心的servlet相关:
首先是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>
  <display-name>Archetype Created Web Application</display-name>
<!--   org.springframework.web.servlet.DispatcherServlet -->
    <servlet>
        <servlet-name>jjmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>configMVC.properties</param-value>
         </init-param>
         <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>jjmvc</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

配置文件configMVC.properties很简单,一个mvc包扫描字面量:

scanBase=jj.mvc

适配器类:

package jj.mvc.servlet;

import java.lang.reflect.Method;

public class HandlerAdapter {
    Class<?> handlerClazz ;
    Method handlerMethod;
    String[] parameters;
    public Class<?> getHandlerClazz() {
        return handlerClazz;
    }
    public void setHandlerClazz(Class<?> handlerClazz) {
        this.handlerClazz = handlerClazz;
    }
    public Method getHandlerMethod() {
        return handlerMethod;
    }
    public void setHandlerMethod(Method handlerMethod) {
        this.handlerMethod = handlerMethod;
    }
    public String[] getParameters() {
        return parameters;
    }
    public void setParameters(String[] parameters) {
        this.parameters = parameters;
    }
    
}

最重要的代码,servlet,重点看doDispatch方法实现:

package jj.mvc.servlet;

import jj.mvc.anonation.JJAutowired;
import jj.mvc.anonation.JJController;
import jj.mvc.anonation.JJRequestMapping;
import jj.mvc.anonation.JJService;
import org.springframework.web.bind.annotation.RequestMapping;

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.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;

public class JJservlet extends HttpServlet{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    
    private  Properties properties=new Properties() ;
    
    private  List<Class<?>> clazzList=new ArrayList<Class<?>>();
    
    private  Map<String,Object> beanMap=new HashMap<String,Object>();
    
    private  Map<String,HandlerAdapter> requestMapping=new HashMap<String,HandlerAdapter>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        super.doPost(req, resp);
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        try {
            Object result=doDispatch(req,resp);
            resp.getWriter().println(result);
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // TODO Auto-generated method stub
        super.init(config);
        //contextConfigLocation属性时我们在web.xml里注册过的。对应于本案例就是 "configMVC.properties"
        String contextConfigLocation=config.getInitParameter("contextConfigLocation");
        try {
            properties.load(this.getClass().getResourceAsStream(contextConfigLocation));
            //我们在configMVC.properties配置的scanBase属性值。
            String scanBase=properties.getProperty("scanBase");
            //其实就是springMVC的<context:component-scan base-package="xx.yyy.zzz" />
            //扫描包,意味着scanBase值相关的这些路径下的java文件需要我们去扫描其注解,可以理解为@Component,只不过springMVC的实现颗粒度很细,我们可以指定@Controller,@Service,@Repositoty,include,exclude等
            doScan(scanBase);
            //初始化bean,也就是常说的控制反转IOC,当然我们这里没有实现一个专门的context上下文容器去很好的管理这些bean ,另外这里都是单例模式,而springMVC可以通过@scope实现非单例模式,比如原型模式等。
            //在这里,我想说一个题外话,单例模式可以解决spring bean的@autowired属性注入循环依赖(a->b,b->c,c->a这种),原型则不行,
            // 因为单例模式在初始化时有Map维护正在初始化的类的记录/原型则没有。当然如果通过构造器驻入,无论哪种模式都会报错。
            initBean();
            //这一步就是装配,也就是常说的依赖注入DI
            doAutowired();
            //注册url和处理器(这里只是简单的controller方法)的映射关系
            doRequestMapping();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }

    public void doScan(String scanBase) {
        File file =new File(scanBase);
        //递归的扫描,是java字节码文件则把class类名加入clazzList,是文件夹则递归
        if(file.isDirectory()) {
            for(File lfile:file.listFiles()) {
                doScan(lfile.getAbsolutePath());
            }
        }else {
            clazzList.add(file.getClass());
        }
    }
    
    public void initBean() throws InstantiationException, IllegalAccessException {
        String beanName;
        String annotationValue;
        //从我们doScan注册的clazzList中遍历拿到需要初始化的bean类(@Component类)
        for(Class<?> clazz:clazzList) {
            if(clazz.isAnnotationPresent(JJController.class)) {
                //如果是@JJController注解的bean,看看有没有写value,没有就取类名,比如我们的BaseController它的beanName就是BaseController
                beanName=clazz.getSimpleName();
                annotationValue=clazz.getAnnotation(JJController.class).value().trim();
                if(!annotationValue.equals("")) {
                    beanName=annotationValue;
                }
                //初始化一个类实例放入beanMap(单例模式的实现,另外这里我们也没有实现构造器入参)
                beanMap.put(beanName, clazz.newInstance());
                
            }else if(clazz.isAnnotationPresent(JJService.class)) {
                //如果是@JJService注解的bean,看看有没有写value,没有就取类名,比如我们的MyServiceImpl它的beanName就是MyServiceImpl
                beanName=clazz.getSimpleName();
                annotationValue=clazz.getAnnotation(JJService.class).value().trim();
                if(!annotationValue.equals("")) {
                    beanName=annotationValue;
                }
                Object instance = clazz.newInstance();
                beanMap.put(beanName, instance);
                //因为在controller层,我们注入的service一般都抽象成接口,所以同样的实例我们要通过接口名做key再放一次
                for(Class<?> interFace:clazz.getInterfaces()) {
                    beanMap.put(interFace.getSimpleName(), instance);
                }
                
            }
        }
    }
    
    public void doAutowired() throws IllegalArgumentException, IllegalAccessException {
        for(Entry<String, Object> beanEntry: beanMap.entrySet()) {
            Object bean = beanEntry.getValue();
            //如果一个bean的属性有autowired注解,那么需要从beanMap中拿出来把它装配进bean
            for(Field beanFiled : bean.getClass().getFields()) {
                if(beanFiled.isAnnotationPresent(JJAutowired.class)) {
                    /*System.out.println("beanFiled:"+beanFiled.getType().getName());
                    System.out.println("beanFiled Simple:"+beanFiled.getType().getSimpleName());*/
                    //请注意,这里有个问题,如果是被@autowired()注解的bean在initBean时,其beanName不是类名,那么无法装进来,除非实现对@Qualifier注解或者@Resource的提取,这里就不讨论了
                    String beanFiledType=beanFiled.getType().getSimpleName();
                    //反射装配
                    beanFiled.set(bean, beanMap.get(beanFiledType));
                }
            }
        }
    }
    
    public void doRequestMapping() {
        for(Class<?> clazz:clazzList) {
            //映射表注册,因为@JJRequestMapping都在@JJController中,所以遍历@JJController修饰的类
            if(clazz.isAnnotationPresent(JJController.class)) {
                String urlbase="";
                if(clazz.isAnnotationPresent(RequestMapping.class)) {
                    urlbase=clazz.getAnnotation(JJRequestMapping.class).value();
                }
                //实际上springMVC的实现不是这样的,估计会加上method.getModifiers(),应该是拿到当前类的public方法,也是为什么非public方法无法做controller映射的原因
                for(Method method:clazz.getDeclaredMethods()) {
                    if(method.isAnnotationPresent(JJRequestMapping.class)) {
                        //urlMapping = 类的公共JJRequestMapping+方法JJRequestMapping
                        String urlMapping=urlbase+method.getAnnotation(JJRequestMapping.class).value();
                        //把处理类,处理方法封装成handlerAdapter,并绑定相应url添加入requestMapping
                        HandlerAdapter handlerAdapter=new HandlerAdapter();
                        handlerAdapter.setHandlerClazz(clazz);
                        handlerAdapter.setHandlerMethod(method);
                        requestMapping.put(urlMapping, handlerAdapter);
                    }
                }
            }
        }
    }
    
    public Object doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        String url=req.getRequestURI();
        //拿到请求参数构建一个入参Map,这里我们只考虑普通的x-www-form-urlencoded提交,其他有关文件和其他流式的方式不做实现
        Map<String, String[]> parametersMap= req.getParameterMap();
        //构建入参数组
        String[][] parameters=new String[parametersMap.size()][];
        int i=0;
        //填充入参数组
        for(Entry<String, String[]> paraSet : parametersMap.entrySet()) {
            parameters[i++]=paraSet.getValue();
        }

        //requestMapping是一个<url,请求处理器>的map,这是我们通过@JJController的@JJRequestMapping()里的值实现的,servlet在初始化时就已经把映射关系注入了这个map
        HandlerAdapter handlerAdapter=requestMapping.get(url);
        if(handlerAdapter!=null) {
            /**
             * 实际SpringMVC的处理很复杂,不是返回一个执行方法这么简单,大家可以去看看源码,大致通过一下几步:
             * 1.将映射请求注册到处理器HandlerMapping;
             * 2.HandlerMapping会把请求url映射为HandlerExecutionChain类型的handler对象;(包含过滤器的整个执行链)
             * 3.将handler对象作为参数传递给HandlerAdapter的实例化对象,调用其handler方法会生成一个ModelAndView对象;并且在这个过程会有过滤器的环绕执行
             * 我们这里只是简易的实现,并且也没有抽象成ModelAndView视图对象
             */
            Method invokeMethod=handlerAdapter.getHandlerMethod();
            //反射调用相关方法,注意:我们这里因为没有ModelAndView所以也就没有做视图处理,service 方法只是单纯的把方法return值通过流写回去。
            return invokeMethod.invoke(handlerAdapter.getHandlerClazz(), handlerAdapter.getParameters());
        }else {
            //找不到则抛出错误;说明该url没有相关JJRequestMapping注册过
            throw new IllegalAccessException();
        }
    }

}

HandlerExecutionChain.png
上一篇下一篇

猜你喜欢

热点阅读