0. 前書き
今回の記事は「SE入門編」シリーズの一部です。完全版はこちらにあります:
1. 前提条件
-
前提条件として、DBおよびWebプロジェクトの構築が完了していることが前提です。もし構築がまだ完了していない場合は、以下の記事を参照してください。
-
今回の実装では、メイン画面の表示のみに焦点を当てます。実装に際しては、SpringやThymeleafの基本実装が理解されていることが前提です。
-
また、メイン画面においてはメニューの実装は行いませんので、メニューの実装については他の記事をご参照ください。
-
実装にはjQueryとBootstrapを使用しますので、これらを使いこなすスキルが必要です。
【参考記事】
DBの構築手順:
WEBプロジェクトの構築手順:
CRUDの基本実装:
メニュー画面の実装:
この記事のソースコードはGitHubにアップロードしましたので、ぜひダウンロードして確認してください。
2. 今回の記事で出来たこと
- メイン画面の実装、ハンバーガーメニューの実装。
- log4j2を使用したログの出力実装。
- エラー画面の実装。
2. 1 メイン画面の実装。
3つのエリアに分かれています。
- バーナーエリア:システムのロゴとログインユーザーの情報を表示するエリア。
- メニューエリア:前回作成したメニューをここに表示し、更にハンバーガーメニューを実装しました。
- メインコンテンツエリア:メニューをクリックすると、その機能の画面が表示されるエリア。
完成した画面の効果は以下の通りです:
http://localhost:8000/main
ハンバーガーメニューがオープンされた状態の画面では、クローズ時との区別を明確にするために東京大学のホームページが表示されています。
ハンバーガーメニューがクローズされた状態の画面では、クローズ時に東京大学のホームページが全幅で表示されています。
メニューから「ユーザーメンテナンス画面」をクリックすると、ユーザー登録画面が表示されます。
ソースを解説(一部ソースしか解析しないです):
2. 1. 1 バーナー画面の実装。
バーナーエリアはHTMLの基本しか備えていないため、詳細な説明は省略します。
ログインユーザー情報には現時点では固定の値(ADMIN)が設定されています。ログイン画面とセッション管理機能が完成した際には、それに基づいて実装を行い予定です。
- 画面要素部分:
<header class="header w-100">
<div style="position: relative;">
<div class="LayoutLeft Banner w-100">
<h1 style="font-size: 40m; color:#FFFFFF;">
WEB Asset Portal System
</h1>
<p class="userInfo text-center" >
ADMIN
</p>
</div>
</div>
</header>
- CSS部分、classの定義(header、LayoutLeft、Banner、userInfo)、他のclass(w-100、text-center)はBootstrapの標準を使ってます。
.header {
height: 120px;
border-top: solid 4px #8a2be2;
}
div.Banner {
height: 120px;
background-image: url(../images/banner.png);
background-repeat: repeat-x;
}
p.userInfo {
width: 80px;
float: right;
color: #000000;
background-color: #88dce0;
}
.LayoutLeft {
float: left;
}
2. 1. 2 ハンバーガーメニュー部分の実装。
- 画面要素部分、ハンバーガーメニューのボタン(オープン/クローズ):
<button class="hamburger" id="btnHamburger">
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
</button>
- 画面要素部分、メニュー本体:前回作成したメニューをiframeに表示しています(userIDは現時点では固定値ですが、ログイン画面とセッション管理機能が完成したら、その実装を修正します)。
<nav class="LayoutLeft LayoutMenu col-xl-3" id="menu" style="display: true;">
<iframe class="w-100 h-100" src="/menu?userID=ADMIN"></iframe>
</nav>
- CSS部分、ハンバーガーメニューのボタン(オープン/クローズ):オープン時は閉じるボタン(×)を表示し、クローズ時は三本線を表示するように実装しています。
.hamburger {
background-color: #f0f0f0;
border-radius: 50%;
position: fixed;
top: 135px;
left: 10px;
border: 0;
cursor: pointer;
width: 32px;
height: 32px;
}
.hamburger:focus {
outline: 0;
}
.hamburger .line {
background-color: #000;
display: block;
margin: 4px auto;
height: 2px;
width: 20px;
transition: all .1s ease-in;
}
.hamburger.open .line:nth-child(1) {
transform: translateY(6px) rotate(45deg);
}
.hamburger.open .line:nth-child(2) {
opacity: 0;
}
.hamburger.open .line:nth-child(3) {
transform: translateY(-6px) rotate(-45deg);
}
- CSS部分、メニュー本体:
LayoutRight:レイアウトを「横並び」にし、左揃えに設定しています。
LayoutMenu:メニュー表示エリアの高さを設定しており、今回は自分のモニターを合わせて600pxに設定しています。
col-xl-3: Bootstrapの標準CSSで、画面を12等分に分割しているうちの3つ分を占める意味です。詳細な説明はBootstrapの公式サイトをご参照ください。
.LayoutMenu {
height: 600px;
}
.LayoutLeft {
float: left;
}
JS部分:jQueryを使用して、メニューエリアの表示/非表示およびメインコンテンツエリアの表示幅を再設定しています。
$(document).ready(function() {
$("#btnHamburger").toggleClass('open'); // ハンバーガーメニューボタン(初期はオープン状態)
// ハンバーガーメニューのボタンをクリックする時のイベント処理
$('#btnHamburger').on('click', function() {
toggleClasses();// ハンバーガーメニューのオープン/クローズ処理
});
// ハンバーガーメニューのオープン/クローズ処理
function toggleClasses() {
$("#btnHamburger").toggleClass('open'); // ハンバーガーメニューボタン(オープン/クローズ時切り替え)
$("#menu").fadeToggle(500); // メニューを表示するかしないかを切り替え
$("#container").toggleClass("col-xl-9"); // メインコンテンツエリア表示サイズをリセット
$("#container").toggleClass("col-xl-12");
}
setTimeout(toggleClasses, 500);
setTimeout(toggleClasses, 100);
});
2. 1. 3 メインコンテンツ表示エリアの実装。
デフォルトで blank.html を表示しています。メニューをクリックすると、その機能の画面が表示される。
col-xl-9 : Bootstrap の標準CSSで、画面を12等分に分割しているうちの9つ分を占める意味です。
<div class="LayoutLeft LayoutContents text-center col-xl-9" id="container" >
<iframe id="test" class="w-100 h-100" style="padding: 5px;" is="x-frame-bypass" src="/blank"></iframe>
</div>
2. 1. 4 JAVA部分の実装。
メイン画面のバックエンド側は現時点ではとてもシンプルです、セッション管理機能が出来たら実装を修正します。
MainController.java:画面遷移しかないです。
@RequestMapping(value = "/main")
@ResponseBody
public ModelAndView mainAction(ModelAndView model, HttpServletRequest request) {
String userID = request.getParameter("userID");
if (userID == null) {
userID = "";
}
return model;
}
@RequestMapping(value = "/blank")
@ResponseBody
public ModelAndView blankAction(ModelAndView model, HttpServletRequest request) {
return model;
}
2. 2 log4j2を使ってログを出力の実装。
Spring Bootプロジェクトではlogbackはデフォルトのログライブラリとして使用されていますが、log4j2は非常に高いパフォーマンスを提供しており、大規模なアプリケーションやトランザクション処理において優れた性能を発揮できるため、今回はlog4j2を採用します。
- pom.xmlの設定:以下の2か所を追加します
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
- src/main/javaの下にlog4j2.xmlを追加する。設定内容は:
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<Properties>
<Property name="app_name">demo</Property>
<Property name="date">%d{yyyy-MM-dd HH:mm:ss.SSS}</Property>
<Property name="daily_log">logs/${app_name}_web_%d{yyyy-MM-dd}.log</Property>
</Properties>
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${date}, [${app_name}], [ %-5level ], %logger{10}, %msg %n" />
</Console>
<RollingFile name="File" fileName="logs/${app_name}_web.log" filePattern="${daily_log}.zip">
<PatternLayout pattern="${date}, [${app_name}], [ %-5level ], %logger{10}, %msg %n" />
<Policies>
<TimeBasedTriggeringPolicy />
</Policies>
</RollingFile>
</appenders>
<loggers>
<root level="info">
<appender-ref ref="Console" />
<appender-ref ref="File" />
</root>
</loggers>
</configuration>
現時点でのフォルダ構造及び出力されたlogファイルです。
2. 3 エラー画面。
- エラー発生時の画面:
- Detailsボタンを押すとき、エラー詳細情報が表示される。(404の時、詳細情報がありません)
2. 3. 1 JAVA部分、AppErrorController.java、ErrorController インターフェースの実装クラス。
@Controller
@RequestMapping("/error") // エラーページへのマッピング
public class AppErrorController implements ErrorController
- リクエストのエラーコードからHTTPステータスを取得するメソッド
/**
* リクエストのエラーコードからHTTPステータスを取得
* @param req HTTPリクエスト情報
* @return HTTPステータス
*/
public static HttpStatus getAppStatusCode(HttpServletRequest req) {
int statusCode = (Integer)req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); // リクエストからエラーコードを取得する
for (HttpStatus code: HttpStatus.values()) {
if (code.value() == statusCode)
return code;
}
return HttpStatus.OK;
}
- レスポンス用の エラー情報を抽出するメソッド。
/**
* レスポンス用の エラー情報を抽出する。
*
* @param req HTTPリクエスト情報
* @return エラー情報
*/
private Map<String, Object> getErrorAttributes(HttpServletRequest req) {
// DefaultErrorAttributes クラスで詳細なエラー情報を取得する
ServletWebRequest swr = new ServletWebRequest(req);
DefaultErrorAttributes dea = new DefaultErrorAttributes();
ErrorAttributeOptions eao = ErrorAttributeOptions.of(
ErrorAttributeOptions.Include.BINDING_ERRORS,
ErrorAttributeOptions.Include.EXCEPTION,
ErrorAttributeOptions.Include.MESSAGE,
ErrorAttributeOptions.Include.STACK_TRACE);
return dea.getErrorAttributes(swr, eao);
}
- レスポンス用の ResponseEntity オブジェクトを返すメソッド
エラーコード、異常種類、エラーメッセージなどを設定する。
/**
* レスポンス用の ResponseEntity オブジェクトを返す。
*
* @param req HTTPリクエスト情報
* @return JSON レスポンス用の ResponseEntity オブジェクト
*/
public ResponseEntity<Map<String, Object>> getAppErrorEntity(HttpServletRequest req) {
// エラー情報を取得
Map<String, Object> attr = getErrorAttributes(req);
// HTTP ステータスを決める
HttpStatus status = getAppStatusCode(req);
// 出力したい情報をセットする
Map<String, Object> body = new HashMap<String, Object>();
body.put("status", status.value());
body.put("timestamp", attr.get("timestamp"));
body.put("error", attr.get("error"));
body.put("exception", attr.get("exception"));
body.put("message", "システムエラーが検知されました。至急、システム管理者にご連絡下さい!");
body.put("errors", attr.get("errors"));
body.put("trace", attr.get("trace"));
body.put("path", attr.get("path"));
// 情報を JSON で出力する
return new ResponseEntity<>(body, status);
}
- HTML レスポンス用の ModelAndView オブジェクトを返すメソッド
/**
* HTML レスポンス用の ModelAndView オブジェクトを返す。
*
* @param req リクエスト情報
* @param view レスポンス情報
* @return HTML レスポンス用の ModelAndView オブジェクト
*/
@RequestMapping
public ModelAndView error(HttpServletRequest req, ModelAndView view) {
AppLogger.writeLog(Level.INFO, "---------------------error() start-------------");
ResponseEntity<Map<String, Object>> err = getAppErrorEntity(req);
view.addObject("errBody", err.getBody());
// view.addObject("errStatus", err.getStatusCode());
// ビュー名にerror.htmlをセット
view.setViewName("error");
AppLogger.writeLog(Level.INFO, "---------------------error() end -------------");
return view;
}
2. 3. 2 HTML部分、error.html、エラー情報を表示する画面。
- 画面要素部分、エラー情報を取得し表示する。
<body th:if="${errBody}" th:object="${errBody}" >
<!-- エラー内容を表示 -->
<h1 th:text="*{status}"></h1>
<p th:text="*{path}"></p>
<p th:text="*{timestamp}"></p>
<p th:text="*{message}"></p>
<p th:text="*{error}"></p>
<p th:text="*{exception}"></p>
<button id="buttonDetails">Details</button>
<div id="divDetails" th:text="*{trace}" style="display: none;"></div>
</body>
- JS部分、Detailsボタンを押す時、エラー詳細情報を表示する。
$(document).ready(function(){
$("#buttonDetails").on("click",function(){
$("#divDetails").slideToggle(500);
});
});
おわりに
全体のソースコードはGitHubにアップロードしましたので、ぜひダウンロードして確認してください。
また、この記事では一部のソースコードしか説明していませんが、説明を希望する特定の部分があればリクエストしていただければと思います。詳細や特定の質問についてお知らせいただければ、それに基づいて訂正や説明を提供できます。
次回はログイン画面及びセッション管理機能 を実装していきたいと思います、お楽しみして待ってください。
それではまた。