[Spring]SpringBoot Unified Function Processing

Time:2024-2-27


preamble

In the daily use of the Spring framework for development, for some boards, you may need to implement an identical function, this function can be to verify your login information, can also be other, but due to the various boards to achieve this function of the code logic are the same, if a board a board to add, the development of the efficiency will be very low, so Spring has also thought of so Spring also thought of this, for us programmers to provide SpringBoot unified function processing method implementation, we can use directly. This article I will take you to learn together SpringBoot unified function processing.

1. Interceptor

Normal logic to determine whether the user is logged in through the session to determine, for some websites, many of the features are required to log in before the user can be used, if this time through the session to determine that the user is not logged in the state of words, our servers will force the user to log in, through the code to show that this is the case:

//Verify that the user is logged in
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("userInfo") == null) {
    return "You are not logged in, please log in and try the function again";
}

We want to determine which function before the user’s login information, you need to add the above code in the module of the function, if you need to add less functionality is good, if a lot, then it will take a lot of time, then some people say: can I encapsulate the code into a function, and then which module needs to use only need to call these functions on it? Can be, but the use of functions to encapsulate the code or the need to call the function on the basis of the original code, and it does not look so elegant. So is there a way not to change the original code, but also make us write the code is very elegant it SpringBoot provides us with a function – theInterceptor.

1.1 What is an interceptor

In Spring Boot, an interceptor is a component used to perform a specific action before or after processing a request. Interceptors are usually used to implement some generic functionality such as permission validation, logging, etc. Interceptors can intercept requests passing through a Controller and perform specific actions before and after the request is processed.

The idea of interceptors fits right in with the idea of authentication validation before we execute other functionality, and not only can interceptors work before a method is executed, but after the method is executed. Our interceptors can also play a role.

[Spring]SpringBoot Unified Function Processing

1.2 Use of interceptors

Interceptors are used in two steps:

  1. Defining Interceptors
  2. Registering a Configuration Interceptor

1.2.1 Customizing Interceptors

Our custom interceptor needs to implement theHandlerInterceptor interface and override the methods in this interface.

package com.example.springbootbook2.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("Executing before target method execution...") ;
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute("userInfo") == null) {
        	response.setStatus(401);
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("Executed after target method execution...") ;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("Executed after view rendering is complete, and finally executed...") ;
    }
}
  • The preHandle() method is executed before the execution of the target method; return true to continue the execution of the following code; return false to interrupt the subsequent operation.
  • The postHandle() method is executed after the execution of the target method.
  • The afterCompletion() method is executed after the view has been rendered, it is also executed after the postHandle() method, and is less used nowadays because the front-end and back-end are separated and the back-end basically doesn’t have access to the rendering of the view.

1.2.2 Registering a Configuration Interceptor

Registering a configuration interceptor requires the implementation of theWebMvcConfiguer interface and implements theaddInterceptors Methods.

package com.example.springbootbook2.config;

import com.example.springbootbook2.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") //specify the path configuration interceptor
                .excludePathPatterns("/user/login"); //specify the path without configuring the interceptor
    }
}

Before adding the interceptor for identity verification, we can directly access the specified function page via the corresponding url.

[Spring]SpringBoot Unified Function Processing

And after we add the interceptor, then we’ll see if we can access certain features directly.

[Spring]SpringBoot Unified Function Processing
[Spring]SpringBoot Unified Function Processing

After we start the code after adding the interceptor, click refresh to find that we can not directly access certain functions, and then with the front-end we can realize the forced login function.

1.3 Interceptor Explained

With the interception program in place, we’ll move on to the details of how to use the interceptor. There are two main parts to the details of using the interceptor:

  1. Interceptor’s interceptor path configuration
  2. Interceptor Implementation Principles

1.3.1 Intercepting paths

The interceptor path refers to which requests this interceptor, which we defined, takes effect on, and we configure the interceptor when we register it via theaddPathPatterns() method to specify which HTTP requests to block, or via theexcludePathPatterns() method specifies which requests to not intercept, and above our/** Indicates that all HTTP requests are blocked. In addition to blocking all requests, there are some other blocking settings:

Interception pathhidden meaninggive an example
/*Necessary PathsMatches /user, /book, /login, but not /user/login.
/**Arbitrary level pathMatches /user, /user/login, /user/reg.
/book/*Numerical paths under /bookCan match /book/addBook, can’t match /book/addBook/1, /book
/book/**Any level path under /bookMatches /book, /book/addBook, /book/addBook/2, but not /user/login.

These blocking rules block the use of URLs for this purpose, including static files (such as photographs, JS, and CSS files).

Once we know how to configure the intercept path, we can solve the problem of authentication on the web. Because/** will intercept all requests, including front-end page requests, so we need to exclude front-end page requests from being intercepted.

This will happen if we don’t talk about front-end page requests outside:

[Spring]SpringBoot Unified Function Processing
Then we can implement the full identity verification forced login function if we don’t add an interceptor to the front-end page request.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    private List<String> excludePath = Arrays.asList("/user/login",
            "/css/**",
            "/js/**",
            "/pic/**",
            "/**/*.html"); // The /** in /**/*html denotes all files ending in html in all paths * denotes wildcards
            
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(excludePath);
    }
}

When we are not logged in and then access other features then it forces us to jump to the login page:

[Spring]SpringBoot Unified Function Processing

1.3.2 Interceptor Execution Flow

The normal sequence of calls looks like this:

[Spring]SpringBoot Unified Function Processing
Once an interceptor is in place, the corresponding business processes are performed before the Controller is invoked, and the process is executed as shown in the following figure:

[Spring]SpringBoot Unified Function Processing

  1. When we add an interceptor, the request will be intercepted by the interceptor before executing the Controller method, executing thepreHandle() method, which returns a boolean value, and if it returns true, then the operation will continue; if it returns false, then the operation will not be performed.
  2. After the methods in the Controller have finished executing, they will continue executing thepostHandle() methodology as well asafterHandle() method, which, when executed, returns the response data to the browser.

You can see the execution flow of these three methods in the doDispatch method of the DispatcherServlet class in the source code:

[Spring]SpringBoot Unified Function Processing

1.3.3 Adapter model

The adapter pattern is also used during interceptor execution:

[Spring]SpringBoot Unified Function Processing
Adapter Pattern (Adapter Pattern) is a common design pattern in computer programming, mainly used to solve the interface matching problem between two incompatible classes. By converting the interface of one class to another interface expected by the client, two classes that could not work together because of interface mismatch can now work together.

The advantage of the adapter pattern is that it enables classes that would otherwise not work together due to incompatible interfaces to work together, improving the flexibility and scalability of the system. It can also reduce code duplication because multiple sources can share the same adapter.

HandlerAdapter is mainly used to support different types of handlers (e.g., Controller, HttpRequestHandler, or HttpRequestHandler).
The SpringMVC service provider is also able to adapt to a unified request handling process (e.g., Servlets). In this way, SpringMVC can be adapted to a unified request processing flow through a unified interface.
The following table describes how to handle requests from the various processors in the system.

[Spring]SpringBoot Unified Function Processing
Originally, target and adaptee were two things that could not be properly interfaced, but through the adapter, they can be interfaced, similar to the following situation:

[Spring]SpringBoot Unified Function Processing
Adaptor mode kok:

  • Target: The target interface (can be an abstract class or interface) that the customer wants to use directly.
  • Adaptee: Adaptee, but not compatible with Target
  • Adapter: The adapter class, the centerpiece of this pattern. This pattern is centered on converting an adapter to a target object by inheriting or introducing an adapter object.
  • client: The object that needs to use the adapter.

Slf4j, which we learned about earlier, uses the adapter pattern. slf4j provides a series of api’s for printing the date, and log4j or logback is used to print the date, so we, as the users, only need to call the api’s of slf4j.

/**
* slf4j interface
*/
interface Slf4jApi{
	void log(String message);
}

/**
* log4j interface
*/
class Log4j{
	void log4jLog(String message){
		System.out.println("Log4j prints :"+message);
	}
}

/**
* :: slf4j and log4j adapters
*/
class Slf4jLog4JAdapter implements Slf4jApi{
	private Log4j log4j;
	public Slf4jLog4JAdapter(Log4j log4j) {
		this.log4j = log4j;
	}
	@Override
	public void log(String message) {
		log4j.log4jLog(message);
	}
}

/**
* :: Guest mobilization
*/
public class Slf4jDemo {
	public static void main(String[] args) {
		Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());
		slf4jApi.log("Printing the date using slf4j");;
	}
}

As you can see, we don’t need to change the api of log4j, but only need to convert it through the adapter, so that we can replace the date framework and ensure the smooth operation of the system.

In general, the adapter mode can be considered a “compensation mode” to remedy design flaws. The use of this mode is a “last resort” and would not have been necessary if it had been coordinated at the beginning of the design process to avoid the problem of incompatible connectors.

Therefore, the adapter pattern is mainly used when you want to modify the code you are working on, and you want to realize new functions using the original code. For example, for version upgrades.

2. Harmonization of data return formats

If we have completed the development of a project, but at this time, we suddenly feel that the return value of the back-end of the return value of the various functions to return information is not comprehensive enough, we need to add some return information, then this means that the return value of all the methods need to be made to modify, which is also a lot of work. At this point SpringBot provides us with a solution – unified data return format.

SpringBoot Unified Data Return Format inserts some code logic into each method before the return value is returned to achieve the function of changing the return value.

Harmonized data return format use@ControllerAdvice respond in singingResponseBodyAdvice The way it is realized.ControllerAdvice Represents the controller notification class.

We start by defining a uniform return type:

public enum ResultCode {
    SUCCESS(0),
    FAIL(-1),
    UNLOGIN(-2);

    //0-Success -1 Failure -2 Not logged in
    private int code;

    ResultCode(int code) {
        this.code = code;
    }


    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}
@Data
public class Result<T> {
    /**
     * :: Operational status codes
     */
    private ResultCode code;  //0- Succeeded -1 Failed -2 No login
    /**
     * :: Error messages
     */
    private String errMsg;
    /**
     * :: Data
     */
    private T data;

    public static <T> Result<T> success(T data){
        Result result = new Result();
        result.setCode(ResultCode.SUCCESS);
        result.setErrMsg("");
        result.setData(data);
        return result;
    }
    public static <T> Result<T> fail(String errMsg){
        Result result = new Result();
        result.setCode(ResultCode.FAIL);
        result.setErrMsg(errMsg);
        result.setData(null);
        return result;
    }
    public static <T> Result<T> fail(String errMsg,Object data){
        Result result = new Result();
        result.setCode(ResultCode.FAIL);
        result.setErrMsg(errMsg);
        result.setData(data);
        return result;
    }
    public static <T> Result<T> unlogin(){
        Result result = new Result();
        result.setCode(ResultCode.UNLOGIN);
        result.setErrMsg("The user is not logged in");
        result.setData(null);
        return result;
    }

}
package com.example.springbootbook2.config;

import com.example.springbootbook2.model.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return Result.success(body);
    }
}

predecessorResponseBodyAdvice interface, you need to implement thesupports methodology andbeforeBodyWrite method, the supports method only needs to change the return value to true, indicating whether to execute the beforeBodyWrite method or not, returning true means execute, false means not execute, the body parameter in the beforeBodyWrite method is the return value of our original method.

Once we closed the interceptor and visited the book list page, we realized that our return type had changed:

[Spring]SpringBoot Unified Function Processing
However, there is a problem with this Uniform Data Return Format, as an error will occur if our return value is of type String:

[Spring]SpringBoot Unified Function Processing
[Spring]SpringBoot Unified Function Processing
SpringMVC registers a number of native HttpMessageConverters by default (in order of precedence, these are
ByteArrayHttpMessageConverter ,StringHttpMessageConverter , SourceHttpMessageConverter ,AllEncompassingFormHttpMessageConverter )
[Spring]SpringBoot Unified Function Processing
where the AllEncompassingFormHttpMessageConverter adds the corresponding
HttpMessageConverter

[Spring]SpringBoot Unified Function Processing

After introducing the Jackson package in the dependency, the container automatically registers the MappingJackson2HttpMessageConverter to the
Spring selects the appropriate HttpMessageConverter from the messageConverters chain based on the type of data returned. when the returned data is not a string, the MappingJackson2HttpMessageConverter is used to write the returned object. When the returned data is non-string, use the MappingJackson2HttpMessageConverter to write the returned object. When the returned data is a string, the StringHttpMessageConverter is traversed first, and it is considered to be the first HttpMessageConverter in the MappingJackson2HttpMessageConverter chain.
StringHttpMessageConverter can be used.

[Spring]SpringBoot Unified Function Processing
[Spring]SpringBoot Unified Function Processing
However, instead of subclass StringHttpMessageConverter, addDefaultHeaders method defines the receiving parameter as String.
When t is of type Result, the type mismatch “Result cannot be cast to java.lang.String” exception occurs.

How to solve the problem when the return type is String? If the return result is of type String, use the built-in Jackson provided by SpringBoot to serialize the information.

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof String) {
            return objectMapper.writeValueAsString(Result.success(body));
        }else if (body instanceof Result) {
            return body;
        }else {
            return Result.success(body);
        }
    }
}
  • The main purpose of @SneakyThrows is to solve Java’s exception handling problem. When we throw an exception in our code, if the exception is wrapped in a method that doesn’t have the throws keyword to declare that it will throw the exception, the compiler will report an error. By using @SneakyThrows, you can tell the compiler, “I realize that this method may throw an exception, but I promise to handle it in a catch block.” That way the compiler won’t report an error.

With this processing, no error occurs when the returned data type is String.

[Spring]SpringBoot Unified Function Processing
Advantages of harmonizing data return formats:

  1. To enable front-end programmers to better receive and parse data returned by the back-end data interface
  2. Reduce the cost of communication between front-end programmers and back-end programmers, and just implement it in a certain format, since all interfaces return this way
  3. Facilitate the maintenance and revision of project data.
  4. It facilitates standardization of the back-end technology department, so that there will be no weird and strange returns.

3. Harmonization of exception handling

When exceptions occur in our program, we need to resolve them, and because we did the processing of the unified data return format earlier, the exceptions here can also be handled in a unified manner.

Unified exception handling is implemented using @ControllerAdvice + @ExceptionHandler, where @ControllerAdvice represents the controller notification class and @ExceptionHandler is the exception handler, and the combination of the two means that a notification is executed when an exception occurs, i.e., a method is performed. event.

package com.example.springbootbook2.config;

import com.example.springbootbook2.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
@Slf4j
@ResponseBody // Because none of the returned data is of view type, this annotation is added to prevent problems
public class ErrorHandler {
    @ExceptionHandler
    public Result<String> exception(Exception e) {
        log.error("Exception occurred: e{}",e);;
        return Result.fail(" internal exception ");
    }

    @ExceptionHandler
    public Result<String> exception(NullPointerException e) {
        log.error("Exception occurred: e{}",e);;
        return Result.fail("NullPointerException, please contact administrator");;
    }

    @ExceptionHandler
    public Result<String> exception(ArithmeticException e) {
        log.error("Exception occurred: e{}",e);;
        return Result.fail("ArithmeticException exception, please contact an administrator");
    }
}
@Controller
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/t1")
    public Integer test1() {
        return 10/0;
    }
}

[Spring]SpringBoot Unified Function Processing

The order of this Exception and ArithmeticException can be disregarded, this will not report an Exception exception just because the Exception was caught earlier, but will be caught based on the closest exception that occurs to itself.

Recommended Today

Installation and Configuration of Nextcloud on Ubuntu Server – Build Nextcloud Private Cloud Disk with Public Remote Access

Article Catalog summaries1. Environment setup2. Testing of local area network access3. Intranet penetration3.1 ubuntu local installation of cpolar3.2 Creating tunnels3.3 Testing public network access 4 Configure a fixed http public address4.1 Reservation of a second-level subdomain4.1 Configuring fixed second-level subdomains4.3 Testing access to fixed second-level subdomains on the public network summaries Nextcloud, which is an […]