环境为jdk1.8,maven 3.3.3 , tomcat 8,dubbo 2.5.3
本文中会提到AnnotationBean,该bean在dubbo 2.5.7开始被@Deprecated,替代的可以使用@EnableDubbo注解,在2.5.8开始可以使用@DubboComponentScan注解
public interface IUserService {
User selectUser(int id);
}
注意User实体需要序列化实现Serializable
接口。接口和实体抽出为公共jar包,供服务端和客户端使用。
package com.chengli.dubbo.service;
import com.alibaba.dubbo.config.annotation.Service;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: chengli
* @Date: 2018/8/16 17:41
*/
/**注意这里的Service注解是dubbo的注解*/
@Service
public class UserService implements IUserService {
private final List list = new ArrayList() {{
add(new User(1, "张三", 10));
add(new User(2, "李四", 23));
add(new User(3, "王五", 34));
add(new User(4, "赵六", 43));
add(new User(5, "田七", 36));
add(new User(6, "黄八", 52));
}};
@Override
public User selectUser(int id) {
return list.stream().findAny().get();
}
}
package com.chengli.dubbo.env;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.AnnotationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: chengli
* @Date: 2018/8/18 17:39
*/
@Configuration
public class DubboConfig {
@Bean
public ApplicationConfig applicationConfig(){
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("spring-dubbo-server");
return applicationConfig;
}
@Bean
public AnnotationBean annotationBean() {
AnnotationBean annotationBean = new AnnotationBean();
annotationBean.setPackage("com.chengli.dubbo.service");
return annotationBean;
}
@Bean
public RegistryConfig registryConfig(){
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("N/A");
return registryConfig;
}
/**默认就是dubbo协议,可以不用配置此Bean*/
@Bean
public ProtocolConfig protocolConfig(){
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20880);
return protocolConfig;
}
}
该测试中没有使用数据库,服务端从list中取出数据返回,如果使用数据库,加上以下配置(此示例中可以省略)。
package com.chengli.dubbo.env;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* @Author: chengli
* @Date: 2018/8/16 17:41
*/
@Configuration
@ComponentScan(basePackages = {"com.chengli.dubbo.service"})
public class RootConfig {
@Bean
public DataSource createDataSource() {
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setUsername("root");
dataSource.setPassword("000000");
return dataSource;
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactoryBean createSqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean
public MapperScannerConfigurer createMapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.chengli.dubbo.mapper");
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
return mapperScannerConfigurer;
}
}
package com.chengli.dubbo.env;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MyApplicationContextInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class>[] getRootConfigClasses() {
return new Class[]{DubboConfig.class, RootConfig.class};
}
protected Class>[] getServletConfigClasses() {
return new Class[0];
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
package com.chengli.dubbo.env;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.AnnotationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: chengli
* @Date: 2018/8/18 18:19
*/
@Configuration
public class DubboConfig {
@Bean
public ApplicationConfig applicationConfig() {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setName("spring-dubbo-client");
return applicationConfig;
}
@Bean
public AnnotationBean annotationBean() {
AnnotationBean annotationBean = new AnnotationBean();
annotationBean.setPackage("com.chengli.dubbo.controller");
return annotationBean;
}
@Bean
public RegistryConfig registryConfig(){
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("N/A");
return registryConfig;
}
}
package com.chengli.dubbo.env;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* @Author: chengli
* @Date: 2018/8/16 17:41
*/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.chengli.dubbo.controller"})
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/jsp/", ".jsp");
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.TEXT_HTML)
.mediaType(".json", MediaType.APPLICATION_JSON)
.mediaType(".html",MediaType.TEXT_HTML);
}
}
package com.chengli.dubbo.env;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* @Author: chengli
* @Date: 2018/8/16 17:35
*/
public class MyApplicationContextInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class>[] getRootConfigClasses() {
return new Class[]{};
}
/** 这里的配置非常重要,DubboConfig.class, MvcConfig.class要么都配置在springmvc容器中,
* 要么都配置在spring容器中,否则会出现reference空指针问题
*/
protected Class>[] getServletConfigClasses() {
return new Class[]{DubboConfig.class, MvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
package com.chengli.dubbo.controller;
import com.alibaba.dubbo.config.annotation.Reference;
import com.chengli.dubbo.service.IUserService;
import com.chengli.dubbo.service.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class IndexController {
/**注意这里的注解是dubbo的注解,这里没有使用注册中心,为直连方式*/
@Reference(url = "dubbo://localhost:20880", interfaceClass = IUserService.class, check = true)
private IUserService userService;
@RequestMapping("/")
public ModelAndView index() {
ModelAndView mv = new ModelAndView("success");
User user = userService.selectUser(1);
System.out.println("user : " + user);
mv.addObject("user", user);
return mv;
}
}
以上就是dubbo纯注解配置,配置非常简单,只是把原来xml中的配置,全部都转换成java类表示而已。上面只是引子,实际想说的是reference为空的问题。刚开始测试的时候一直有这个问题。网上查了很多资料,但是将的都不是很清楚。这里作一个深入的剖析。
具体来说就是在客户端, private IUserService userService;
userService为null,当时出现此异常时客户端的配置如下:
public class MyApplicationContextInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class>[] getRootConfigClasses() {
return new Class[]{DubboConfig.class};
}
protected Class>[] getServletConfigClasses() {
return new Class[]{ MvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
DubboConfig使用spring父容器加载,MvcConfig使用springmvc容器加载,实际上spring官网也是推荐我们在使用的时候用父子容器的配置。但是这里为什么就出现问题了呢?也许我们搞清楚几个问题,就会清楚:
*/
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
/**
* The default servlet name. Can be customized by overriding {@link #getServletName}.
*/
public static final String DEFAULT_SERVLET_NAME = "dispatcher";
/**从这里的代码可以看出,会先初始化spring容器,再初始化springmvc容器*/
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
/**这一行代码实际上就是向web.xml的容器中一样,向servetContext中注册一个ContextLoaderListener,listener中持有rootAppContext父容器*/
super.onStartup(servletContext);
/**这里注册DispatchServlet,它持有servletAppContext子容器*/
registerDispatcherServlet(servletContext);
}
......
......
}
可能说到这里还会有点晕乎?这两行代码只不过是分别向ServletContext 容器中分别放了一个listener和一个servlet而已,怎么能证明两个容器的加载顺序? 别急
还记得DispatcherServlet中配置的loadOnStartup参数吗?我们看一下它的注释:
/**
* Sets the loadOnStartup
priority on the Servlet
* represented by this dynamic ServletRegistration.
*
* A loadOnStartup value of greater than or equal to
* zero indicates to the container the initialization priority of
* the Servlet. In this case, the container must instantiate and
* initialize the Servlet during the initialization phase of the
* ServletContext, that is, after it has invoked all of the
* ServletContextListener objects configured for the ServletContext
* at their {@link ServletContextListener#contextInitialized}
* method.
*
*
If loadOnStartup is a negative integer, the container
* is free to instantiate and initialize the Servlet lazily.
*
*
The default value for loadOnStartup is -1
.
*
*
A call to this method overrides any previous setting.
*
* @param loadOnStartup the initialization priority of the Servlet
*
* @throws IllegalStateException if the ServletContext from which
* this ServletRegistration was obtained has already been initialized
*/
一般我们在配置DispatcherServlet的时候,会将loadOnStartup的参数配置为1,表示在容器启动时ServletContext初始化的时候就实例化DispatcherServlet并初始化,然而这一切都都在所有listener执行完毕之后。所以两个容器的加载顺序显而易见。
那么还是没有解决我们的问题,现在知道了两个容器的加载顺序又怎么样?我们继续研究:
还记得我们客户端的配置吗?有一个AnnotationBean
:
@Bean
public AnnotationBean annotationBean() {
AnnotationBean annotationBean = new AnnotationBean();
annotationBean.setPackage("com.chengli.dubbo.controller");
return annotationBean;
}
它就是用来扫描所有带有dubbo注解的类,然后做一些小动作来完成dubbo的功能。
我们仔细看一下这个类:
public class AnnotationBean extends AbstractConfig implements DisposableBean, BeanFactoryPostProcessor, BeanPostProcessor, ApplicationContextAware {
/**省略类实现内容*/
}
我们可以看到,该AnnotationBean
实现了BeanPostProcessor
接口,BeanPostProcessor
是干嘛的? 它提供了两个接口:
public interface BeanPostProcessor {
/**bean初始化方法执行之前执行*/
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
/**bean初始化方法执行之后执行*/
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
在spring容器初始化时,每个Bean初始化时,在初始化方法执行之前会执行BeanPostProcessor的postProcessBeforeInitialization
方法,来对Bean做一些自定义操作。这就是dubbo和spring勾搭在一起的地方。
我们看一下AnnotationBean
在postProcessBeforeInitialization
做了什么?
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
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);
}
Reference reference = field.getAnnotation(Reference.class);
if (reference != null) {
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);
}
}
return bean;
}
实际上就是利用java反射机制,对Bean中@Reference标注的属性赋值。其实在这里我们已经可以想到为什么会出现null空指针异常了,因为在如上错误配置的时候,在spring容器所有bean初始化的时候,springmvc容器还没初始化,还没有controller,所以userService为null。
我们看一下dubbo的@Service注解是如何生效的,实际上@Service是生成代理类,与@Reference不同,代理必须在类初始化之后,所以是在postProcessAfterInitialization
方法中完成的。
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (! isMatchPackage(bean)) {
return bean;
}
Service service = bean.getClass().getAnnotation(Service.class);
if (service != null) {
ServiceBean
本文使用的代码:
https://github.com/lchpersonal/spring-dubbo