struts2源码分析-IOC容器的实现机制(下篇)
来源:互联网 发布:dbc数据库怎么打开 编辑:程序博客网 时间:2024/06/05 11:43
以前看到一篇关于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方法,先看一下该方法声明
/** * 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注解的源码:
/** * <p>Annotates members and parameters which should have their value[s] * injected. * * @author crazybob@google.com (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为一个接口,其实现类为ContainerImpl,当我们要进行注入时,其实调用的就是ContainImpl类的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(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<Injector> 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; } };
其实大家可以看到,其实该对象就是个Map,只不过增加了缓存的功能,其key为一Class对象,就是被注入对象的Class对象,其value为该对象注入所需注入器,当第一次为某一对象进行注入时,获取的注入器肯定为null,这时就会去查找该对象注入所需要的注入器。
大家可以看到获取注入器用的是ReferenceCache的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; }
从它的注释也可以看出来,如果说没有找到指定key的值,该方法则会创建一个新的值放入Map中并返回,这个新的值其实就是注入器列表。
这是internalCreate方法中的两句源码:
FutureTask<V> futureTask = new FutureTask<V>( new CallableCreate(key));//中间省略...futureTask.run();
这里新建了一个CallableCreate对象封装到了futureTask对象中,后面又调用了futureTask.run();在run方法内容调用的又是一个Sync对象的innerRun()方法,在unnerRun方法中调用了CallableCreate对象的call方法,而该call方法中调用的正是injectors声明中注册的create(key)回调方法。
好了,现在可以将注意力集中到该create(key)方法上了来,该中调用了addInjectors(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构造方法内问,根据field类型与name参数作为key在容器中查找对应容器托管对象的InternalFactory,下面是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()方法,要设置的值就是相应的InternalFactory的create方法返回的值。到这里大家对struts2中的注入原理应该有一定的了解了吧。
二、获取容器托管对象
这是在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构造一个Key对象,其实Key就是一个用于存储type与name的一个POJO,用它作为key去容器中查找相应的InternalFactory对象,然后调用InternalFactory对象的create方法返回该值。
这里大家可能会问:struts2中bean不是可以声明变量范围吗,如singleton,request,session,thread的吗,这里只是直接调用了InternalFactory的create方法,这样能达到目的吗,前面也已经说了容器中存储这些工厂其好处就是可以控制对象的产生,这些功能逻辑都已经封装在InternalFactory当中的,具体的请参看struts2源码分析-IOC容器的实现机制(上篇),里面有很详细的说明。
struts2中呢大量应用了模版方法,回调方法,刚看可能会不习惯,多看几遍应该没有问题的。希望对大家有所帮助,如有错误请指正。
- struts2源码分析-IOC容器的实现机制(下篇)
- struts2源码分析-IOC容器的实现机制(上篇)
- struts2源码分析-IOC容器的实现机制(上篇)
- struts2源码分析-IOC容器的实现机制(上篇)
- struts2源码分析-IOC容器的实现机制(上篇)
- struts2源码分析-IOC容器的实现机制(上篇)
- struts2源码分析-IOC容器的实现机制(上篇)
- struts2源码分析--IOC容器的实现(操作以及容器的初始化)
- Struts2容器详解---IoC源码分析
- Spring源码分析----IOC容器的实现(IoC容器的初始化过程(定位、载入解析、注册))
- (十)Spring事务处理 - IoC容器的事务处理源码分析
- Spring源码分析(一)-Spring IoC容器的设计
- Spring IOC 源码分析:容器的启动
- Spring源码解析之IoC容器系列的设计实现(IoC容器系列概况)
- Spring源码分析----IoC容器(一)
- Spring源码分析----IoC容器(二)
- ioc容器源码分析(一)
- 【Spring源码--IOC容器的实现】-- 综述
- birt session 过期问题,跨域问题
- rcp新建工程没有图标的问题解决方案
- 高精度计时器
- __FUNCSIG__ __FUNCDNAME__ __FUNCTION__ __func__
- hdu 1061 数学
- struts2源码分析-IOC容器的实现机制(下篇)
- linux Qt进行qwt的安装与使用
- hessian (Java 篇)
- 怎样写参数个数可变的宏
- Uva1 1391/LA 3713 - Astronauts 构图2-sat...更正了toposort过程...
- 注意javascript变量声明提升的陷阱
- 不能被声明为虚函数的C++函数
- uva 10763 Foreign Exchange
- mysql问题: alter导致速度慢