struct2源码解读(8)之container原理

struct2源码解读之container原理

container翻译成中文的意思是容器,通俗地来说,就是struct2的运行环境。这个所谓的运行环境,有点类似于一个容器,里面装着各种对象,当struct2处理aciton请求的,就会容器中取相应的对象。下面探讨下container的实现原理。container是一个接口,主要有两个方法,一个是inject() 一个是getInstance();getInstance()是从容器取出对象,inject()是依赖注入。struts在启动的时候,就把配置文件中的对象放入到container中。下面我们介绍下它的实现原理。

public interface Container extends Serializable {

  //默认name属性
  String DEFAULT_NAME = "default";
   //依赖注入
  void inject(Object o);
  <T> T inject(Class<T> implementation);
   //根据不同条件从容器取出对象
  <T> T getInstance(Class<T> type, String name);
  <T> T getInstance(Class<T> type);
  
  Set<String> getInstanceNames(Class<?> type);
  void removeScopeStrategy();
}

一、往容器中放入对象

前面探讨过,struct2把所有配置信息都封装到实现factory接口的实现类中(具体解析过程请参考struct2源码解读之解析bean),然后把factories作为参数实例化一个container对象,containerImpl是其实现类。

代码清单:实例化一个container对象
public Container create(boolean loadSingletons) {
    ensureNotCreated();
    created = true;
    //factories作为参数
    final ContainerImpl container = new ContainerImpl(
        new HashMap<Key<?>,InternalFactory<?>>(factories));
    if (loadSingletons) {
       //loadSingletons为false,代码忽滤不计
    }
    container.injectStatics(staticInjections);
    return container;
  }

把factories作为参数实例化一个container对象,那么在container的构造函数中必有this.factories=factories,果不其然,在container的构造函数中

代码清单:
ContainerImpl(Map<Key<?>,InternalFactory<?>> factories) {
    //初始化factories属性
    this.factories = factories;
    //... 
    this.factoryNamesByType = Collections.unmodifiableMap(map);
  }

这样,在struct2初始化的过程中,所有对象就以factories形式“放入”到了container容器中了。

二、往容器中取出对象

前面讲到,既然是容器,那就有存和放。上面探讨了往容器里面放入对象,下面就来探讨下怎么从容器中取出对象。从container接口的方法名称来看,container是用getInstant()方法取出容器的对象的。

代码清单:getInstant()
 //通过class或者是name属性取出对象
  <T> T getInstance(Class<T> type,String name);
//通过class取出对象
  <T> TgetInstance(Class<T> type);

这两个方法都是通过不同的条件来取出容器中的对象。从container实现类的这个方法来看

代码清单:通过class和name属性取出对象
public <T> T getInstance(final Class<T> type, final String name) {
    return callInContext(newContextualCallable<T>() {
       //调用callable.call()方法
      public T call(InternalContextcontext) {
        return getInstance(type, name,context);
      }
    });
  }
代码清单:通过name属性取出对象
  public <T> T getInstance(final Class<T> type) {
    return callInContext(newContextualCallable<T>() {
      public T call(InternalContextcontext) {
        return getInstance(type, context);
      }
    });
  }

都是调用了callInContext()方法,顾名思义,callInContext,callin (从..取出)Context(上下文),这里是指从容器里面取出。ContextualCallable是一个接口,提供了call方法,这里也体验了接口的编程思想,当我们实现这个接口的时候,再指定我们所需要的call方法。

代码清单:
<T> TcallInContext(ContextualCallable<T> callable) {
    Object[] reference = localContext.get();
    if (reference[0] == null) {
      //this.container=container  指定容器为containerImpl
      reference[0] = new InternalContext(this);
      try {
         //调用call方法,取出对象
        return callable.call((InternalContext)reference[0]);
      } finally {
        // Only remove the context if this call created it.
        reference[0] = null;
      }
    } else {
      // Someoneelse will clean up this context.
      return callable.call((InternalContext)reference[0]);
    }
  }

在这个方法中,最后调用callable.call()方法,也就是getInstance(type,name,context)方法(在实现callable接口的时候指定),这个name如果不设置的话,就使用默认的“default”为name属性

<T> T getInstance(Class<T>type, InternalContext context) {
    return getInstance(type, DEFAULT_NAME, context);
  }

我们来看下这个getInstance(type,name,context)方法, 这个context就是containerImpl

  <T> T getInstance(Class<T> type, String name, InternalContext context) {
    ExternalContext<?> previous = context.getExternalContext();
    //获得key值
    Key<T> key = Key.newInstance(type, name);
    context.setExternalContext(ExternalContext.newInstance(null, key, this));
    try {
      //通过key值获取factory
      InternalFactory o = getFactory(key);
      if (o != null) {
            //通过工厂模式得到对象实例
          return getFactory(key).create(context);
      } else {
          return null;
      }
    } finally {
      context.setExternalContext(previous);
    }
  }

我们之前说过,在封装配置信息到factory对象后,会以Key(type,name)为键值,factory为value值,把这个factory对象保存到一个名为factories的map集合中,这里通过Key值就能找到这个factory对象,然后调用factory的create()方法就能实例化出相应key(tyoe,name)的对象。从而就能从容器中取出了这个对象。这里不懂的话,回头看下工作模式的实现原理。

三、依赖注入

struct2通过inject()方法实现依赖注入,而spring通过getBean方法实现依赖注入,所谓的依赖注入就是在实例化一个对象的时候,把这个对象的属性的值都设定好。

//根据object依赖注入
public void inject(final Object o) {
    callInContext(new ContextualCallable<Void>() {
      public Void call(InternalContext context) {
        inject(o, context);
        return null;
      }
    });
  }
  //根据class依赖注入
  public <T> T inject(final Class<T> implementation) {
    return callInContext(new ContextualCallable<T>() {
      public T call(InternalContext context) {
        return inject(implementation, context);
      }
    });
  }

这个方法和getInstance类似,不同的是callabel的call方法。这里调用的是重载后的inject()方法。

3.1.通过object依赖注入

通过object依赖注入的最终会调用inject(o,context)方法

void inject(Object o, InternalContext context) {
    //封装注解信息到injector对象
    List<Injector> injectors = this.injectors.get(o.getClass());
    //循环遍历注解对象的inject方法,实现依赖注入
    for (Injector injector : injectors) {
      injector.inject(context, o);
    }
  }

这个injectors是什么鬼?我们来看看它的定义

代码清单: injectors 
 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;
        }
      };

这个injectors其实就是一个map,一个ReferenceCache类型的map,这个ReferenceCache是struct2设计的缓存机制,我们来看看它重写后的get()方法,重写后的get()方法首先会向缓存(map)拿数据,如果发现数据不再缓存(map)中,那么这时就让缓存去加载所需要的数据,等待缓存将数据加载完毕之后,再将所需的数据返回

@SuppressWarnings("unchecked")
  @Override public V get(final Object key) {
     //调用map.get()方法从缓存中取出值
    V value = super.get(key);
    //如果缓存中没有,调用internalCreate方法,否则的话返回缓存中的值
    return (value == null)
      ? internalCreate((K) key)
      : value;
  }

这个internalCreate方法就是去加载所需要的数据。

  V internalCreate(K key) {
    try {
      //创建一个 FutureTask,一旦运行就执行给定的 Callable
      FutureTask<V> futureTask = new FutureTask<V>(
          new CallableCreate(key));
       
      Object keyReference = referenceKey(key);
      //结果只有在计算完成时获取
      Future<V> future = futures.putIfAbsent(keyReference, futureTask);
      if (future == null) {
        try {
          if (localFuture.get() != null) {
            throw new IllegalStateException(
                "Nested creations within the same cache are not allowed.");
          }
          localFuture.set(futureTask);
          futureTask.run();
          //获取缓存数据
          V value = futureTask.get();
          putStrategy().execute(this,
              keyReference, referenceValue(keyReference, value));
          return value;
        } finally {
          localFuture.remove();
          futures.remove(keyReference);
        }
      } else {
        // wait for winning thread.
        return future.get();
      }
    } 
  }

这个方法用到了FutureTask类,主线程可以在完成自己的任务后,再去获取结果,这样就能确保所有对象都放入container放入容器后再进行依赖注入。当运行FutureTask.get()方法时,会调用给定callable的call()方法(特别注意:如果传进是一个runnable,则会运行runnable的run()方法,实现原理后面单独一个篇幅讲解)。这里传进了一个callableCreate

FutureTask<V> futureTask = new FutureTask<V>(
          new CallableCreate(key));

我们来看看这个callableCreate的call方法

 public V call() {
      // 从缓存取值
      V value = internalGet(key);
      //如果存在 直接返回该值
      if (value != null) {
        return value;
      }
      // 如果不存在,调用create方法创建一个值
      value = create(key);
      if (value == null) {
       //异常信息
      }
      return value;
    }
  }

在call方法中就调用了create方法。这个create()方法是一个abstract类型,需要在实现类中实现,这又回到了我们在实现这个类的时候

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

所以最终还是执行了RrferenceCache重写的create()方法。在这个方法中,调用了addInjectors()方法,解析key这个类的@inject注解(这个key是RrferenceCache.get(o.getClass()),为什么用class类型?用class类型可获得该类的父类,属性和方法),并把每个注解封装成一个Injector对象,然后把所有的Injector放到一个list集合中。

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

    // 解析所有父类的@inject注解,这里用到了递归
    addInjectors(clazz.getSuperclass(), injectors);
    // 解析fields属性的注解,这里知道为什么用o.getClass()类型了吧?
    addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);
    //解析methods的注解
    addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);
  }

这里分Fields解析和Methods解析,上面说到会把每个@inject注解封装到相应的Inject对象,其实这个Inject只是一个接口,它有两个实现类:一个是FieldInjector,另外一个是MethodInjector;所以Fields解析和Methods解析也相应的封装成FieldInjector对象和MethodInjector对象。

  void addInjectorsForMethods(Method[] methods, boolean statics,
      List<Injector> injectors) {
    addInjectorsForMembers(Arrays.asList(methods), statics, injectors,
        new InjectorFactory<Method>() {
          public Injector create(ContainerImpl container, Method method,
              String name) throws MissingDependencyException {
              //返回一个MethodInjector对象
            return new MethodInjector(container, method, name);
          }
        });
  }

  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 {
              //返回一个FieldInjector对象
            return new FieldInjector(container, field, name);
          }
        });
  }

这里也用到了工厂模式,如果要返回FieldInjector对象和MethodInjector对象,那肯定会调用InjectoryFactory.create()方法。我们来看看他们的共同方法addInjectorsForMembers()

 <M extends Member & AnnotatedElement> void addInjectorsForMembers(
      List<M> members, boolean statics, List<Injector> injectors,
      InjectorFactory<M> injectorFactory) {
      //循环遍历,如果是method,menbers就是所有的methods;如果是Field,menbers就是所有的Fields
    for (M member : members) {
      //statics传进来的是false,因此isStatic(member)==false,条件才为true,因此要不是静态类型的才解析
      if (isStatic(member) == statics) {
        //得到注解
        Inject inject = member.getAnnotation(Inject.class);
        if (inject != null) {
          try {
             //调用工厂的create方法,返回相应对象,并把对象放到一个list集合中
            injectors.add(injectorFactory.create(this, member, inject.value()));
          } 
          //异常信息
          }
        }
      }
    }
  }

这样就封装了所有的注解信息(名称,方法/名称,容器)到了injector对象。

我们再看回container.inject()方法

 void inject(Object o, InternalContext context) {
    //封装注解信息到Injector对象
    List<Injector> injectors = this.injectors.get(o.getClass());
    //循环遍历注解对象的inject()方法实现依赖注入
    for (Injector injector : injectors) {
      injector.inject(context, o);
    }
  }

3.1.1. Method 注入

比如说,MethodInjector对象的inject()方法

  public void inject(InternalContext context, Object o) {
      try {
        method.invoke(o, getParameters(method, context, parameterInjectors));
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

这里执行了被注解方法的方法。如在set方法加了@inject注解,则会在inject(Object)时,则会调用object的该set()方法;如过在构造函数上加了@inject注解,则会在inject(Object)时,则会调用object的构造方法。这个getParameters()是对加了@inject注解的param依赖注入,如果没有加,则返回空

private static Object[] getParameters(Member member, InternalContext context,
      ParameterInjector[] parameterInjectors) {
      //如果param没有加@inject返回null
    if (parameterInjectors == null) {
      return null;
    }
    //循环遍历
    Object[] parameters = new Object[parameterInjectors.length];
    for (int i = 0; i < parameters.length; i++) {
      //调用数组中injector的inject方法给param依赖注入
      parameters[i] = parameterInjectors[i].inject(member, context);
    }
    return parameters;
  }

这个parameterInjectors是在实例化MethodInjector对象时指定的

  public MethodInjector(ContainerImpl container, Method method, String name)
        throws MissingDependencyException {
      this.method = method;
        if (!method.isAccessible()) {
             //如果没有权限访问,抛出异常
        }
      //获取param类型
      Class<?>[] parameterTypes = method.getParameterTypes();
      if (parameterTypes.length == 0) {
        //异常信息
      }
      //找出有@inject注解的param,封装到ParameterInjector对象(这是container的一个内部类),并加到parameterInjectors 数组中
      parameterInjectors = container.getParametersInjectors(
          method, method.getParameterAnnotations(), parameterTypes, name);
    }

ParameterInjector对象的inject()方法,就是调用工厂模式的create方法,创建出相应的实例。

 T inject(Member member, InternalContext context) {
      ExternalContext<?> previous = context.getExternalContext();
      context.setExternalContext(externalContext);
      try {
        //实例化params对象
        return factory.create(context);
      } finally {
        context.setExternalContext(previous);
      }
    }

3.1.2.Field注入

field注入比较简单,就是直接调用field的set方法

 public void inject(InternalContext context, Object o) {
      ExternalContext<?> previous = context.getExternalContext();
      context.setExternalContext(externalContext);
      try {
         //set()方法
        field.set(o, factory.create(context));
      } catch (IllegalAccessException e) {
        //异常信息
      } finally {
        context.setExternalContext(previous);
      }
    }

参数是直接用factory的create()方法返回一个对象实例,这个factory也是在FieldInjector实例化时指定的

  public FieldInjector(ContainerImpl container, Field field, String name)
        throws MissingDependencyException {
      this.field = field;
        if (!field.isAccessible()) {
          //没有权限访问就抛出异常
        }
      Key<?> key = Key.newInstance(field.getType(), name);
       //找到属性值的factory对象实例
      factory = container.getFactory(key);
      if (factory == null) {
         //异常信息
      }

      this.externalContext = ExternalContext.newInstance(field, key, container);
    }

3.2.通过class依赖注入

如果以一个Object的class类作为参数依赖注入的话,也就是container.inject(class),会调用inject(class, container)方法

<T> T inject(Class<T> implementation, InternalContext context) {
    try {
      //获得标有@inject的构造函数
      ConstructorInjector<T> constructor = getConstructor(implementation);
      //强制转换
      return implementation.cast(
          constructor.construct(context, implementation));
    } //异常信息
  }

我们来看看class.cast()方法,这个方法是把cast参数里面的类型强制装换成class的类型

public T cast(Object obj) {
	//异常信息略
	//强制转成传入class类的类型
	return (T) obj;
    }

而cast的参数为constructor.construct(container, class).这个constructor是一个ConstructInjector对象,上面的getConstructor也是调用ReferenceCache.get()方法封装有@inject的注解到ConstructInjector对象

 //constructors 定义
  Map<Class<?>, ConstructorInjector> constructors =
      new ReferenceCache<Class<?>, ConstructorInjector>() {
        @Override
        @SuppressWarnings("unchecked")
        protected ConstructorInjector<?> create(Class<?> implementation) {
          return new ConstructorInjector(ContainerImpl.this, implementation);
        }
      };
  //.ReferenceCache.get()
@SuppressWarnings("unchecked")
  <T> ConstructorInjector<T> getConstructor(Class<T> implementation) {
    return constructors.get(implementation);
  }

特别注意,ReferenceCache.get()方法最终会调用creat()方法,之前inject(object)调用的是addInjectors方法,这里的ceate方法是直接new一个ConstructInjector对象

  ConstructorInjector(ContainerImpl container, Class<T> implementation) {
      this.implementation = implementation;
      //获取class带有@inject注解的构造方法,如果没有,返回无参构造方法
      constructor = findConstructorIn(implementation);
        if (!constructor.isAccessible()) {
               //如果没有权限访问,抛出异常信息
        }
      MissingDependencyException exception = null;
      Inject inject = null;
      ParameterInjector<?>[] parameters = null;
        
      try {
        inject = constructor.getAnnotation(Inject.class);
         //参数的依赖注入
        parameters = constructParameterInjector(inject, container, constructor);
      } 
      parameterInjectors = parameters;

      if ( exception != null) {
         //异常信息
      }
      //这个又调用了injectors.get()方法,封装所有带@inject注解的到injector对象
      injectors = container.injectors.get(implementation);
    }

得到这个ConstructInjector对象,最后调用ConstructInjector.construct返回一个对象,然后就有了上面的用class.cast()强转成class<T> T类型的对象

    Object construct(InternalContext context, Class<? super T> expectedType) {
      ConstructionContext<T> constructionContext =
          context.getConstructionContext(this);

      if (constructionContext.isConstructing()) {
        return constructionContext.createProxy(expectedType);
      }
      T t = constructionContext.getCurrentReference();
      if (t != null) {
        return t;
      }
      try {
        // First time through...
        constructionContext.startConstruction();
        try {
          Object[] parameters =
              getParameters(constructor, context, parameterInjectors);
              //params对象实例化
          t = constructor.newInstance(parameters);
          constructionContext.setProxyDelegates(t);
        } finally {
          constructionContext.finishConstruction();
        }
        constructionContext.setCurrentReference(t);

        // 循环遍历injector的inject方法
        for (Injector injector : injectors) {
          injector.inject(context, t);
        }
        return t;
      } 
      //异常信息
    }
  }

从这里可以看出,用一个object类型做参数做依赖注入和以一个class类型做参数做依赖注入,区别就在于class类型的要先实例化成object类型,多了一个construct对象.

四、总结

从上面分析,我们知道了一个容器的工作原理:在解析配置文件的时候,把所有配置对象封装到factory接口的实现类中,并以key(class,name)为键值,factory为value值保存到一个map中,当要往容器取出对象的时候,通过getInstance方法,在以key值为条件取出这个factory,然后调用factory的create方法实例化这个对象。container另外一个工作是依赖注入,当要inject的时候,会先往缓存中取对象,如果没有就通过反射找出所有@inject属性的属性或者方法,把他们封装到injector对象,然后循环遍历这些对象的inject()方法;如果是属性的话,就调用set方法,如果是method的话就调用invoke方法,通过factory.create把对象实例化并设置好。

时间: 2024-10-14 02:06:39

struct2源码解读(8)之container原理的相关文章

struct2源码解读(3)之解析配置文件

struct2源码解读之解析配置文件 上篇博文讲到struct2在初始化时,在创建Dispacher之后,会Dispacher.init()中会对配置文件进行解析,下面就让我们一起探讨下struct2是如何解析配置文件的. public Dispatcher initDispatcher( HostConfig filterConfig ) {           //创建Dispacher实例         Dispatcher dispatcher = createDispatcher(f

struct2源码解读(5)之解析bean标签

struct2源码解读之解析bean标签 上篇博文,我们大致分析了struct2是如何解析struct2配置文件的,包括default.properties和struct*.xml,但有些细节比较繁琐,如struct2是如何解析bean标签的和struct2是如何解析packag标签的,还没详细分析,这篇博文将详细解析struct2是如何解析bean标签的,package的解析将留在下篇博文进行讲解. 一.bean标签 我们先来看下bean标签是怎么样的? 代码清单:structs.xml <?

struct2源码解读(9)之处理Action请求

struct2源码解读之处理Action请求 我们前面讨论过了struct2的初始化,我们先来回顾下  public void init(FilterConfig filterConfig) throws ServletException {         InitOperations init = new InitOperations();         try {             FilterHostConfig config = new FilterHostConfig(fil

struct2源码解读(11)之执行action请求中篇

struct2源码解读之执行action请求(2) 上篇博文介绍了执行action请求前的一些准备工作,包括封装http参数到一个map中,获得一个值栈对象和配置信息configuration,并创建一个执行action请求的actionProxy对象,并对这个对象进行了初始化,包括指定默认执行方法和对actionName对应的类进行依赖注入以及调出拦截器. 然后就到了我们执行action请求的工作.  if (mapping.getResult() != null) {            

struct2源码解读(1)之struts2启动

struct2源码解读(1)之struts启动 之前用struct2.spring.hibernate在开发一个电子商城,每天都在重复敲代码,感觉对struct2.spring.hibernate的理解都在使用层面上,虽然敲了几个月代码,但是技术水平还是得不到显著提高.于是就想着研究下struct2.spring.hibernate的源码,研究完后不仅对struct2.spring.hibernate加深了了解,同时也加强了java的学习,例如xml的解析,字符操作,线程等等,受益匪浅.想着当初

zorka源码解读之Instrument实现原理

主要用到三方面技术: beanshell来实现可扩展:告诉zorkaAgent插桩的具体需求,包括插桩的方法和值.插桩的时机.插桩追踪记录方式等. Instrument来通过代理的方式访问JVM,实现在类加载的时候访问类字节码. ASM字节码操纵框架,用于实现真正底层的类字节码的修改. 一.beanshell实现插桩具体需求(bsh如何将要插桩的内容告诉代理程序的?)给classTransformer添加SpyDefinition的实例,SpyDefinition里面定义了各种插桩行为,这些行为

OpenCV2马拉松第27圈——SIFT论文,原理及源码解读

计算机视觉讨论群162501053 转载请注明:http://blog.csdn.net/abcd1992719g/article/details/28913101 简介 SIFT特征描述子是David G. Lowe 在2004年的ijcv会议上发表的论文中提出来的,论文名为<<Distinctive Image Featuresfrom Scale-Invariant Keypoints>>.这是一个很强大的算法,主要用于图像配准和物体识别等领域,但是其计算量相比也比较大,性价

structs2源码解读(6)之解析package标签

structs2源码解读之解析package标签 上面讨论过,在创建Dispacher对象时,调用dispacher.init()方法完成初始化,在这个方法中先创建各种配置文件的解析器(ConfigurationProvider),然后循环遍历这些解析器的register()方法解析各个配置文件.  for (final ContainerProvider containerProvider : providers)         {             containerProvider

spring 源码解读与设计详解:2 BeanFactory

在spring的官网中我们看到,spring的产品已经发展的非常壮大,然而很多产品对于很多公司来讲用的非常少,甚至用不到.因此本系列的源码解读也不会涉及全部的spring的产品.而是只对spring的核心功能IoC和AOP进行解释. 所谓源码解读,解读的是什么?实际上源码解读读的更多的是源码的注释,因为一个类的作用.一个接口或者一个方法的作用,我们往往是要根据注释才知道,这也是为什么在代码规范中,注释是一个非常重要的模块的原因. 参考: Spring源码分析--BeanFactory体系之接口详