LoginSignup
6
4

More than 5 years have passed since last update.

Spring Boot - Controllerのエンドポイントプレフィックスを動的に設定する

Last updated at Posted at 2018-02-10

1. やりたいこと

  • 外部Jarとして取り込まれる
  • 外部Jar内にController(RestController)を含む
  • Controllerのエンドポイントプレフィックスを取り込むアプリケーション側で決めることができる
  • 例えば「/api/demo」⇒「/webapi/demo」など

2. バージョンや条件など

  • Java 8
  • Spring Boot 1.5.10.RELEASE

3. やったこと

RequestMappingHandlerMappingを継承したクラス

ApiRequestMappingHandlerMapping.java
package jp.gr.java_conf.pekokun.springbootdemo.externalapi.app;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

@Component
public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

    @Value("${demo.externalapi.prefix:api}")
    private String prefix;

    public ApiRequestMappingHandlerMapping() {
        // (1)
        setOrder(0);
    }

    @Override
    protected boolean isHandler(Class<?> beanType) {
        // (2)
        return AnnotatedElementUtils.hasAnnotation(beanType, Api.class);
    }

    @Override
    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
        Api api = method.getDeclaringClass().getAnnotation(Api.class);
        if (Objects.isNull(api)) {
            // No-annotated Api, then do nothing.
            return;
        }

        // (3)
        List<String> patterns = mapping.getPatternsCondition().getPatterns().stream()
                .map(p -> getPathMatcher().combine(prefix, p))
                .collect(Collectors.toList());

        // (4)
        super.registerHandlerMethod(handler, method,
            new RequestMappingInfo(
                new PatternsRequestCondition(patterns.toArray(new String[patterns.size()])),
                mapping.getMethodsCondition(),
                mapping.getParamsCondition(),
                mapping.getHeadersCondition(),
                mapping.getConsumesCondition(),
                mapping.getProducesCondition(),
                mapping.getCustomCondition()));

    }

}

(1) setOrder(int order)で何かしらの値を設定します。
     継承元のAbstractHandlerMapping に以下の設定があるためです。
     private int order = Integer.MAX_VALUE; // default: same as non-Ordered

(2) 処理対象のBeanであるか判定をします。
     BeanのクラスにApiがアノテーションされているかを判定します。
     デフォルトでは以下のようにControllerもしくはRequestMappingがアノテーションされているかを判定しています。


    /**
     * {@inheritDoc}
     * Expects a handler to have a type-level @{@link Controller} annotation.
     */
    @Override
    protected boolean isHandler(Class<?> beanType) {
        return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
    }

(3) エンドポイントプレフィックスを付加したパターンを生成します。

(4) (3)で生成したパターンを用いてRequestMappingInfoを生成し、それを使って登録します。

対象のControllerにマークするためのAnnotation

Api.java
package jp.gr.java_conf.pekokun.springbootdemo.externalapi.app;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ResponseBody;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
@ResponseBody
public @interface Api {

}

対象のController

DemoRestController.java
package jp.gr.java_conf.pekokun.springbootdemo.externalapi.app;

import org.springframework.web.bind.annotation.GetMapping;

// (1)
@Api
public class DemoRestController {

    @GetMapping(path = "demo")
    public String get() {
        return "DEMO";
    }

}

(1) ControllerやRestController、RequestMappingは付けません。
     デフォルトのRequestMappingHandlerMappingの対象となるためです。

4. Github

おわり。

追記(2018/8/15)

  • Spring Boot 2.0.3.RELEASE でも動きました。
  • 上記の@Api@ResponseBodyが付いているため、RestControllerのような動きになります。
  • @ResponseBodyを付けなければ、Controllerのように動きます。例えば次のようなAnnotationを用意します。
View.java
package jp.gr.java_conf.pekokun.springbootdemo.externalapi.app;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface View {

}
  • カスタムするRequestMappingHandlerMappingの内容も動作に合わせて変更します。

おわり。

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4