springV1版本

手写spring基础版本,一窥探spring如何运行。
搭建spring环境,web.xml配置servlet,书写application.xml信息,需要依赖反转的类加上注解,控制层增加@controller注解。
控制层,业务层等在使用对象时候都只是声明了,并未new出来一个对象,做码农都知道需要对象要new出来,这个时候jvm才分配堆内存的。在使用过程居然不报NullPointerException这个异常,那就说明哪个功能模块已经替咱们做了这部分的工作。答案是spring。

书写springv1版本

1.DispatcherServlet -->书写自己的Servlet 继承自HttpServlet
2.web.xml 配置手写servlet信息
3.配置信息文件:基础包名下需要被扫描到
4.书写注解类:@SongService、@SongController、@SongRequestMapping、@SongRequestParam、@SongAutowired;至于注解上的Target等信息沿用spring上的就可以(此处只为把代码码出来)。
下面来看下项目整体

1.项目结构

使用maven,引入servlet的jar包

书写自定义servlet类

1.继承HttpServlet,实现其中init(),doPost(),doGet()三个方法。
2.init()工作
3.想想如何把这几者分部工作内容串起来呢?

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件信息 - 确定扫描基类
        doLoadConfig(config.getInitParameter("contextConfigLocation").replace("classpath:",""));
        //2.扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));
        //3.初始化扫描到相关的类,并且将他们放入到IOC容器之中
        doInstance();
        //4.完成依赖注入
        doAutowired();
        //5.初始化HandlerMapping
        initHanlderMapping();
        System.err.println("init 完成!!!");
    }

准备工作:

  • 加载配置文件到内存中;
  • 配置文件读取哪些基类需要被扫描 (简版:配置文件只配置哪些基类被扫描到),加载符合条件到集合;
  • 基类扫描集合再次通过是否添加了注解条件来过滤存放在某个容器 - 是叫ioc容器吗?
    1.遍历扫描类的集合,是否添加了自定义注解,是通过反射实例化出来添加ioc容器中;
    2.需要注意注解自定义beanName的别称逻辑;
  • ioc容器对象(key-value)是实例化,但里面对应字段还没做到与对象之间的对应关系,因此需要去做字段的实例化,不然调用过程中就会出现空指针错误;
  • url - method,如何做到映射关联?
    1.遍历ioc容器,找到是控制层的类,找到url定义的注解,基础的url就拿到,然后遍历该类method下定义的url,组装起来,如果出现重复的url,抛出异常;
    2.添加url-method关系到集合中;
public class SongDispatcherServlet extends HttpServlet {

    //保存配置文件内容
    private Properties contextConfig = new Properties();
    //保存所有扫描到的类名
    private List classNames = new ArrayList();
    //传说中的ioc容器,为此来揭开它神秘的面纱
    private Map ioc = new HashMap();
    //保存url 很Method的对应关系
    private Map handlerMapping = new HashMap();

    @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 {
        //真正执行-
        try {
            doDispath(req,resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("500 Excetion! Detail :"+Arrays.toString(e.getStackTrace()));
        }
    }

    /**
     * 通过req加载url - method
     * @param req
     * @param resp
     */
    private void doDispath(HttpServletRequest req, HttpServletResponse resp) throws Exception{
        //绝对路径
        String url = req.getRequestURI();
        //处理成相对路径
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath,"").replaceAll("/+","/");
        if(!this.handlerMapping.containsKey(url)){
            resp.getWriter().write("404 not found!!!");
            return;
        }
        Method method = this.handlerMapping.get(url);
        //通过反射拿到信息
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        //为了方便这块写死,后期需要优化
        Map paramMap = req.getParameterMap();
        method.invoke(ioc.get(beanName),new Object[]{req,resp,paramMap.get("name")[0]});
    }


    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件信息 - 确定扫描基类
        doLoadConfig(config.getInitParameter("contextConfigLocation").replace("classpath:",""));
        //2.扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));
        //3.初始化扫描到相关的类,并且将他们放入到IOC容器之中
        doInstance();
        //4.完成依赖注入
        doAutowired();
        //5.初始化HandlerMapping
        initHanlderMapping();
        System.err.println("init 完成!!!");
    }

    private void initHanlderMapping() {
        if(ioc.isEmpty()){return;}
        for (Map.Entry entry :
                ioc.entrySet()) {
            Class clazz = entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(SongController.class)){continue;}
            String baseURL= "";
            if(clazz.isAnnotationPresent(SongRequestMapping.class)){
                SongRequestMapping requestMapping = clazz.getAnnotation(SongRequestMapping.class);
                baseURL = requestMapping.value();
            }
            //默认获取所有public方法
            for (Method method :
                    clazz.getMethods()) {
                if(!method.isAnnotationPresent(SongRequestMapping.class)){
                    continue;
                }
                SongRequestMapping requestMapping = method.getAnnotation(SongRequestMapping.class);
                String url = ("/" + baseURL + "/" + requestMapping.value()).replaceAll("/+","/");

                handlerMapping.put(url,method);
                System.err.println("Mapped:"+url+","+method);
            }
        }
    }

    private void doAutowired() {
        if(ioc.isEmpty()){return;}
        for (Map.Entry entry : ioc.entrySet()){
            //Declared 所有的,特定的 字段,包括private/protected/default
            //正常来说,普通的oop编程只能拿到public的属性
            Field [] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field :
                    fields) {
                if(!field.isAnnotationPresent(SongAutowired.class)){
                    continue;
                }
                SongAutowired songAutowired = field.getAnnotation(SongAutowired.class);
                String beanName = songAutowired.value().trim() ;
                if("".equals(beanName)){
                    beanName = field.getType().getName();
                }
                //private 在accessible=true 才能被操作
                field.setAccessible(true);
                try{
                    //用反射机制,动态给字段赋值 -- 实例化
                    field.set(entry.getValue(),ioc.get(beanName));
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }

    private void doInstance() {
        //初始化,为DI做准备
        if(classNames.isEmpty()){return;}
        try {
            for (String className : classNames) {
                Class clazz = Class.forName(className);
                //什么样的类才需要初始化
                //加了注解的类,才能被初始化,如何判断?
                //1.默认类名首字母小写
                String beanName = toLowerFirstCase(clazz.getSimpleName());

                if(clazz.isAnnotationPresent(SongController.class)){
                    Object instance = clazz.newInstance();
                    //Spring默认类名首字母小写
                    ioc.put(beanName,instance);
                }else if(clazz.isAnnotationPresent(SongService.class)){
                    Object instance = clazz.newInstance();
                    //2.自定义的beanName
                    SongService service = clazz.getAnnotation(SongService.class);
                    if(!"".equals(service.getClass())){
                        beanName = service.value();
                    }
                    //3.根据类型自动赋值
                    for (Class i : clazz.getInterfaces()) {
                        if(ioc.containsKey(i.getName())){
                            throw new Exception("The " + i.getName() + "is exists !!!");
                        }
                        ioc.put(i.getName(),instance);
                    }
                }else{
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //如果类名本身是小写就会有问题
    //这个方法本身就是大写转小写。只关注方法本身
    private String toLowerFirstCase(String simpleName) {
        char [] chars = simpleName.toCharArray();
        //大小写字母的ASCII码相差32
        chars[0] += 32;
        return String.valueOf(chars);
    }


    private void doScanner(String scanPackage) {
        //scanPackage = com.song.springv1
        //转换为文件路径,实际上就是把.替换成/就OK
        //classpath
        URL url = this.getClass().getClassLoader().getResource("/"+scanPackage.replaceAll("\\.","/"));
        File classPath = new File(url.getFile());
        for (File file : classPath.listFiles()) {
            if(file.isDirectory()){
                doScanner(scanPackage + "." + file.getName());
            }else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                classNames.add(className);
            }
        }
    }

    /**
     * 加载配置文件信息
     */
    private void doLoadConfig(String contextConfigLocation) {
        //web.xml读取配置文件信息
        InputStream fis = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try{
            //并且将其读取出来的放到Properties
            //把配置文件内容读出来到内存中
            contextConfig.load(fis);
        }catch (Exception ex){
            ex.printStackTrace();
        }finally {
            if(null!=fis){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

准备画个图,懒得画。有空并记得就补上。
源码地址:https://github.com/spp1987/dreamProject.git

你可能感兴趣的:(springV1版本)