You are no stranger to Assert assertions. When we do unit testing, we look at the composite expectations of business transactions. We can verify them through assertions. The common methods of assertions are as follows:
public class Assert { /** * Result = expected is correct */ static public void assertEquals(Object expected, Object actual); /** * Result= Expectations are correct */ static public void assertNotEquals(Object unexpected, Object actual); /** * condition == true Then correct */ static public void assertTrue(boolean condition); /** * condition == false Then correct */ static public void assertFalse(boolean condition); /** * Always wrong */ static public void fail(); /** * If the result is not empty, it is correct */ static public void assertNotNull(Object object); /** * If the result is empty, it is correct */ static public void assertNull(Object object); /** * It is correct if two object references are equal (the above is equivalent to requests, which is similar to using "= =" to compare two objects) */ static public void assertSame(Object expected, Object actual); /** * Correct if two object references are not equal */ static public void assertNotSame(Object unexpected, Object actual); /** * It is correct if two arrays are equal */ public static void assertArrayEquals(Object[] expecteds, Object[] actuals); /** * For this separate introduction, please refer to the blog: https://www.cnblogs.com/qdhxhz/p/13684458.html */ public static <T> void assertThat(T actual, Matcher<? super T> matcher); };
Using assertions can make our code look more refreshing, such as:
@Test public void test1() { Order order = orderDao.selectById(orderId); Assert.notNull(order, "Order does not exist."); } @Test public void test2() { // Another way of writing Order order = orderDao.selectById(orderId); if (order == null) { throw new IllegalArgumentException("Order does not exist."); } }
When comparing the two methods, do you obviously feel that the first one is more elegant and the second one is relatively ugly if {...} Code block. So magical assert What exactly does notnull () do?
The following is part of the source code of Assert:
public abstract class Assert { public Assert() { } public static void notNull(@Nullable Object object, String message) { if (object == null) { throw new IllegalArgumentException(message); } } }
As you can see, Assert is actually helping us put if {...} Encapsulated, isn't it amazing. Although it is very simple, it is undeniable that the coding experience has improved at least one level.
So can we imitate Assert and write a custom assertion class? However, the exceptions thrown after assertion failure are not built-in exceptions such as IllegalArgumentException, but exceptions defined by ourselves.
Now let's try.
public interface Assert { /** * Create exception * @param args * @return */ BaseException newException(Object... args); /** * Create exception * @param t * @param args * @return */ BaseException newException(Throwable t, Object... args); /** * Assert that object obj is not empty. If the object obj is empty, an exception is thrown * * @param obj Object to be judged */ default void assertNotNull(Object obj) { if (obj == null) { throw newException(obj); } } /** * Assert that object obj is not empty. If the object obj is empty, an exception is thrown * The exception message supports parameter passing, avoiding string splicing before judgment * * @param obj Object to be judged * @param args message Parameter list corresponding to placeholder */ default void assertNotNull(Object obj, Object... args) { if (obj == null) { throw newException(args); } } }
Note:
- Only part of the source code of the Assert interface is given here. For more assertion methods, please refer to the source code of the project.
- BaseException is the base class for all custom exceptions.
- The default method defined in the interface is the new syntax of Java 8.
The above Assert assertion method is defined using the default method of the interface. Then, do you find that the exception thrown after the assertion fails is not a specific exception, but provided by two newException interface methods.
Because the exceptions in the business logic basically correspond to specific scenarios. For example, the user information is obtained according to the user id, and the query result is null. At this time, the exception thrown may be UserNotFoundException and has special characteristics
The specified exception code (such as 7001) and exception information "user does not exist". Therefore, the specific exception thrown depends on the implementation class of Assert.
After seeing this, you may have questions. According to the above practice, there are not many exceptions. I have to implement the Assert interface, and then write and define the same number of assertion classes and exception classes, which is obviously anti-human,
It's not as smart as you think. Don't worry, just listen to me carefully.
1, Understanding Enum
The custom exception BaseException has two attributes, namely code and message. Is it conceivable that any class generally defines these two attributes?
Yes, enumeration class. Let's see how I combine Enum and Assert. I believe I will brighten your eyes. As follows:
public interface IResponseEnum { int getCode(); String getMessage(); }
We define a business exception class
/** * Business exception * When an exception occurs during business processing, the exception can be thrown * */ public class BusinessException extends BaseException { private static final long serialVersionUID = 1L; public BusinessException(IResponseEnum responseEnum, Object[] args, String message) { super(responseEnum, args, message); } public BusinessException(IResponseEnum responseEnum, Object[] args, String message, Throwable cause) { super(responseEnum, args, message, cause); } }
Business exception assertion implementation class, which inherits two interfaces at the same time, one is Assert interface and the other is IResponseEnum interface
public interface BusinessExceptionAssert extends IResponseEnum, Assert { @Override default BaseException newException(Object... args) { String msg = MessageFormat.format(this.getMessage(), args); return new BusinessException(this, args, msg); } @Override default BaseException newException(Throwable t, Object... args) { String msg = MessageFormat.format(this.getMessage(), args); return new BusinessException(this, args, msg, t); } }
Business exception enumeration
/** * Business exception enumeration */ @Getter @AllArgsConstructor public enum BusinessResponseEnum implements BusinessExceptionAssert { USER_NOT_FOUND(6001, "No user information found"), ORDER_NOT_FOUND(7001, "No order information found"), ; /** * Return code */ private int code; /** * Return message */ private String message; }
In this way, if it is a business exception, you can define a new enumeration object in BusinessResponseEnum. For each exception added in the future, you only need to add an enumeration instance, and you don't need each exception anymore
An exception class has been defined. Next, let's use the following:
/** * Query user information */ public User queryDetail(Integer userId) { final User user = this.getById(userId); // Check is not empty BusinessResponseEnum.USER_NOT_FOUND.assertNotNull(user); return user; }
If you do not use assertions, the code might be as follows:
/** * Query user information */ public User queryDetail(Integer userId) { final User user = this.getById(userId); if (user == null) { throw new UserNotFoundException(); // Or so throw new BusinessException(6001, "No user information found"); } return user; }
Using enumeration classes in combination with (inheriting) Assert, you only need to define different enumeration instances according to specific exceptions, such as user above_ NOT_ FOUND,ORDER_NOT_FOUND, you can throw specific exceptions for different situations
(this refers to carrying specific exception codes and exception messages), which not only does not need to define a large number of exception classes, but also has good readability of assertions.
II. Verify unified exception handling
After throw throws an exception, we can handle the exception uniformly. If necessary, we can add the exception log and store it in the database, which is helpful for subsequent troubleshooting.
/** * Global exception handler * * @author xub * @date 2022/2/28 10:52 am */ @Slf4j @Component @ControllerAdvice @ConditionalOnWebApplication public class ViewExceptionResolver { /** * production environment */ private final static String ENV_PROD = "prod"; @Autowired private UnifiedMessageSource unifiedMessageSource; /** * Current environment */ @Value("${spring.profiles.active}") private String profile; /** * Get internationalization message * * @param e abnormal * @return */ public String getMessage(BaseException e) { String code = "response." + e.getResponseEnum().toString(); String message = unifiedMessageSource.getMessage(code, e.getArgs()); if (message == null || message.isEmpty()) { return e.getMessage(); } return message; } /** * Business exception * * @param e abnormal * @return Abnormal result */ @ExceptionHandler(value = BusinessException.class) @ResponseBody public CommandResult handleBusinessException(BaseException e) { log.error(e.getMessage(), e); return CommandResult.ofFail(e.getResponseEnum().getCode(), getMessage(e)); } /** * Custom exception * * @param e abnormal * @return Abnormal result */ @ExceptionHandler(value = BaseException.class) @ResponseBody public CommandResult handleBaseException(BaseException e) { log.error(e.getMessage(), e); return CommandResult.ofFail(e.getResponseEnum().getCode(), getMessage(e)); } /** * Undefined exception * * @param e abnormal * @return Abnormal result */ @ExceptionHandler(value = Exception.class) @ResponseBody public CommandResult handleException(Exception e) { log.error(e.getMessage(), e); if (ENV_PROD.equals(profile)) { // When it is a production environment, it is not suitable to display specific exception information to users, such as database exception information int code = CommonResponseEnum.SERVER_ERROR.getCode(); BaseException baseException = new BaseException(CommonResponseEnum.SERVER_ERROR); String message = getMessage(baseException); return CommandResult.ofFail(code, message); } return CommandResult.ofFail(CommonResponseEnum.SERVER_ERROR.getCode(), e.getMessage()); } }
III. test and verification
Here, the request address, service id=10, does not exist in the database, so an error will be reported.
localhost:8085/user/getUserInfo?userId=10
The expected effect has been achieved.
IV. summary
With the combination of assertion and enumeration classes and unified exception handling, most exceptions can be caught. Why do you say most exceptions? Because when spring cloud security is introduced, there will be exceptions
There are authentication / authorization exceptions, gateway service degradation exceptions, cross module call exceptions, remote call third-party service exceptions, etc. the capture methods of these exceptions are different from those described in this article, but they are limited to space and will not be described in detail here,
There will be a separate article in the future.
Project address: Encapsulate exceptions with assert to make the code more elegant (with project source code)
thank
This article provides a good idea for myself. I write it down basically according to this idea
Introduction and practice of unified exception handling: https://www.jianshu.com/p/3f3d9e8d1efa