后端微服务项目中出现的问题整理2022年11月
- 后端微服务项目中出现的问题整理2022年11月
- 1.SpringBoot-Mail-Service(Spring邮箱服务)
- 报错截图
- 解决办法
- 方法一:使用`@Resource`注解
- 方法二:添加`(required=false)`
- @Resource和@Autowired区别
- 2.反射无法获取子类继承到的父类中的属性
- 继承关系
- 1.顶层Entity:BasicEntity
- 2.子类:UserEntity
- 原因说明
- 解决
- TimeMetaHandler时间填充策略类
- 3.关于如何使用异步编排+Spring全局线程池
- 1.构建全局线程池
- 2.Service中引入全局线程池
- 3.CompleteFuture使用全局线程池
- 4.异步编排中return导致的后续任务接受值类型错误
- 解决
- 构建全局异常处理类
后端微服务项目中出现的问题整理2022年11月
1.SpringBoot-Mail-Service(Spring邮箱服务)
在使用Spring邮箱服务时发现@Autowired
注入JavaMailSenderImpl
的时候报错显示无法自动装配。找不到 ‘JavaMailSenderImpl’ 类型的 Bean。,当时自己直接解决了,但是还是想想整理一下
报错截图
解决办法
方法一:使用@Resource
注解
@Resource
private JavaMailSenderImpl mailSender;
方法二:添加(required=false)
@Autowired(required = false)
private JavaMailSenderImpl mailSender;
@Resource和@Autowired区别
1、 @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
2、 @Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
3、@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按 byName自动注入
来源:@Qgchun. javamailsenderimpl注入爆红
2.反射无法获取子类继承到的父类中的属性
使用反射的getDeclaredField
方法只能获取到当前类中的属性无法获取到其父类中继承而来的属性所以会报noSuchFieldException
如下图:
我在最后这一环处理上出现了这个问题
即如下两行:
final Field createTime = autoFillObjectClass.getDeclaredField("createTime");
final Field updateTime = autoFillObjectClass.getDeclaredField("updateTime");
继承关系
1.顶层Entity:BasicEntity
顶层声明:createTime,updateTime和deleted三个通用属性
package cn.fly.commons.entity.common;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @author Syf200208161018
* @date 2022/11/19 7:38
* @ClassName:BasicEntity
* @Effect:BasicEntity is used for basic entity
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BasicEntity {
@TableField(value = "create_time", fill = FieldFill.INSERT)
public LocalDateTime createTime;
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
public LocalDateTime updateTime;
@TableField(value = "deleted")
public boolean deleted;
}
2.子类:UserEntity
子类中直接去继承顶层的BasicEntity
package cn.fly.commons.entity.common;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @author Syf200208161018
* @date 2022/10/23 18:55
* @ClassName:User
* @Effect:User is used to user entity
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User extends BasicEntity{
@TableId(value = "user_id",type = IdType.ASSIGN_ID)
private Long userId;
@TableField(value = "username")
private String username;
@TableField(value = "password")
private String password;
@TableField(value = "nickname")
private String nickname;
@TableField(value = "email")
private String email;
}
原因说明
因为UserEntity继承了BasicEntity获取了createTime,updateTime属性但是反射的getDeclaredField
无法获取父类中继承而来的属性所以出现无属性异常
解决
使用final Field[] fields = autoFillObjectClass.getFields();
方法将所有的属性获取到,这个方法可以获取继承到的属性,但是继承的属性必须设置成public
关键字修饰的
完整代码如下:
TimeMetaHandler时间填充策略类
package cn.fly.commons.handler.meta;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* @author Syf200208161018
* @date 2022/10/27 13:46
* @ClassName:TimeMetaHandler
* @Effect:TimeMetaHandler is used for 进行类中的创建时间和更新时间的填充代替Mybatis-plus的默认填充策略
*/
public class TimeMetaHandler {
public static <T> T createTimeHandler(T autoFillObject) throws NoSuchFieldException, IllegalAccessException {
final Class<?> autoFillObjectClass = autoFillObject.getClass();
//获取属性上的注解,判断是否携带@TableField(value = "create_time" , fill = FieldFill.INSERT)
final Field createTime = autoFillObjectClass.getDeclaredField("createTime");
final FieldFill fill = createTime.getAnnotation(TableField.class).fill();
if (FieldFill.INSERT.equals(fill)) {
createTime.setAccessible(true);
createTime.set(autoFillObject, LocalDateTime.now());
}
return autoFillObject;
}
public static <T> T updateTimeHandler(T autoFillObject) throws NoSuchFieldException, IllegalAccessException {
final Class<?> autoFillObjectClass = autoFillObject.getClass();
//find @TableField(value = "update_time",fill = FieldFill.INSERT_UPDATE)
final Field updateTime = autoFillObjectClass.getDeclaredField("updateTime");
final FieldFill fill = updateTime.getAnnotation(TableField.class).fill();
if (FieldFill.INSERT_UPDATE.equals(fill)) {
updateTime.setAccessible(true);
updateTime.set(autoFillObject, LocalDateTime.now());
}
return autoFillObject;
}
/**
* if you wanna unified insert createTime and updateTime you can use this
*
* @param autoFillObject
* @param <T>
* @return
* @throws NoSuchFieldException
*/
public static <T> T unifiedTimeHandler(T autoFillObject) throws NoSuchFieldException, IllegalAccessException {
final Class<?> autoFillObjectClass = autoFillObject.getClass();
// final Field createTime = autoFillObjectClass.getDeclaredField("createTime");
// final Field updateTime = autoFillObjectClass.getDeclaredField("updateTime");
Field createTime = null;
Field updateTime = null;
final Field[] fields = autoFillObjectClass.getFields();
for (Field field : fields) {
if (field.getName()=="createTime") {
createTime = field;
}
if (field.getName()=="updateTime") {
updateTime = field;
}
}
final FieldFill createFill = createTime.getAnnotation(TableField.class).fill();
final FieldFill updateFill = updateTime.getAnnotation(TableField.class).fill();
if (FieldFill.INSERT.equals(createFill) && FieldFill.INSERT_UPDATE.equals(updateFill)) {
createTime.setAccessible(true);
updateTime.setAccessible(true);
createTime.set(autoFillObject, LocalDateTime.now());
updateTime.set(autoFillObject, LocalDateTime.now());
}
return autoFillObject;
}
}
3.关于如何使用异步编排+Spring全局线程池
我们之前常常使用Spring框架的全局线程池+@Async
注解实现异步任务,但是如果大家接触到异步编排的话其实能意识到这样的方法一定是没有异步编排做起来灵活易于控制的,所以我们自然想到异步编排结合全局线程池
1.构建全局线程池
一般我喜欢将全局线程池放置到commons模块中,构建如下:
我们使用ThreadPoolTaskExecutor
对于线程池进行构建
/**
* @author Syf200208161018
* @date 2022/11/28 16:07
* @ClassName:GlobalThreadPoolExecutor
* @Effect:GlobalThreadPoolExecutor is used for 全局线程池
*/
@Configuration
public class GlobalThreadPoolExecutor {
@Bean
public ThreadPoolTaskExecutor globalThreadPool(){
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//设置核心线程数量
executor.setCorePoolSize(3);
//设置最大线程数
executor.setMaxPoolSize(5);
//设置队列中的最大可存储数量
executor.setQueueCapacity(10);
//设置线程名称前缀
executor.setThreadNamePrefix("eprop-");
//设置rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
// 对拒绝task的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//加载
executor.initialize();
return executor;
}
}
2.Service中引入全局线程池
如下使用@Resource
注解即可引入
@Resource
private ThreadPoolTaskExecutor globalThreadPoolExecutor;
3.CompleteFuture使用全局线程池
CompletableFuture.supplyAsync(() -> {
//业务代码
}),globalThreadPoolExecutor);
4.异步编排中return导致的后续任务接受值类型错误
我们在使用异步编排的时候当发生有个地方需要return直接结束的使用其实就会引发这个问题
错误如下:
//异步编排-----------------------------------------------------
final CompletableFuture<Serializable> thread1 = CompletableFuture.supplyAsync(() -> {
//获取当前用户的Team信息
final Team res = epropTeamClient.getTeamByUserId(userId);
//判断res返回值是否为空
if (Objects.isNull(res)) {
return ResultJSONData.fail(ErrorCode.GET_TEAMINFO_FAIL.getCode(), SuccessMsg.GET_TEAMINFO_SUCCESS.getMsg());
}
//转换Team-->TeamMemberInfoBO
return TeamConvertor.INSTANCE.convertTeamToTeamMemberInfoBO(res);
}, globalThreadPoolExecutor).thenApply(res->{
//获取到TeamId后进入TeamUser和TeamUserAuth两个表中进行查询
//通过TeamId找到所有的团队成员id
final List<TeamUser> teamMemberIdList = epropTeamClient.getTeamMemberIdList();
final List<TeamUserInfoDTO> teamUserInfoDTOS = TeamConvertor.INSTANCE.convertTeamUserToTeamUserInfoDTO(teamMemberIdList);
return teamUserInfoDTOS;
});
我们可以看到在第一个任务中判断到res为空的时候就会做一个return,但是就是因为这个操作,导致我们后面的任务发生了问题,请看截图:
这里明显发现第二个任务的接收的参数的类型出现了问题,这就是return导致的,只要把return语句去除就会正常了!
解决
但是如何解决这个问题呢?
我们首先要记得大家尽量不要在CompletableFuture的线程任务中写return的业务语句(指的是结束整个异步编排结束返回)
然后我们要知道CompletableFuture中遇到异常会激发exceptionally方法,所以我们的处理应该是在最后加上如下语句:
.exceptionally(e->{
e.printStackTrace();
return null;
});
完整代码:
final CompletableFuture<TeamMemberInfoBO> thread1 = CompletableFuture.supplyAsync(() -> {
//获取当前用户的Team信息
final Team res = epropTeamClient.getTeamByUserId(userId);
//判断res返回值是否为空
if (Objects.isNull(res)) {
Thread.currentThread().interrupt();
if (Thread.currentThread().isInterrupted()) {
throw new ThreadActivelyStopException(ErrorCode.GET_TEAMINFO_FAIL);
// return ResultJSONData.fail(ErrorCode.GET_TEAMINFO_FAIL.getCode(), SuccessMsg.GET_TEAMINFO_SUCCESS.getMsg());
}
}
//转换Team-->TeamMemberInfoBO
final TeamMemberInfoBO teamMemberInfoBO = TeamConvertor.INSTANCE.convertTeamToTeamMemberInfoBO(res);
//获取到TeamId后进入TeamUser和TeamUserAuth两个表中进行查询
//通过TeamId找到所有的团队成员id
final List<TeamUser> teamMemberIdList = epropTeamClient.getTeamMemberIdList(teamMemberInfoBO.getTeamId());
final List<TeamUserInfoDTO> teamUserInfoDTOS = TeamConvertor.INSTANCE.convertTeamUserToTeamUserInfoDTO(teamMemberIdList);
//通过UserId获取到用户信息
for (TeamUserInfoDTO re : teamUserInfoDTOS) {
User memberInfo = userClient.getUserInfo(String.valueOf(re.getUserId()));
re.setUsername(memberInfo.getUsername());
teamMemberInfoBO.getTeamMemberList().add(re);
}
return teamMemberInfoBO;
}, globalThreadPoolExecutor).thenApply((res) -> {
//获取团队成员身份信息
for (TeamUserInfoDTO dto : res.getTeamMemberList()) {
final List<TeamUserAuth> resList = epropTeamClient.getTeamUserAuthInfo(res.getTeamId(), dto.getUserId());
//若user_auth是0则是项目经理
for (TeamUserAuth teamUserAuth : resList) {
if(teamUserAuth.getUserAuth()==0){
res.setTeamLeader(dto.getUsername());
}
}
//转换
final List<TeamUserAuthDTO> teamUserAuthDTOS = TeamConvertor.INSTANCE.convertTeamUserAuthTODTO(resList);
dto.setAuthList(teamUserAuthDTOS);
}
return res;
}).exceptionally(e->{
e.printStackTrace();
return null;
});
这样的话当发现有异常的时候就会直接返回null了,也不会影响正常的返回
构建全局异常处理类
这里我们要说一下,我的异步编排跟Feign一起使用,所以也就是子模块业务出现错误会在子模块中报错,同时回到夫模块这里会显示Feign的调用模块错误500,这就表示我们的异步编排中出现了这个错误,我们就可以通过全局异常处理来抓取进行特殊的处理,但是整个项目里我并没有这样做,因为从逻辑上我返回了null其实已经是说明查不到这个数据了,但是在这里我还是要给大家扩展一下这个全局异常处理
以下是一个简单的示例:
/**
* @author Syf200208161018
* @date 2022/11/30 22:37
* @ClassName:ThreadActivelyStopExceptionHandler
* @Effect:ThreadActivelyStopExceptionHandler is used for
*/
@ControllerAdvice(annotations = {RestController.class})
@ResponseBody
public class ThreadActivelyStopExceptionHandler {
@ExceptionHandler({ThreadActivelyStopException.class})
public ResultJSONData handleException(ThreadActivelyStopException e){
return ResultJSONData.fail(e.getErrorCode().getCode(),e.getErrorCode().getMsg());
}
@ExceptionHandler({NullPointerException.class})
public ResultJSONData handleNullPointException(){
return ResultJSONData.fail(ErrorCode.NULL_POINT_EXCEPTION_FAIL.getCode(),ErrorCode.NULL_POINT_EXCEPTION_FAIL.getMsg());
}
}
我们使用@RestControllerAdvice
扫描到对应注解所含有的方法,当出现错误的时候就会抓取到进行处理,这个处理大家就自由发挥了