webx2.0-学习笔记

概要

通过前面的总结,已经大致了解了serice框架和webx框架的原理,这篇总结主要是通过一个例子的介绍来学习利用service框架和webx框架完成web的开发的原理。

假设前提条件是这样的:框架已经初始化结束,也就是说webxControll已经缓存好了,现在有一个页面访问的请求来到,下面是时序图

时序图

webx2.0-学习笔记_第1张图片

类图

webx2.0-学习笔记_第2张图片

处理流程

参照时序图,流程如下:

  1. 生成Rundata

  2. 取得pipelineService实例,并开始执行pipeline

  3. 执行一个个的valve直到PerformActionValve

  4. 取得moduleLoaderService实例

  5. 规则化moduleName

  6. 实例化module

  7. 查询module所有的成员变量

  8. 实例化所有的依赖项

  9. 为module注入所有的属性

  10. 缓存该module

  11. execute该module

  12. 继续执行valve渲染页面......

接下来主要是按照流程的顺序总结遇到的重点、难点和疑点

平时都是从WebxComponent取service,但是这个component是什么时候设置的呢?

上面流程的第一步的时候设置的,参考如下代码:

// 在path中取出componentPath。
        String   component        = null;
        String[] components       = controller.getComponentNames();
        String   defaultComponent = controller.getDefaultComponentName();
 
        for (int i = 0; i < components.length; i++) {
            String componentName = components[i];
 
            // 排除defaultComponent
            if (!componentName.equals(defaultComponent)) {
                if (path.equals("/" + componentName) || path.startsWith("/" + componentName + "/")) {
                    component     = componentName;
                    componentPath = "/" + componentName;
                    path          = path.substring(componentName.length() + 1);
 
                    break;
                }
            }
        }
 
        // 如果不存在名为aaa的component,则componentPath="",componentPathInfo="/aaa/bbb/ccc"
        if (component == null) {
            component     = defaultComponent;
            componentPath = "";
        }

流程中哪些是单例,哪些是原型

从字面来理解,首先rundata肯定不是单例的,其次module需要缓存,那应该是单例吧?

其实Rundata是ThreadLoacl变量,每个线程维护一份独立的拷贝,因此跑到单例的环境中毫无影响;通过前面的总结也可以知道,框架故意如此设计,将不变的pipeline和变动的rundata区分开,就是为此而已!

PerformActionValve里面取得了module之后,去调用其execute方法有个地方需要注意,不要写多余的代码

看代码:

//valve里面
Module module = moduleLoaderService.getModule(TurbineConstant.ACTION_MODULE, action);
module.execute(rundata);
//homepage里面
protected void execute(RunData rundata, TemplateContext context)
{...}
//父类TemplateScreen里面
protect void execute (Rundata rundata)
{    .......
     execute(rundata,context);
     ......
}

为什么用Factory的方式取得module,并且还配备了一个Factory List,需要这么多吗?

采用factory的方式实例化module,是为了隐藏module的实例化过程,不仅要考虑是不是抽象类的问题,还需要考虑怎么注入属性的问题,况且需要注入的这些属性可能是bean,也可能是sevice,也可能只是普通的变量而已,实例化的过程比较复杂,所以采用工厂的方式来隐藏这一过程,类似于门面模式吧,当然不仅仅如此,请看下面:

配置多个Factory是为了拓展,举个例子,有可能我们的module是用js或者python来写的,无可厚非,用脚本写维护起来方便,所以是有这个需求的。

其实,在默认的情况下,我们用的都是DefaultFactory。

实例化Module之前,框架是如何查找到class的?

// 查找module类
        for (int i = 0; (i < packages.length) && (module == null); i++) {
            String moduleClassName = getModuleClassName(packages[i], moduleType, moduleName);
 
            // 查找class
            module = instantiateModule(moduleClassName);
        }

首先,要在module.packages里面配置查找路径

[webx.xml]
<service name="ModuleLoaderService">
    <property name="module.packages">
        <value>com.alibaba.mymodule</value>
    </property>
</service>


实例化Module为什么要判断module是不是抽象类,难道抽象类也可以实例吗?

框架提供了对抽象类的支持,当不是抽象类的时候,直接放射的方式实例化;当module是抽象类的时候,框架会采用代理的方式,自然可以实例了啊,不过在注入属性的时候,抽象类是有区别的,下面会介绍。

if (Modifier.isAbstract(moduleClass.getModifiers())) {
            // 用CGLIB创建子类
            Enhancer enhancer = new Enhancer();
 
            enhancer.setSuperclass(moduleClass);
 
            DefaultInterceptor defaultCallback = new DefaultInterceptor();
            ModuleInterceptor  moduleCallback  = new ModuleInterceptor();
 
            enhancer.setCallbacks(new Callback[] { defaultCallback, moduleCallback });
            enhancer.setCallbackFilter(moduleCallback);
 
            module = (Module) enhancer.create();
        } else {
            // 直接实例化
            try {
                module = (Module) moduleClass.newInstance();
            } catch (Exception e) {
                throw new ModuleLoaderException(e);
            }
        }


开发的时候在编写module时只要写set方法就可以用里面的成员变量了,可是没有给beanFactory管理啊?

并没有交给beanFactory去管理,而是moduleFactory自己去管理属性的注入了。

那怎么配置就可以实现属性的注入了呢?

有两种方法来指定这些properties: 基于setter,和基于abstract getter :

public class MyScreen extends TemplateScreen {
    private HelloBean hello;
    public void setHello(HelloBean hello) {
        this.hello = hello;
    }
    public void execute(RunData rundata, Template) throws WebxException {
        ... hello.getGreeting() ...
    }
}

如果不加特别的配置,那么DefaultModuleFactory已经可以支持将Service注入到module中。例如在module中定义下面的abstract getter:

protected abstract URIBrokerService getURIBrokerService();

如果想注入Bean,就需要利用 ReferenceResolver 接口了:

public interface ReferenceResolver {
    Object getObject(String name, Class type);
}

框架已经实现了一个SpringReferecneResolver,用来从Spring容器中取得对象。为了使用它,我们必须增加一些配置:

[webx.xml]
<service name="ModuleLoaderService">
    <property name="module.packages">
        <value>com.alibaba.mymodule</value>
    </property>
    <property name="module.ref.resolver">
        <property name="class" value="com.alibaba.turbine.service.moduleloader.factory.SpringReferenceResolver"/>
    </property>
</service>
<service name="BeanFactoryService" class="com.alibaba.service.spring.DefaultBeanFactoryService">
    <property name="bean.descriptors">
        <value>beans.xml</value><!-- 定义了spring容器,其中的对象均可注入到module中 -->
    </property>
</service>
[beans.xml] —— Spring容器
<beans>
    <bean id="hello" class="com.alibaba.HelloBean">
        <property name="greeting" value="hello, world"/>
    </bean>
</beans>

知道怎么用了,那框架是如何实现的?

通过给DefaultModuleFactory注册一个spring处理器来实现可以支持bean的获取,在DefaultModuleFactory中将处理器包装成一个Wapper,为什么要这样呢,我觉得是一个策略模式的应用,因为这个对象可能是serviceManage管理的,也可能是beanfactory管理的,对于这两种算法采用策略模式设置默认算法,和算法次序。还是看代码吧:

public Object getObject(String name, Class type) {
            if (StringUtil.isEmpty(name)) {
                return null;
            }
 
            // 先调用配置文件中的resolver来取得对象
            if (moduleResolver != null) {
                Object obj = moduleResolver.getObject(name, type);
 
                if (obj != null) {
                    return obj;
                }
            }
 
            // 如果没取到,看看是不是service
            if (Service.class.isAssignableFrom(type)) {
                Service                service;
                ServiceInstanceContext serviceContext = getModuleLoaderService().getServiceConfig();
 
                service = serviceContext.getService(name);
 
                if (service == null) {
                    String invertCase;
 
                    if (Character.isLowerCase(name.charAt(0))) {
                        invertCase = Character.toUpperCase(name.charAt(0)) + name.substring(1);
                    } else {
                        invertCase = Character.toLowerCase(name.charAt(0)) + name.substring(1);
                    }
 
                    service = serviceContext.getService(invertCase);
                }
 
                return service;
            }
 
            return null;
        }
    }

现在取到了对象,下面就是注入了,通过上面的总结发现了,注入可以通过set和get两种方式,而且第二种方式必须是抽象类,这是怎么搞的:

先看set,先是通过PropertyUtils.getPropertyDescriptors方法遍历出所有的成员变量,然后取得成员变量的实例,最后注入(只有拥有方法诸如“setABC()”才会被识别出有“aBC”成员变量,这个无可厚非,但是哪怕类中并没有这个成员变量,也能识别出,真是这个方法的bug所在)

protected void injectDependences(Module module, Class moduleClass) {
        PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(moduleClass);
 
        for (int i = 0; i < propertyDescriptors.length; i++) {
            PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
            String             propertyName       = propertyDescriptor.getName();
            Class              propertyType       = propertyDescriptor.getPropertyType();
            Method             propertySetter     = propertyDescriptor.getWriteMethod();
 
            if (propertySetter == null) {
                continue; // 非可写属性,忽略
            }
 
            Object value;
 
            try {
                value = resolver.getObject(propertyName, propertyType);
 
                if (value == null) {
                    continue; // 值为空,忽略
                }
 
                if (!propertyType.isInstance(value)) {
                    log.debug("Reference type for " + propertyName + " does not match: expected="
                              + propertyType.getName() + ", actually=" + value.getClass().getName());
 
                    continue; // 类型不匹配,忽略
                }
            } catch (Exception e) {
                log.warn("Resolving reference (" + propertyType.getName() + ") " + propertyName
                         + " failed", e);
 
                continue; // 取值失败,忽略
            }
 
            try {
                propertySetter.invoke(module, new Object[] { value });
            } catch (Exception e) {
                log.debug("Setting property " + propertyName + " for module "
                          + moduleClass.getName() + " failed", e);
            }
        }
    }

再看get,从下面代码其实可以看到,只有抽象类才走下面的流程,框架会识别方法名诸如“getABC”,通过resolver返回aBC对象,当然是是实现在代理类中了,最后依然走上面set的注入属性的方法:

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
                throws Throwable {
            Object returnValue = null;
            String methodName  = method.getName();
            Class  returnType  = method.getReturnType();
            String objectName  = methodName.substring("get".length());
 
            if (Character.isUpperCase(objectName.charAt(0))) {
                objectName = Character.toLowerCase(objectName.charAt(0)) + objectName.substring(1);
            }
 
            returnValue = resolver.getObject(objectName, returnType);
 
            return returnValue;
        }


你可能感兴趣的:(webx)