SpringBoot統一功能處理

在現代Web應用開發中,Spring Boot已經成爲一個非常流行的框架,它提供了快速搭建生產級別應用程序的捷徑。其中,Spring MVC框架作爲Spring Boot的一部分,爲處理HTTP請求和響應提供了一種模型-視圖-控制器(MVC)模式實現方式。然而,隨着業務邏輯的複雜化,處理不同類型的請求可能會變得繁瑣且重複性高。爲了解決這個問題,我們可以利用Spring Boot來實現統一的異常處理和結果映射,從而提高代碼的可維護性和可讀性。

1. 創建全局異常處理器 (Global Exception Handler)

首先,我們需要定義一個基類來處理所有未被其他控制器方法捕獲的異常。這個基類應該繼承自`ResponseEntityExceptionHandler`並且包含特定異常的處理邏輯。例如:

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice // 將本類的作用域限制到當前應用程序上下文中的控制器上
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
// 自定義異常處理邏輯
@ExceptionHandler(value = Exception.class) // 這裏可以替換爲具體的異常類型
public final ResponseEntity<Object> handleAllExceptions(Exception ex, WebRequest request) {
return new ResponseEntity<>(new ErrorInfo(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR),
HttpStatus.INTERNAL_SERVER_ERROR);
}
}

在這個例子中,我們使用了一個名爲 `ErrorInfo` 的對象來封裝錯誤信息,它可以用來返回給客戶端以進行友好地錯誤展示。

2. 配置RestTemplate或FeignClient

如果你的應用程序使用了`RestTemplate`或者`FeignClient`來進行外部服務的調用,你可能需要在這些組件上也設置統一的異常處理策略。這可以通過擴展`RestTemplateCustomizer`接口並添加自定義配置來完成:

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringMvcContract;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import feign.codec.Encoder;
import feign.jackson.JacksonDecoder;
import feign.okhttp.OkHttpClient;
import feign.slf4j.Slf4JLogger;
import okhttp3.ConnectionPool;
import okhttp3.Dispatcher;
import okhttp3.OkHttpClient.Builder;
import retrofit2.Retrofit;

@Component
public class RestConfiguration {
@Bean
public OkHttpClient customOkHttpClient(ConnectionPool connectionPool, Dispatcher dispatcher) {
Builder builder = new OkHttpClient.Builder();
builder.connectionPool(connectionPool);
builder.dispatcher(dispatcher);
return builder.build();
}

@Bean
public Encoder encoder() {
return new Retrofit.GsonConverterFactory().encoder();
}

@Bean
public Decoder decoder() {
return new JacksonDecoder();
}

@Bean
public Logger logger() {
return new Slf4JLogger();
}

@Bean
public Contract contract() {
return new SpringMvcContract();
}

@Bean
public Client client(OkHttpClient okHttpClient, ConnectionPool connectionPool,
Dispatcher dispatcher, Encoder encoder, Decoder decoder, Logger logger, Contract contract) {
return Feign.builder()
.logger(logger)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.client(okHttpClient)
.connectionPool(connectionPool)
.dispatcher(dispatcher)
.retryer(new DefaultRetryer())
.errorDecoder(new CustomErrorDecoder())
.build();
}

private static class CustomErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
throw new UnsupportedOperationException("Not implemented yet!");
}
}

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.defaultErrorHandler(false).build();
}
}

在上述示例中,我們通過`RestTemplateBuilder`定製了`RestTemplate`實例,以便它可以使用我們自己實現的異常處理器。如果你使用的是`FeignClient`,那麼你可以看到我們也對`Feign`進行了類似的配置。

3. 結果映射器 (Result Mapper)

除了異常處理之外,對於不同的操作成功的結果,你也可能想要有一個統一的格式來返回給客戶端。爲此,你可以創建一個`MessageBodyWriter`來實現這種轉換。例如:

import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Objects;

@Provider
@Produces({ MediaType.APPLICATION_JSON })
public class JsonMessageBodyWriter implements MessageBodyWriter<Object> {

@Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
if (type == Object.class || Objects.equals(mediaType, MediaType.WILDCARD)) {
return true;
} else if (genericType instanceof ParameterizedType && ((ParameterizedType) genericType).getRawType() == List.class) {
return true;
}
return false;
}

@Override
public long getSize(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1L;
}

@Override
public void writeTo(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
try {
JsonUtil.writeValue(entityStream, t);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}

這個`MessageBodyWriter`將確保任何序列化爲JSON的對象都能正確寫入響應流。此外,我們還應該註冊這個`MessageBodyWriter`以便`Jackson`能夠找到它:

import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.info.License;
import io.swagger.v3.oas.annotations.servers.Server;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@OpenAPIDefinition(
info = @Info(title = "Your API Title Here", version = "1.0", license = @License(name = "Apache License Version 2.0"), contact = @Contact(email = "yourEmailHere")),
servers = {@Server(url = "https://example.com/api")}
)
@EnableSwagger2
@EnableOpenApi
public class ApiConfig extends ResourceConfig {
public ApiConfig() {
packages(true, "your.package.here");

add(new JacksonJsonProvider());
add(new JsonMessageBodyWriter());
}
}

現在,無論是在控制器的正常流程還是在全局異常處理過程中,所有的JSON輸出都將遵循上述的約定格式。這種方法簡化了編碼過程,提高了應用的健壯性,同時爲用戶提供了一致的體驗。

为您推荐