dubbo注解的使用

使用非常简单,下面贴出关键部分

//provider的配置文件

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

http://code.alibabatech.com/schema/dubbo

http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

//服务接口类

@com.alibaba.dubbo.config.annotation.Service

public class PostService {

………………省略……………………

}

//consumer端配置

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

http://code.alibabatech.com/schema/dubbo

http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

//服务引用

@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配置部分如下

xmlns:context="http://www.springframework.org/schema/context"

xmlns:mvc="http://www.springframework.org/schema/mvc"

xmlns:p="http://www.springframework.org/schema/p"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context.xsd

http://www.springframework.org/schema/mvc

http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

……………………省略…………………………

"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

"http://java.sun.com/dtd/web-app_2_3.dtd" >

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上一堆乱问,终不得解,生无可恋,下面一步步解决问题 开始之前提出如下疑问

spring是什么时候跟dubbo勾搭在一起的为什么PostService上面加上dubbo的Service注解,服务类就会被加载为什么dubbo官方文件所说的通过Reference引用服务却为NULL

dubbo和spring结合

到底dubbo是怎么和spring组合的呢,先看如下

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"

xsi:schemaLocation="http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans.xsd

http://code.alibabatech.com/schema/dubbo

http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

注意到**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类

dubbo的service、reference是如何初始化的

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的实例化的

service的实例化过程

第一步

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 serviceConfig = new ServiceBean(service);

if (void.class.equals(service.interfaceClass())

&& "".equals(service.interfaceName())) {

if (bean.getClass().getInterfaces().length > 0) {

serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);

} else {

throw new IllegalStateException("Failed to export remote service class " + bean.getClass().getName() + ", cause: The @Service undefined interfaceClass or interfaceName, and the service class unimplemented any interfaces.");

}

}

if (applicationContext != null) {

serviceConfig.setApplicationContext(applicationContext);

if (service.registry() != null && service.registry().length > 0) {

List registryConfigs = new ArrayList();

for (String registryId : service.registry()) {

if (registryId != null && registryId.length() > 0) {

registryConfigs.add((RegistryConfig)applicationContext.getBean(registryId, RegistryConfig.class));

}

}

serviceConfig.setRegistries(registryConfigs);

}

if (service.provider() != null && service.provider().length() > 0) {

serviceConfig.setProvider((ProviderConfig)applicationContext.getBean(service.provider(),ProviderConfig.class));

}

if (service.monitor() != null && service.monitor().length() > 0) {

serviceConfig.setMonitor((MonitorConfig)applicationContext.getBean(service.monitor(), MonitorConfig.class));

}

if (service.application() != null && service.application().length() > 0) {

serviceConfig.setApplication((ApplicationConfig)applicationContext.getBean(service.application(), ApplicationConfig.class));

}

if (service.module() != null && service.module().length() > 0) {

serviceConfig.setModule((ModuleConfig)applicationContext.getBean(service.module(), ModuleConfig.class));

}

if (service.provider() != null && service.provider().length() > 0) {

serviceConfig.setProvider((ProviderConfig)applicationContext.getBean(service.provider(), ProviderConfig.class));

} else {

}

if (service.protocol() != null && service.protocol().length > 0) {

List protocolConfigs = new ArrayList();

for (String protocolId : service.registry()) {

if (protocolId != null && protocolId.length() > 0) {

protocolConfigs.add((ProtocolConfig)applicationContext.getBean(protocolId, ProtocolConfig.class));

}

}

serviceConfig.setProtocols(protocolConfigs);

}

try {

serviceConfig.afterPropertiesSet();

} catch (RuntimeException e) {

throw (RuntimeException) e;

} catch (Exception e) {

throw new IllegalStateException(e.getMessage(), e);

}

}

//

serviceConfig.setRef(bean);

serviceConfigs.add(serviceConfig);

//通过注册中心暴露dubbo的服务

serviceConfig.export();

}

//返还spring容器,有点类似于装饰

return bean;

}

至此,dubbo通过service注解实现spring容器管理完毕

reference实例过程

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获取的

Null出现的原因

首先得知道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一些细节的地方还不够了解;遇到问题不放弃、不抛弃,加油

精彩链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: 

发表评论

返回顶部暗黑模式