自己实现一个SpringIOC——(1)

文章中的代码下载:https://github.com/yangbishang/springIoc

Spring框架中最重要也是最广为人知的就是AOP和IOC了吧,AOP我已经讲过了,今天我们就讲讲IOC,对于一些基本概念我就不赘述了,而且讲了也很难深刻的理解,今天我们就自己编写一个简易的框架来实现IOC,至于我是怎么知道的,嘿嘿,我是看了这里的视频:https://segmentfault.com/l/1500000013061317 , 所以如果有些小伙伴觉得我下面的内容比较看不懂的话,你们就看看这里的视频吧!

首先我们先看看代码的框架:

我们这就讲讲DispatcherServlet部分,不懂的可以看我github代码和视频。

我们这个框架主要分为三个部分,流程也是按这三个部分走的:

  1. 找到bean
  2.  载入并注册bean
  3. 注入bean

 

 

1 找到bean

找到bean在什么地方,是对BeanDefinition的资源定位,是由ResourceLoader通过统一的Resource接口来完成,这个接口对各中形式的Resource都提供了统一接口,比如Xml,比如annotation。而这些都是由ResourceLoader来完成的

   /**
     *
     * 找到bean
     *
     * */
    private void scanBase(String basePackages) {  //"com.yy"
        //file:/D:/IdeaProject/springIoc/springIoc/springioc/target/springioc/WEB-INF/classes/com/yy/
        URL url = this.getClass().getClassLoader().getResource("/" + replacePath(basePackages));//getResource全部是带斜杠的,所以我们要把点换成斜杠
        String path = url.getFile();    ///D:/IdeaProject/springIoc/springIoc/springioc/target/springioc/WEB-INF/classes/com/yy/
        File file = new File(path);  //生成一个文件
        String [] strFiles = file.list();  //得到文件底下的所有的文件名
        for(String strFile: strFiles) {
            File eachFile = new File(path + strFile);
            if(eachFile.isDirectory()) {            //如果是目录,就递归继续向里面查找
                scanBase(basePackages +"."+eachFile.getName());
            }else {
                System.out.println("class name" + eachFile.getName());
                classNames.add(basePackages +"." + eachFile.getName());
            }
        }
    }
    //将字符串中的.换成/
    String replacePath(String path) {
        return path.replaceAll("\\.","/");
    }

扫描这个项目,将所有类的文件名.Class存在l链表classNames中

2 载入并注册bean

找到bean后,将bean注册到我们的IOC容器中。Spring是通过一些ApplicationContext来完成的,比如FileSystemXmlApplicationContext, ClassPathXmlApplicationContext以及我们最常见的XmlWebApplicationContext,读取之后将bean注册到IOC容器中,简单来说,就是把读取的bean都放到一个map中。

    /**
     *
     * 载入并注册bean
     *
     * */
    //把classNames循环一遍,看里面有哪些类,然后把它生成出来加载进去
    private void filterAndInstance() throws Exception {
        if( classNames.size() == 0) {
            return;
        }
        //循环获取类名
        for(String className : classNames) {
            Class clazz = Class.forName(className.replace(".class", ""));
            if(clazz.isAnnotationPresent(Controller.class)) {  //如果clazz字节码上面带了一个Annotation,并且Annotion是controller,就实例化一个controller对象出来
                //获取bean实例
                Object instance = clazz.newInstance();
                //获取注解的value---将controller上的名字取出来(fish)
                String key = ((Controller)clazz.getAnnotation(Controller.class)).value();
                //将bean交付给IOC
                instanceMap.put(key, instance);  //eg:("fish" , FishController)
            }else if(clazz.isAnnotationPresent(Service.class)) { //如果clazz字节码上面带了一个Annotation,并且Annotion是Service
                //获取bean实例
                Object instance = clazz.newInstance();
                //获取注解的value
                String key = ((Service)clazz.getAnnotation(Service.class)).value();
                //将bean交付给IOC
                instanceMap.put(key, instance);   //eg:("fishServiceImpl" , FishServiceImpl)
            }else {
                continue;
            }
        }
    }

然后根据classNams中的文件名反射得到@Controller和@Service 所注释的类的Class对象,然后将对象放入了instanceMap中,这些Class对象所对应的key就是@注释中的值。其实说白了就是就是将Controller层和Service层中的类的Class对象存在了在Map中,然后用@注释名来当Map的key。

 

3 注入bean

当我们要用bean时,由IOC容器自动的注入进去。

    /**
     *
     * 注入bean
     *
     * */
    //把ioc容器里的bean注入到指定地方(instanceMap中的值注入到@qualifier)p--DI
    //把instanceMap循环一遍,把它每一个对象字节码中的file取出来,看看里面有没有Qualifier的注解,如果有的话,把qualifier中的value取出来,然后把value对应的对象注入到这里
    private void springDi() throws IllegalArgumentException, IllegalAccessException {

        if(instanceMap.size() == 0) {
            return;
        }
        /**
         * 循环获取实例
         * */
        for(Map.Entry<String, Object> entry: instanceMap.entrySet()) {
            //获取所有的类变量
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            //查看上面有没有qualifer的标识,如果有qualifer的标识,把它的value取出来,通过这个值我们就可以拿到它的实例
            for(Field  field: fields) {
                //包含Qualifer注解
                if(field.isAnnotationPresent(Qualifier.class)){
                    String key = ((Qualifier)field.getAnnotation(Qualifier.class)).value();
                    field.setAccessible(true);
                    //注入qualifier----即把fishService注入进去了
                    field.set(entry.getValue(), instanceMap.get(key)); //set(Object,value)将指定对象参数上的此Field对象表示的字段设置为指定的新值。obj:其字段应被修改的对象 ; value:修改了 obj的新值    
                }
                //autowired
            }
        }


    }

循环遍历出每个instanceMap中Class对象的变量,判断这个变量上面有没有@Qualifer注解,有的化则拿到@qualifer的注解值,然后通过这个值在instanceMap中对应的Class对象更新(注入)到参数上去。

其实关于IOC部分就已经弄完了,当然我们还要处理一下springMVC部分

    //把路径给生成出来(springMVC)
    private void mvc() {
        if(instanceMap.size() == 0) {
            return;
        }

        //循环获取实例
        for(Map.Entry<String, Object> entry: instanceMap.entrySet()) {
            //将含有controller的所有实例都取出来
            if(entry.getValue().getClass().isAnnotationPresent(Controller.class)) {//entry得到对象,得到字节码,看看是否含有controller注解
                String ctlUrl = ((Controller)entry.getValue().getClass().getAnnotation(Controller.class)).value(); //将@Controller(“值”)中的值取出来
                Method[] methods = entry.getValue().getClass().getMethods();//将controller中的带有@RequestMapping注解的方法取出来
                for(Method method :methods) {            //循环遍历方法,看看方法上面是否有@RequestMapping
                    
                    if(method.isAnnotationPresent(RequestMapping.class)) {
                        String reqUrl = ((RequestMapping)method.getAnnotation(RequestMapping.class)).value();  //将@requestMapping(“值”)中的值取出来
                        String dispatchUrl = "/"+ ctlUrl +"/" + reqUrl;  // /fish/get
                        methodMap.put(dispatchUrl, method);         //每个url对应的方法存好
                    }
                }
            }else {
                continue;
            }
        }
    }

好,上面就将客户端访问路径与方法存入了methodMap中了

最后我们进去servlet中看看客户端是怎样通过输入路径调用到方法的


    @Override
    //把发送过来的url取到,然后和methodMap里面的url相比较,如果有,就把url对应的method执行即可
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // localhost:8080/springioc/fish/get
        String uri = request.getRequestURI();   // /springioc/fish/get
        String projetname = request.getContextPath(); // /springioc  web项目的根路径
        String path = uri.replaceAll(projetname, ""); // //fish/get

        Method method = methodMap.get(path);
        //把controller的value所对应的实例取出来(fish)
        String className = uri.split("/")[2];
        //把实例生成出来
        FishController fishController = (FishController)instanceMap.get(className);
        //调用实例
        try {
            method.invoke(fishController, new Object[] {request,response,null});



        } 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
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        doGet(request, response);
    }

猜你喜欢

转载自blog.csdn.net/qq_36582604/article/details/82630667