作为新手,第一次在项目中用到dubbo,经历的坑比较多,在此先记录一下,后面还会慢慢补充。欢迎小伙伴们提出意见和建议~
首先声明一下项目的架构等信息:
开发环境:Windows7、Linux、JDK1.8、Tomcat8(SpringBoot内置)、Redis、MySQL5.5、Nginx、Ngroke;
开发工具:IntelliJ IDEA、HBuilder;
架构情况:前后端分离。
后端:SpringBoot+SpringMVC+SpringDataJPA、SpringSession、SpringSecurity、Dubbo、Swagger2、Lombok;
前端技术有BootStrap、Vue.js、Axios、jQuery等。
废话不多说,直接正题:
坑1:dubbo无法注册服务
踩坑原因:
注解方式@service注册服务时,使用了@Transactional注解
解释:
目前dubbo版本中,被事务代理的业务逻辑对象不能通过使用@Service注解暴露服务,因为被cglib或者Java Proxy代理的类不能被dubbo的annotation扫描到@Service注解。
解决办法:
第一步:将Parant项目中pom.xml的dubbo的版本替换为2.5.7或以上。
<properties>
//其他依赖的版本.....
<dubbo.version>2.5.7</dubbo.version>
</properties>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
<exclusion>
<groupId>org.jboss.netty</groupId>
<artifactId>netty</artifactId>
</exclusion>
</exclusions>
<version>${dubbo.version}</version>
</dependency>
第二步:在要注册服务的业务层中com.alibaba.dubbo.config.annotation.Service注解中添加interfaceClass属性,指定要暴露服务的接口。
@org.springframework.stereotype.Service
//在alibaba的Service注解上添加interfaceClass属性,指定要暴露服务的接口。
@Service(interfaceClass = UserService.class)
public class UserServiceImpl implements UserService {
//代码省略....
}
第三步:在需要添加@Transactional注解的业务层方法上添加@Transactional注解。
坑2:org.springframework.security.AccessDeniedException: Access is denied
踩坑原因:
数据库ROLE表中的权限名称没有加上“ROLE_”前缀。
解释:
当一个未授权的用户访问一个被保护的方法时,就会抛出org.springframework.security.AccessDeniedException: Access is denied。说明权限方面出现了问题。
在SpringSecurity具体配置路径的拦截和所需权限时,并不需要添加“ROLE_”前缀,因为底层已经拼接好了。
/**
* SpringSecurity的具体配置
* @param http 相当于xml中http节点下的配置信息
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
//角色名在底层已经拼接了ROLE_,这里不用添加
http.authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll().mvcMatchers("/depts").hasAnyRole("USER");
//其它路径的拦截可以在这里配置
//http.authorizeRequests().mvcMatchers("/hello/method02").hasAnyRole("MANAGER");
//……
//登录和登出的代码省略...
//...
}
但是,当我们从数据库获取用户和对应的权限信息时,需要获取的必须是带前缀的权限名称,这样才能匹配上。
/**
* 设置认证信息的提供方,这里使用基于数据库的方式提供认证信息的管理
* @param auth 用于构建认证提供方
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
JdbcDaoImpl jdbcDao = new JdbcDaoImpl();
jdbcDao.setDataSource(ApplicationContextUtils.getBean(DruidDataSource.class));
jdbcDao.setUsersByUsernameQuery("select mobile as username,password,status as enabled from student where mobile=? and isSchoolManager = 1");
jdbcDao.setAuthoritiesByUsernameQuery("select\n" +
" s.mobile as username,r.rname as authority\n" +
" from student s\n" +
" join user_role ur\n" +
" on s.sid=ur.sid and ur.status=1\n" +
" join role r\n" +
" on r.roleid=ur.roleid\n" +
" where s.mobile=? and isSchoolManager = 1");
auth.userDetailsService(jdbcDao);
}
解决办法:
将数据库ROLE表内的权限名称加上"ROLE_"前缀,例如“ROLE_STUDENT”。
坑3:前后端分离的结构中,使用SpringSecurity认证账户登录后,后端继续要求登录
踩坑原因:
采用的是axios进行前后端的数据交互。
解释:
前端代码登录操作会保存认证信息到服务器端,同样也会返回cookie信息到前端,后面的所有基于登录的一些数据访问操作需要将这个cookie继续往服务端携带,axios中默认不携带cookie信息到后台,这样每次请求后端都会产生一个新的cookie。
解决办法:
在登录或其他操作的axios中设置withCredentials: true。
axios.get(serverPath + "loginUser", {
withCredentials: true
}).then(function(res) {
console.log("===================");
console.log(res);
});
axios.post(serverPath + "checkLoginInfo", {
mobile: vm.mobile,
password: vm.password
}, {
withCredentials: true,
headers: {
'Content-Type': "application/x-www-form-urlencoded"
},
transformRequest: function(data) {
var ret = '';
for(var it in data) {
ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
}
return ret
}
}).then(function(res) {
});
坑4:在业务层注入远程服务时找不到对应的接口
//发现这里的UserService接口找不到
@Reference
private UserService userService;
踩坑原因:
仔细检查后发现,在此模块的业务层的pom.xml中没有导入UserService接口所在模块的依赖。
解释:
尽管UserService接口所在模块已经将服务暴露出来,但是由于此模块的业务层的pom.xml中没有导入UserService接口所在模块的依赖,因此在此模块中无法找到UserService接口,导包时无法找到,自然就无法注入远程服务。
解决办法:
在此模块的业务层的pom.xml中导入UserService接口所在模块的依赖,然后就能使用import导入UserService接口啦。
坑5:‘org.springframework.data.domain.PageImpl’ could not be instantiated
踩坑原因:
根据这个异常所述,PageImpl不能实例化。
检查我的代码,如下所示,注入远程服务后,通过远程服务调用了getAllStudentsBySchoolId()方法
//注入远程服务
@Reference
private UserService userService;
@Override
public JsonPageResult getAllStudentsBySchoolId(Integer did, Integer page, Integer pageSize) {
return userService.getAllStudentsBySchoolId(did,page,pageSize);
}
而getAllStudentsBySchoolId()方法内时通过将Pageable 作为参数写在repository的方法中来获取了包含分页+排序的结果集的Page对象。并将Page对象作为返回值返回。
PageRequest pageRequest = new PageRequest(
page-1, pageSize, new Sort(new Sort.Order(Sort.Direction.DESC, "applyTime")));
Page<Student> students = userDao.findAllByDrivingSchool_Did(did,pageRequest);
这时就报了’org.springframework.data.domain.PageImpl’ could not be instantiated的异常
解释:
使用dubbo分布式时,传递的参数必须实现Serializable接口,否则就会报异常。而Page接口包括其父接口Slice都没有继承过Serializable接口。
解决办法:
可用某个实现了Serializable接口的对象将Page对象包装起来,然后传递包装对象。接收后再拆出得到Page对象。