preface
Record how Dubbo handles custom exceptions.
Achieve the goal
- Service layer exception, thrown directly to the upper layer,
web
Layer unified capture processing - Return if the exception is a system – defined exception
{"code":xxx,"msg":yyy}
Among themcode
Corresponding toError code
.msg
The corresponding information is abnormal information - Returns if the exception is not system – defined
{"code":-1," MSG ":" unknown error "}
In addition, the abnormal stack information is output to logs for fault locating
The project architecture
Let’s start with a system architecture diagram. This diagram comes from the network. I believe that the distributed cluster architecture of most small and medium-sized enterprises is similar to this design:
A brief description of the following hierarchical architecture:
- Usually there is a special one
The fortress machine
Do unified proxy forwarding, client (PC, mobile, etc.) access bynginx
Unified exposed entrance nginx
Reverse proxy, load balancing toweb
Server, bytomcat
Of a cluster,web
The layer serves only as an entry point for interface requests, with no actual business logicweb
Layer withrpc
Remote calls are registered tozookeeper
thedubbo
Service cluster,dubbo
Services interact with the data layer to process business logic
Separate the front and back ends and use JSON format for data interaction. The format can be unified as follows:
{
"code": 200.// Status code: 200 Success, other status code is failure
"msg": "success".// Message, success is success, other is failure cause
"data": object  // The specific data content can be in any format
}
Copy the code
Mapping to Javabeans can be uniformly defined as:
/ * * *@program: easywits
* @description: the outermost object returned by the HTTP request *@author: zhangshaolin
* @create: the 2018-04-27 10:43 * * /
@Data
@JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class BaseResult<T> implements Serializable{
private static final long serialVersionUID = -6959952431964699958L;
/** * Status code: 200 Successful, other status code: failed */
public Integer code;
/ * * * success for success, the other for failure reason * /
public String msg;
/** ** Specific content */
public T data;
}
Copy the code
Return result utility class encapsulation:
/ * * *@program: easywits
* @description: HTTP return result utility class *@author: zhangshaolin
* @create: the 2018-07-14 thy * * /
public class ResultUtil {
/** * call include data * when access succeeds@param object
* @return* /
public static BaseResult success(Object object){
BaseResult result = new BaseResult();
result.setCode(200);
result.setMsg("success");
result.setData(object);
return result;
}
/** * The call does not contain data *@return* /
public static BaseResult success(a){
return success(null);
}
/** * returns an exception without data *@param code
* @param msg
* @return* /
public static BaseResult error(Integer code,String msg){
BaseResult result = new BaseResult();
result.setCode(code);
result.setMsg(msg);
return result;
}
/** * returns an exception containing data *@paramResultEnum Result enumeration class & EMsp; Unified management & EMSP; code msg *@param object
* @return* /
public static BaseResult error(ResultEnum resultEnum,Object object){
BaseResult result = error(resultEnum);
result.setData(object);
return result;
}
/** * Global base class custom exception exception handling *@param e
* @return* /
public static BaseResult error(BaseException e){
return error(e.getCode(),e.getMessage());
}
/** * returns an exception without data *@paramResultEnum Result enumeration class & EMsp; Unified management & EMSP; code msg *@return* /
public static BaseResult error(ResultEnum resultEnum){
returnerror(resultEnum.getCode(),resultEnum.getMsg()); }}Copy the code
Therefore, the process of simulating a front-end call request could be as follows:
-
Web layer interface
@RestController @RequestMapping(value = "/user") public class UserController { @Autowired UserService mUserService; @Loggable(descp = "User Profile", include = "") @GetMapping(value = "/info") public BaseResult userInfo(a) { returnmUserService.userInfo(); }}Copy the code
-
Service layer interface
@Override public BaseResult userInfo(a) { UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo(); UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId()); return ResultUtil.success(userInfoVo); } Copy the code
Custom system exception
Define a custom exception that is used to manually throw an exception. Note that the base RuntimeException is unchecked:
To put it simply, RuntimeException and its subclasses are unchecked exceptions. Other exceptions are checked exceptions. Unchecked exceptions are exceptions thrown at runtime, and checked exceptions are mandatory errors at compile time
public class BaseException extends RuntimeException{
private Integer code;
public BaseException(a) {}public BaseException(ResultEnum resultEnum) {
super(resultEnum.getMsg());
this.code = resultEnum.getCode(); }... Omit set get method}Copy the code
To facilitate unified management of results, define a result enumeration class:
public enum ResultEnum {
UNKNOWN_ERROR(-1."O (╥﹏╥)o~~ The system is abnormal! Please contact the administrator!!"),
SUCCESS(200."success");
private Integer code;
private String msg;
ResultEnum(Integer code, String msg) {
this.code = code;
this.msg = msg; }}Copy the code
web
Layers uniformly catch exceptions
Define the BaseController abstract class to uniformly catch exceptions thrown by the service layer. All new controllers inherit this class.
public abstract class BaseController {
private final static Logger LOGGER = LoggerFactory.getLogger(BaseController.class);
/** * unified exception handling **@param e
*/
@ExceptionHandler(a)public Object exceptionHandler(Exception e) {
if (e instanceof BaseException) {
// Global base class custom exception returns {code, MSG}
BaseException baseException = (BaseException) e;
return ResultUtil.error(baseException);
} else {
LOGGER.error("System exception: {}", e);
returnResultUtil.error(ResultEnum.UNKNOWN_ERROR); }}}Copy the code
validation
-
The above Web layer interface UserController inherits BaseController and uniformly catches exceptions
-
The service layer assumes that a customized system exception, BaseException, is thrown as follows:
@Override public BaseResult userInfo(a) { UserInfo userInfo = ThreadLocalUtil.getInstance().getUserInfo(); UserInfoVo userInfoVo = getUserInfoVo(userInfo.getUserId()); if(userInfoVo ! =null) { {code:10228 MSG :" User exists! "} } throw new BaseException(ResultEnum.USER_EXIST); } return ResultUtil.success(userInfoVo); } Copy the code
However, after calling the result, the exception caught by the upper layer is not a BaseException, but is thrown as an unknown error. Take a look at Dubbo’s handling of exceptions with some doubt
Dubbo exception handling
Dubbo has uniform exception handling. Here is the main code of Dubbo exception interceptor:
@Override
public Result invoke(Invoker
invoker, Invocation invocation) throws RpcException {
try {
// Service invocation
Result result = invoker.invoke(invocation);
// There is an exception and a non-generalization call
if(result.hasException() && GenericService.class ! = invoker.getInterface()) {try {
Throwable exception = result.getException();
// directly throw if it's checked exception
// If it is a checked exception, throw it
if(! (exceptioninstanceof RuntimeException) && (exception instanceof Exception)) {
return result;
}
// directly throw if the exception appears in the signature
// There is a declaration on the method signature, which is thrown directly
try{ Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); Class<? >[] exceptionClassses = method.getExceptionTypes();for(Class<? > exceptionClass : exceptionClassses) {if (exception.getClass().equals(exceptionClass)) {
returnresult; }}}catch (NoSuchMethodException e) {
return result;
}
// ERROR logs are printed on the server side for exceptions not defined on the method signature
// for the exception not found in method's signature, print ERROR message in server's log.
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + exception.getClass().getName() + ":" + exception.getMessage(), exception);
// The exception class and the interface class are in the same JAR package, directly thrown
// directly throw if exception class and interface class are in the same jar file.
String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface());
String exceptionFile = ReflectUtils.getCodeBase(exception.getClass());
if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) {
return result;
}
// is a JDK exception thrown directly
// directly throw if it's JDK exception
String className = exception.getClass().getName();
if (className.startsWith("java.") || className.startsWith("javax.")) {
return result;
}
// Is an exception of Dubbo itself, thrown directly
// directly throw if it's dubbo exception
if (exception instanceof RpcException) {
return result;
}
// Otherwise, wrap it as RuntimeException and throw it to the client
// otherwise, wrap with RuntimeException and throw back to the client
return new RpcResult(new RuntimeException(StringUtils.toString(exception)));
} catch (Throwable e) {
logger.warn("Fail to ExceptionFilter when called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ":" + e.getMessage(), e);
returnresult; }}/ / return
return result;
} catch (RuntimeException e) {
logger.error("Got unchecked and undeclared exception which called by " + RpcContext.getContext().getRemoteHost()
+ ". service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName()
+ ", exception: " + e.getClass().getName() + ":" + e.getMessage(), e);
throwe; }}Copy the code
Brief description:
- When there is an exception and a non-generalization call, if it is a checked exception, it is thrown directly
- If there is an exception and a non-generalized call has a declaration on the method signature, it is thrown directly
- The exception class and the interface class are the same when there is an exception and a non-generalized call
jar
In the bag, you throw it out - If there is an exception and a non-generalization call, yes
Dubbo
RpcException is thrown directly - When you have an exception and you don’t generalize the call, the rest of the case, all wrapped up in
RuntimeException
Throw to the client
By now it’s obvious that our BaseException is unchecked and doesn’t meet the requirements thrown directly by Dubbo’s exception interceptor, so Dubbo wraps it as a RuntimeException. So in the upper BaseController uniformly caught as a system unknown error.
The solution
- Exception class
BaseException
Same as the interface classjar
Bag, but this way to eachjar
To place an exception class, not unified maintenance management - Explicitly declare a throw on the interface method signature
BaseException
In this way, it is relatively simple and easy to maintain in a unified way, but each interface should declare an exception explicitly. I choose this way to solve the problem
The problem
Return resultutil. error(ResultEnum ResultEnum) {code: XXX MSG: XXX}
If you’ve ever played with Spring, you know that the concept of declarative transactions is to add transaction annotations to the interface, and when an exception occurs, the entire interface performs transaction rollback. Take a look at the pseudocode below:
@Transactional(rollbackFor = Exception.class)
public BaseResult handleData(a){
//1. Add data to table A and return the primary key ID of the newly added data
//2. Perform operations on the database and add data in database B. Associate the data in table A with the primary key ID as the foreign key
//3. Execute successfully Returns the result
}
Copy the code
- This interface declares the rollback of exceptions, and all exceptions are rolled back when sent
- Procedure Step 1 If the data fails to be imported into the database, the primary key ID cannot be obtained theoretically. In this case, a customized exception should be thrown, indicating that the operation fails
- If the data in Step 1 is successfully imported into the database but the data in Step 2 fails to be imported into the database, the data in Step 1 should also be rolled back theoretically. If the abnormal result is returned forcibly, the data in step 1 becomes dirty data. In this case, it is the most reasonable to throw the user-defined exception
Final thoughts
It is best to read the source code in a real problem scenario, and to look at the source code with the problem in mind will make people feel suddenly enlightened.
More original articles will be the first time to push the public account [Zhang Shaolin students], welcome to pay attention to!