通过前面的总结,已经大致了解了serice框架和webx框架的原理,这篇总结主要是通过一个例子的介绍来学习利用service框架和webx框架完成web的开发的原理。
假设前提条件是这样的:框架已经初始化结束,也就是说webxControll已经缓存好了,现在有一个页面访问的请求来到,下面是时序图
参照时序图,流程如下:
生成Rundata
取得pipelineService实例,并开始执行pipeline
执行一个个的valve直到PerformActionValve
取得moduleLoaderService实例
规则化moduleName
实例化module
查询module所有的成员变量
实例化所有的依赖项
为module注入所有的属性
缓存该module
execute该module
继续执行valve渲染页面......
接下来主要是按照流程的顺序总结遇到的重点、难点和疑点
上面流程的第一步的时候设置的,参考如下代码:
// 在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区分开,就是为此而已!
看代码:
//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,是为了隐藏module的实例化过程,不仅要考虑是不是抽象类的问题,还需要考虑怎么注入属性的问题,况且需要注入的这些属性可能是bean,也可能是sevice,也可能只是普通的变量而已,实例化的过程比较复杂,所以采用工厂的方式来隐藏这一过程,类似于门面模式吧,当然不仅仅如此,请看下面:
配置多个Factory是为了拓展,举个例子,有可能我们的module是用js或者python来写的,无可厚非,用脚本写维护起来方便,所以是有这个需求的。
其实,在默认的情况下,我们用的都是DefaultFactory。
// 查找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是抽象类的时候,框架会采用代理的方式,自然可以实例了啊,不过在注入属性的时候,抽象类是有区别的,下面会介绍。
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); } }
并没有交给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; }