Article catalog
preface:
In the previous chapter, the whole framework has been introduced, and the subsequent micro service ecological map has also been slightly introduced. This chapter will start with the construction of the framework. Spring boot and spring cloud are not new to the jr of this article. Let's start with them.
Or attach the project structure chart first:
1. Parent directory
It is an empty maven project. Define the spring boot / clould and other dependent versions in the pom file: the spring boot version is: 2.1.3.RELEASE
Spring cloud: version is Greenwich.SR2 ; declared as import in the parent pom.
2.FW-BOOT
This package is quite simple. It puts the spring boot dependency management here. Why do you do this? Before, a large number of spring boot packages were integrated in the core package, which led to the bloated pom file and poor management. When you carry them out alone, the whole pom will become very clear. For all subsequent spring boot upgrades, new dependencies can only be maintained in this maven module. Many people will wonder if this is a little redundant. In my opinion, if we do some details like this Small adjustments can bring some benefits to the maintenance and expansion of the whole framework, which seems to be nothing, but actually permeates every Framework Developer (feel it). This module will not be exposed to the outside world. It will only be referenced in the core. ok, let's take a look at the reference of pom file: the spring boot dependency is introduced
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>fw-parent</artifactId> <groupId>com.mars.fw</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.mars.fw</groupId> <artifactId>FW-BOOT</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--default tomcat plug-in unit--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>compile</scope> </dependency> <!--default tomcat plug-in unit--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--AOP--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--AOP--> <!--spring Default use yml But sometimes the traditional xml or properties to configure--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <!--Health monitoring--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--Health monitoring--> <!--Autoload configuration--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <!--Autoload configuration--> </dependencies> </project>
3.FW-ClOUD
This is the same as FW-BOOT, which extracts the configuration dependency of spring cloud and references it in the core for the same purpose. Look at the reference of pom file: note that in this, I introduced spring cloud starter Alibaba nacos config and spring cloud starter Alibaba nacos discovery, because I used nacos in the service registry Instead of using Eureka, I have used both of them. I think nacos is good in terms of visual interface and configuration file management, and it's hard to practice. nacos installation deployment use will write a special chapter to introduce the use.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>fw-parent</artifactId> <groupId>com.mars.fw</groupId> <version>1.0.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.mars.fw</groupId> <artifactId>FW-CLOUD</artifactId> <version>1.0.0-SNAPSHOT</version> <packaging>jar</packaging> <name>MARS-FW-CLOUD-STARTER</name> <developers> <developer> <name>dengjinde</name> <email>[email protected]</email> </developer> </developers> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>$</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>2.1.1.RELEASE</version> </dependency> </dependencies> </project>
4.FW-CORE
This is the core. Next, I will explain the practice process in detail. There is nothing very complicated and difficult to understand.
From the above directory structure, I divided the core into web modules and other modules. First, let's talk about the web module: my definition is that all operations related to providing API s and web applications are put in this package; let's think about the basic functions (in fact, the encapsulation and enhancement of spring MVC) that a web application needs to have.
To support the spring web related functions, we need to configure the web related functions first. It is clear that we need to create a new class to implement the interface configuration.
In this process, we mainly deal with the following problems:
1. The cross domain problem of ajax in front-end browser
2. Static resource path setting
3. This method is used to process the return of unified parameters
package com.mars.fw; import com.mars.fw.web.reponse.handler.MarsRespValueHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.mvc.method.annotation.DeferredResultMethodReturnValueHandler; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; /** * @Author King * @Date 2020-04-20 */ @Configuration @EnableAspectJAutoProxy @EnableAutoConfiguration public class MarsWebMvcConfigure implements WebMvcConfigurer { /** * Instance request map processing adapter */ @Autowired private RequestMappingHandlerAdapter requestMappingHandlerAdapter; /** * Solve cross domain problems * <p> * * @param registry */ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowCredentials(true) .allowedMethods("GET", "POST", "DELETE", "PUT") .maxAge(3600); } private CorsConfiguration addcorsConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); List<String> list = new ArrayList<>(); list.add("*"); corsConfiguration.setAllowedOrigins(list); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", addcorsConfig()); return new CorsFilter(source); } /** * mvc Static resource path access configuration * For example: registry.addResourceHandler("/upload/**").addResourceLocations("classpath:/upload/"); * /upload/ Static resources under the path can be accessed * <p> * * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //All directories are accessible registry.addResourceHandler("/static/**"). addResourceLocations("file:/usr/share/nginx/images/"); registry.addResourceHandler("swagger-ui.html") .addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); } /** * @PostConstruct Nonstatic void() methods minimize the impact of annotation on complex logical methods on startup speed * <p>Tomcat6.x above * It runs when the server loads the Servlet and will only be executed once by the server * <p> * When the server loads the Servlet, it runs the execution instance request mapping processing adapter to intercept the customized parameters, and the unified processing returns the standard data structure */ @PostConstruct public void initResponseValue() { final List<HandlerMethodReturnValueHandler> originalHandlers = new ArrayList<>(requestMappingHandlerAdapter.getReturnValueHandlers()); RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null; for (int i = 0; i < originalHandlers.size(); i++) { final HandlerMethodReturnValueHandler valueHandler = originalHandlers.get(i); if (RequestResponseBodyMethodProcessor.class.isAssignableFrom(valueHandler.getClass())) { requestResponseBodyMethodProcessor = (RequestResponseBodyMethodProcessor) valueHandler; break; } } MarsRespValueHandler marsRespValueHandler = new MarsRespValueHandler(requestResponseBodyMethodProcessor); final int deferredPos = obtainValueHandlerPosition(originalHandlers, DeferredResultMethodReturnValueHandler.class); originalHandlers.add(deferredPos + 1, marsRespValueHandler); requestMappingHandlerAdapter.setReturnValueHandlers(originalHandlers); } private int obtainValueHandlerPosition(final List<HandlerMethodReturnValueHandler> originalHandlers, Class<?> handlerClass) { for (int i = 0; i < originalHandlers.size(); i++) { final HandlerMethodReturnValueHandler valueHandler = originalHandlers.get(i); if (handlerClass.isAssignableFrom(valueHandler.getClass())) { return i; } } return -1; } }2.core-web Implementation 4.1.2.1 . unified data return processing implementation
First of all, we hope that the data format returned by our API interface is unified. How to encapsulate the unified data format in spring MVC is not discussed in detail here. First, we define the uniform return code and uniform return data format in the system:
Define an interface of IRCode to customize the implementation of the business layer, and define a King class to implement the code and message contained in the framework;
package com.mars.fw.web.reponse; /** * @Author King * @create 2020/4/20 11:43 */ public interface IRCode { String message(); int code(); }
package com.mars.fw.web.reponse; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import java.io.Serializable; /** * @Author King * @create 2020/4/20 11:24 */ @JsonInclude(JsonInclude.Include.NON_EMPTY) @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) public class King<T> implements Serializable { private static final long serialVersionUID = -2466174531203439862L; public static final Integer SUCCESS_CODE = 1000; public static final String SUCCESS_MSG = "Operation successful"; private int code; private String msg; private T data; public King() { this(SUCCESS_CODE, SUCCESS_MSG, null); } public King(IRCode irCode) { this(irCode.code(), irCode.message(), null); } public King(IRCode irCode, T data) { this(irCode.code(), irCode.message(), data); } public King(int code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } public static <T> King<T> success(IRCode irCode, T data) { return new King(KingCode.SUCCESS, data); } public static <T> King<T> success(String message) { return new King(KingCode.SUCCESS, message); } public static <T> King<T> fail(IRCode irCode, T data) { return new King(KingCode.FAULT, data); } public static <T> King<T> fail(String message) { return new King(KingCode.FAULT, message); } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } }
In addition, we need to define a system enumeration code: KingCode
package com.mars.fw.web.reponse; /** * @Author King * @create 2020/4/20 11:52 */ public enum KingCode implements IRCode { /** * Status code definition */ FAULT(9999, "fail"), SUCCESS(10000, "success"), LOGIN_SUCCESS(10001, "Login successful"), LOGOUT_SUCCESS(10002, "Login failed"), PASS_WRONG(10002, "Password error"), USER_NOT_FIND(10003, "user does not exist"), CAPTCHA_TIMEOUT(10004, "Verification code failure"), CAPTCHA_ERROR(10005, "Verification code error"), LOGIN_LOCK(10006, "This account has been closed"), SMS_EXCEPTION(10007, "SMS login exception"), TOKEN_NOT_EMPTY(90000, "token Cannot be empty"), TOKEN_ERROR(90001, "token Verification failed"), TOKEN_EXCEPTION(90002, "token Verification exception"), URL_EXPIRED(90003, "Visited URL be overdue"), DEFAULT_EXCEPTION(99999, "System exception"); final int code; final String message; @Override public int code() { return this.code; } @Override public String message() { return this.message; } private KingCode(final int code, final String message) { this.code = code; this.message = message; } }
I found that I didn't need to paste the code here and just talk about the core implementation. I went to the source code to see these.
The core implementation is to customize a marsresvaluehandler, which implements the HandlerMethodReturnValueHandler in the spring web package. This handler is provided to us by spring to handle the result handler returned by the controller layer method. We can rewrite the methods to get the data format we need. I have detailed comments in the code. Handlereturnvalue is the key to our processing. We put the processed value back into the spring Web response link, that is this.requestResponseBodyMethodProcessor.handleReturnValue .
package com.mars.fw.web.reponse.handler; import com.mars.fw.web.reponse.King; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor; /** * @description: mvc The return value is uniformly processed into the Response entity structure standard and output to the front end * @author:dengjinde * @date:2020/4/20 */ public class MarsRespValueHandler implements HandlerMethodReturnValueHandler { private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor; /** * Constructor * * @param requestResponseBodyMethodProcessor */ public MarsRespValueHandler(RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor) { this.requestResponseBodyMethodProcessor = requestResponseBodyMethodProcessor; } /** * When the return value is true, the user-defined handler will be opened * * @param returnType * @return */ @Override public boolean supportsReturnType(MethodParameter returnType) { return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); } /** * Custom handler logic returns the standard format automatically when the return value is empty * The logic here can be expanded as needed * * @param returnValue * @param returnType * @param mavContainer * @param webRequest * @throws Exception */ @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { King response = null; if (!King.class.isAssignableFrom(returnType.getParameterType())) { response = new King(King.SUCCESS_CODE, "success", returnValue); } else { response = (King) returnValue; } this.requestResponseBodyMethodProcessor.handleReturnValue(response, returnType, mavContainer, webRequest); } }
After defining the handler, in the previous MarsWebMvcConfigure, weave in the defined handler at the specified location so that the user-defined data can be processed and returned. We are free to define the data format we want and do more. Implementation details can be seen in the source code.
4.1.2.2 Unified exception handlingWith a unified data format return, we often encounter some exceptions. It is definitely not advisable to return exceptions directly. So we need to deal with exceptions in a unified way. Here we only talk about the core implementation, and see the source code for details. The principle of implementation is realized by using the @ controlleradvise controller enhancer provided by spring web.
package com.mars.fw.web.exception.advice; import com.mars.fw.web.exception.KingException; import com.mars.fw.web.reponse.ExceptionCode; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.regex.Pattern; /** * Unified system exception handling * * @Author King * @create 2020/4/20 15:25 */ @Slf4j @ControllerAdvice public class KingExceptionHandlerAdvice { /** * Unified exception handling * * @param request * @param response * @param ex * @return */ @ExceptionHandler(Exception.class) @ResponseBody public Object controllerExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) { String message = ExceptionCode.DEFAULT_EXCEPTION.message(); if (KingException.class.isAssignableFrom(ex.getClass())) { KingException kingException = (KingException) ex; printExceptionLog(kingException, ex); return kingException.transferResponse(); } if (ex instanceof MissingServletRequestParameterException) { MissingServletRequestParameterException exception = (MissingServletRequestParameterException) ex; message = "Required " + exception.getParameterType() + "Type parameter '" + exception.getParameterName() + "' non-existent"; } else if (ex instanceof EmptyResultDataAccessException) { EmptyResultDataAccessException exception = (EmptyResultDataAccessException) ex; String pattern = "No class.*with.*exists!.*"; boolean isMatch = Pattern.matches(pattern, ex.getMessage()); if (isMatch) { message = "Data does not exist, please confirm"; } } else { if (null != ex.getMessage()) { message = "System exception, contact administrator"; } } KingException kingException = new KingException(ExceptionCode.DEFAULT_EXCEPTION, message); printExceptionLog(kingException, ex); if (log.isDebugEnabled()) { log.debug(ex.getMessage(), ex.getStackTrace()); } return kingException.transferResponse(); } /** * Error log * * @param kingException */ private void printExceptionLog(KingException kingException, Exception ex) { if (kingException.getCause() == null) { log.error("######ErrorCode: {},message: {}#######", kingException.getCode().code(), kingException.getMessage()); } else { log.error("######ErrorCode: {},message: {}#######", kingException.getCode().code(), kingException.getMessage(), kingException); } log.error("######ErrorCode: {},message: {}#######", kingException.getCode().code(), kingException.getMessage(), ex); } }
Based on this, we handle the specified exception according to the exception handler according to the exception customized by the team.
4.1.2.3 online document implementationAPI documents are very important. Unified API documents are more important. I think it is more important not to maintain a set of documents by developers. So online documents are very urgent. Here is the updated version of swagger 2: knife4j spring UI
First, the package is introduced into pom:
<!--Third party packages on which other core functions of the framework depend-swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-ui</artifactId> </dependency> <!--Third party packages on which other core functions of the framework depend-swagger -->
package com.mars.fw.web.doc; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /** * @author King * @description swagger Related configuration classes * @date 2020/4/20 */ @EnableSwagger2 @Configuration public class SwaggerConfig { @Bean public Docket createRestApi() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(true) .select() .apis(RequestHandlerSelectors.basePackage("com.mars")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("King custom DOC file") .description("king-swagger-api") .termsOfServiceUrl("http://localhost:9098") .version("v0.1") .build(); } }4.1.2.4 context session
In the microservice, it is almost stateless, different from the usual session; more importantly, we use token to authenticate the user's information, so we need to do some processing for the global reading of the interface's user information. There are many ways to achieve this. I use ThreadLocal to save the global information. After the interface is authorized, global information such as users will be saved to ThreadLocal. ThreadLocal, as we know, is a copy of a thread, which means that each thread holds it, is isolated, and can be accessed anywhere in the thread's life cycle. Based on these, we can meet our needs. In fact, spring transactions also take advantage of the characteristics of ThreaLocal. Of course, some people may consider the memory leak problem of ThreaLocal, but this problem can be avoided as long as we pay attention to the implementation. This is not described in detail here, and will be explained in a separate chapter later. ok, let's look at the implementation:
First, define a global Entry as the carrier of data storage, which can be defined in your own team as needed; then define a context KingContext, As the management class of data; next, define a KingContextProvider interface, in which you can define the access method of the Entry, which is implemented by the business side according to the needs. Here, you want to restrict the implementation of GlobalEntry access, because GlobalEntry is relatively sensitive or can affect the global data.
package com.mars.fw.web.context; import com.mars.fw.web.exception.KingException; import org.apache.commons.lang.StringUtils; /** * Encapsulating the session context takes advantage of the ThreadLocal feature * This is the most common application scenario used to manage global variables in the interface life cycle. It is the storage of user information * <p> * The encapsulation here wants to simplify the complexity of the caller as much as possible * * @Author King * @create 2020/4/21 17:27 */ public class KingContext { private static ThreadLocal<GlobalEntry> CONTEXT = new ThreadLocal<GlobalEntry>(); public static void setContext(GlobalEntry context) { CONTEXT.set(context); } public static void removeContext() { CONTEXT.remove(); } /** * Get user ID * * @return */ public static Long getUserId() { GlobalEntry entry = CONTEXT.get(); Long userId = null == entry ? null : entry.getUserId(); if (null == userId) { throw new KingException("Please log in first"); } return userId; } /** * Get user Code * * @return */ public static String getUserCode() { GlobalEntry entry = CONTEXT.get(); String userCode = null == entry ? null : entry.getUserCode(); if (StringUtils.isBlank(userCode)) { throw new KingException("Please log in first"); } return userCode; } /** * Get token * * @return */ public static String getToken() { GlobalEntry entry = CONTEXT.get(); String token = null == entry ? null : entry.getToken(); if (org.springframework.util.StringUtils.isEmpty(token)) { throw new KingException("Please log in first"); } return token; } /** * Get global stored information * * @return */ public static Object getObject() { GlobalEntry entry = CONTEXT.get(); Object object = null == entry ? null : entry.getObject(); if (org.springframework.util.StringUtils.isEmpty(object)) { throw new KingException("Session context not set"); } return object; } }
ok, this one is a bit long. Let's move on to the next chapter.
Source address: Code cloud source address