使用非常简单,下面贴出关键部分
//provider的配置文件
//服务接口类
@com.alibaba.dubbo.config.annotation.Service
public class PostService {
………………省略……………………
}
//consumer端配置
//服务引用
@Controller
@RequestMapping("/post")
@SessionAttributes("currentUser")
public class PostController {
//注解使用dubbo服务端服务
@Reference
PostService postService;
……………省略……………
}
以上如果只是用spring的容器,而不使用springmvc进行结合使用时是不会出现引用为空的问题的;但是如果不了解spring和springmvc加载配置文件和初始化bean的流程,则极有可能出现postService为Null的情况,错误配置如下
我src/main/resources下面有spring-consumer.xml、spring-mvc.xm两个配置文件,spring-mvc.xml和web.xml配置部分如下
……………………省略…………………………
Archetype Created Web Application
contextConfigLocation
classpath*:/spring-*.xml
org.springframework.web.context.ContextLoaderListener
spring
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-mvc.xml
2
spring
*.do
初步一看完全没有问题,spring启动的时候会扫描controller包,然后初始化我的postService服务类,一切想的都是那么美好,程序一运行,一个大大的空指针异常抛出,然后网上、qq上一堆乱问,终不得解,生无可恋,下面一步步解决问题
开始之前提出如下疑问
到底dubbo是怎么和spring组合的呢,先看如下
注意到**xmlns:dubbo=”http://code.alibabatech.com/schema/dubbo”**了吗,仔细想想spring怎么解析xml配置文件的,NamespaceHandler、NamespaceHandlerSupport、BeanDefinitionParser,spring提供的一种SPI规范,dubbo定义了自己的schema、namespacehandler、beandefinitionparser
/**
* DubboNamespaceHandler
*
* @author william.liangf
* @export
*/
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
}
}

DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions,xml配置文件就这样开始解析,具体不做描述,主要看dubbo是如何搭上spring的车的,解析过程咱们重点看AnnotationsBean的解析,也就是dubbo的DubboBeanDefinitionParser类
DubboBeanDefinitionParser的parse方法被调用后,dubbo定义的几个大的标签application、registry、provider、consumer、annotation等都会被初始化,并包装成RootBeanDefinition在spring的bean容器中
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class> beanClass, boolean required) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
……………………………省略具体解析代码…………………………………
return beanDefinition;
}
重点看annotation实例化过程,也是dubbo注解的关键,进入此类,看到一长串的类
public class AnnotationBean extends AbstractConfig implements DisposableBean, BeanFactoryPostProcessor, BeanPostProcessor, ApplicationContextAware {
不得不提下spring的processer和aware两个SPI点,设计的非常巧妙,完全符合设计六大原则之一的**开闭原则**
//任选一种都支持process和aware两种方式
dubbo充分利用了spring提供的机制进行service的初始化和reference的实例化的
第一步
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
if (annotationPackage == null || annotationPackage.length() == 0) {
return;
}
if (beanFactory instanceof BeanDefinitionRegistry) {
try {
// init scanner
Class> scannerClass = ReflectUtils.forName("org.springframework.context.annotation.ClassPathBeanDefinitionScanner");
Object scanner = scannerClass.getConstructor(new Class>[] {BeanDefinitionRegistry.class, boolean.class}).newInstance(new Object[] {(BeanDefinitionRegistry) beanFactory, true});
// add filter
Class> filterClass = ReflectUtils.forName("org.springframework.core.type.filter.AnnotationTypeFilter");
Object filter = filterClass.getConstructor(Class.class).newInstance(Service.class);
Method addIncludeFilter = scannerClass.getMethod("addIncludeFilter", ReflectUtils.forName("org.springframework.core.type.filter.TypeFilter"));
addIncludeFilter.invoke(scanner, filter);
// scan packages
String[] packages = Constants.COMMA_SPLIT_PATTERN.split(annotationPackage);
Method scan = scannerClass.getMethod("scan", new Class>[]{String[].class});
scan.invoke(scanner, new Object[] {packages});
} catch (Throwable e) {
// spring 2.0
}
}
}
实例化一个ClassPathBeanDefinitionScanner类,通过反射实现有参构造的初始化,将注册bean的(BeanDefinitionRegistry) beanFactory作为参数传递给实例,最后反射调用scanner的scan方法,将service注解的实例增加至spring容器中
第二步
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
//判断是不是dubbo需要处理的bean类,如果是则继续进行处理,不是则不做任何处理
if (! isMatchPackage(bean)) {
return bean;
}
Service service = bean.getClass().getAnnotation(Service.class);
if (service != null) {
ServiceBean至此,dubbo通过service注解实现spring容器管理完毕
reference和service最大的不同是,reference注解生成的实例就没有交给spring容器去管理,而只是作为spring管理bean的一个属性赋值操作,通过反射来实现,代码如下
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
//和service一样
if (! isMatchPackage(bean)) {
return bean;
}
Method[] methods = bean.getClass().getMethods();
for (Method method : methods) {
String name = method.getName();
if (name.length() > 3 && name.startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())
&& ! Modifier.isStatic(method.getModifiers())) {
try {
Reference reference = method.getAnnotation(Reference.class);
if (reference != null) {
Object value = refer(reference, method.getParameterTypes()[0]);
if (value != null) {
method.invoke(bean, new Object[] { });
}
}
} catch (Throwable e) {
logger.error("Failed to init remote service reference at method " + name + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
}
}
}
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
try {
if (! field.isAccessible()) {
field.setAccessible(true);
}
//本项目使用的是PostController,field就是controller中的postService,refer方法则是通过连接注册中心,检测服务是否存在,当然如果配置中check为false就不会现在进行检测
Reference reference = field.getAnnotation(Reference.class);
if (reference != null) {
//refer方法有兴趣可以自己看,牵扯到zk和netty
Object value = refer(reference, field.getType());
if (value != null) {
field.set(bean, value);
}
}
} catch (Throwable e) {
logger.error("Failed to init remote service reference at filed " + field.getName() + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
}
}
//将controller中有reference标示的字段赋值后返回,并没有将字段类实例注入spring容器,确实也没有必要
return bean;
}
private Object refer(Reference reference, Class> referenceClass) { //method.getParameterTypes()[0]
String interfaceName;
if (! "".equals(reference.interfaceName())) {
interfaceName = reference.interfaceName();
……………………省略……………………
return referenceConfig.get();
}
至此reference标注的实例也初始化完成,service和referece返回的都是dubbo的代理类com.alibaba.dubbo.common.bytecode.Proxy,用到了jdk的Proxy、InvocationHandler来生成代理类(没有javassist的情况),再次声明,reference标注的对象不会被spring容器管理,是无法通过factory.getBean获取的
首先得知道spring容器初始化过程
public class ContextLoaderListener extends ContextLoader implements ServletContextListener
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
我配置了监听,会读取spring-*.xml配置文件,会初始化一个xmlwebapplicationcontext也就是应用的rootContext顶级容器,这个容器在serverletcontext上下文中,等监听初始化完毕后,我们配置的dispatcherservlet开始初始化
/**
* Overridden method of {@link HttpServletBean}, invoked after any bean properties
* have been set. Creates this servlet's WebApplicationContext.
*/
@Override
protected final void initServletBean() throws ServletException {
try {
//初始化mvc的容器
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
}
protected WebApplicationContext initWebApplicationContext() {
//获取监听初始化的顶级容器
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
//初始化XmlWebApplicationContext开始,它会读取配置文件spring-mvc.xml
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
//将mvc容器放入servletcontext上下文中
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
监听初始化的容器读取了所有的配置文件,并初始化了controller类,同时将dubbo注解reference的实例set给了controller,而后servlet初始化过程中又再一次读取了spring-mvc.xml配置文件,同时也对controller进行了初始化,但是与顶级容器初始化不同的是,它没有加载dubbo实现的DubboNamespaceHandler,也就是说reference实例化的过程都没有进行,因此在mvc容器中的controller是没有注入reference标注的实例的,因此出现NULL的情况
遇到这样的问题是一种幸运,也是一种不幸,幸运的是通过debug看源码的过程增加了对spring的了解以及框架的优秀设计,不幸的是对于spring一些细节的地方还不够了解;遇到问题不放弃、不抛弃,加油