赞
踩
环境为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<User> 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官网也是推荐我们在使用的时候用父子容器的配置。但是这里为什么就出现问题了呢?也许我们搞清楚几个问题,就会清楚:
spring父容器和springmvc子容器的加载顺序是怎么样的?
dubbo的注解是何时生效的?
带着这两个问题,我们继续往下讲:
*/
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 <code>loadOnStartup</code> priority on the Servlet
* represented by this dynamic ServletRegistration.
*
* <p>A <tt>loadOnStartup</tt> 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.
*
* <p>If <tt>loadOnStartup</tt> is a negative integer, the container
* is free to instantiate and initialize the Servlet lazily.
*
* <p>The default value for <tt>loadOnStartup</tt> is <code>-1</code>.
*
* <p>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<Object> serviceConfig = new ServiceBean<Object>(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<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
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<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
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);
serviceConfig.export();
}
return bean;
}
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。