質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
MVC

MVC(Model View Controller)は、オブジェクト指向プログラミングにおけるモデル・ビュー・コントローラーの総称であり、ソフトフェア開発で使われている構築パターンとしても呼ばれます。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Spring

Spring Framework は、Javaプラットフォーム向けのオープンソースアプリケーションフレームワークです。 Java Platform上に、 Web ベースのアプリケーションを設計するための拡張機能が数多く用意されています。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

Q&A

解決済

3回答

15686閲覧

【Java SpringBoot】Controllerの前処理、後処理の実装

shafi

総合スコア13

MVC

MVC(Model View Controller)は、オブジェクト指向プログラミングにおけるモデル・ビュー・コントローラーの総称であり、ソフトフェア開発で使われている構築パターンとしても呼ばれます。

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Spring

Spring Framework は、Javaプラットフォーム向けのオープンソースアプリケーションフレームワークです。 Java Platform上に、 Web ベースのアプリケーションを設計するための拡張機能が数多く用意されています。

Spring Boot

Spring Bootは、Javaのフレームワークの一つ。Springプロジェクトが提供する様々なフレームワークを統合した、アプリケーションを高速で開発するために設計されたフレームワークです。

0グッド

1クリップ

投稿2020/02/25 11:24

Spring MVC の実装において、前処理、後処理の設計がうまくいかず相談したいです。

まず、Controllerの構成をbefore(), execute(), after() の構成で考えております。
そして、Controllerの作成方針として1API=1Controllerとしてます。

各Controllerは親の存在があり、その親を必ず継承するよう対応する形になります。
親は複数種類存在し、各Controllerは必要となる親を継承する形になります。

以下、呼称を親を親Controller、それを継承するものを子Controllerとします。

上記の条件で実装するにあたって、
親Controllerクラスにbefore(), after()を持たせ
子Controllerクラスにexecute()を実装するとします。

execute()には、@RequestMapping("/uri")を付与します。

Clientからリクエストが送信された際に、execute()が呼ばれます。
が、その際にbefore()が呼ばれる設計としたいです。
また、execute()の処理の終了後にafter()が呼ばれるようにしたいです。

Spring AOP 使用し実装に試みたものの、before()の実装がAdviceクラスになってしまい、
親Controllerでの実行は無理そうでした。

before()内に、Clientからのリクエストメッセージを親クラスのフィールドに持たせる方針であるため、
どうしても親クラス内でbefore()を実行したいです。
(レスポンスも同様にafter()にて設定を行いたい)

なにか良い方法はありませんでしょうか。

以下、イメージとなる実装を記載します。

Java

1@Controller 2@RequestMapping("/URI_ACCOUNT") 3public class LoginController extends BaseBackendController { 4 5 @Autowired 6 AccountManageService service; 7 8 @Override 9 @RequestMapping("/URI_LOGIN") 10 public void execute() { 11 12 // リクエスト情報取得 13 UserInfoDto userInfoDto = (UserInfoDto) super.getRequestData(); 14 15 // ログイン処理 16 service.login(userInfoDto); 17 } 18 19} 20

java

1 2 3@RestController 4public abstract class BaseBackendController implements BaseController { 5 6 @Autowired 7 private RequestDto requestDto; 8   9  /** 前処理 */// ここで本来はexecuteの呼び出しを行った際に最初に呼ばれる想定 10 public void before() { 11     // Requestメッセージ取得 12 RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); 13 ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) 14 attributes; 15 HttpServletRequest request = servletRequestAttributes.getRequest(); 16 try { 17   // RequestBody取得       18 requestData.setRequestDto( 19 request.getReader().lines().collect(Collectors.joining("\r\n")); 20 } catch (IOException e) { 21   throw new RuntimeException(e); 22 } 23 } 24 25 public void after() {} 26 27 protected Request getRequestData() { 28 return this.requestDto.getRequest(); 29 } 30} 31

お手数おかけしますが、お助け願います。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/02/25 13:57

で アフターは何をしたいの?
shafi

2020/02/25 13:59

AfterはControllerの後処理、Excute()の後処理を実行したいです。 基本的にはResponseの処理を埋め込もうと考えていますが、その設計はまだ起こしていない状態です。
退会済みユーザー

退会済みユーザー

2020/02/25 14:02

おこしてないのになぜアフターをやりたいのか
shafi

2020/02/25 14:08

共通クラスを実装を考えています。 親クラスに共通して持つものとしてBefore、Afterのセットで持たせようと考えております。 なのでAfterは定義上用意していますが、 例えば、Beforeでファイルオープン。 Afterでファイルクローズなどの処理想定しています。 厳密には設計途上の段階です。
guest

回答3

0

例えば、Beforeでファイルオープン。

Afterでファイルクローズなどの処理想定しています。

上記を達成できる バッドノウハウ

※ Spring の利点が著しく損なわれますw

import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class ControllerBase { Logger logger = LogManager.getLogger(); public void before() { logger.info("before"); } public void after() { logger.info("after"); } // bean (Controller)に DI完了後に動作 // before を呼ぶ @PostConstruct public final void postConstruct() { this.before(); } // bean (Controller)が破棄されるときに動作 // after を呼ぶ @PreDestroy public final void preDestroy() { this.after(); } }
import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; @Scope("request") @Controller public class IndexController extends ControllerBase { @GetMapping("/") @ResponseBody public String index() { return "index"; } }

投稿2020/02/25 14:38

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shafi

2020/02/25 14:48

@asahina1979 ご回答ありがとうございます。 一つの案として検討させていただきます。 DI完了時、破棄時に処理を割り込めること自体知りませんでした。 ありがとうございます。
退会済みユーザー

退会済みユーザー

2020/02/25 14:53

書いてある通り「バッドノウハウ」だからね
guest

0

SpringMVCの実装ポリシーとしてはControllerの前処理や後処理は、@ControllerAdviceで実装するものです。これに倣わずに独自拡張をした場合は、Controllerの初期処理も気にしなければなりませんし、それでもbeforeやafterを実装したいのでしたら、Springの制御層に手をいれれば可能ですが、その場合はSpringMVCやSpringBootのバージョンアップにより動作しなくなる恐れがあるでしょう。

https://qiita.com/kazuki43zoo/items/757b557c05f548c6c5db

投稿2020/02/25 14:06

A-pZ

総合スコア12011

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shafi

2020/02/25 14:32

@A-pZ ご回答ありがとうございます。 設計当初では、Spring AOPのAceptを利用し、BaseControllerでの実装を試してみましたが、 うまくいかずご相談した形でした。 BaseControllerを継承した子クラスで、execute()の実装のみを行い、ほかの初期処理や共通処理等はすべて親クラスに持たせて子クラスの負担をと考えたのですが難しい場合は今回の件はやめる検討もしています。 みなさんの意見を拝見する限り、難しいため再設計の形になりそうですね。 制御層まで手を入れる予定はありません。
guest

0

ベストアンサー

Spring (Boot)でコントローラーの前後に処理を挟み込みたい場合は、AOPのほかに

  • Filter
  • Interceptor

などの利用が考えられますが、AOPの利用が設計上無理ということであれば、これらも利用に適さないかもしれません。
なので、設計の方を少し見直してみてはどうでしょうか?という提案レベルの回答になります。

以下はInterceptorを利用した方法の一案になります。

BaseBackendControllerの代わりになるBaseBackendServiceを実装します。
(before/afterメソッドの引数の種類やクラスの命名に特に意味はありません。実装をイメージしやすいように考えました。)

java

1@Service 2public class BaseBackendService implements BaseBackend { 3 4 public void before(HttpServletRequest request, HttpServletResponse response) { 5 // 前処理 6 } 7 8 public String after(HttpServletRequest request, HttpServletResponse response) { 9 // 後処理 10 11 // 戻り先のビューを決定 12 String viewName = "account/" + " ... "; 13 14 return viewName; 15 } 16 17 public void doCommonBackend() { 18 // 共通処理 19 } 20 21}

インターセプターの前後の処理で、BaseBackendService#before、BaseBackendService#afterを呼び出します。この例ではafterメソッドがビュー名を返すので、それをmodelAndView.setViewNameに指定します。

java

1@Component 2public class BaseBackendInterceptor implements HandlerInterceptor { 3 4 private final BaseBackendService baseBackendService; 5 6 public BaseBackendInterceptor(BaseBackendService baseBackendService) { 7 this.baseBackendService = baseBackendService; 8 } 9 10 @Override 11 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 12 baseBackendService.before(request, response); 13 return true; 14 } 15 16 @Override 17 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 18 String viewName = baseBackendService.after(request, response); 19 20 // ここでビューを指定 21 modelAndView.setViewName(viewName); 22 } 23 24}

インターセプターを登録します。この例では/account/**というアクセスのときに処理されるように登録しています。

java

1@Configuration 2public class WebMvcConfig implements WebMvcConfigurer { 3 4 private final BaseBackendInterceptor baseBackendInterceptor; 5 6 public WebMvcConfig(BaseBackendInterceptor baseBackendInterceptor) { 7 this.baseBackendInterceptor = baseBackendInterceptor; 8 } 9 10 @Override 11 public void addInterceptors(InterceptorRegistry registry) { 12 registry.addInterceptor(baseBackendInterceptor).addPathPatterns("/account/**"); 13 } 14 15}

コントローラーの実装です。ここではビューは指定しないので戻り値の型はvoidです。

java

1@Controller 2@RequestMapping("/account") 3public class LoginController { 4 5 @RequestMapping("/login") 6 void execute() { 7 // なにかの処理 8 } 9 10}

BaseBackendServiceに実装した何かの処理をコントローラーでも呼び出したい場合は、BaseBackendServiceをインジェクトして呼び出すことができます。
(ただし、この実装にするとインターセプターを利用する意味が無くなるのですが。)

java

1@Controller 2@RequestMapping("/account") 3public class LoginController { 4 5 private final BaseBackendService baseBackendService; 6 7 public LoginController(BaseBackendService baseBackendService) { 8 this.baseBackendService = baseBackendService; 9 } 10 11 @RequestMapping("/login") 12 void execute(HttpServletRequest request, HttpServletResponse response) { 13 // baseBackendService.before(request, response); 14 15 // なにかの処理 16 17 // 共通処理 18 baseBackendService.doCommonBackend(); 19 20 // なにかの処理 21 22 // String viewName = baseBackendService.after(request, response); 23 // return viewName; 24 } 25 26}

簡単ですが案は以上になります。
ちなみに、やりたいことが認証周りであれば独自の認証処理よりSpring Securityを利用するほうがいいかもしれません。

投稿2020/02/25 13:35

rubytomato

総合スコア1752

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2020/02/25 14:00

ぼくのかんがえた(以下略)の人がどこまで納得するかだよなぁ
shafi

2020/02/25 14:23

@rubytomato ご回答ありがとうございます。 今一度、インタセプタの検討して再設計を検討してみます。 インタセプタ内でフィールド等の保持は難しいですが、 リクエストを受けたControllerにて自分で取ってもらう形とします。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問