概述
学习一个框架,需要了解框架如何初始化,框架的处理流程,框架如何配置,和参数是如何注入的。本文从一个具体的示例出发总结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
下面就主要基于这几种方式来分析注入的原理
注入方式详解
@Autowired注入
【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的前因后果:
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的生成
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的目的
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>