webx3.0-参数注入

概述

学习一个框架,需要了解框架如何初始化,框架的处理流程,框架如何配置,和参数是如何注入的。本文从一个具体的示例出发总结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>


你可能感兴趣的:(webx)