学习一个框架,需要了解框架如何初始化,框架的处理流程,框架如何配置,和参数是如何注入的。本文从一个具体的示例出发总结X3中各种参数的注入。
要学会如何使用一个框架,首先需要知道如何配置配置文件,其次需要了解业务代码如何编写。配置文件相比X2封装性更强,更加令人难以理解,乃至于Bean的ID开发人员都能不知道;相比于业务代码编写这块,虽然也封装了很多,但不置可否从一方面来讲确实很容易上手,但另一方面,对于不了解原理的开发人员而言很难理解各种参数是怎么来的?框架怎么跑到这个模块的?
从一个petstore的例子开始,该screen做了修改:
package com.alibaba.sample.petstore.web.admin.module.screen; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import com.alibaba.citrus.turbine.Context; import com.alibaba.citrus.turbine.dataresolver.Param; import com.alibaba.sample.petstore.biz.store.StoreManager; import com.alibaba.sample.petstore.dal.dao.CategoryDao; import com.alibaba.sample.petstore.dal.dao.ProductDao; import com.alibaba.sample.petstore.dal.dataobject.Category; import com.alibaba.sample.petstore.dal.dataobject.Product; import com.alibaba.sample.petstore.web.common.karry; public class ItemList { @Autowired private StoreManager storeManager; private ProductDao productDao; @Autowired private CategoryDao categoryDao; @Autowired private HttpSession session; public void setProductDao(productDao) {this.productDao = productDao;} @SuppressWarnings("unchecked") public void execute(@Param("productId") String productId, Context context, @karry String testKarry) throws Exception { System.out.print(testKarry); List items = storeManager.getAllItems(productId); Product product = productDao.getProductById(productId); Category category = categoryDao.getCategoryById(product.getCategoryId()); context.put("category", category); context.put("product", product); context.put("items", items); } }
上面代码的业务部分不难看懂,但是却注入了很多参数,对于这些参数,基于注入方式分类如下:
1、Spring IOC的set注入,对应于ProductDao
2、Spring IOC的@Autowired注解自动注入,对应于categoryDao
3、Spring IOC的基于类型的注入,webx3对一些短生命周期的对象做了特殊处理,使之能注入到单例中,对应于session
4、参数的注入,webx3基于DataResolver服务将request的参数解析出来注入到模块的方法中,对应于productId
5、自己拓展的DataResolver服务,对应于testKarry
下面就主要基于这几种方式来分析注入的原理
【AutowiredAnnotationBeanPostProcessor】 protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { Field field = (Field) this.member; try { Object value = null; if (this.cached) { if (this.cachedFieldValue instanceof DependencyDescriptor) { DependencyDescriptor descriptor = (DependencyDescriptor) this.cachedFieldValue; TypeConverter typeConverter = beanFactory.getTypeConverter(); value = beanFactory.resolveDependency(descriptor, beanName, null, typeConverter); } else if (this.cachedFieldValue instanceof RuntimeBeanReference) { value = beanFactory.getBean(((RuntimeBeanReference) this.cachedFieldValue).getBeanName()); } else { value = this.cachedFieldValue; } } else { Set<String> autowiredBeanNames = new LinkedHashSet<String>(1); TypeConverter typeConverter = beanFactory.getTypeConverter(); DependencyDescriptor descriptor = new DependencyDescriptor(field, this.required); this.cachedFieldValue = descriptor; value = beanFactory.resolveDependency(descriptor, beanName, autowiredBeanNames, typeConverter); if (value != null) { registerDependentBeans(beanName, autowiredBeanNames); if (autowiredBeanNames.size() == 1) { String autowiredBeanName = autowiredBeanNames.iterator().next(); if (beanFactory.containsBean(autowiredBeanName)) { if (beanFactory.isTypeMatch(autowiredBeanName, field.getType())) { this.cachedFieldValue = new RuntimeBeanReference(autowiredBeanName); } } } } else { this.cachedFieldValue = null; } this.cached = true; } if (value != null) { ReflectionUtils.makeAccessible(field); field.set(bean, value); } } catch (Throwable ex) { throw new BeanCreationException("Could not autowire field: " + field, ex); } } }
上面代码有3点值得注意:
1、从类名可以看出,入口是BeanPostProcessor
2、value的获得主要有两种途径,先看get 的这种途径,另一种途径下面会用到再细说
3、获得value之后是通过field的set方法直接塞进去的,这里没有setter方法
参看上面的代码,流程便从:value = beanFactory.resolveDependency(descriptor, beanName, null, typeConverter);开始
【DefaultListableBeanFactory】 public Object resolveDependency(DependencyDescriptor descriptor, String beanName, Set autowiredBeanNames, TypeConverter typeConverter) throws BeansException { Class type = descriptor.getDependencyType(); if (type.isArray()) { Class componentType = type.getComponentType(); Map matchingBeans = findAutowireCandidates(beanName, componentType, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(componentType, "array of " + componentType.getName(), descriptor); } return null; } if (autowiredBeanNames != null) { autowiredBeanNames.addAll(matchingBeans.keySet()); } TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter()); return converter.convertIfNecessary(matchingBeans.values(), type); } 。。。。。。
protected Map findAutowireCandidates(String beanName, Class requiredType, DependencyDescriptor descriptor) { String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this, requiredType, true, descriptor.isEager()); Map result = new LinkedHashMap(candidateNames.length); for (Iterator it = this.resolvableDependencies.keySet().iterator(); it.hasNext();) { Class autowiringType = (Class) it.next(); if (autowiringType.isAssignableFrom(requiredType)) { Object autowiringValue = this.resolvableDependencies.get(autowiringType); if (autowiringValue instanceof ObjectFactory && !requiredType.isInstance(autowiringValue)) { autowiringValue = ((ObjectFactory) autowiringValue).getObject(); } if (requiredType.isInstance(autowiringValue)) { result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue); break; } } }
哪些对象需要byType:request、response、requestContext的各种子类,因为线程空间该类型是唯一的,因此下面将介绍request的前因后果:
【RequestContextBeanFactoryPostProcessor】 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { // 先注册request/response/session,再从beanFactory中取得requestContexts。 // 创建全局的request实例。 register(beanFactory, ServletRequest.class, createProxy(HttpServletRequest.class, beanFactory.getBeanClassLoader(), new RequestObjectFactory())); // 创建全局的session实例。 register(beanFactory, HttpSession.class, createProxy(HttpSession.class, beanFactory.getBeanClassLoader(), new SessionObjectFactory())); // 创建全局的response实例。 register(beanFactory, ServletResponse.class, createProxy(HttpServletResponse.class, beanFactory.getBeanClassLoader(), new ResponseObjectFactory())); // 取得requestContexts时会激活requestContexts的初始化。 // 由于request/response/session已经被注册,因此已经可被注入到requestContexts的子对象中。 RequestContextChainingService requestContexts = (RequestContextChainingService) beanFactory.getBean( requestContextsName, RequestContextChainingService.class); // 创建全局的request context实例。 for (RequestContextInfo<?> info : requestContexts.getRequestContextInfos()) { Class<? extends RequestContext> requestContextInterface = info.getRequestContextInterface(); Class<? extends RequestContext> requestContextProxyInterface = info.getRequestContextProxyInterface(); register( beanFactory, requestContextInterface, createProxy(requestContextProxyInterface, beanFactory.getBeanClassLoader(), new RequestContextObjectFactory(requestContextProxyInterface))); }
从类名可以看出,注册是何时开始的。注册了request、response、session和request context的各个子类的实例,有几个问题:
1、从注册调用的接口来看,并没有注册在BeanFactory中,查看源码发现注册在了resolvableDependencies这个map中,这个map有什么用?
该map是为byType装配而准备的,之前一直不理解resolvableDependencies干嘛用的,因为很少关注byType的注入。
2、为什么要注册request级别的对象,装配给singlon对象,不是不安全吗?
这里采用了代理的方式,request级别的对象被包装成单例对象,仅仅将自身的引用暴露出来。
3、request级别的对象如何确保在每个线程中单例,而每个线程都保留自身的拷贝的?
下面的代码可以看到,RequestContextHolder保留了request的ThreadLoacl变量。
private static class RequestObjectFactory implements ObjectFactory { public Object getObject() { RequestAttributes requestAttrs = RequestContextHolder.currentRequestAttributes(); if (!(requestAttrs instanceof ServletRequestAttributes)) { throw new IllegalStateException("Current request is not a servlet request"); } HttpServletRequest request = ((ServletRequestAttributes) requestAttrs).getRequest(); if (request == null) { throw new IllegalStateException("Current request is not a servlet request"); } return request; } } public abstract class RequestContextHolder { private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader()); private static final ThreadLocal requestAttributesHolder = new NamedThreadLocal("Request attributes"); private static final ThreadLocal inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context"); 。。。。。。
4、上面的代码也看到了,request倒是线程本地了,但是各个request context子类是怎么确保每个线程保留副本的呢?
public Object getObject() { HttpServletRequest request = (HttpServletRequest) super.getObject(); RequestContext requestContext = RequestContextUtil.findRequestContext(request, requestContextInterface); if (requestContext == null) { throw new IllegalStateException("Current request does not support request context: " + requestContextInterface.getName()); } return requestContext; } } public static <R extends RequestContext> R findRequestContext(HttpServletRequest request, Class<R> requestContextInterface) { return findRequestContext(getRequestContext(request), requestContextInterface); } public static <R extends RequestContext> R findRequestContext(RequestContext requestContext, Class<R> requestContextInterface) { do { if (requestContextInterface.isInstance(requestContext)) { break; } requestContext = requestContext.getWrappedRequestContext(); } while (requestContext != null); return requestContextInterface.cast(requestContext); }
上面的代码同时也说明了,为什么request都要持有requestContext的原因,这只是一方面,另一方面在request的处理上也会用到的。
5、现在单例依赖单例的问题倒是实现了,但问题是怎么保证这个单例包装的request是每次请求的内容呢,毕竟现在都是null啊?
将引用暴露给requestContextHolder,在每次请求过来的时候,组装requestContext,最后生成的request复制给requestContextHolder所持有的request,因此request得到了更新。
6、通过上面的描述,对于单例依赖原型bean有了新的认识,总结下单例依赖原型的三种方式的异同点
TODO
Request的生成位于每次请求到来时requestContext的组装阶段
public RequestContext getRequestContext(ServletContext servletContext, HttpServletRequest request, HttpServletResponse response) { assertInitialized(); RequestContext requestContext = new SimpleRequestContext(servletContext, request, response); // 将requestContext放入request中,以便今后只需要用request就可以取得requestContext。 // 及早设置setRequestContext,以便随后的prepareRequestContext就能使用。 RequestContextUtil.setRequestContext(requestContext); for (RequestContextFactory<?> factory : factories) { requestContext = factory.getRequestContextWrapper(requestContext); // 调用<code>requestContext.prepare()</code>方法 prepareRequestContext(requestContext); // 将requestContext放入request中,以便今后只需要用request就可以取得requestContext。 RequestContextUtil.setRequestContext(requestContext); } setupSpringWebEnvironment(requestContext.getRequest()); return requestContext; }
分析上面的代码,框架采用了装饰者模式实现对request的处理,结合接口的调用,有问题如下:
1、request为什么要放入request中?
上面也分析过了,request context封装了每阶段request的上下文环境和行为。但是request context holder中仅仅持有request,为了能得到任一阶段的request contex必须这么做,置于拿来干什么,自然是为了context的行为了
2、在request context组装的过程中,有哪些对象需要被封装?
首先要被封装的是request context自身,这点是无容置疑的。其实每个request context所持有的request和response根据具体的request context进行分别封装,对于需要对request进行处理的request context来说,需要封装一份新的request而保证不将旧的request引用丢弃,各司其职,尽量减少耦合,其实还是有耦合的,后一个request毕竟还是依赖前一个request
3、为什么要用工厂设计模式的办法,感觉多此一举了?
主要有两点:一是每个request context的实例化方式不一样;二是工厂里持有每个request context的环境。结合上面两点可以看到,工厂关心的是为request 准备环境和生产各种request context,而开发人员只需要关心从工厂中取到request context,request context只需要关心依据配置定义各种行为
4、prepareRequestContext是干嘛的?
行为定义在了request context内部,旨在定义request和response在handle和conmiit之前需要进行的操作,比如说setCharset,毕竟不是每个request context都需要做这种操作的。不过我想貌似放在commit阶段也不为过吧?不过就是不合理罢了。
5、setSpringWebEnv是干嘛的?
将最终装配好的request context引用传递给request context holder所持有的request,达到更新request的目的
应用的地方比较多,这里就举2个具有代表的例子
A、TurbinRunDataResolverFactory
【TurbineRunDataResolverFactory】 public class TurbineRunDataResolverFactory implements DataResolverFactory { private final HttpServletRequest request; public TurbineRunDataResolverFactory(HttpServletRequest request) { this.request = assertProxy(request); } public DataResolver getDataResolver(DataResolverContext context) { // 当所需要的对象未定义时,resolver factory仍可以创建,但在取得resolver时报错。 // 这样使得同一套配置可用于所有环境,仅当你需要注入特定对象时,才报错。 assertNotNull(request, "no HttpServletRequest proxy defined"); int resolvableTypeIndex = getResolvableTypeIndex(context); if (resolvableTypeIndex < 0) { return null; } return new TurbineRunDataResolver(context, resolvableTypeIndex); } protected final TurbineRunDataInternal getTurbineRunData() { return (TurbineRunDataInternal) TurbineUtil.getTurbineRunData(request); } protected final String getModuleType(DataResolverContext context) { ModuleInfo moduleInfo = context.getExtraObject(ModuleInfo.class); if (moduleInfo != null) { return moduleInfo.getType(); } else { return null; } } private class TurbineRunDataResolver extends AbstractDataResolver { private final int resolvableTypeIndex; private TurbineRunDataResolver(DataResolverContext context, int resolvableTypeIndex) { super("TurbineRunDataResolver", context); this.resolvableTypeIndex = resolvableTypeIndex; } public Object resolve() { switch (resolvableTypeIndex) { case index_TurbineRunData: // 取得TurbineRunData/Navigator return getTurbineRunData(); case index_HttpServletRequest: // 取得当前的request return getTurbineRunData().getRequest(); 。。。。。。
public static TurbineRunData getTurbineRunData(HttpServletRequest request) { return getTurbineRunData(request, false); } public static TurbineRunData getTurbineRunData(HttpServletRequest request, boolean create) { TurbineRunData rundata = (TurbineRunData) request.getAttribute(TURBINE_RUNDATA_KEY); if (rundata == null && create) { rundata = new TurbineRunDataImpl(request); request.setAttribute(TURBINE_RUNDATA_KEY, rundata); } return assertNotNull(rundata, "TurbineRunData not found in request attributes"); }
解析上面的代码,有几个需要注意的地方:
(1)这是用来做参数注入的,将request装配成turbine风格的rundata,注入到module的参数中,最重要的是context
(2)request是byType注入的,皆为bean啊
(3)第一次调用getTurbineRundata时生成rundata
B、ParameterResolverFactory
public class ParameterResolverFactory implements DataResolverFactory { private final ParserRequestContext parserRequestContext; public ParameterResolverFactory(ParserRequestContext parserRequestContext) { this.parserRequestContext = assertProxy(parserRequestContext); } public DataResolver getDataResolver(DataResolverContext context) { // 当所需要的对象未定义时,resolver factory仍可以创建,但在取得resolver时报错。 // 这样使得同一套配置可用于所有环境,仅当你需要注入特定对象时,才报错。 assertNotNull(parserRequestContext, "no ParserRequestContext defined"); // 单个参数 Param paramAnnotation = context.getAnnotation(Param.class); if (paramAnnotation != null) { String[] defaultValues = getDefaultValues(paramAnnotation, context); String paramName = DataResolverUtil.getAnnotationNameOrValue(Param.class, paramAnnotation, context, !isEmptyArray(defaultValues)); return new ParameterResolver(context, defaultValues, paramName); } } private String[] getDefaultValues(Param param, DataResolverContext context) { String defaultValue = trimToNull(param.defaultValue()); if (defaultValue == null) { return param.defaultValues(); } else { // 避免defaultValue和defaultValues同时出现。 assertTrue(isEmptyArray(param.defaultValues()), "use @Param(... defaultValue=\"...\") or @Param(... defaultValues={...}): %s", context); return new String[] { defaultValue }; } } /** * 用来解析单个参数的resolver。 */ private class ParameterResolver extends AbstractDataResolver { private final String[] defaultValues; private final String paramName; private ParameterResolver(DataResolverContext context, String[] defaultValues, String paramName) { super("ParameterResolver", context); this.defaultValues = defaultValues; this.paramName = paramName; } public Object resolve() { ParameterParser params = parserRequestContext.getParameters(); Class<?> paramType = context.getTypeInfo().getRawType(); MethodParameter methodParameter = context.getExtraObject(MethodParameter.class); return params.getObjectOfType(paramName, paramType, methodParameter, defaultValues); } }
解析上面的代码,有几个值得注意的地方:
1、上面的例子注入的是request,这里注入的是parseRequestContext
2、parserRequestContext.getParameters调用的便是该request context的行为
DataResolver在Webx3中代表一个数据解析器,从上下文(request,session,request context) 解析出值,注入到响应的参数中。
传统的写法:
public void execute(Rundata rundata, Context context) { String name = rundata.getParameters().getString("name"); context.put("name", name); }
参数注入的写法:
public void execute(@Param("name") String name, Context context) { context.put("name", name); }
依据class中的参数取得dataResolver的过程
执行resolver参数注入的过程
package com.alibaba.sample.petstore.web.common; import java.lang.annotation.*; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.PARAMETER }) public @interface karry { String name() default "chenshuai"; }
package com.alibaba.sample.petstore.web.common; import static com.alibaba.citrus.util.Assert.*; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import com.alibaba.citrus.service.dataresolver.DataResolver; import com.alibaba.citrus.service.dataresolver.DataResolverContext; import com.alibaba.citrus.service.dataresolver.DataResolverFactory; public class KarryResolverFactory implements DataResolverFactory { @Autowired private HttpServletRequest request; public DataResolver getDataResolver(DataResolverContext context) { karry ky = context.getAnnotation(karry.class); if (ky == null) { return null; } return new KarryResolver(context); } public class KarryResolver implements DataResolver { private final DataResolverContext context; public KarryResolver(DataResolverContext context){ this.context = assertNotNull(context, "data resolver context"); } public Object resolve() { karry ky = context.getAnnotation(karry.class); return ky.name(); } } }
<services:data-resolver xmlns="<a href="http://www.alibaba.com/schema/services/data-resolver/factories"> <turbine-rundata-resolver /> <parameter-resolver /> <form-resolver /> <dr-factories:factory class="com.alibaba.sample.petstore.web.common.KarryResolverFactory" /> </services:data-resolver>