通过struts2源码分析-IOC容器的实现机制(上篇),可以从中了解了很多关于struts2-IOC容器初始化的东西,如果容器托管对象是什么,<bean>节点中为什么有了type属性还要有name属性,ContainerBuilder构建Container原理等。主要是讲解了IOC容器的初始化过程,而对从容器中获取容器托管对象以及注入原理一笔带过了。该篇文章将详细讲解如何从容器中获取容器托管对象以及注入原理。

讲解之前有些结论是必须要知道的:

  1. 什么是容器托管对象?

    按struts2/XWork的配置元素节点可分为两类,一类是<bean>节点和<constant>节点,这两个节点为“容器配置元素”,另一类是package节点,这个节点下的所有配置定义为"事件映射关系"。其中<bean>节点广泛用于定义框架级别的内置对象和自定义对象,而constant节点和Properties文件中的配置选项则被用于定义系统级别的运行参数。<bean>节点,<constant>节点加上Propeties文件中的配置选项统称为"容器配置元素",就是因为它们所定义的对象的生命周期都是由容器管理的,所以这些对象就是容器托管对象。

  2. Container中的容器托管对象并不是直接存储在容器中的,实际上容器中存储的是对象的工厂,有了对象工厂就可以随时在运行期 获得对象的实例,存储工厂有个好处就是可以控制对象的产生,例如是要返回一个新的对象呢,还是返回一个单例对象,或者说返 回一个线程安全的对象等。

一、依赖注入

依赖注入也就是调用Container中的inject方法,先看一下该方法声明

  void inject(Object o);

  <T> T inject(Class<T> implementation);

方法有两个,第一个是传入一个Object,对该对象进行注入,注意,这里的对象可以是任意对象,不一定要是容器托管对象。第二个方法传入一个Class对象,完成注入并返回一个注入好了的对象实例。

下面是@Injector注解的源码:

@Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface Inject {

  String value() default DEFAULT_NAME;

  boolean required() default true;
}

从其声明可以看到,该注释可标于方法,构造器,字段,参数列表中,这也对应了进行注入时可能用到的各种类型的注入器。其value属性为查找InternalFactory(容器托管对象的工厂)的name属性值,默认值为default

Container为一个接口,其实现类为ContainerImpl,当我们要进行注入时,其实调用的就是ContainImpl类的inject方法。如下ContainerImplvoid inject(Object o)的实现:

public void inject(final Object o) {
    callInContext(new ContextualCallable<Void>() {
      public Void call(InternalContext context) {
        inject(o, context);
        return null;
      }
    });
  }

在该实现方法当中调用的是callInContext方法,这是一个模版方法,在后面可以看到,从容器中获取对象的方法getInstance内部调用的也是该方法,其目的是将所有接口操作进行规范化定义,作为一个统一操作的入口,并将它们纳入一个线程安全的上下文执行执行环境中。而具体的执行逻辑则被封装于ContextualCallable接口的回调实现之内。对于不同的接口实现,它们会拥有不同的逻辑,而这些逻辑则由ContextualCallable接口实现类来完成,正因为这样才有可能inject方法与getInstance方法内部调用的都是同一个callInContext方法。这就是模版方法的使用,模版方法只是规定了程序的执行流程,而程序执行的具体逻辑可以由各调用者自己指定。这里public Void call(InternalContext context)即是那个回调方法。

如下是callInContext方法的源码:

<T> T callInContext(ContextualCallable<T> callable) {
    Object[] reference = localContext.get();
    if (reference[0] == null) {
      reference[0] = new InternalContext(this);
      try {
        return callable.call((InternalContext)reference[0]);
      } finally {
        // Only remove the context if this call created it.
        reference[0] = null;
      }
    } else {
      // Someone else will clean up this context.
      return callable.call((InternalContext)reference[0]);
    }
  }

在该方法当中就是调用了前面注册的回调方法call,其余的操作都是在获取一个正确的执行上下文。在回调方法call中调用了void inject(Object o, InternalContext context)方法,我们进行该方法的内部看看:

void inject(Object o, InternalContext context) {
    List<Injector> injectors = this.injectors.get(o.getClass());
    for (Injector injector : injectors) {
      injector.inject(context, o);
    }
  }

该方法其中的逻辑很简单,首先获取该对象所需要的注入器(Injector),再循环迭代第一个注入器,调用注入器的inject方法完成注入。

这里需要讲的是这句List&lt;Injector&gt; injectors = this.injectors.get(o.getClass());这是从一个缓存对象(Map)中获取所需注入器的操作,这是ContainerImplinjectors的声明:

  final Map<Class<?>, List<Injector>> injectors =
      new ReferenceCache<Class<?>, List<Injector>>() {
        @Override
        protected List<Injector> create(Class<?> key) {
          List<Injector> injectors = new ArrayList<Injector>();
          addInjectors(key, injectors);
          return injectors;
        }
      };

其实大家可以看到,其实该对象就是个Map,只不过增加了缓存的功能,其key为一Class对象,就是被注入对象的Class对象,其value为该对象注入所需注入器,当第一次为某一对象进行注入时,获取的注入器肯定为null,这时就会去查找该对象注入所需要的注入器。

大家可以看到获取注入器用的是ReferenceCacheget方法,这是get方法的源代码:

  @SuppressWarnings("unchecked")
  @Override public V get(final Object key) {
    V value = super.get(key);
    return (value == null)
      ? internalCreate((K) key)
      : value;
  }

如果说没有找到指定key的值,该方法则会创建一个新的值放入Map中并返回,这个新的值其实就是注入器列表。

这是internalCreate方法中的两句源码:

FutureTask<V> futureTask = new FutureTask<V>(
          new CallableCreate(key));
//中间省略...

futureTask.run();

这里新建了一个CallableCreate对象封装到了futureTask对象中,后面又调用了futureTask.run();run方法内容调用的又是一个Sync对象的innerRun()方法,在innerRun方法中调用了CallableCreate对象的call方法,而该call方法中调用的正是injectors声明中注册的create(key)回调方法。

好了,现在可以将注意力集中到该create(key)方法上了来,该中调用了addInjectors(key, injectors);方法,该方法源码为:

  void addInjectors(Class clazz, List<Injector> injectors) {
    if (clazz == Object.class) {
      return;
    }

    // Add injectors for superclass first.
    addInjectors(clazz.getSuperclass(), injectors);

    // TODO (crazybob): Filter out overridden members.
    addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);
    addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);
  }

该方法是一个递归方法,首先查找父类注入器,先为父类实施注入,其次是自己。这里addInjectorsForFieldsaddInjectorsForMethods调用的都是一个名为addInjectorsForMembers的方法,下面是 addInjectorsForMembers源码:

<M extends Member & AnnotatedElement> void addInjectorsForMembers(
      List<M> members, boolean statics, List<Injector> injectors,
      InjectorFactory<M> injectorFactory) {
    for (M member : members) {
      if (isStatic(member) == statics) {
        Inject inject = member.getAnnotation(Inject.class);
        if (inject != null) {
          try {
            injectors.add(injectorFactory.create(this, member, inject.value()));
          } catch (MissingDependencyException e) {
            if (inject.required()) {
              throw new DependencyException(e);
            }
          }
        }
      }
    }
  }

该方法只是调用InjectorFactorycreate方法返回一个Injector并放入到注入器列表当中,create方法呢又是一个回调方法,这里以FieldInjector为例,这是addInjectorsForFields方法的源码:

void addInjectorsForFields(Field[] fields, boolean statics,
      List<Injector> injectors) {
    addInjectorsForMembers(Arrays.asList(fields), statics, injectors,
        new InjectorFactory<Field>() {
          public Injector create(ContainerImpl container, Field field,
              String name) throws MissingDependencyException {
            return new FieldInjector(container, field, name);
          }
        });
  }

create方法中传入容器对象,Field, name返回了一个字段注入器,方法注入器原理也是如些,这里就不说了。

FieldInjector构造方法内问,根据field类型与name参数作为key在容器中查找对应容器托管对象的InternalFactory,下面是FieldInjectorinject方法的实现:

public void inject(InternalContext context, Object o) {
      ExternalContext<?> previous = context.getExternalContext();
      context.setExternalContext(externalContext);
      try {
        field.set(o, factory.create(context));
      } catch (IllegalAccessException e) {
        throw new AssertionError(e);
      } finally {
        context.setExternalContext(previous);
      }
    }

大家可以看到最终调用的就是通过反射技术调用field.set()方法,要设置的值就是相应的InternalFactorycreate方法返回的值。到这里大家对struts2中的注入原理应该有一定的了解了吧。

二、获取容器托管对象

这是在Container接口中的声明:

  <T> T getInstance(Class<T> type, String name);

  <T> T getInstance(Class<T> type);

其实这两个方法的实质是一样的,第二个其实就是一个简单方法,相当于getInstance(type,DEFAULT_NAME);就是以type和name作为联合主键进行查找。

再看看ContainerImpl的具体实现:

public <T> T getInstance(final Class<T> type, final String name) {
    return callInContext(new ContextualCallable<T>() {
      public T call(InternalContext context) {
        return getInstance(type, name, context);
      }
    });
  }

这里调用的方法如同上面所说,都是callInContext这个模版方法,其真正逻辑实现是getInstance(type,name,context)方法,其源码为:

@SuppressWarnings("unchecked")
  <T> T getInstance(Class<T> type, String name, InternalContext context) {
    ExternalContext<?> previous = context.getExternalContext();
    Key<T> key = Key.newInstance(type, name);
    context.setExternalContext(ExternalContext.newInstance(null, key, this));
    try {
      InternalFactory o = getFactory(key);
      if (o != null) {
          return getFactory(key).create(context);
      } else {
          return null;
      }
    } finally {
      context.setExternalContext(previous);
    }
  }

该方法代码也很简单,首先根据typename构造一个Key对象,其实Key就是一个用于存储typename的一个POJO,用它作为key去容器中查找相应的InternalFactory对象,然后调用InternalFactory对象的create方法返回该值。
这里大家可能会问:struts2中bean不是可以声明变量范围吗,如singleton,request,session,thread的吗,这里只是直接调用了InternalFactorycreate方法,这样能达到目的吗,前面也已经说了容器中存储这些工厂其好处就是可以控制对象的产生,这些功能逻辑都已经封装在InternalFactory当中的,具体的请参看struts2源码分析-IOC容器的实现机制(上篇),里面有很详细的说明。

struts2中呢大量应用了模版方法,回调方法,刚看可能会不习惯,多看几遍应该没有问题的。希望对大家有所帮助。
-------------------------------- END -------------------------------

及时获取更多精彩文章,请关注公众号《Java精讲》。