SpringMVC
- SpringMVC は Servlet と同じような機能を持ち、どちらも Web 層開発技術です。
- SpringMVC は Java で実装された MVC モデルベースの軽量 Web フレームワークです。
- Servlet と比べて使いやすく、開発が便利で、柔軟性も高いです。
現在、多くのプロジェクトは SpringBoot フレームワークで開発されています。そのため、この記事では重要な概念を中心に説明し、細かい実装は深く扱いません。
ケース
Step 1:座標を導入する
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>Step 2:SpringMVC Controller クラスを作成する
Servlet と同じような機能を持ちます。
@Controller //このクラスがBeanであることを宣言する
public class userControler {
@RequestMapping("/save") //現在の処理のアクセスパスを設定する
@ResponseBody //現在の処理の戻り値タイプを設定する
public String save(){
System.out.println("user save...");
return "{'info':'springmvc'}";
}
}Step 3:SpringMVC 設定クラスを作成する
@Configuration
@ComponentScan("com.protectark.mysqlt")
public class SpringMvcConfig {
}Step 4:Servlet コンテナ起動設定クラスを定義する
このクラスの中で Spring の設定を読み込みます。
public class servletInConfig extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext act = new AnnotationConfigWebApplicationContext();
act.register(SpringMvcConfig.class);
return act;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}アノテーション説明
| アノテーション | 種類 | 位置 | 役割 |
|---|---|---|---|
| @Controller | クラスアノテーション | SpringMVC Controller クラス上 | SpringMVC の核心 Controller Bean を設定する |
| @RequestMapping(地址) | メソッドアノテーション | SpringMVC Controller クラス内のメソッド上 | 現在の Controller メソッドのリクエストパスを設定する |
| @ResponseBody | メソッドアノテーション | SpringMVC Controller メソッド定義の上 | 現在の Controller メソッドの戻り値を直接レスポンス内容にする |
動作フロー
サーバー起動時の初期化:
- サーバーが起動し、
ServletContainersInitConfigクラスを実行して Web コンテナを初期化する。 createServletApplicationContextメソッドを実行し、WebApplicationContextオブジェクトを作成する。SpringMvcConfigを読み込む。@ComponentScanにより対応する Bean を読み込む。UserControllerを読み込み、各@RequestMappingの名前が具体的なメソッドに対応する。getServletMappingsメソッドを実行し、すべてのリクエストが SpringMVC を通るように定義する。
単一リクエストの流れ:
localhost/saveへリクエストを送る。- Web コンテナはすべてのリクエストが SpringMVC を通ることを確認し、リクエストを SpringMVC に渡す。
- リクエストパス
/saveを解析する。 /saveに対応するsave()メソッドを実行する。save()を実行する。@ResponseBodyがあるため、save()の戻り値をレスポンスボディとして返す。
設定クラスの簡略化
Spring は AbstractDispatcherServletInitializer の子クラスとして、AbstractAnnotationConfigDispatcherServletInitializer を提供しています。
public class servletInConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{springConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{springMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}リクエストとレスポンス
パス衝突を解決する
チームで複数人が開発する場合、リクエストパスの衝突を避ける必要があります。
通常は、モジュール名をリクエストパスのプレフィックスとして設定します。
方法:
- 各メソッドの
@RequestMappingパスを変更する。例:/user/save、/book/save - クラスのリクエストパスを設定する。例:クラスに
/user、メソッドに/save
| アノテーション | 種類 | 位置 | 役割 |
|---|---|---|---|
| @RequestMapping(value) | メソッド / クラスアノテーション | SpringMVC Controller クラスまたはメソッド上 | 現在の Controller メソッドのリクエストパスを設定する。クラス上に設定すると、全メソッド共通のプレフィックスになる |
@Controller
@RequestMapping("/user")
public class userControler {
@RequestMapping("/save")
@ResponseBody
public String save(){
return "{'info':'springmvc'}";
}
}中文文字化けを解決する
送信された中国語が文字化けする場合、Servlet コンテナ起動設定クラス servletInConfig で親クラスのメソッドをオーバーライドし、Web コンテナに文字エンコーディングフィルターを追加します。
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}リクエストパラメータと仮引数名が違う場合
たとえば、メソッドの仮引数名が username と age で、実際のリクエストパラメータが name、age の場合、username は null になります。
この場合は @RequestParam("name") を使い、パラメータ名を指定します。
@Controller
public class userControler {
@RequestMapping("/save")
@ResponseBody
public String save(@RequestParam("name") String username,
@RequestParam("age") int age){
System.out.println(username + age);
return "{'info':'springmvc'}";
}
}パラメータをエンティティクラスで受け取る
必要なパラメータが多い場合、通常はエンティティクラスを作り、それで受け取ります。
@Controller
public class userControler {
@RequestMapping("/save")
@ResponseBody
public String save(User user){
return "{'info':'springmvc'}";
}
}public class User{
private String username;
private int age;
//その他の属性
//getter、setter、toStringを実装する
}リクエストの属性名がエンティティクラス内の属性名と完全に一致する場合、自動的に user に入ります。
JSON データを渡す
JSON データはリクエストヘッダーのパラメータではなく、リクエストボディにあります。
そのため、受け取るときは、リクエストボディのデータであることを示すアノテーションが必要です。
まず JSON データ変換用の依存を導入します。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>次に、SpringMVC に JSON 変換を有効にすることを伝えます。SpringMvcConfig に @EnableWebMvc を追加します。
@Configuration
@ComponentScan("com.protectark.mysqlt")
@EnableWebMvc
public class SpringMvcConfig {
}JSON はリクエストボディにあるため、Controller メソッドの仮引数では @RequestParam ではなく @RequestBody を使います。
@RequestMapping("/inputuser")
@ResponseBody
public String init(@RequestBody User user){
System.out.println("input user ==>"+user);
return "{'info':'springMVC'}";
}| アノテーション | 種類 | 位置 | 役割 |
|---|---|---|---|
@EnableWebMvc | 設定クラスアノテーション | SpringMVC 設定クラス上 | SpringMVC の複数の補助機能を有効にする |
@RequestBody | 仮引数アノテーション | Controller メソッド仮引数前 | リクエストボディ内のデータをリクエスト引数へ渡す。一つのハンドラーメソッドで一回だけ使える |
RequestBody と RequestParam の比較
違い
@RequestParamは URL パラメータ、フォームパラメータ(application/x-www-form-urlencoded)を受け取る@RequestBodyは JSON データ(application/json)を受け取る
使用場面
- 後期の開発では JSON 形式のデータ送信が中心になるため、
@RequestBodyがよく使われる - JSON 以外の形式なら、
@RequestParamでリクエストパラメータを受け取る
- 後期の開発では JSON 形式のデータ送信が中心になるため、
@PathVariable は仮引数アノテーションであり、パスパラメータと仮引数の関係をバインドします。
@RequestMapping("/users/{id}")
@ResponseBody
public String init(@PathVariable Integer id){
System.out.println("input id ==>"+id);
return "{'info':'springMVC'}";
}日付型パラメータの受け渡し
SpringMVC は String 型のパラメータを直接受け取り、自動で日付型に変換できます。形式を指定したい場合は @DateTimeFormat を使います。
@RequestMapping("/inputuser")
@ResponseBody
public String init(@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date date){
System.out.println("input date ==>"+date);
return "{'info':'springMVC'}";
}| アノテーション | 種類 | 位置 | 役割 |
|---|---|---|---|
@DateTimeFormat | 仮引数アノテーション | SpringMVC Controller メソッド仮引数前 | 日付時間型データの形式を設定する |
レスポンス
JSON データを返したい場合は、エンティティクラスを返すだけでよいです。
@RequestMapping("/select")
@ResponseBody
public User select(){
User user = new User();
return user;
}SpringMVC は自動でエンティティクラスを JSON データに変換します。
REST スタイル
概念
REST(Representational State Transfer)は、表現状態転移という考え方です。
- 従来のリソース表現
http://localhost/user/getById?id=1http://localhost/user/saveUser
- REST スタイルのリソース表現
http://localhost/user/1http://localhost/user
メリット:記述が簡単になり、リソースへのアクセス方式を隠せます。URL だけでは、そのリソースに対してどの操作をするか分かりません。
REST スタイルでは、リソースへアクセスするとき、HTTP メソッドで操作を区別します。
http://localhost/users:すべてのユーザー情報を検索、GEThttp://localhost/users/1:指定ユーザー情報を検索、GEThttp://localhost/users:ユーザー情報を追加、POSThttp://localhost/users:ユーザー情報を変更、PUThttp://localhost/users/1:ユーザー情報を削除、DELETE
この方式は一種の約束であり、REST スタイルと呼ばれます。この方式でリソースへアクセスすることを RESTful と呼びます。
モジュール名は通常、複数形を使います。例:users、books、accounts。
RESTful クイック開発
REST スタイルでは JSON 通信がよく使われます。そのため、各メソッドに @ResponseBody が必要になります。これを Controller クラス上に置くこともできます。さらに、@Controller と @ResponseBody を合わせた @RestController が提供されています。
リクエスト方式も主に四種類なので、@RequestMapping から次のアノテーションが派生しています。
@PostMapping@GetMapping@PutMapping@DeleteMapping
@RestController
@RequestMapping("/users")
public class userControler {
@GetMapping()
public String save(@Requestbody User user){
return "{'info':'springmvc'}";
}
@DeleteMapping()
public String save(@Requestbody User user){
return "{'info':'springmvc'}";
}
}統一結果クラス
統一結果クラスには次のメリットがあります。
一、保守性の向上
- 戻り値を集中管理でき、統一的な変更と拡張がしやすい。
- エラー処理ロジックを明確に定義でき、保守とデバッグがしやすい。
二、安定性と信頼性の向上
- 例外情報を標準化し、フロントエンドの解析エラーを避ける。
- グローバル例外処理により、システムクラッシュを防ぐ。
三、チーム協作効率の向上
- フロントエンドとバックエンドに明確な規約を提供し、コミュニケーションコストを減らす。
- 再利用性が高く、コードの重複を減らす。
四、監視と統計がしやすい
- ログ形式を統一し、分析と監視がしやすい。
- データ統計分析に役立ち、問題やボトルネックを発見しやすい。
通常、統一結果クラスは code、message、data の三部分で構成されます。
@Data
public class Result<T> implements Serializable {
private Integer code;
private String msg;
private T data;
public static <T> Result<T> success() {
Result<T> result = new Result<T>();
result.code = 1;
return result;
}
public static <T> Result<T> success(T object) {
Result<T> result = new Result<T>();
result.data = object;
result.code = 1;
return result;
}
public static <T> Result<T> error(String msg) {
Result result = new Result();
result.msg = msg;
result.code = 0;
return result;
}
}code は通常、事前に定義したステータスコードを一つのクラスにまとめて管理します。
public class Code {
public static final Integer SAVE_OK = 200;
/* など */
}例外ハンドラー
基本概念
開発中、例外は避けられません。例外が発生しやすい場所と原因は次の通りです。
- フレームワーク内部の例外:使い方が不適切なため
- データ層の例外:外部サーバー障害などのため
- 業務層の例外:業務ロジックの記述ミスのため
- 表示層の例外:データ収集、検証などのルール違反のため
- ツールクラスの例外:実装が厳密でなく、堅牢性が足りないため
すべての層で例外が発生する可能性があるため、すべて上位へ投げ、表示層で統一処理します。
Spring が提供する例外ハンドラーは、プロジェクト内の例外を集中処理できます。
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
return new Result(666,null);
}
}注意:
- 例外ハンドラーは
SpringMVC技術に属するため、SpringMvcConfigが認識できるパスに置く必要があります。通常はcontrollerパッケージ下に置きます。 - 統一結果クラスを返す必要があるため、例外ハンドラーも結果クラスを返すようにします。
プロジェクト内の例外処理
プロジェクト例外分類:
- 業務例外(BusinessException)
- 規約通りのユーザー操作によって発生する例外
- 規約違反のユーザー操作によって発生する例外
- システム例外(SystemException)
- 実行中に予測できるが避けられない例外
- その他の例外(Exception)
- 開発者が予測していなかった例外
処理方針:
- 業務例外:対応するメッセージをユーザーへ返し、正しい操作を促す
- システム例外:固定メッセージをユーザーへ返し、運用担当者へ通知し、ログを記録する
- その他の例外:固定メッセージをユーザーへ返し、開発者へ通知し、ログを記録する
例外を定義する
分類に基づき、システム例外と業務例外を定義できます。exception パッケージを作成し、カスタム例外を保存します。
public class SystemException extends RuntimeException{
private Integer code;
public SystemException(Integer code) {
this.code = code;
}
public SystemException(String message, Integer code) {
super(message);
this.code = code;
}
public SystemException(String message, Throwable cause, Integer code) {
super(message, cause);
this.code = code;
}
}同じように BusinessException も作成します。
カスタム例外を投げる
業務層で例外が発生しそうな場所を包み、カスタム例外へ変換して投げます。
public User getById(Integer id){
User user = userMapper.getById(id);
if (user == null) {
throw new SystemException(Code.SYSTEM_NULL,"结果为空");
}
return user;
}カスタム例外を処理する
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex){
/* ログ記録 */
/* 運用担当者へ通知 */
return new Result(ex.getCode,ex.getMessage);
}
@ExceptionHandler(Exception.class)
public Result doException(Exception ex){
return new Result(666,null);
}
}インターセプター
インターセプター(Interceptor)は、メソッド呼び出しを動的に遮断する仕組みです。SpringMVC では、Controller メソッドの実行を動的に遮断できます。
役割:
- 指定メソッドの呼び出し前後に、事前に設定したコードを実行する
- 元のメソッドの実行を止める
主な用途:
- 権限とアクセス制御:ユーザー認証と権限確認を行い、特定リソースへのアクセスを制限する
- ログと性能監視:リクエスト情報を記録し、性能ボトルネックを分析する
- データ処理:リクエストデータの前処理とレスポンスデータの後処理を行う
- 重複送信防止と検証:フォームの重複送信を防ぎ、フォームデータを検証する
- システム連携:システム間のやり取りを制御し、共通業務ロジックを実行する
フィルターとの違い
- 所属が違う:Filter は Servlet 技術、Interceptor は SpringMVC 技術
- 遮断対象が違う:Filter はすべてのアクセスを強化する。Interceptor は SpringMVC へのアクセスだけを強化する
- 設計目的が違う:Interceptor は業務ロジック前後の処理に重点がある。Filter はリクエストとレスポンスの一般的なフィルタリングに重点がある
- 実行順序が違う:Interceptor は Spring MVC 内の登録順またはアノテーション順で実行される。Filter は Web コンテナの設定順で
doFilterを実行する
使用方法
まず interceptor パッケージを作成します。このパッケージは controller パッケージ下に置いてもよいです。インターセプターは表示層を強化するためです。
インターセプタークラスを作成し、HandlerInterceptor インターフェースを実装します。
@Component
public class Interceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}SpringMVC 設定クラスを作成します。
@Configuration
public class SpringMvcConfig extends WebMvcConfigurationSupport {
@Autowired
private Interceptor interceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/users");
}
}複数インターセプターを設定する
Interceptor をもとに Interceptor2 を作成し、SpringMvcConfig を変更します。
@Configuration
public class SpringMvcConfig extends WebMvcConfigurationSupport {
@Autowired
private Interceptor interceptor;
@Autowired
private Interceptor2 interceptor2;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(interceptor).addPathPatterns("/users");
registry.addInterceptor(interceptor2).addPathPatterns("/users");
}
}