手写Spring之IOC容器初始化及依赖注入

概述

spring的IOC容器初始化会做大量的工作,但是基本分为以下三个步骤:
1.定位:定位资源文件的位置,import、classpath、url(一般我们的资源文件都是在类路径classpath下)
2.加载:解析配置文件,把bean包装成BeanDefinition对象(BeanDefinition相当于是保存在内存中的配置文件,保存了所有跟类属性相关的信息。)
3.注册:将已经注册的BeanDefinition对象存入IOC容器中
IOC容器初始化完成之后,就是依赖注入了。
依赖注入就是把BeanDefinition里的信息读取出来,利用反射机制或者代理机制创建对象,一个bean对应一个BeanDefinition。新创建的对象不会放入我们印象中的IOC容器中,而是会存入到另外一个cache容器。所以,我们真实的读取bean是从cache容器中拿的。
下面先来实现spring的IOC容器初始化:
1》spring有很多的启动入口,这里自定义一个DispatchServlet作为启动入口,web.xml配置信息如下。

<web-app>
  <display-name>Archetype Created Web Application</display-name>

  <servlet>
    <servlet-name>springMini</servlet-name>
    <servlet-class>com.ft.spring.servlet.DispatchServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:application.properties</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>springMini</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

另外,application.properties文件中的内容为:

scanPackage=com.ft.spring.demo

2》DispatchServlet类就是自己要模拟初始化IOC容器的类。

/**
 * 因为我在web.xml里做了配置,所以当Tomcat的启动时会调用init(),
 * 在init()里实现了自定义的定位、加载、注册及依赖注入方法。
 */
public class DispatchServlet extends HttpServlet{

    private Properties contextConfig=new Properties();
    //存储实例化的对象,当做IOC容器
    private Map<String,Object> beanMap=new ConcurrentHashMap<String,Object>();
    //存储扫描包后,所有的类名称
    private List<String> classNames=new ArrayList<String>();

    @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 {
        System.out.println("================调用doPost================");
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //初始化IOC容器

        //定位        拿到配置we.xml里的classpath:application.properties
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        //加载
        doScanner(contextConfig.getProperty("scanPackage"));

        //注册
        doRegistry();

        //依赖注入
        //在Spring中,是通过调用getBean方法才触发依赖注入的
        doAutoWired();

        //如果是springmvc会多一个HandlerMapping
        //将@RequestMapping中配置的url和一个Method关联上
        //以便于从浏览器获得用户输入的url后,能够找到具体执行的Method,然后反射去调用

    }

    //定位
    private void doLoadConfig(String location) {
        //在spring中,是通过Reader来查找定位的
        //这里直接通过类加载器来加载资源文件application.properties,并得到一个输入流
        InputStream is=this.getClass().getClassLoader().getResourceAsStream(location.replace("classpath:",""));
        try {
            //配置文件里的键值对加载到了Properties里面
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(null != is){is.close();}
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    //加载
    private void doScanner(String packageName) {//com.ft.spring.demo
        //将类路径下com/ft/spring/demo的所有内容加载到url对象中
        URL url=this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.","/"));
        //得到demo的绝对路径,从而得到demo下的所有目录
        File files=new File(url.getFile());
        //类被加载后(.class),拿到这些类的全名,存入classNames里面,如:com.ft.spring.demo.action.DemoAction
        for (File f:files.listFiles()){
            if(f.isDirectory()){
                doScanner(packageName+"."+f.getName());
            }else{
                classNames.add(packageName+"."+f.getName().replace(".class",""));
            }
        }
    }

    //注册
    private void doRegistry() {
        if(classNames.isEmpty()){
            return;
        }
        try {
            //将相关联的类都去实例化放入IOC容器,如类、类在另一个类中存在引用
            for(String className:classNames){
                Class<?> clazz=Class.forName(className);
                //在Spring中用的多个子方法来处理的
                if(clazz.isAnnotationPresent(Controller.class)){//如果该类加了Controller注解
                    //将类名首字母小写
                    String beanName=lowerFirstCase(clazz.getSimpleName());
//                  在Spring中,这个阶段是不会直接put instance,这里put的是BeanDefinition
                    beanMap.put(beanName,clazz.newInstance());
                }else if(clazz.isAnnotationPresent(Service.class)){//如果该类加了Service注解
                    Service service=clazz.getAnnotation(Service.class);
                    //这里需要分以下几种情况
                    //1.默认使用类名首字母小写注入
                    //2.如果自己定义了beanName,那么优先使用自己定义的beanName(@Service("xxx"))
                    //3.如果是一个接口,使用接口的类型去自动注入
                    //在Spring中同样会分别调用不同的方法 autowriedByName autowritedByType
                    String beanName=service.value();//拿@Service("xxx")里面的xxx
                    if("".equals(beanName.trim())){//如果没有自己定义beanName,则使用默认的
                        //当前的类名首字母小写作为beanName
                        beanName=lowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance=clazz.newInstance();
                    beanMap.put(beanName,instance);

                    //如果是接口
                    Class<?>[] interfaces=clazz.getInterfaces();
                    for(Class<?> i:interfaces){
                        beanMap.put(i.getName(),instance);
                    }

                }else{
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //依赖注入
    private void doAutoWired() {
        if(beanMap.isEmpty()){
            return;
        }
        for(Map.Entry<String,Object> entry:beanMap.entrySet()){
            //通过类的实例拿到类的反射,再取得类里面的所有字段信息
            Field[] fields=entry.getValue().getClass().getDeclaredFields();
            for(Field field:fields){
                if(!field.isAnnotationPresent(Autowired.class)){
                    continue;
                }
                //如果该字段上加了Autowired
                Autowired autowired=field.getAnnotation(Autowired.class);
                String beanName=autowired.value().trim();//Autowired("xxx")
                if("".equals(beanName)){
                    beanName = field.getType().getName();
                }
                field.setAccessible(true);

                try {
                    field.set(entry.getValue(),beanMap.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 字符串首字母小写
     * @param str
     * @return
     */
    private String lowerFirstCase(String str){
        char [] chars = str.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}

3》以上的依赖注入是采用注解方式的,而注解也是自定义的,各个自定义注解信息如下。

//作用在参字段上
@Target({ElementType.FIELD})
//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

    String value() default "";
}

//作用在类上
@Target({ElementType.TYPE})
//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {

    String value() default "";
}

//作用在类和方法上
@Target({ElementType.TYPE,ElementType.METHOD})
//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {

    String value() default "";
}

//作用在参数上
@Target({ElementType.PARAMETER})
//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {

    String value() default "";
}

//作用在类上
@Target({ElementType.TYPE})
//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {

    String value() default "";
}

总结:从这个案例中,可以清楚的了解到spring从启动到IOC容器初始化的过程,以及注解方式是怎么进行注入的。

猜你喜欢

转载自blog.csdn.net/fu123123fu/article/details/80342196