springbootブロックの3つの方式と非同期実行の思考を実現します。
springbootブロック
実際のプロジェクトでは、要求パラメータ、応答結果、方法消耗、統一的な権限検証などを出力する必要があります。
ここではまずHTTP要求の3つのよくあるブロッキングの実現を紹介し、その違いを比較します。
(1)Asppectによるブロック
(2)Handler Intercepterによるスクリーンセーバー
(3)ReponseBody Adviceによるスクリーンショット
おすすめの読書:
統一ログフレーム:https://github.com/houbb/auto-log
springboot入門事例
皆さんの学習のために、まず基本的なspringbootの例から話します。
まぜん導入
必要なjarバッグを導入します。
最も簡単なスタートクラスを実現します。
簡単なcontrollerを最初に実現します。
Asppectに基づく
個人的にとても好きな方式です。
主な用途:
(1)ログの出参/入参
(2)一括設定TraceId
(3)メソッドの呼び出しにかかる時間の統計
Handler Intercepterによる
欠点は主にController層に用いられる。
ResonseBody Adviceに基づく
このインターフェースにはbeforeBodyWriteの方法があります。パラメータbodyはレスポンスオブジェクトのレスポンス体です。この方法で応答体に対していくつかの統一的な操作をすることができます。
暗号化、署名などです。
アプリケーションを開始します。ページアクセス:
http://localhost:18080/index
ページ応答:
{respCode}:「00」、「respDesc」:「成功」、「result」:「ok」
バックエンドログ:
c.g.h.s.l.a.LogHandler Interceptr : LogHandler Interceptor啢preHandle要求住所:/index
c.g.h.s.l.a.aspect.AsppectLog Interceptor : IndexController.index()パラメータ:[]
IndexCotroller莾index:AsyncResp{respCode='00',repDesc='成功',result='ok'}
c.g.h.s.l.a.aspect.AsppectLog Interceptor : IndexCotroller.index()結果:AsyncResp{respCode='00',repDesc='成功',result='ok')
c.g.h.s.l.a.aspect.MyResponseBody Advice : MyResponseBody Advice啱beforeBodyWrite要求住所:/index
c.g.h.s.l.a.aspect.MyResponseBody Advice : MyResponseBody Advice応答結果:AyncResp{respCode='00',repDesc='成功',respDesc='を返します。
c.g.h.s.l.a.LogHandler Interceptr : LogHandler Interceptor啣postHandle呼び出し
ここで実行する先着順も比較的明確であり、ここではもはや説明しない。
非同期実行
もちろん、これらの内容だけなら、この文章の重点ではない。
次に、非同期を導入したらどうなるかを一緒に見てみましょう。
非同期スレッドを定義
springbootでは、非同期スレッド池を定義するのは、非常に簡単です。
(1)メインスレッドは戻りのためにAyncResopを作成します。
(2)スレッド池非同期は、特定のサブメソッドを実行し、対応する値を設定します。
思考
次に、皆さんに質問します。
http://localhost:18080/asyncを要求したら、
(1)ページで得られた戻り値は何ですか?
(2)Asppectログ出力の戻り値は?
(3)ResonseBody Adviceログ出力の戻り値は何ですか?
ここでちょっと止まって、答えをメモしてください。
テスト
私たちのページ要求http://localhost:18080/async。
ページの応答は以下の通りです。
{respCode}:「00」、「respDesc」:「成功」、「result」:「ok」
バックエンドのログ:
c.g.h.s.l.a.LogHandler Interceptr : LogHandler Interceptor铅preHandle要求住所:/async
c.g.h.s.l.a.aspect.AsppectLog Interceptor : MyAsyncController.hello(.)パラメータ:[org.apphe.cantalina.com nnector.RequestFacade@7e931750」
Controller〓〓〓〓の結果:Async Resp{respCode='null',repDesc='null',result='null'
c.g.h.s.l.a.aspect.AsppectLog Interceptor : MyAsyncController.hello(.)結果:AsyncResp{respCode='null',repDesc='null',reult='null'。
c.g.h.s.l.a.aspect.MyResponseBody Advice : MyResponseBody Advice啱beforeBodyWrite要求住所:/async
c.g.h.s.l.a.aspect.MyResponseBody Advice : MyResponseBody Advice応答結果:AyncResp{respCode='00',repDesc='成功',respDesc='を返します。
c.g.h.s.l.a.LogHandler Interceptr : LogHandler Interceptor啣postHandle呼び出し
比べてみると、上記の問題の答えが分かります。
(1)ページで得られた戻り値は何ですか?
{respCode}:「00」、「respDesc」:「成功」、「result」:「ok」
非同期実行完了の結果が得られます。
(2)Asppectログ出力の戻り値は?
Arync Resp{respCode='null',repDesc='null',reult='null'
非同期の結果を取得できませんでした。
(3)ResonseBody Adviceログ出力の戻り値は何ですか?
Async Resop{respCode='00',repDesc='成功',reult='ok''
非同期実行完了の結果が得られます。
反省する
springのページに対する応答は、私たちが考えているのとは少し違っているかもしれません。直接に同期結果を得るのではないことが分かります。
ここまで書いて、mvcに対する自分の理解はずっと表面だけに留まっています。
Asppectの形式は多くのフレームで使われていますが、ここでは非同期の実行結果が得られないことが分かり、問題があります。
以上で、springbootでブロックを実現するための3つの方法と非同期的な思考についての記事を紹介します。もっと関連するspringbootのスクリーンショットの内容は以前の文章を検索したり、以下の関連記事を見たりしてください。これからもよろしくお願いします。
実際のプロジェクトでは、要求パラメータ、応答結果、方法消耗、統一的な権限検証などを出力する必要があります。
ここではまずHTTP要求の3つのよくあるブロッキングの実現を紹介し、その違いを比較します。
(1)Asppectによるブロック
(2)Handler Intercepterによるスクリーンセーバー
(3)ReponseBody Adviceによるスクリーンショット
おすすめの読書:
統一ログフレーム:https://github.com/houbb/auto-log
springboot入門事例
皆さんの学習のために、まず基本的なspringbootの例から話します。
まぜん導入
必要なjarバッグを導入します。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.10</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.10</version>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
クラスを開始最も簡単なスタートクラスを実現します。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
定義コントローラ簡単なcontrollerを最初に実現します。
@RestController
public class IndexController {
@RequestMapping("/index")
public AsyncResp index() {
AsyncResp asyncResp = new AsyncResp();
asyncResp.setResult("ok");
asyncResp.setRespCode("00");
asyncResp.setRespDesc(" ");
System.out.println("IndexController#index:" + asyncResp);
return asyncResp;
}
}
その中のAsync Resopの定義は以下の通りです。
public class AsyncResp {
private String respCode;
private String respDesc;
private String result;
// getter & setter & toString()
}
スクリーンショットの定義Asppectに基づく
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
*
* @author binbin.hou
* @since 1.0.0
*/
@Aspect
@Component
@EnableAspectJAutoProxy
public class AspectLogInterceptor {
/**
*
* @since 1.0.0
*/
private static final Logger LOG = LoggerFactory.getLogger(AspectLogInterceptor.class);
/**
* controller public
*/
@Pointcut("execution(public * com.github.houbb.springboot.learn.aspect.controller..*(..))")
public void pointCut() {
//
}
/**
*
*
* @param point point
* @return result
* @throws Throwable if any
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
try {
//1. MDC
//
String signatureShortStr = point.getSignature().toShortString();
//2.
Object[] args = point.getArgs();
LOG.info("{} : {}", signatureShortStr, Arrays.toString(args));
//3.
Object result = point.proceed();
LOG.info("{} : {}", signatureShortStr, result);
return result;
} finally {
// mdc
}
}
}
この実現の利点は,一般的で,注釈と組み合わせてより柔軟で強力な機能を実現できることである。個人的にとても好きな方式です。
主な用途:
(1)ログの出参/入参
(2)一括設定TraceId
(3)メソッドの呼び出しにかかる時間の統計
Handler Intercepterによる
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author binbin.hou
* @since 1.0.0
*/
@Component
public class LogHandlerInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(LogHandlerInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 、
logger.info("LogHandlerInterceptor#preHandle :{}", request.getRequestURI());
if (request.getDispatcherType().equals(DispatcherType.ASYNC)) {
return true;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("LogHandlerInterceptor#postHandle ");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
その後、対応するurlとブロックの関係を指定する必要があります。
import com.github.houbb.springboot.learn.aspect.aspect.LogHandlerInterceptor;
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.WebMvcConfigurerAdapter;
/**
* spring mvc
* @since 1.0.0
*/
@Configuration
public class SpringMvcConfig extends WebMvcConfigurerAdapter {
@Autowired
private LogHandlerInterceptor logHandlerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logHandlerInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/version");
super.addInterceptors(registry);
}
}
この方式の利点は、urlによって柔軟に異なるブロックを指定できることです。欠点は主にController層に用いられる。
ResonseBody Adviceに基づく
このインターフェースにはbeforeBodyWriteの方法があります。パラメータbodyはレスポンスオブジェクトのレスポンス体です。この方法で応答体に対していくつかの統一的な操作をすることができます。
暗号化、署名などです。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* @author binbin.hou
* @since 1.0.0
*/
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Object> {
/**
*
* @since 1.0.0
*/
private static final Logger LOG = LoggerFactory.getLogger(MyResponseBodyAdvice.class);
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
// false, beforeBodyWrite
return true;
}
@Override
public Object beforeBodyWrite(Object resp, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
String uri = serverHttpRequest.getURI().getPath();
LOG.info("MyResponseBodyAdvice#beforeBodyWrite :{}", uri);
ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) serverHttpRequest;
HttpServletRequest servletRequest = servletServerHttpRequest.getServletRequest();
//
//
LOG.info("MyResponseBodyAdvice#beforeBodyWrite :{}", resp);
return resp;
}
}
テストアプリケーションを開始します。ページアクセス:
http://localhost:18080/index
ページ応答:
{respCode}:「00」、「respDesc」:「成功」、「result」:「ok」
バックエンドログ:
c.g.h.s.l.a.LogHandler Interceptr : LogHandler Interceptor啢preHandle要求住所:/index
c.g.h.s.l.a.aspect.AsppectLog Interceptor : IndexController.index()パラメータ:[]
IndexCotroller莾index:AsyncResp{respCode='00',repDesc='成功',result='ok'}
c.g.h.s.l.a.aspect.AsppectLog Interceptor : IndexCotroller.index()結果:AsyncResp{respCode='00',repDesc='成功',result='ok')
c.g.h.s.l.a.aspect.MyResponseBody Advice : MyResponseBody Advice啱beforeBodyWrite要求住所:/index
c.g.h.s.l.a.aspect.MyResponseBody Advice : MyResponseBody Advice応答結果:AyncResp{respCode='00',repDesc='成功',respDesc='を返します。
c.g.h.s.l.a.LogHandler Interceptr : LogHandler Interceptor啣postHandle呼び出し
ここで実行する先着順も比較的明確であり、ここではもはや説明しない。
非同期実行
もちろん、これらの内容だけなら、この文章の重点ではない。
次に、非同期を導入したらどうなるかを一緒に見てみましょう。
非同期スレッドを定義
springbootでは、非同期スレッド池を定義するのは、非常に簡単です。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
*
*
* @author binbin.hou
*/
@Configuration
@EnableAsync
public class SpringAsyncConfig {
@Bean(name = "asyncPoolTaskExecutor")
public AsyncTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(10);
executor.setQueueCapacity(10);
executor.setCorePoolSize(10);
executor.setWaitForTasksToCompleteOnShutdown(true);
return executor;
}
}
非同期実行のコントローラ
@RestController
public class MyAsyncController extends BaseAsyncController<String> {
@Override
protected String process(HttpServletRequest request) {
return "ok";
}
@RequestMapping("/async")
public AsyncResp hello(HttpServletRequest request) {
AsyncResp resp = super.execute(request);
System.out.println("Controller#async :" + resp);
return resp;
}
}
その中でBaseAsyncControllerの実現は以下の通りである。
@RestController
public abstract class BaseAsyncController<T> {
protected abstract T process(HttpServletRequest request);
@Autowired
private AsyncTaskExecutor taskExecutor;
protected AsyncResp execute(HttpServletRequest request) {
//
AsyncResp resp = new AsyncResp();
try {
taskExecutor.execute(new Runnable() {
@Override
public void run() {
try {
T result = process(request);
resp.setRespCode("00");
resp.setRespDesc(" ");
resp.setResult(result.toString());
} catch (Exception exception) {
resp.setRespCode("98");
resp.setRespDesc(" ");
}
}
});
} catch (TaskRejectedException e) {
resp.setRespCode("99");
resp.setRespDesc(" ");
}
return resp;
}
}
executeの実現も簡単です。(1)メインスレッドは戻りのためにAyncResopを作成します。
(2)スレッド池非同期は、特定のサブメソッドを実行し、対応する値を設定します。
思考
次に、皆さんに質問します。
http://localhost:18080/asyncを要求したら、
(1)ページで得られた戻り値は何ですか?
(2)Asppectログ出力の戻り値は?
(3)ResonseBody Adviceログ出力の戻り値は何ですか?
ここでちょっと止まって、答えをメモしてください。
テスト
私たちのページ要求http://localhost:18080/async。
ページの応答は以下の通りです。
{respCode}:「00」、「respDesc」:「成功」、「result」:「ok」
バックエンドのログ:
c.g.h.s.l.a.LogHandler Interceptr : LogHandler Interceptor铅preHandle要求住所:/async
c.g.h.s.l.a.aspect.AsppectLog Interceptor : MyAsyncController.hello(.)パラメータ:[org.apphe.cantalina.com nnector.RequestFacade@7e931750」
Controller〓〓〓〓の結果:Async Resp{respCode='null',repDesc='null',result='null'
c.g.h.s.l.a.aspect.AsppectLog Interceptor : MyAsyncController.hello(.)結果:AsyncResp{respCode='null',repDesc='null',reult='null'。
c.g.h.s.l.a.aspect.MyResponseBody Advice : MyResponseBody Advice啱beforeBodyWrite要求住所:/async
c.g.h.s.l.a.aspect.MyResponseBody Advice : MyResponseBody Advice応答結果:AyncResp{respCode='00',repDesc='成功',respDesc='を返します。
c.g.h.s.l.a.LogHandler Interceptr : LogHandler Interceptor啣postHandle呼び出し
比べてみると、上記の問題の答えが分かります。
(1)ページで得られた戻り値は何ですか?
{respCode}:「00」、「respDesc」:「成功」、「result」:「ok」
非同期実行完了の結果が得られます。
(2)Asppectログ出力の戻り値は?
Arync Resp{respCode='null',repDesc='null',reult='null'
非同期の結果を取得できませんでした。
(3)ResonseBody Adviceログ出力の戻り値は何ですか?
Async Resop{respCode='00',repDesc='成功',reult='ok''
非同期実行完了の結果が得られます。
反省する
springのページに対する応答は、私たちが考えているのとは少し違っているかもしれません。直接に同期結果を得るのではないことが分かります。
ここまで書いて、mvcに対する自分の理解はずっと表面だけに留まっています。
Asppectの形式は多くのフレームで使われていますが、ここでは非同期の実行結果が得られないことが分かり、問題があります。
以上で、springbootでブロックを実現するための3つの方法と非同期的な思考についての記事を紹介します。もっと関連するspringbootのスクリーンショットの内容は以前の文章を検索したり、以下の関連記事を見たりしてください。これからもよろしくお願いします。