In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/02 Report--
This article mainly introduces "what is unified exception handling in Spring". In daily operation, I believe that many people have doubts about what is unified exception handling in Spring. The editor consulted all kinds of data and sorted out simple and easy-to-use operation methods. I hope it will be helpful to answer the doubts about "what is unified exception handling in Spring". Next, please follow the editor to study!
So there will be a large number of try {...} catch {...} finally {...} code blocks in the code, which not only has a lot of redundant code, but also affects the readability of the code.
Compare the following two pictures to see which style of code you are writing now? Then which coding style do you prefer?
Ugly try catch code block, as shown below:
Elegant Controller, as shown below:
The above example is only in the Controller layer, but if it is in the Service layer, there may be more blocks of try catch code. This will seriously affect the readability and "aesthetics" of the code.
So if it were me, I would definitely prefer the second, I can focus more on the development of business code, and the code will become more concise.
Since the business code does not explicitly catch and handle exceptions, and exceptions must still be handled, otherwise the system will crash easily, so there must be other places to catch and handle these exceptions.
So the question is, how to handle all kinds of exceptions gracefully?
What is uniform exception handling
Spring added an annotation @ ControllerAdvice in version 3.2, which can be used with @ ExceptionHandler, @ InitBinder, @ ModelAttribute and other annotations.
For the role of these notes, do not do too much detail here, if you do not understand, you can refer to the new Spring 3.2 note @ ControllerAdvice, first have a general understanding.
However, the only thing related to exception handling is the annotation @ ExceptionHandler, which literally means exception handler.
Its practical effect is that if you define an exception handling method in a Controller class and add the annotation to the method, the exception handling method will be executed when the specified exception occurs.
It can use the data binding provided by SpringMVC, such as injecting HttpServletRequest, and so on, and it can also accept a currently thrown Throwable object.
However, in this way, a set of such exception handling methods must be defined in each Controller class, because exceptions can be various.
As a result, there is a lot of redundant code, and if you need to add an exception handling logic, you have to modify all the Controller classes, which is not elegant.
Of course, you might say, define a base class like BaseController, and that's all right.
While this is true, it is still not perfect because such code is intrusive and coupled.
Simple Controller, why do I have to inherit such a class, in case I already inherit another base class? Everyone knows that Java can only inherit one class.
Is there a way to apply defined exception handlers to all controllers without coupling with Controller?
So the annotation @ ControllerAdvice appears, which simply applies the exception handler to all controllers, rather than a single controller.
With this annotation, we can implement: define a set of handling mechanism for various exceptions in an independent place, such as a single class, and then add the annotation @ ControllerAdvice to the signature of the class to uniformly handle different exceptions at different stages.
This is the principle of unified exception handling. Note that the above classifies exceptions by stages, which can be roughly divided into two categories: exceptions before entering Controller and Service layer exceptions.
For more information, please refer to the following figure:
Anomalies in different stages
target
Eliminate more than 95% of try catch code blocks, check business anomalies in an elegant Assert (assertion) way, and focus on business logic instead of writing redundant try catch code blocks.
Unified exception handling practice
Before defining a unified exception handling class, let's introduce how to elegantly determine an exception and throw an exception.
Replace throw exception with Assert (assertion)
Presumably Assert (assertion) is familiar to everyone, such as org.springframework.util.Assert of the Spring family, which is often used when we write test cases. Using assertions can make us feel silky when coding.
For example:
@ Test public void test1 () {... User user = userDao.selectById (userId); Assert.notNull (user, "user does not exist.");.} @ Test public void test2 () {/ / another way to write User user = userDao.selectById (userId); if (user = = null) {throw new IllegalArgumentException ("user does not exist.");}}
Do you feel that the first way to determine whether it is not empty is elegant, and the second is the relatively ugly if {...} code block.
So what's going on behind the magic Assert.notNull ()? 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);}
You can see that Assert actually helps us encapsulate if {.}, 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 org.springframework.util.Assert and write an assertion class, but the exceptions thrown after assertion failure are not built-in exceptions such as IllegalArgumentException, but our own defined exceptions.
① Assert
Let's try this:
Public interface Assert {/ * create exception * @ param args * @ return * / BaseException newException (Object... Args); / * create an exception * @ param t * @ param args * @ return * / BaseException newException (Throwable t, Object... Args); / *
Asserts that the 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);}} / * *
Asserts that the object obj is not empty. If the object obj is empty, an exception is thrown *
Exception information message supports passing parameters to avoid string concatenation before judgment * * @ param obj * @ param args message placeholder parameter list * / default void assertNotNull (Object obj, Object...) Args) {if (obj = = null) {throw newException (args);}
Asserts that the 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);}} / * *
Asserts that the object obj is not empty. If the object obj is empty, an exception is thrown *
Exception information message supports passing parameters to avoid string concatenation before judgment * * @ param obj * @ param args message placeholder parameter list * / default void assertNotNull (Object obj, Object...) Args) {if (obj = = null) {throw newException (args);}
The above Assert assertion method is defined using the default method of the interface, and then did you find that when the assertion fails, the exception thrown is not a specific exception, but is provided by two newException interface methods.
Because the exceptions that occur in the business logic basically correspond to specific scenarios, for example, if the user information is obtained according to the user id, the query result is null, and the exception thrown may be UserNotFoundException.
And there is a specific exception code (such as 7001) and the exception message "user does not exist". So it's up to Assert's implementation class to decide exactly what exception to throw.
See here, you may have such a question, according to the above, it is not how many abnormal cases, there must be the definition of the same number of assertions and exception classes, which is obviously anti-human, this is not as clever as imagined. Don't worry, just listen to me in detail.
② 's considerate Enum
Custom exception BaseException has two attributes, namely, code and message. Have you thought of any class that generally defines these two attributes?
Yes, it's an enumeration class. Let's see how I combine Enum and Assert, and I'm sure I'll shine before your eyes.
As follows:
Public interface IResponseEnum {int getCode (); String getMessage ();} / * *
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);}} 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) } @ Getter @ AllArgsConstructor public enum ResponseEnum implements BusinessExceptionAssert {/ * Bad licence type * / BAD_LICENCE_TYPE (7001, "Bad licence type."), / * * Licence not found * / LICENCE_NOT_FOUND (7002, "Licence not found."); / * * error code * / private int code / * * return message * / private String message;}
Do you feel bright when you see this? two enumerated examples are defined in the code example:
BAD_LICENCE_TYPE
LICENCE_NOT_FOUND
It corresponds to two kinds of anomalies: BadLicenceTypeException and LicenceNotFoundException respectively.
In the future, for each additional exception, you only need to add an enumeration instance, and you no longer need to define an exception class for each exception.
Then let's take a look at how to use it. Suppose LicenceService has a method to verify the existence of Licence, as follows:
/ * check {@ link Licence} there is * @ param licence * / private void checkNotNull (Licence licence) {ResponseEnum.LICENCE_NOT_FOUND.assertNotNull (licence);}
Without assertions, the code might be as follows:
Private void checkNotNull (Licence licence) {if (licence = = null) {throw new LicenceNotFoundException (); / / or such throw new BusinessException (7001, "Bad licence type.");}}
Using enumeration classes combined with (inheriting) Assert, you only need to define different enumeration instances according to specific exception conditions, such as BAD_LICENCE_TYPE and LICENCE_NOT_FOUND above, and you can throw specific exceptions for different situations (in this case, carrying specific exception codes and exception messages).
This not only does not have to define a large number of exception classes, but also has a good readability of assertions, of course, the benefits of this scheme are far more than these, please continue to read the following article, slowly understand.
Note: the above example is specific to a specific business, and some exceptions are common, such as: server busy, network exception, server exception, parameter verification exception, 404, etc.
So there are CommonResponseEnum, ArgumentResponseEnum and ServletResponseEnum, of which ServletResponseEnum will be described in detail later.
Define a unified exception handler class
@ Slf4j @ Component @ ControllerAdvice @ ConditionalOnWebApplication @ ConditionalOnMissingBean (UnifiedExceptionHandler.class) public class UnifiedExceptionHandler {/ * 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 exception * @ 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 exception * @ return exception result * / @ ExceptionHandler (value = BusinessException.class) @ ResponseBody public ErrorResponse handleBusinessException (BaseException e) {log.error (e.getMessage (), e) Return new ErrorResponse (e.getResponseEnum (). GetCode (), getMessage (e));} / * * Custom exception * * @ param e exception * @ return exception result * / @ ExceptionHandler (value = BaseException.class) @ ResponseBody public ErrorResponse handleBaseException (BaseException e) {log.error (e.getMessage (), e) Return new ErrorResponse (e.getResponseEnum () .getCode (), getMessage (e)) } / * Controller layer related exception * * @ param e exception * @ return exception result * / @ ExceptionHandler ({NoHandlerFoundException.class, HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, TypeMismatchException.class, HttpMessageNotReadableException.class HttpMessageNotWritableException.class, / / BindException.class, / / MethodArgumentNotValidException.class HttpMediaTypeNotAcceptableException.class, ServletRequestBindingException.class, ConversionNotSupportedException.class, MissingServletRequestPartException.class, AsyncRequestTimeoutException.class}) @ ResponseBody public ErrorResponse handleServletException (Exception e) {log.error (e.getMessage (), e) Int code = CommonResponseEnum.SERVER_ERROR.getCode (); try {ServletResponseEnum servletExceptionEnum = ServletResponseEnum.valueOf (e.getClass (). GetSimpleName ()); code = servletExceptionEnum.getCode ();} catch (IllegalArgumentException E1) {log.error ("class [{}] not defined in enum {}", e.getClass (). GetName (), ServletResponseEnum.class.getName ()) } if (ENV_PROD.equals (profile)) {/ / as a production environment, it is not suitable to show specific exception information to users, such as 404. Code = CommonResponseEnum.SERVER_ERROR.getCode (); BaseException baseException = new BaseException (CommonResponseEnum.SERVER_ERROR); String message = getMessage (baseException); return new ErrorResponse (code, message);} return new ErrorResponse (code, e.getMessage ()) } / * * Parameter binding exception * * @ param e exception * @ return exception result * / @ ExceptionHandler (value = BindException.class) @ ResponseBody public ErrorResponse handleBindException (BindException e) {log.error ("Parameter binding check exception", e); return wrapperBindingResult (e.getBindingResult ()) } / * * parameter check exception, combine all the failed param exceptions into an error message * * @ param e exception * @ return exception result * / @ ExceptionHandler (value = MethodArgumentNotValidException.class) @ ResponseBody public ErrorResponse handleValidException (MethodArgumentNotValidException e) {log.error ("parameter binding check exception", e) Return wrapperBindingResult (e.getBindingResult ());} / * * wraps the binding exception result * * @ param bindingResult binding result * @ return exception result * / private ErrorResponse wrapperBindingResult (BindingResult bindingResult) {StringBuilder msg = new StringBuilder (); for (ObjectError error: bindingResult.getAllErrors ()) {msg.append (",") If (error instanceof FieldError) {msg.append (FieldError) error). GetField ()) .append (":");} msg.append (error.getDefaultMessage () = = null? ": error.getDefaultMessage ());} return new ErrorResponse (ArgumentResponseEnum.VALID_ERROR.getCode (), msg.substring (2)) } / * * No exception defined * * @ param e exception * @ return exception result * / @ ExceptionHandler (value = Exception.class) @ ResponseBody public ErrorResponse handleException (Exception e) {log.error (e.getMessage (), e) If (ENV_PROD.equals (profile)) {/ / as a production environment, it is not suitable to show 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 new ErrorResponse (code, message);} return new ErrorResponse (CommonResponseEnum.SERVER_ERROR.getCode (), e.getMessage ());}}
As you can see, the above classifies exceptions into several categories, but there are actually only two categories, one is ServletException and ServiceException. Do you remember the classification by phase mentioned above?
That is, it corresponds to the exception before entering the Controller and the exception in the Service layer, and then the ServiceException is divided into custom exceptions and unknown exceptions.
The corresponding relationship is as follows:
Exceptions before entering Controller: handleServletException, handleBindException, handleValidException.
Custom exceptions: handleBusinessException, handleBaseException.
Unknown exception: handleException.
Next, these exception handlers are described in detail.
① exception handler description
HandleServletException: a HTTP request that makes a series of checks on the request information and the target controller information before arriving at the Controller.
Here is a brief statement:
1.NoHandlerFoundException: first of all, check whether there is a corresponding controller according to the request URL, and if not, the exception will be thrown, that is, the familiar 404 exception.
2.HttpRequestMethodNotSupportedException: if the matching result is a list, the difference is that the HTTP method is different, such as Get, Post, etc., try to match the requested HTTP method with the controller of the list. If there is no controller corresponding to the HTTP method, the exception will be thrown.
3.HttpMediaTypeNotSupportedException: then compare the request header with what the controller supports.
For example, for the content-type request header, if the controller's parameter signature contains the annotation @ RequestBody, but the value of the requested content-type request header does not contain application/json, then the exception will be thrown (of course, this exception will be thrown more than that).
4.MissingPathVariableException: no path parameters were detected. For example, URL is: / licence/ {licenceId}, and the parameter signature contains @ PathVariable ("licenceId").
When the URL of the request is / licence, if the URL is not clearly defined as / licence, it will be determined as: missing path parameter.
5.MissingServletRequestParameterException: the request parameter is missing. For example, the parameter @ RequestParam ("licenceId") String licenceId is defined, but if the request is not carried with it, the exception will be thrown.
6.TypeMismatchException: parameter type matching failed. For example, if the receiving parameter is Long, but the value passed in is a string, then the type conversion will fail, and the exception will be thrown.
7.HttpMessageNotReadableException: the exact opposite of the example given by HttpMediaTypeNotSupportedException above.
That is, the request header carries "content-type: application/json;charset=UTF-8", but the receiving parameter does not add the annotation @ RequestBody, or if the json string carried by the request body fails to deserialize into pojo, the exception will also be thrown.
8.HttpMessageNotWritableException: if the returned pojo fails in the serialization to json process, the exception is thrown.
9.HttpMediaTypeNotAcceptableException: unknown.
10.ServletRequestBindingException: unknown.
11.ConversionNotSupportedException: unknown.
12.MissingServletRequestPartException: unknown.
13.AsyncRequestTimeoutException: unknown.
HandleBindException: parameter verification exception, which is described in detail later.
HandleValidException: parameter verification exception, which is described in detail later.
HandleBusinessException, handleBaseException: handles custom business exceptions, but handleBaseException handles all business exceptions except BusinessException. At present, these two can be merged into one.
HandleException: handles all unknown exceptions, such as those that failed to operate on the database.
Note: the exception information returned by the above two processors, handleServletException and handleException, may be different in different environments. It is assumed that these exception messages are all contained in the framework and are generally in English, so it is not good to show them to users directly, so the exception information represented by SERVER_ERROR is returned uniformly.
② is different from the 404 of ordinary people.
As mentioned above, a NoHandlerFoundException exception is thrown when the request does not match the controller.
However, this is not the case by default, and a page similar to the following appears by default:
Whitelabel Error Page
How did this page appear? In fact, when 404 occurs, the default is not to throw an exception, but forward jumps to the / error controller, and Spring also provides the default error controller.
As follows:
BasicErrorController
So, how to make 404 also throw an exception, just add the following configuration to the properties file:
Spring.mvc.throw-exception-if-no-handler-found=true spring.resources.add-mappings=false
In this way, it can be captured in the exception handler, and then the front end can jump to the 404 page as soon as it captures a specific status code.
③ catches the exception corresponding to 404
Return the result uniformly
Before verifying the uniform exception handler, by the way, uniformly returns the results. To put it bluntly, it is actually to unify the data structure of the returned results.
Code and message are required fields in all the returned results, and when you need to return data, you need another field, data.
So first define a BaseResponse as the base class for all returned results; then define a generic return class CommonResponse, inheriting BaseResponse, and adding the field data.
To distinguish between success and failure, the result is returned, so another ErrorResponse is defined.
Finally, there is a common return result, that is, the returned data with paging information, because this kind of interface is more common, it is necessary to define a separate return result class QueryDataResponse.
This class inherits from CommonResponse, but restricts the type of data field to the field that defines the paging information in QueryDdata,QueryDdata, that is, totalCount, pageNo, pageSize, records.
Among them, only CommonResponse and QueryDataResponse are commonly used, but the names are very long, so why not define two super-simple classes instead?
So R and QR are born, and when you return the result, you just need to write: new R (data), new QR (queryData).
All the definitions of the return result class will not be posted here.
Verify unified exception handling
Because this set of unified exception handling can be said to be universal, all can be designed as a common package, which can be introduced into each new project / module in the future. So in order to verify, you need to create a new project and introduce the common package.
Main code
The following is the main source code for verification:
@ Service public class LicenceService extends ServiceImpl {@ Autowired private OrganizationClient organizationClient; / * query {@ link Licence} details * @ param licenceId * @ return * / public LicenceDTO queryDetail (Long licenceId) {Licence licence = this.getById (licenceId); checkNotNull (licence); OrganizationDTO org = ClientUtil.execute (()-> organizationClient.getOrganization (licence.getOrganizationId () Return toLicenceDTO (licence, org);} / * * get * @ param licenceParam paging query parameters * @ return * / public QueryData getLicences (LicenceParam licenceParam) {String licenceType = licenceParam.getLicenceType (); LicenceTypeEnum licenceTypeEnum = LicenceTypeEnum.parseOfNullable (licenceType); / / assertion, non-empty ResponseEnum.BAD_LICENCE_TYPE.assertNotNull (licenceTypeEnum) LambdaQueryWrapper wrapper = new LambdaQueryWrapper (); wrapper.eq (Licence::getLicenceType, licenceType); IPage page = this.page (new QueryPage (licenceParam), wrapper); return new QueryData (page, this::toSimpleLicenceDTO) } / * * add {@ link Licence} * @ param request request body * @ return * / @ Transactional (rollbackFor = Throwable.class) public LicenceAddRespData addLicence (LicenceAddRequest request) {Licence licence = new Licence (); licence.setOrganizationId (request.getOrganizationId ()); licence.setLicenceType (request.getLicenceType ()); licence.setProductName (request.getProductName ()) Licence.setLicenceMax (request.getLicenceMax ()); licence.setLicenceAllocated (request.getLicenceAllocated ()); licence.setComment (request.getComment ()); this.save (licence); return new LicenceAddRespData (licence.getLicenceId ()) } / * entity-> simple dto * @ param licence {@ link Licence} entity * @ return {@ link SimpleLicenceDTO} * / private SimpleLicenceDTO toSimpleLicenceDTO (Licence licence) {/ / omitted} / * entity-> dto * @ param licence {@ link Licence} entity * @ param org {@ link OrganizationDTO} * @ return {@ link LicenceDTO} * / private LicenceDTO toLicenceDTO (Licence licence OrganizationDTO org) {/ / omitted} / * check {@ link Licence} exists * @ param licence * / private void checkNotNull (Licence licence) {ResponseEnum.LICENCE_NOT_FOUND.assertNotNull (licence) }}
PS: the DAO framework used here is mybatis-plus. When started, the automatically inserted data is:
-- licence INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated) VALUES (1, 1, 'user','CustomerPro', 100 INSERT INTO licence 5); INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated) VALUES (2, 1,' user','suitability-plus', 200189) INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated) VALUES (3, 2, 'user','HR-PowerSuite', 100 user','HR-PowerSuite', 4); INSERT INTO licence (licence_id, organization_id, licence_type, product_name, licence_max, licence_allocated) VALUES (4, 2,' core-prod','WildCat Application Gateway', 16) -- organizations INSERT INTO organization (id, name, contact_name, contact_email, contact_phone) VALUES (1, 'customer-crm-co',' Mark Balster', 'mark.balster@custcrmco.com',' 823-555-1212'); INSERT INTO organization (id, name, contact_name, contact_email, contact_phone) VALUES (2, 'HR-PowerSuite',' Doug Drewry','doug.drewry@hr.com', '920-555-1212')
Start verification
Catch custom exceptions
① gets the licence details that do not exist: http://localhost:10000/licence/5. Successfully responded to the request: licenceId=1.
Check non-empty
Catch Licence not found exception
Licence not found
② gets the licence list based on the licence type that does not exist: http://localhost:10000/licence/list?licenceType=ddd. The optional licence type is: user, core-prod.
Check non-empty
Catch Bad licence type exception
Bad licence type
Catch exceptions before entering Controller
① accesses an interface that does not exist: http://localhost:10000/licence/list/ddd
Catch 404 exception
The ② HTTP method does not support: http://localhost:10000/licence.
PostMapping
Catch Request method not supported exception
Request method not supported
③ check exception 1: http://localhost:10000/licence/list?licenceType=
GetLicences
LicenceParam
Capture parameter binding check exception
Licence type cannot be empty
④ verifies the abnormal 2:post request, which is simulated using postman.
AddLicence
LicenceAddRequest
Request url is the result
Capture parameter binding check exception
Note: because the exception information of the parameter binding check exception is obtained in the same way as other exceptions, the exceptions of these two cases are separated from the exceptions before entering Controller.
The following is the logic for collecting exception information:
Collection of abnormal information
Catch unknown exception
Suppose we now casually add a field test to Licence without modifying the database table structure, and then visit: http://localhost:10000/licence/1.
Add test field
Catch database exception
Error querying database
Summary: as you can see, all the tested exceptions can be caught and returned in the form of code and message.
For each project / module, when defining business exceptions, you only need to define an enumeration class, then implement the interface BusinessExceptionAssert, and finally define the corresponding enumeration instance for each business exception, instead of defining many exception classes. It is also convenient to use, similar to assertions.
Expansion: in the production environment, if an unknown exception or ServletException is caught, because it is a long list of exception information, it is unprofessional to show it to the user directly, so we can do this: when it is detected that the current environment is a production environment, then directly return "network exception".
The production environment returned "network exception"
You can modify the current environment in the following ways:
Modify the current environment to a production environment
At this point, the study of "what is unified exception handling in Spring" is over. I hope to be able to solve your doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.