struts2 원본 분석 - IOC 용기의 실현 메커니즘 (다음 편)

이전에 struts2-IOC 용기의 실현에 대한 원본 분석에 관한 블로그(상편)를 보았는데 매우 고전적으로 썼다. 자신도 그 중에서 struts2-IOC 용기의 초기화에 관한 많은 것을 알게 되었다. 만약에 용기의 위탁 관리 대상이 무엇인지, 노드에 type 속성이name 속성이 왜 있는지, Container Builder가 Container 원리를 구축하는 등 여러 가지 방법을 알게 되었다.아쉽게도 블로거는 전편만 썼는데 주로 IOC 용기의 초기화 과정을 설명했고 용기에서 용기의 위탁 관리 대상과 주입 원리를 한 획 지나갔다.블로거는 다음 편을 기대한다고 했지만 오랜 시간이 지나도 이 블로거의 출현을 기대하지 않았다.그래서 스스로 연구하고 연구하며 자신의 역량을 헤아리지 않고 다음 편을 이어가려고 합니다. 잘못된 점은 여러분의 지적을 바랍니다.
말하기 전에 몇 가지 결론은 반드시 알아야 한다.
1. 용기 위탁 관리 대상은 무엇입니까?
struts2/XWork의 설정 요소 노드에 따라 두 종류로 나눌 수 있는데 하나는 노드와 노드이다. 이 두 노드는'용기 설정 요소'이고 다른 하나는 패키지 노드이다. 이 노드 아래의 모든 설정은'이벤트 맵 관계'로 정의된다.여기서 노드는 프레임 단계의 내장 대상과 사용자 정의 대상을 정의하는 데 광범위하게 사용되고,constant 노드와Properties 파일의 설정 옵션은 시스템 단계의 운행 파라미터를 정의하는 데 사용됩니다.n> 노드, 노드에 Propeties 파일의 설정 옵션을 합쳐서'용기 설정 요소'라고 부른다. 그들이 정의한 대상의 생명 주기는 모두 용기가 관리하기 때문에 이런 대상은 용기 위탁 관리 대상이다.
2. Container의 용기 위탁 관리 대상은 용기에 직접 저장된 것이 아니다. 실제로 용기에 저장된 것은 대상의 공장이다. 대상 공장이 있으면 언제든지 운행 기간에 대상을 얻을 수 있는 실례가 있다. 저장 공장은 대상의 발생을 통제할 수 있다. 예를 들어 새로운 대상을 되돌려야 하는지, 아니면 하나의 단일 대상을 되돌려야 하는지, 또는 하나의 라인이 안전한 대상을 되돌려야 하는지 등이다.
의존 주입
의존 주입은 바로 Container의 inject 방법을 호출하는 것입니다. 우선 이 방법 설명을 보십시오.
/**
   * Injects dependencies into the fields and methods of an existing object.
   */
  void inject(Object o);

  /**
   * Creates and injects a new instance of type {@code implementation}.
   */
  <T> T inject(Class<T> implementation);

방법은 두 가지가 있는데 첫 번째는 Object에 전송되어 이 대상을 주입하는 것이다. 여기에 있는 대상은 임의의 대상이 될 수 있으며 반드시 용기 위탁 관리 대상이 되는 것은 아니다.두 번째 방법은 Class 대상을 전송하고 주입을 완성하고 주입된 대상의 실례를 되돌려줍니다.
다음은 @Injector 메모의 소스입니다.
4
/**
 * <p>Annotates members and parameters which should have their value[s]
 * injected.
 *
 * @author [email protected] (Bob Lee)
 */
@Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface Inject {

  /**
   * Dependency name. Defaults to {@link Container#DEFAULT_NAME}.
   */
  String value() default DEFAULT_NAME;

  /**
   * Whether or not injection is required. Applicable only to methods and
   * fields (not constructors or parameters).
   */
  boolean required() default true;
}
성명에서 알 수 있듯이 이 주석은 방법, 구조기, 필드, 파라미터 목록에 표시될 수 있으며 주입할 때 사용할 수 있는 다양한 종류의 주입기에 대응한다.그 value 속성은 InternalFactory (용기 위탁 관리 대상의 공장) 의name 속성 값을 찾습니다. 기본값은default입니다.
Container는 하나의 인터페이스이고 실제 클래스는 Container Impl이다. 우리가 주입을 진행할 때 실제적으로 호출하는 것은 Contain Impl 클래스의 inject 방법이다.
이것은 ContainerImpl에서void 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(Internal Context 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을 호출하고 나머지 작업은 올바른 실행 상하문을 가져오는 것입니다.콜백 방법 콜에서 void inject(Object o, Internal Context 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 injectors =this이다.injectors.get(o.getClass());이것은 캐시 객체(Map)에서 필요한 입력기를 가져오는 작업이며 ContainerImpl의 injectors에 대한 설명입니다.
/**
   * Field and method 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;
        }
      };

사실 이 대상은 맵이고 캐시 기능만 추가되었습니다. 키는 1Class 대상이고 주입된 대상의 Class 대상입니다. 그value는 이 대상에 필요한 주입기를 주입합니다. 처음에 특정한 대상에 주입할 때 획득한 주입기는null입니다. 이때 이 대상에 필요한 주입기를 찾습니다.
입력기를 가져오는 방법은 Reference Cache의 get 방법입니다. 이것은 get 방법의 원본 코드입니다.
/**
   * {@inheritDoc}
   *
   * If this map does not contain an entry for the given key and {@link
   * #create(Object)} has been overridden, this method will create a new
   * value, put it in the map, and return it.
   *
   * @throws NullPointerException if {@link #create(Object)} returns null.
   * @throws java.util.concurrent.CancellationException if the creation is
   *  cancelled. See {@link #cancel()}.
   */
  @SuppressWarnings("unchecked")
  @Override public V get(final Object key) {
    V value = super.get(key);
    return (value == null)
      ? internalCreate((K) key)
      : value;
  }

주석에서 알 수 있듯이 지정한 키의 값을 찾지 못하면 맵에 새 값을 만들어서 되돌려줍니다. 이 새 값은 사실 주입기 목록입니다.
Internal Create 메서드의 두 가지 소스입니다.
FutureTask<V> futureTask = new FutureTask<V>(
          new CallableCreate(key));
// ...

futureTask.run();

여기에 새 Callable Create 대상이future Task 대상에 봉인되었고, 다음에future Task가 호출되었습니다.run();런 방법의 내용에 대한 호출은 Sync 대상의 innerRun () 방법이고, unnerRun 방법에는Callable Create 대상의call 방법이 호출되며, 이call 방법에는 injectors 성명에 등록된create (키) 리셋 방법이 호출됩니다.
자, 이제 이create(key) 방법에 집중할 수 있습니다. 이에는ddInjectors(key, injectors)가 호출되었습니다.메서드, 메서드 소스:
/**
   * Recursively adds injectors for fields and methods from the given class to
   * the given list. Injects parent classes before sub classes.
   */
  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);
  }

이 방법은 귀속 방법으로 먼저 부류 주입기를 찾아 부류에 주입을 실시하고 그 다음은 자신이다.여기addInjectorsForFields와addInjectorsForMethods가 호출하는 방법은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);
            }
          }
        }
      }
    }
  }

이 방법은 InjectorFactory의create 방법을 호출하여 하나의 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 구조 방법에서 필드 유형과name 파라미터에 따라 키로 용기에서 용기 위탁 관리 대상을 찾는 인터넷 팩토리를 묻는다. 다음은FieldInjector 클래스 inject 방법의 실현이다.
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 () 메서드, 설정할 값은 인터넷 팩토리의create 메서드가 반환하는 값입니다.여기까지 struts2의 주입 원리에 대해 어느 정도 알고 계시겠죠?
2. 용기 위탁 관리 대상 획득
Container 인터페이스의 선언입니다.
/**
   * Gets an instance of the given dependency which was declared in
   * {@link com.opensymphony.xwork2.inject.ContainerBuilder}.
   */
  <T> T getInstance(Class<T> type, String name);

  /**
   * Convenience method. Equivalent to {@code getInstance(type,
   * DEFAULT_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);
    }
  }

이 방법의 코드도 매우 간단하다. 먼저 type,name에 따라 키 대상을 구성한다. 사실 키는 type과name을 저장하는 POJO이다. 키로 용기에서 해당하는 인터넷 팩토리 대상을 찾은 다음에 인터넷 팩토리 대상을 호출하는create 방법으로 이 값을 되돌려준다.
여기에 struts2에서 bean은 변수의 범위를 설명할 수 있지 않습니까?예를 들어singleton,request,session,thread의 경우 인터넷 팩토리의create 방법을 직접 호출했을 뿐입니다. 이렇게 하면 목적을 달성할 수 있습니까? 앞에서 말했듯이 용기에 저장된 이 공장들의 장점은 바로 대상을 제어할 수 있는 것입니다. 이런 기능 논리는 모두 인터넷 팩토리에 봉인되어 있습니다.구체적인 것은struts2 원본 분석-IOC 용기의 실현 메커니즘(상편)을 참고하십시오. 상세한 설명이 있습니다.
struts2에서는 템플릿 방법, 리셋 방법을 대량으로 응용했는데 방금 보니까 익숙하지 않을 수도 있어요. 몇 번 더 보면 문제가 없을 거예요.여러분에게 도움이 되었으면 좋겠습니다. 잘못이 있으면 바로잡아 주십시오.

좋은 웹페이지 즐겨찾기