SIer だけど技術やりたいブログ

SpringのContextHolderいろいろ

Spring Java

Springには、…ContextHolderというクラスがある。スレッドローカルに値を保存しておくことで、情報をいろんなところから参照できるようにする。

スレッドローカルは…まあスレッド固有の値ですよね。(あたりまえ)

TomcatなどのAPサーバはリクエストごとにworkerスレッドを割り当てるので、
Java ブロッキングとかノンブロッキングを理解したい - SIerだけど技術やりたいブログ

@AsyncやExecutorServiceを利用して別スレッドでXXXContextHolderを利用しない限り、どこからでもスレッドローカルに登録した同じ値を参照できる。具体的なクラスは、

Spring MVCRequestContextHolderリクエスト情報
Spring SecuritySecurityContextHolder認証情報

使い方

Spring Bootでリクエストと認証のログを出してみる。spring-boot-starter-securityがclasspathに含まれてるとデフォルトでDIGEST認証がかかるので、今回は認証にそれを使う。

ログ処理はHandlerInterceptorを使う。
引数にHttpServletRequestが取れるけど、わざとRequestContextHolderから取得する。

@Slf4j
public class LoggingInterceptor extends HandlerInterceptorAdapter {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    String msg = (String) request.getParameter("msg");
    log.info("request : " + msg);

    SecurityContext sc = SecurityContextHolder.getContext();
    Authentication authentication = sc.getAuthentication();
    log.info("authentication : " + authentication);
    return true;
  }
}

HandlerInterceptorを登録する。

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter{

    @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoggingInterceptor())
        .addPathPatterns("/**");
  }
}

リクエストするためのメソッドを用意する。

@RestController
@SpringBootApplication
public class ContextholderApplication {

  public static void main(String[] args) {
    SpringApplication.run(ContextholderApplication.class, args);
  }

  @GetMapping(value = "sample", params = "msg")
  public String param() {
    return "success";
  }
}

起動するとログに認証パスワードが表示されるので、それを使ってリクエストする。

...
2017-02-18 19:39:47.925  INFO 13184 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-02-18 19:39:47.955  INFO 13184 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-02-18 19:39:48.227  INFO 13184 --- [           main] b.a.s.AuthenticationManagerConfiguration : 

Using default security password: e5ad05bf-3daa-4a1b-91ad-81d726ed712a
curl --user user:e5ad05bf-3daa-4a1b-91ad-81d726ed712a http://localhost:8080/sample?msg=hello

ログに出る。

2017-02-18 19:40:01.634  INFO 13184 --- [nio-8080-exec-1] com.example.LoggingInterceptor           : request : hello
2017-02-18 19:40:01.634  INFO 13184 --- [nio-8080-exec-1] com.example.LoggingInterceptor           : authentication : org.springframework.security.authentication.UsernamePasswordAuthenticationToken@442b5a9f: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_USER

うん。

・・・うん。