
最新のビルドシステムを使用すると、ソースからアプリケーションをコンパイルおよび実行するプロセスを完全に自動化できます。 ターゲットマシンでは、JDKのみが必要であり、コレクター自体を含む他のすべてがオンザフライでロードされます。 アセンブリプロセスを正しく構築し、たとえば、データベースの起動、SQLスクリプトの実行、Java、Javascript、CSSファイルのコンパイル、サーブレットコンテナの起動などの2つのコマンドを取得するだけです。 これは、Gradle、HSQLDB、Liquibase、Googleクロージャーコンパイル、およびGrettyを使用して実装されます。 記事の詳細。
内容
はじめに
最近、有望なGradleビルダーに対処しようとして、私は不快な機能を見つけました。 多くの人が、個々のことをどれだけ素晴らしく、簡単かつ迅速に行えるかについて書いています。 ただし、全体像を自分で収集する必要があります。 例として小さなアプリケーションを使用して、Gradleマテリアルのこの欠点を排除しようとします。 アプリケーション自体では、Java EEの現在関連性のある基礎を検討しようとします。
アプリケーションの作成と実行を開始する前に、要件を決定します。 それらは小さい-3ページ:1つのルート、すべてにアクセス可能、もう1つは許可ユーザーのみにアクセス可能。 最後のログインページ。新しいユーザーを作成するためのページです。 複雑で明白ではありません。 典型的な「こんにちは、世界!」 しかし、このような小さな例でも、Java EEの威力と巨大さを示すには十分です。 通常の開発では、プロジェクトを実行するために必要な構成ファイルの数は恐ろしく膨大になります。
幸いなことに、この問題は開始時にのみ発生します。 そして、サーバー言語としてJavaを選択するだけではありません。 サーバーアプリケーション自体は完全には機能しません。 データベースまたは少なくとも1つのサーブレットコンテナなどの外部リソースが必要です。 データベースを事前に準備し、テーブルを作成してデータを入力する必要があります。 後者は、厳密なスキームのない非リレーショナルデータベースを使用する場合でも必要です。
ロギングポリシーの設定、データベースの操作、JavaScriptおよびCSS処理の自動化。 アプリケーションを使用する前に設定する必要があるものがたくさんあります。 やってみます。
使用済みのアプリケーション、プラグイン、ライブラリ
リストは大きくなりますが、アプリケーションを正常に起動および変更するには、最初のアイテムとインターネットへのアクセスのみが必要です。 さらに、開発環境を配置することをお勧めしますが、原則として、コンソールを介してアセンブリを実行することで開発環境をなくすことができます。
プロジェクトの最初の立ち上げ
Gradleを使用する新しいプロジェクトを作成します。 作成されたプロジェクトの構造。

ご覧のとおり、新しいプロジェクトにも多くのファイルが含まれています。 従来、これらは2つのグループに分けられています。 バージョン管理システム(VCS)に保存する必要があるものと保存しないものがあります。
次のファイルをVCSに保存する必要があります。
- build.gradle-プロジェクトのビルド方法を説明するファイル。
- settings.gradle-プロジェクトをビルドするための一般的な設定。
- srcフォルダー-ソースコードとプロジェクトリソースが含まれています。
- LICENSE.txt-プロジェクトが配布されるライセンス(デフォルトでは作成されません)。
- gradlew、gradlew.bat、gradle-wrapper.jar、gradle-wrapper.propertiesは、gradle-wrapperのユーティリティファイルです。 Wrapperはgradle上の軽量シェルであり、必要に応じて特定の(gradle-wrapper.propertiesで指定された)バージョンのコレクターをダウンロードします。 したがって、プロジェクトをビルドするためにgradleを事前にインストールする必要はありません。
次のファイルはプロジェクトの一部ですが、VCSに保存しないでください。
- gradle.properties-ローカルgradle設定を持つファイル(デフォルトでは作成されません)。
- tit.iml-アイデアプロジェクトファイル
- サービスフォルダー.gradle、build、out
各バージョンシステムには、VCSに保存するフォルダー/ファイルを記述する独自のファイルがあります。 すべての開発者の間で統一性が維持されるように、それ自体もVCSに保存する必要があります。 gitを使用する場合、.gitignoreの内容はおよそ次のとおりです。
.gitignore*.iml .idea/ /out/ /gradle.properties
新しいプロジェクトを開始するには、サーブレットコンテナをダウンロードして展開できるGradle用のGrettyプラグインを使用します。 プラグインはbuild.gradleファイルで接続されます。
plugins { id "org.akhikhl.gretty" version "1.2.4" }
プラグインを使用すると、サーバーのダウンロード、インストール、および起動が1つのコマンドに削減されます。
gradlew jettyStart
「jettyStart」を選択して、Ideaから直接起動することもできます。 gradleのジョブリスト。 このコマンドでは、jettyバージョン9がダウンロードされ、自動的に展開されます。 プロジェクトは
localhost:8080 / gull /で利用可能になります。 サーバーserverを停止するには、「jettyStop」を使用します。
jettyRunは、Ideaから起動すると正しく機能しないため、使用しないでください(起動後すぐに終了します)。
サーバーをデバッグモードで起動するには、「jettyStartDebug」を使用します
詳細「jettyStartDebug」コマンドで起動すると、サーバーはデバッガが接続されるまで待機します。 Ideaでこれを行うには、メニューrun-> Edit Configurationを使用して新しい構成を作成する必要があります

新しい「リモート」構成を追加します。 デフォルト設定は変更しません。

これで、新しい構成を選択してサーバーに参加できます。
Spring MVC
デフォルトでは、プロジェクトは1つの「ルート」ページのみを表示できます。 機能を拡張するには、
model-view-controllerパターンを実装するSpring MVCを使用します。 私たちの場合、プレゼンテーションはJSPページであり、Javaコントローラクラスがデータモデルを生成します。

ライブラリを接続します。build.gradle。
dependencies { compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion }
settings.gradle
gradle.ext.springVersion = '4.2.2.RELEASE'
spring-webmvcライブラリをプロジェクトに接続すると、ライブラリ自体が依存しているため、Springカーネルが暗黙的に追加されます。 必要に応じて、次のライブラリを指定してカーネルを明示的に追加できます。
依存関係の
実装を通じて、異種のクラスとファイルを単一のアプリケーションに収集するのは、Springのコアであると言えます。
Springの最新バージョンでは、構成全体を
コードで直接指定することができ
ます 。 このアプローチは私にとって過激すぎるようです。結局のところ、データベースに接続する構成は、コードではなくxmlファイルとしてより適切に見えます。 したがって、XMLを介した部分、アノテーションを介した部分の混合アプローチを使用します。
アプリケーションをデプロイする方法の説明はweb.xmlに保存されます。 3つの要素が含まれています。
- リスナー-構成ファイルの表示(デフォルトのapplicationContext.xml)
- サーブレット-リクエストを処理するクラス
- servlet-mapping-指定されたサーブレットが担当するリクエストを記述します。 たとえば、「/」を指定するとすべての要求がインターセプトされ、「/ admin /」を指定すると「admin」でのみ開始されます。 複数のマッピングが1つのパスを指す場合、最も正確なマッピングが使用されます。
web.xml<?xml version = "1.0" encoding = "UTF-8"?>
<web-app xmlns:xsi = "
www.w3.org/2001/XMLSchema-instance "バージョン= "2.4"
xmlns = "
java.sun.com/xml/ns/j2ee "
xsi:schemaLocation = "
java.sun.com/xml/ns/j2ee java.sun.com/xml/ns/j2ee/web-app_2_4.xsd ">
<servlet-name>ディスパッチャー</ servlet-name>
<servlet-class> org.springframework.web.servlet.DispatcherServlet </ servlet-class>
<load-on-startup> 1 </ load-on-startup>
<servlet-mapping>
<servlet-name>ディスパッチャー</ servlet-name>
<url-pattern> / </ url-pattern>
</ servlet-mapping>
<listener-class> org.springframework.web.context.ContextLoaderListener </ listener-class>
</ web-app>
applicationContext.xml-アプリケーション全体に共通のBeanについて説明します。
applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
dispatcher-servlet.xml-特定のサーブレットのBeanが含まれます。 注サーブレットのデフォルトの構成ファイルは、「-servlet.xml」が追加されたサーブレットと同じ名前です。 サーブレット名の階層を維持するには、「/」を使用できます。 たとえば、サーブレット '
admin / dispatcher 'は、ファイル 'src / main / webapp / WEBINF /
admin / dispatcher -servlet.xml'に対応します。
dispatcher-servlet.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:view-controller path="/" view-name="index"/> <mvc:annotation-driven/> <context:component-scan base-package="com.intetm.web"/> </beans>
dispatcher-servlet.xmlでは、ビューのJSPがどこにあるかを示すBeanが定義されています。 ルート( "/")ページが指定され、行<mvc:annotation-driven /> "Spring-mvcアノテーションを含むチェックする。
Springは
Controllerアノテーションでマークされたすべてのクラスを検索し、その中に@RequestMappingアノテーションを持つメソッドがあります。 このメソッドは、入力パラメーターとして、データを入力する必要があるモデルを受け入れます。 ビューの名前は、出力パラメーターとして指定されます。 注釈パラメーター値は、処理中のアドレスです。
package com.intetm.web.login; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class LoginController { private static final String HELLO_VIEW = "hello"; @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello(Model model) { model.addAttribute("subject", "world"); return HELLO_VIEW; } }
ビュー内のモデル変数へのアクセスは、「$ {parametr_name}」の構築を通じて行われます。 hello.jspでの使用例。
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> Hello, ${subject}! </body> </html>
デフォルトページで、ウェルカムページでリンクを指定します。
index.jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <a href="hello">Hello</a> </body> </html>
ファイルを追加および編集した結果、2ページのアプリケーションが作成されます。 プロジェクトを開始し、結果を確認します。
サーバー設定を構成する
デフォルトのサーバー設定は、単純なアプリケーションを起動するときに適切かもしれませんが、後で変更する必要があります。 ポート、サーバーコンテキストを構成する方法を見てみましょう。 ファイルをクラスパスに追加します。

ポートを構成するには、grettyにbuild.gradleの適切なポートを使用するように指示します。
def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort gretty { httpPort = serverHttpPort }
そしてsettings.gradleのデフォルトポート
//default config gradle.ext.serverHttpPort = 8080
これで、変数 'serverHttpPort'がgradle.propertiesで見つかった場合、それが使用されます。 それ以外の場合、settings.gradleのデフォルト値が使用されます。 settings.gradleはgitにあり、gradle.propertiesは除外されているため、一方で、gitと競合することなくデフォルト値を集中的に更新し、値をローカルに設定できます。

サーバーコンテキストの場合、VCSにデフォルトファイルを保存し、ローカルで自由に変更可能なコピーを作成することも推奨されます。 serverContextFile変数を指定して、ローカルコピーと共有コピーを切り替えます。 デフォルトでは、VCSからのコピーが使用されます。 さらに、トレーニングのために、Gradleでローカルコピーを作成するタスクを実行します。
build.gradle。
def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile } task copyEnvironment(type: Copy) { from 'src/test/resources/environment' into serverResourcesPath }
デフォルトsettings.gradle
gradle.ext.serverResourcesPath = "dev/resources" gradle.ext.serverContextFile = "src/test/resources/environment/jetty-context.xml"
jettyの空の構成ファイルsrc / test / resources / environment / jetty-context.xml
<?xml version="1.0"?> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> <Configure id="ExampleServer" class="org.eclipse.jetty.server.Server"> </Configure>
ファイルのコピーがdev / resourcesフォルダーに作成されます。 将来、devフォルダーはログとデータベースの保存にも使用されます。 ランダムコミットを除外するには、VCSからdevフォルダー全体を除外します。
同様に、サーバーのクラスパスを構成できます。 たとえば、ログ設定で「logback.xml」ファイルを追加します。
build.gradle。
def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set gretty { … classPath = serverClassPath }
settings.gradle
gradle.ext.serverClassPath = gradle.serverResourcesPath + "/classpath"
ロギング

Javaでのロギングシステムの歴史は、かなり
混乱して悲しいものです。 プロジェクトは、slf4jとlogbackの多かれ少なかれ関連する組み合わせを使用します。 これを行うために、build.gradleに2つの依存関係が追加されました。
dependencies { compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion … }
settings.gradleで使用されているバージョン
gradle.ext.slf4jVersion = '1.7.13' gradle.ext.logbackVersion = '1.1.3'
ログバックには、ログの書き込み方法を記述するlogback.xmlファイルが必要です。 典型的な設定ファイルには、次のコンポーネントが含まれています
- アペンダー-ロギング用のチャネル(コンソール、ファイル、またはその他)。 メッセージの記録形式、ファイルパス、ファイルローテーションポリシーを設定します。
- ルート-指定したレベル以上のすべてのメッセージをインターセプトし、指定したアペンダーに転送します。
- ロガー-ルートとは異なり、指定されたパッケージに含まれるクラスからのメッセージのみをインターセプトします。 (クラスは標準の方法でメッセージを記録し、ロガー名として完全なクラス名を指定すると理解されています)
サンプルlogback.xmlファイル <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.padual.com/java/logback.xsd" scan="true" scanPeriod="10 seconds"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> <encoder> <pattern> %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{128} - %msg%n </pattern> </encoder> </appender> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>dev/logs/error.log</file> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>dev/logs/old/%d{yyyy-MM-dd}.error.log</fileNamePattern> <maxHistory>7</maxHistory> </rollingPolicy> <encoder> <pattern> %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{128} - %msg%n </pattern> </encoder> </appender> <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>dev/logs/debug.log</file> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>DEBUG</level> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>dev/logs/old/%d{yyyy-MM-dd}.debug.log</fileNamePattern> <maxHistory>7</maxHistory> </rollingPolicy> <encoder> <pattern> %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{128} - %msg%n </pattern> </encoder> </appender> <appender name="SQL_FILE" class="ch.qos.logback.core.FileAppender"> <file>dev/logs/sql.log</file> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>dev/logs/sql.lg</level> </filter> <append>false</append> <encoder> <pattern> %d{yyyy-MM-dd HH:mm:ss} %-5level %logger{128} - %msg%n </pattern> </encoder> </appender> <logger name="com.intetm" level="DEBUG"> <appender-ref ref="DEBUG_FILE"/> </logger> <logger name="org.hibernate.type" level="ALL"> <appender-ref ref="SQL_FILE"/> </logger> <logger name="org.hibernate" level="DEBUG"> <appender-ref ref="SQL_FILE"/> </logger> <root level="ERROR"> <appender-ref ref="STDOUT"/> <appender-ref ref="ERROR_FILE"/> </root> </configuration>
LoginControllerにログを記録する行を追加します。
logger.debug("hello page");
完全なファイル package com.intetm.web.login; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); private static final String HELLO_VIEW = "hello"; @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello(Model model) { logger.debug("hello page"); model.addAttribute("subject", "world"); return HELLO_VIEW; } }
プロジェクトを開始します。
localhost / gull / helloが入力されると、dev \ log \ debug.logファイルにエントリが表示されるようにします。
データベースの開始

ユーザー、パスワード、ロールに関する情報を保存するには、データベースが必要です。 データベースをアプリケーション自体に直接埋め込むことは理論的には可能ですが、スケーリング、接続、外部エディターなどに問題があります。 したがって、データベースはアプリケーションの外部にあります。
Oracleおよびその他の重いデータベースには、事前インストールが必要です。 これは、スケーラビリティと驚異的なパフォーマンスに対する料金です。 このようなデータベースは、数万人のユーザーを抱える戦闘作戦に適しています。 開発中、このような負荷は予想されないため、小さな
HSQLデータベースを使用します。
HSQLはインストールを必要としません。jarファイルから直接起動されるか、小さなラッパーを使用してGradleから直接起動されます。 残念ながら、サーバーモードでGradleからHSQLを実行する標準的な方法が見つからなかったため、小さな自転車を作成して別のファイルに入れました。
database.gradle apply plugin: 'java' task startDatabase() { group = 'develop' outputs.upToDateWhen { return !available() } doLast { def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbFile = project.properties['dbFile'] ?: gradle.dbFile def dbName = project.properties['dbName'] ?: gradle.dbName def className = "org.hsqldb.server.Server"; def filePath = "file:${projectDir}/${dbFile};user=${dbUser};password=${dbPassword}"; def process = buildProcess(className, filePath, dbName) wait(process) } } def buildProcess(className, filePath, dbName) { def javaHome = System.getProperty("java.home"); def javaBin = javaHome + File.separator + "bin" + File.separator + "java"; def classpath = project.buildscript.configurations.classpath.asPath; def builder = new ProcessBuilder(javaBin, "-cp", classpath, className, "-database.0", filePath, "-dbname.0", dbName); builder.redirectErrorStream(true) builder.directory(projectDir) def process = builder.start() process } def wait(Process process) { def ready = "From command line, use [Ctrl]+[C] to abort abruptly" def reader = new BufferedReader(new InputStreamReader(process.getInputStream())) def line; while ((line = reader.readLine()) != null) { logger.quiet line if (line.contains(ready)) { break; } } } import groovy.sql.Sql task stopDatabase() { group = 'develop' outputs.upToDateWhen { return available() } doLast { def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl def dbDrive = project.properties['dbDrive'] ?: gradle.dbDrive ClassLoader loader = Sql.class.classLoader project.buildscript.configurations.classpath.each { File file -> loader.addURL(file.toURI().toURL()) } //noinspection GroovyAssignabilityCheck Sql sql = Sql.newInstance(dbUrl, dbUser, dbPassword, dbDrive) as Sql sql.execute('SHUTDOWN;') sql.close() } } boolean available() { try { int dbPort = project.properties['dbPort'] ?: gradle.dbPort as int String dbHost = project.properties['dbHost'] ?: gradle.dbHost Socket ignored = new Socket(dbHost, dbPort); ignored.close(); return false; } catch (IOException ignored) { return true; } }
build.gradleでは、ファイルをインクルードし、hsqlの使用を示すだけで済みます。
buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } … apply from: 'database.gradle'
settings.gradle //lib version gradle.ext.hsqldbVersion = '2.3.2' //default database config gradle.ext.dbName = "xdb" gradle.ext.dbFile = "dev/database/devDB" gradle.ext.dbUser = "SA" gradle.ext.dbPassword = "password" gradle.ext.dbPort = 9001 gradle.ext.dbHost = "localhost" gradle.ext.dbUrl = "jdbc:hsqldb:hsql://${gradle.dbHost}:${gradle.dbPort}/${gradle.dbName}" gradle.ext.dbDrive = "org.hsqldb.jdbc.JDBCDriver"
コンソールまたはIdeaのGradleタスクメニューからデータベースを起動できます
gradlew startDatabase
コマンドの実行後、Ideaを含む外部エディターを介してデータベースに接続できます。 デフォルトのユーザー/パスワードは「SA」/「password」です。 アドレスはjdbc:hsqldb:hsql:// localhost:9001 / xdb
データベースのシャットダウンも同様です。
gradlew stopDatabase
テーブル作成

アプリケーションでリレーショナルデータベースの使用を開始する前に、テーブル、インデックスなどを作成する必要があります。 厳格なスキームがない場合、非リレーショナルデータベースにデータをすぐに投入できます。 ただし、どちらの場合もデータの入力自体を行う必要があります。
Liquibaseは、SQLスクリプトの実行を効率化するために使用されます。 Liquibaseは指定された順序でスクリプトを実行でき、同じスクリプトが2回実行されないようにします。 既に実行されたスクリプトを含むファイルが変更されると、危険な状況を警告します。 特定のポイントまたは期間へのロールバックをサポートします。 Liquibaseを使用すると、複数のデータベースを操作する際のエラーの数が大幅に削減されます(戦闘、テストなど)。
liquibaseを接続し、ファイルを取得する場所、タスクを流し、作成する場所を記述します。
build.gradle
plugins { id 'org.liquibase.gradle' version '1.1.1' } def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } }
完全なファイル buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } }
changelog.xmlファイルにコンテンツを入力するだけです。
記事のアドバイスに従って、次のスクリプト構造が使用されます。
/src /sql /main /changelog.xml /v-1.0 /2015.11.28_01_Create_User_table.sql ... /changelog-v.1.0-cumulative.xml /v-2.0 ... /changelog-v.2.0-cumulative.xml
各バージョンの累積スクリプトのみがメインのchangelog.xmlファイルに含まれています。
changelog.xml <?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <include file="src/sql/main/V-1.0/changelog-v.1.0-cumulative.xml"/> </databaseChangeLog>
Changelog-v.1.0-cumulative.xmlには、アプリケーションのバージョン1のすべてのスクリプトが含まれています。
changelog-v.1.0-cumulative.xml <?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <changeSet id="Version 1 tag" author="Sivodedov Dmitry"> <tagDatabase tag="Version 1"/> </changeSet> <include file="src/sql/main/V-1.0/2015.11.28_01_Create_User_table.sql"/> </databaseChangeLog>
特定の変更リストは、最低レベルでのみ保持されます。
2015.11.28_01_Create_User_table.sql タスク「updateDbMain」を開始すると、必要に応じてデータベースが自動的に開始されます。結果は2つのテーブルになります。
データベースを満たす
さらに、データベースでのみ開発用のスクリプトを実行する別のタスクを作成します。空のデータベースでのテストは難しく、SQLを介したテストデータの入力は賢明な方法なので、これは便利です。build.gradleの新しいタスクとそのパラメーター liquibase { activities { … dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } }
完全なファイル buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } }
メインリストchangelog.xmlsrc\sql\dev\changelog.xml
<?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <include file="src/sql/dev/V-1.0/2015.11.28_01_Create_User.sql"/> </databaseChangeLog>
最初のバージョンの変更のリスト。changelog-v.1.0-cumulative.xmlsrc\sql\dev\V-1.0\changelog-v.1.0-cumulative.xml
<?xml version="1.0" encoding="UTF-8"?> <databaseChangeLog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd"> <include file="src/sql/dev/V-1.0/2015.11.28_01_Create_User.sql"/> </databaseChangeLog>
ユーザーの直接追加。2015.11.28_01_Create_User.sqlsql\dev\V-1.0\2015.11.28_01_Create_User.sql
ユーザー認証
これで、データベースを設定して入力した後、ユーザーを認証するためのデータソースとして使用できます。Springのコンポーネントの1つであるSpring Securityを使用します。Spring Securityを接続するbuild.gradle
dependencies { runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion runtime group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion // , gretty. // , classpath . gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion }
buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion runtime group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } }
settings.gradle
gradle.ext.springSecurityVersion = '4.0.2.RELEASE'
springセキュリティ設定ファイルで、authentication-managerを作成します。パスワードハッシュには、比較的強力なBCryptが使用されます。データベースへのアクセスは、次のコントラクトを使用して、2つの単純なSQLクエリを介して行われます。- users-by-username-query-ユーザーIDを指定すると、ユーザーが見つかった場合に1行(名前、パスワード、ロック)のみを返します。そうでなければ何も。
- authority-by-username-query-指定されたユーザーIDのペアのリスト(ユーザー名、ロール)。
権限のないユーザーには、「匿名」の役割があります。USERロールを持つユーザーのみがhelloページにアクセスできます。注意!
データベースでは、ユーザーロールはROLE_USERとして記述されていますが、構成で「USER」が指定されています。security.xml <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd"> <http> <intercept-url pattern="/hello**" access="hasRole('USER')"/> <form-login default-target-url="/"/> <logout logout-url="/logout" logout-success-url="/"/> <anonymous username="guest" granted-authority="ANONYMOUS"/> <http-basic/> <remember-me/> </http> <authentication-manager> <authentication-provider> <password-encoder ref="encoder"/> <jdbc-user-service data-source-ref="dbDataSource" users-by-username-query="SELECT username, password, enabled FROM Users WHERE username= ?" authorities-by-username-query="SELECT u1.username, u2.authority FROM Users u1, Authorities u2 WHERE u1.id = u2.userId AND u1.UserName = ?"/> </authentication-provider> </authentication-manager> <beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"> <beans:constructor-arg name="strength" value="10"/> </beans:bean> </beans:beans>
Springがデータベースに接続できるようにするには、さらにいくつかの手順が必要です。データベースにアクセスするBeanを定義します。dbContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:jee="http://www.springframework.org/schema/jee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd"> <jee:jndi-lookup id="dbDataSource" jndi-name="jdbc/Database" expected-type="javax.sql.DataSource"/> </beans>
春のセキュリティ設定とデータベース設定を含むファイルをメインアプリケーションコンテキストに接続します。applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <import resource="security.xml"/> <import resource="dbContext.xml"/> </beans>
データベースへの接続をアプリケーションからコンテナに転送し、フィルターを作成します。フィルターは、リクエストが対応するサーブレットに到達する前であっても、すべての接続をインターセプトし、セキュリティ設定に従ってそれらを処理します。web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app.xsd"> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <resource-ref> <description>Datasource</description> <res-ref-name>jdbc/Database</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> </web-app>
データベース自体へのリンクをサーバーコンテキストに追加します。jetty-context.xml <?xml version="1.0"?> <!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> <Configure id="ExampleServer" class="org.eclipse.jetty.server.Server"> <New id="DS" class="org.eclipse.jetty.plus.jndi.Resource"> <Arg> <Ref refid="wac"/> </Arg> <Arg>jdbc/Database</Arg> <Arg> <New class="org.hsqldb.jdbc.JDBCDataSource"> <Set name="DatabaseName">jdbc:hsqldb:hsql://localhost:9001/xdb</Set> <Set name="User">SA</Set> <Set name="Password">password</Set> </New> </Arg> </New> </Configure>
これでサーバーを起動し、localhostページ8080 / gull / helloに移動して、ページに入る前にユーザーとパスワードを入力する必要があることを確認できます。このようなユーザー(管理者/パスワード)は、最終段階で作成され、データベースに追加されました。ログインページを変更する
現在、サードパーティツールによってデータベースに追加されたユーザーのみがページにアクセスできます。アプリケーション自体から直接ユーザーを作成できるようにしましょう。簡素化および利便性のために、この機能をログインページに直接追加します。そのため、最初に標準のログインページを独自のログインページに変更します。security.xmlでログインページを使用することを指定します。 <form-login login-page="/login" default-target-url="/"/>
完全なファイル <beans:beans xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd"> <http> <intercept-url pattern="/hello**" access="hasRole('USER')"/> <form-login login-page="/login" default-target-url="/"/> <logout logout-url="/logout" logout-success-url="/"/> <anonymous username="guest" granted-authority="ANONYMOUS"/> <http-basic/> <remember-me/> </http> <authentication-manager> <authentication-provider> <password-encoder ref="encoder"/> <jdbc-user-service data-source-ref="dbDataSource" users-by-username-query="SELECT username, password, enabled FROM Users WHERE username= ?" authorities-by-username-query="SELECT u1.username, u2.authority FROM Users u1, Authorities u2 WHERE u1.id = u2.userId AND u1.UserName = ?"/> </authentication-provider> </authentication-manager> <beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"> <beans:constructor-arg name="strength" value="10"/> </beans:bean> </beans:beans>
dispatcher-servlet.xmlで表示するリンクを追加します <mvc:view-controller path="/login" view-name="login"/>
完全なファイル <?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:view-controller path="/" view-name="index"/> <mvc:view-controller path="/login" view-name="login"/> <mvc:annotation-driven/> <context:component-scan base-package="com.intetm.web"/> </beans>
ログインページを作成します。login.jsp <%@ page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Login Page</title> </head> <body> <div align="center"> <h3>Login with Username and Password</h3> <form:form id='formLogin' action='./login' method='POST'> <table> <tr> <td>username:</td> <td><input type='text' name='username' value='' autofocus></td> </tr> <tr> <td>password:</td> <td><input type='password' name='password'/></td> </tr> <tr> <td><input type='checkbox' name='remember-me'/></td> <td>remember-me</td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit"/></td> </tr> </table> </form:form> </div> </body> </html>
ハローページに終了オプションを追加します。hello.jsp <%--suppress ELValidationInJSP --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Title</title> </head> <body> <c:url value="/logout" var="logoutUrl"/> <form action="${logoutUrl}" method="post" id="logoutForm"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> </form> <script> function formSubmit() { document.getElementById("logoutForm").submit(); } </script> <c:if test="${pageContext.request.userPrincipal.name != null}"> <h2> Welcome : ${pageContext.request.userPrincipal.name} | <a href="javascript:formSubmit()"> Logout</a> </h2> </c:if> Hello, ${subject}! </body> </html>
JSPで条件ステートメントを使用するには、ライブラリを追加で接続する必要があります。ライブラリjstlを接続しますbuild.gradle
dependencies { runtime group: 'org.apache.taglibs', name: 'taglibs-standard-impl', version: gradle.jstlVersion }
settings.gradle
gradle.ext.jstlVersion = '1.2.5'
実行して確認する必要があります。JavaScriptを使用する
JavaScriptがなければサイトはほとんどできません。クライアント側でコードを実行すると、ページを変更したり、リロードせずに入力データを検証したり、オンザフライでロードする「デスクトップ」アプリケーションの本格的な類似物を作成したりできます。そして、ダウンロードが長くならないように、最適化の世話をする必要があります。JavaScriptを2段階で最適化します。まず、分散ファイルを1つに結合します。これにより、サブクエリの数が減り、ダウンロード速度にプラスの効果があります。その後、受信したファイルから不要なものがすべて削除されます-スペース、コメント、変数名が短縮されます。もちろん、最適化は手動で行われるのではなく、コンパイラーに任されます。 GradleプラグインラッパーでGoogle Closure Compilerを使用します。注意!
コードと標準ライブラリを組み合わせないでください。後者は、gooqleサーバーから接続する方が適切です。高い確率で、それらは既にクライアントのキャッシュにあり、ダウンロードする必要はありません。プラグインをgradleに接続します。build.gradle plugins { id "com.eriwen.gradle.js" version "1.12.1" }
縮小するファイルを作成します。login.js $(function () { $("#tabs").tabs(); });
login.jspに変更を加えます。Googleサーバーからjqueryライブラリだけでなく、将来の縮小ファイルも接続します。 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script> <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css"> <script src="js/login.js"></script>
完全なファイル <%@ page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Login Page</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script> <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css"> <script src="js/login.js"></script> </head> <body> <div align="center"> <div id="tabs"> <ul> <li><a href="#tabs-1">Sign in</a></li> <li><a href="#tabs-2">Create user</a></li> </ul> <div id="tabs-1"> <h3>Login with Username and Password</h3> <form:form id='formLogin' action='./login' method='POST'> <table> <tr> <td>username:</td> <td><input type='text' name='username' value='' autofocus></td> </tr> <tr> <td>password:</td> <td><input type='password' name='password'/></td> </tr> <tr> <td><input type='checkbox' name='remember-me'/></td> <td>remember-me</td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit"/></td> </tr> </table> </form:form> </div> <div id="tabs-2"> <h3>Create user</h3> <form:form id='formCreate' action='./createUser' method='POST'> <table> <tr> <td>username:</td> <td><input type='text' name='username' value=''></td> </tr> <tr> <td>password:</td> <td><input type='password' name='password'/></td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit"/></td> </tr> </table> </form:form> </div> </div> </div> </body> </html>
dispatcher-servlet.xmlで、完成したファイルが保存されるフォルダーを指定します。 <mvc:resources mapping="/js/**" location="/js/"/>
完全なファイル <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:view-controller path="/" view-name="index"/> <mvc:view-controller path="/login" view-name="login"/> <mvc:resources mapping="/js/**" location="/js/"/> <mvc:annotation-driven/> <context:component-scan base-package="com.intetm.web"/> </beans>
javascriptファイルを収集および縮小するbuild.gradleタスクに追加することのみが残ります。Google Closure Compilerはソースマップの作成方法も知っているため、理論的には縮小されたファイルをすぐに接続できます。実際には、デバッグ中に変数は短縮され、デバッグが困難になります。したがって、それを簡単にします-開発中は縮小せずに行い、最終アセンブリにのみ接続します。 javascript.source { login { js { srcDir "src/main/js/login" include "*.js" } } } combineJs { source = javascript.source.login.js.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/combine/login.js") } minifyJs { source = combineJs //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/min/login.js") //sourceMap = file("${buildDir}/js/min/login.sourcemap.json") closure { warningLevel = 'QUIET' } } def dev = true; task copyJs(type: Copy) { group = 'develop' from(dev ? combineJs : minifyJs) as String into "src/main/webapp/js" } compileJava.dependsOn.add(copyJs)
完全なファイル buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' id "com.eriwen.gradle.js" version "1.12.1" } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion runtime group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion runtime group: 'org.apache.taglibs', name: 'taglibs-standard-impl', version: gradle.jstlVersion gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath inplaceMode = "hard" } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } } javascript.source { login { js { srcDir "src/main/js/login" include "*.js" } } } combineJs { source = javascript.source.login.js.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/combine/login.js") } minifyJs { source = combineJs //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/min/login.js") //sourceMap = file("${buildDir}/js/min/login.sourcemap.json") closure { warningLevel = 'QUIET' } } def dev = true; task copyJs(type: Copy) { group = 'develop' from(dev ? combineJs : minifyJs) as String into "src/main/webapp/js" } compileJava.dependsOn.add(copyJs)
ご覧のとおり、copyJsプロシージャは、モードに応じて圧縮バージョンまたは非圧縮バージョンを接続します。プロシージャを手動で呼び出す必要がないように、Javaファイルのコンパイルに依存関係を追加しました。これで、すべてのファイルが同時にコンパイルされます。CSSを使用する
CSSの操作はJavaScriptと変わりません。ファイルも1つの圧縮ファイルにマージされます。プラグインと関数の名前は異なり、JavaScriptの代わりにCSS文字が使用されます。プラグインとタスクをbuild.gradleに追加します。 plugins { id "com.eriwen.gradle.css" version "1.11.1" } css.source { login { css { srcDir "src/main/css/login" include "*.css" } } } combineCss { source = css.source.login.css.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/css/combine/login.css") } minifyCss { source = combineCss dest = file("${buildDir}/css/min/login.css") yuicompressor { // Optional lineBreakPos = -1 } } task copyCss(type: Copy) { group = 'develop' from(dev ? combineCss : minifyCss) as String into "src/main/webapp/css" } compileJava.dependsOn.add(copyCss)
完全なファイル buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' id "com.eriwen.gradle.js" version "1.12.1" id "com.eriwen.gradle.css" version "1.11.1" } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion runtime group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion runtime group: 'org.apache.taglibs', name: 'taglibs-standard-impl', version: gradle.jstlVersion gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath inplaceMode = "hard" } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } } javascript.source { login { js { srcDir "src/main/js/login" include "*.js" } } } combineJs { source = javascript.source.login.js.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/combine/login.js") } minifyJs { source = combineJs //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/min/login.js") //sourceMap = file("${buildDir}/js/min/login.sourcemap.json") closure { warningLevel = 'QUIET' } } css.source { login { css { srcDir "src/main/css/login" include "*.css" } } } combineCss { source = css.source.login.css.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/css/combine/login.css") } minifyCss { source = combineCss dest = file("${buildDir}/css/min/login.css") yuicompressor { // Optional lineBreakPos = -1 } } def dev = true; task copyJs(type: Copy) { group = 'develop' from(dev ? combineJs : minifyJs) as String into "src/main/webapp/js" } task copyCss(type: Copy) { group = 'develop' from(dev ? combineCss : minifyCss) as String into "src/main/webapp/css" } compileJava.dependsOn.add(copyJs) compileJava.dependsOn.add(copyCss)
login.cssを作成しますlogin.css .tab-centered { width: 500px; } .tab-centered .ui-tabs-nav { height: 2.35em; text-align: center; } .tab-centered .ui-tabs-nav li { display: inline-block; float: none; margin: 0; }
ページにCSSを追加します。 <link rel="stylesheet" href="css/login.css"> ... <div id="tabs" class="tab-centered">
完全なファイル <%@ page contentType="text/html" pageEncoding="UTF-8" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Login Page</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js"></script> <link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/smoothness/jquery-ui.css"> <script src="js/login.js"></script> <link rel="stylesheet" href="css/login.css"> </head> <body> <div align="center"> <div id="tabs" class="tab-centered"> <ul> <li><a href="#tabs-1">Sign in</a></li> <li><a href="#tabs-2">Create user</a></li> </ul> <div id="tabs-1"> <h3>Login with Username and Password</h3> <form:form id='formLogin' action='./login' method='POST'> <table> <tr> <td>username:</td> <td><input type='text' name='username' value='' autofocus></td> </tr> <tr> <td>password:</td> <td><input type='password' name='password'/></td> </tr> <tr> <td><input type='checkbox' name='remember-me'/></td> <td>remember-me</td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit"/></td> </tr> </table> </form:form> </div> <div id="tabs-2"> <h3>Create user</h3> <form:form id='formCreate' action='./createUser' method='POST'> <table> <tr> <td>username:</td> <td><input type='text' name='username' value=''></td> </tr> <tr> <td>password:</td> <td><input type='password' name='password'/></td> </tr> <tr> <td colspan='2'><input name="submit" type="submit" value="submit"/></td> </tr> </table> </form:form> </div> </div> </div> </body> </html>
cssファイルの取得元であるspring-mvcを指定します。 <mvc:resources mapping="/css/**" location="/css/"/>
dispatcher-servlet.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:view-controller path="/" view-name="index"/> <mvc:view-controller path="/login" view-name="login"/> <mvc:resources mapping="/js/**" location="/js/"/> <mvc:resources mapping="/css/**" location="/css/"/> <mvc:annotation-driven/> <context:component-scan base-package="com.intetm.web"/> </beans>
コンパイル時に、JavaファイルとJavaScriptファイル、CSSファイルの両方が同時にコンパイルされるようになりました。結果はlocalhostページで利用可能です:8080 / gull / loginORM
レストサービスを作成する前に、ORMの形式でデータベースを操作するときにレイヤーを追加します。特定のORM実装に依存しないように、javaee-apiライブラリの一部である共通のJPAインターフェースを使用します。特定の実装(休止状態)は、起動時にのみ接続します。build.gradle compile group: 'javax', name: 'javaee-api', version: gradle.javaxVersion compile group: 'org.springframework', name: 'spring-orm', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-tx', version: gradle.springVersion runtime group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-2.1-api', version: gradle.hibernateJpaVersion runtime group: 'org.hibernate', name: 'hibernate-core', version: gradle.hibernateVersion runtime group: 'org.hibernate', name: 'hibernate-entitymanager', version: gradle.hibernateVersion
完全なファイル buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' id "com.eriwen.gradle.js" version "1.12.1" id "com.eriwen.gradle.css" version "1.11.1" } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile group: 'javax', name: 'javaee-api', version: gradle.javaxVersion runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-orm', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-tx', version: gradle.springVersion runtime group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion runtime group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-2.1-api', version: gradle.hibernateJpaVersion runtime group: 'org.hibernate', name: 'hibernate-core', version: gradle.hibernateVersion runtime group: 'org.hibernate', name: 'hibernate-entitymanager', version: gradle.hibernateVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion runtime group: 'org.apache.taglibs', name: 'taglibs-standard-impl', version: gradle.jstlVersion gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath inplaceMode = "hard" } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } } javascript.source { login { js { srcDir "src/main/js/login" include "*.js" } } } combineJs { source = javascript.source.login.js.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/combine/login.js") } minifyJs { source = combineJs //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/min/login.js") //sourceMap = file("${buildDir}/js/min/login.sourcemap.json") closure { warningLevel = 'QUIET' } } css.source { login { css { srcDir "src/main/css/login" include "*.css" } } } combineCss { source = css.source.login.css.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/css/combine/login.css") } minifyCss { source = combineCss dest = file("${buildDir}/css/min/login.css") yuicompressor { // Optional lineBreakPos = -1 } } def dev = true; task copyJs(type: Copy) { group = 'develop' from(dev ? combineJs : minifyJs) as String into "src/main/webapp/js" } task copyCss(type: Copy) { group = 'develop' from(dev ? combineCss : minifyCss) as String into "src/main/webapp/css" } compileJava.dependsOn.add(copyJs) compileJava.dependsOn.add(copyCss)
settings.gradle。 gradle.ext.javaxVersion = '7.0' gradle.ext.hibernateVersion = '5.0.2.Final' gradle.ext.hibernateJpaVersion = '1.0.0.Final'
完全なファイル rootProject.name = 'gull' //lib version gradle.ext.springVersion = '4.2.2.RELEASE' gradle.ext.springSecurityVersion = '4.0.2.RELEASE' gradle.ext.javaxVersion = '7.0' gradle.ext.hibernateVersion = '5.0.2.Final' gradle.ext.hibernateJpaVersion = '1.0.0.Final' gradle.ext.slf4jVersion = '1.7.13' gradle.ext.logbackVersion = '1.1.3' gradle.ext.hsqldbVersion = '2.3.2' gradle.ext.jstlVersion = '1.2.5' //default server config gradle.ext.serverHttpPort = 8080 gradle.ext.serverResourcesPath = "dev/resources" gradle.ext.serverContextFile = "src/test/resources/environment/jetty-context.xml" gradle.ext.serverClassPath = "src/test/resources/environment/classpath" //default database config gradle.ext.dbName = "xdb" gradle.ext.dbFile = "dev/database/devDB" gradle.ext.dbUser = "SA" gradle.ext.dbPassword = "password" gradle.ext.dbPort = 9001 gradle.ext.dbHost = "localhost" gradle.ext.dbUrl = "jdbc:hsqldb:hsql://${gradle.dbHost}:${gradle.dbPort}/${gradle.dbName}" gradle.ext.dbDrive = "org.hsqldb.jdbc.JDBCDriver"
JPAでは、2つのBeanを作成する必要があります。- EntityManagerFactory-メインBeanは、データベース、ORM、およびコード間のブリッジとして機能します。
- TransactionManager-トランザクションを管理します。
dbContext.xml <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceXmlLocation" value="classpath:persistence.xml"/> <property name="persistenceUnitName" value="defaultUnit"/> <property name="dataSource" ref="dbDataSource"/> <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/> <property name="jpaDialect" ref="jpaDialect"/> </bean> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/> <bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <context:component-scan base-package="com.intetm.db.dao"/>
完全なファイル <?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:jee="http://www.springframework.org/schema/jee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <jee:jndi-lookup id="dbDataSource" jndi-name="jdbc/Database" expected-type="javax.sql.DataSource"/> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceXmlLocation" value="classpath:persistence.xml"/> <property name="persistenceUnitName" value="defaultUnit"/> <property name="dataSource" ref="dbDataSource"/> <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/> <property name="jpaDialect" ref="jpaDialect"/> </bean> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/> <bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <context:component-scan base-package="com.intetm.db.dao"/> </beans>
すでに休止状態になっているため、設定ファイルpersistence.xmlを作成する必要があります。ロギングポリシー、データベースのタイプ、データベース構造を更新するためのポリシー、およびその他のサーバー固有のデータが含まれています。プロジェクトからファイルを取り出し、サーバーのclassPathにのみ接続します。 <?xml version="1.0" encoding="UTF-8"?> <persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence.xsd" version="2.1"> <persistence-unit name="defaultUnit" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <jta-data-source>jdbc/Database</jta-data-source> <class>com.intetm.db.entity.User</class> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/> <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"/> <property name="hibernate.connection.charSet" value="UTF-8"/> <property name="hibernate.validator.apply_to_ddl" value="false"/> <property name="hibernate.validator.autoregister_listeners" value="false"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.hbm2ddl.auto" value="validate"/> </properties> </persistence-unit> </persistence>
「hibernate.hbm2ddl.auto」パラメーターで「validate」ではなく「update」を指定すると、hibernateは欠落している列とテーブルを追加することに注意してください。これは、SQLスクリプトを記述するよりも簡単な方法のようですが、少なくとも2つの事実を考慮する必要がありますa)hibernateは非破壊的なデータベース変更のみを行います。つまり、テーブルの列は削除されません。b)すべてのデータ移行は、サードパーティのメカニズムによって行われる必要があります。プロジェクトが成長するにつれて、これらの制約に遭遇する可能性は急速に増加します。ラッパーとDAOを作成するためだけに残ります。注釈によって示されるもの。User.java-テーブルのラッパー。クラス自体にはEntity注釈が付いています。テーブルの名前はによって示される表。クラスには、パラメーターのないコンストラクターが必要です。 @Entity @Table(name = User.TABLE) public class User { … }
テーブルの列に表示されるクラスのフィールドには、列名の付いた@Columnアノテーションが付いています。これらのフィールドには、標準のgetおよびsetメソッドが存在する必要があります。 @Column(name = COLUMN_USER_NAME) private String userName;
User.java package com.intetm.db.entity; import javax.persistence.*; import java.util.ArrayList; import java.util.List; import java.util.UUID; @Entity @Table(name = User.TABLE) public class User { public static final String TABLE = "Users"; public static final String COLUMN_ID = "id"; public static final String COLUMN_USER_NAME = "userName"; public static final String COLUMN_PASSWORD = "password"; public static final String COLUMN_ENABLED = "enabled"; @Id @Column(name = COLUMN_ID, columnDefinition = "BINARY(16)") private UUID id; @Column(name = COLUMN_USER_NAME) private String userName; @Column(name = COLUMN_PASSWORD) private String password; @Column(name = COLUMN_ENABLED) private boolean enabled; @ElementCollection(targetClass = Authority.class) @Enumerated(EnumType.STRING) @CollectionTable(name = Authority.TABLE, joinColumns = @JoinColumn(name = Authority.COLUMN_USERID, referencedColumnName = COLUMN_ID)) @Column(name = Authority.COLUMN_AUTHORITY) private List<Authority> authorities; public User() { } public User(String userName, String password, Authority authority) { this.userName = userName; this.password = password; this.enabled = true; this.authorities = new ArrayList<>(); this.authorities.add(authority); } public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public List<Authority> getAuthorities() { return authorities; } public void setAuthorities(List<Authority> authorities) { this.authorities = authorities; } }
Authority.java-注釈のない単純な列挙型。テーブルへのリンクを示すすべての注釈は、User.javaで指定されます。このクラスでは、テーブルと列の名前のみがレンダリングされます。Authority.java package com.intetm.db.entity; public enum Authority { ROLE_ADMIN, ROLE_USER, ROLE_ANONYMOUS; public static final String TABLE = "authorities"; public static final String COLUMN_USERID = "userid"; public static final String COLUMN_AUTHORITY = "authority"; }
AbstractDaoは、すべてのTaoの一般化された祖先です。@PersistenceContextと@PersistenceUnitの2つの注釈が含まれています。これにより、SpringはentityManagerとentityManagerFactoryを置換する場所を決定します。データベース内のオブジェクトをロード、保存、検索する一般的な方法もあります。 @PersistenceContext private EntityManager entityManager; @PersistenceUnit private EntityManagerFactory entityManagerFactory;
AbstractDao.java package com.intetm.db.dao; import javax.persistence.*; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Path; import javax.persistence.criteria.Root; import java.util.List; import java.util.Map; public abstract class AbstractDao<Entity, ID> { private final Class entryClass; @PersistenceContext private EntityManager entityManager; @PersistenceUnit private EntityManagerFactory entityManagerFactory; public AbstractDao(Class entryClass) { this.entryClass = entryClass; } public void persist(Entity entity) { entityManager.persist(entity); } public void merge(Entity entity) { entityManager.merge(entity); } public void delete(Entity entity) { entityManager.remove(entity); } @SuppressWarnings("unchecked") public CriteriaQuery<Entity> createCriteriaQuery() { return this.getCriteriaBuilder().createQuery(entryClass); } @SuppressWarnings("unchecked") public Entity find(ID id) { return (Entity) entityManager.find(entryClass, id); } public List<Entity> find(CriteriaQuery<Entity> criteriaQuery) { TypedQuery<Entity> query = entityManager.createQuery(criteriaQuery); return query.getResultList(); } public List<Entity> find(Object... keysAndValues) { CriteriaBuilder criteriaBuilder = this.getCriteriaBuilder(); CriteriaQuery<Entity> criteriaQuery = this.createCriteriaQuery(); Root root = criteriaQuery.from(entryClass); fillQuery(criteriaQuery, keysAndValues, root, criteriaBuilder); return find(criteriaQuery); } public List<Entity> find(Map<String, Object> parameters) { Object[] array = toArray(parameters); return find(array); } @SuppressWarnings("unchecked") public long count(Object... keysAndValues) { CriteriaBuilder criteriaBuilder = this.getCriteriaBuilder(); CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class); Root<Entity> root = criteriaQuery.from(entryClass); criteriaQuery.select(criteriaBuilder.count(root)); fillQuery(criteriaQuery, keysAndValues, root, criteriaBuilder); return getEntityManager().createQuery(criteriaQuery).getSingleResult(); } public long count(Map<String, Object> parameters) { Object[] array = toArray(parameters); return count(array); } private void fillQuery(CriteriaQuery criteriaQuery, Object[] keysAndValues, Root root, CriteriaBuilder criteriaBuilder) { if (keysAndValues.length % 2 != 0) { throw new IllegalArgumentException("Expected even count argument, receive odd"); } for (int i = 0; i < keysAndValues.length; i += 2) { Path parameterPath = root.get((String) keysAndValues[i]); Object parameterValue = keysAndValues[i + 1]; criteriaQuery.where(criteriaBuilder.equal(parameterPath, parameterValue)); } } private Object[] toArray(Map<String, Object> parameters) { Object[] array = new Object[parameters.size() * 2]; int i = 0; for (Map.Entry<String, Object> parameter : parameters.entrySet()) { array[i] = parameter.getKey(); i++; array[i] = parameter.getValue(); i++; } return array; } public List<Entity> selectAll() { CriteriaQuery<Entity> criteriaQuery = createCriteriaQuery(); criteriaQuery.from(entryClass); return find(criteriaQuery); } public EntityManager getEntityManager() { return entityManager; } public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } public Object getEntityManagerFactory() { return entityManagerFactory; } public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { this.entityManagerFactory = entityManagerFactory; } public CriteriaBuilder getCriteriaBuilder() { return this.entityManager.getCriteriaBuilder(); } }
UserDaoは、Userテーブルの特定のDao実装です。@ Repositoryアノテーションでマークされ、Springが特別なBeanの必要性を示していることを示します。Springは、アノテーション@PersistenceContextおよび@PersistenceUnitを持つクラスのフィールドを探し、AbstractDao基本クラスでそれらを見つけて入力します。 @Repository("userDao") public class UserDao extends AbstractDao<User, UUID>
UserDao.java package com.intetm.db.dao; import com.intetm.db.entity.User; import org.springframework.stereotype.Repository; import java.util.UUID; @Repository("userDao") public class UserDao extends AbstractDao<User, UUID> { public UserDao() { super(User.class); } @Override public void persist(User user) { if (user.getId() == null) { user.setId(UUID.randomUUID()); } super.persist(user); } public boolean isUserExsist(String userName) { return count(User.COLUMN_USER_NAME, userName) != 0; } }
RESTサービス
レストサービスは、アプリケーションへの入り口と出口であり、その結果はすべて異なります。入力データストリームはアプリケーションによって制御されないため、明らかに不正なデータが含まれている可能性があります。これにより、サービスに追加のエラー処理要件が課されます。リクエストの処理中にエラーが発生した場合、データベースを制御不能に変更して、クライアントのスタックトレースに渡さないでください。エラーが発生した場合の正しい動作は、適用されたすべての変更のロールバック、エラーのログ記録、およびクライアントへの正しいメッセージの返信です。この動作を実装してみましょう。まず、ユーザーを作成するサービスを作成します。まず、ユーザーが存在するかどうかを確認します。その場合、エラーがスローされます。別のケースでは、パスワードがハッシュされ、ユーザーがデータベースに保存されます。LoginService package com.intetm.service.login; import com.intetm.db.dao.UserDao; import com.intetm.db.entity.Authority; import com.intetm.db.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional; public class LoginService { @Autowired private UserDao userDao; @Autowired private PasswordEncoder encoder; @Transactional public User createUser(String userName, String password, Authority authority) throws UserExistsException { if (userDao.isUserExsist(userName)) { throw new UserExistsException(userName); } String hash = encoder.encode(password); User user = new User(userName, hash, authority); userDao.persist(user); return user; } public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } public PasswordEncoder getEncoder() { return encoder; } public void setEncoder(PasswordEncoder encoder) { this.encoder = encoder; } }
彼によってスローされた例外。UserExistsException package com.intetm.service.login; public class UserExistsException extends Exception { public UserExistsException(String userName) { super("User " + userName + " already exists!"); } }
LoginControllerで、作成要求を処理するメソッドを追加します。2つのパラメーター(ユーザー、パスワード)を受け取り、ユーザーの簡単な説明を返すか、エラーをスローします。これはアプリケーションエラーではなく、不正なデータであり、ログ記録の必要がないため、Stacktraceは保存しません。 @RequestMapping(value = "/createUser", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST) @ResponseBody public UserDetails createUser(@RequestParam String username, @RequestParam String password) throws ServiceException { try { User user = loginService.createUser(username, password, ROLE_USER); return new UserDetails(user); } catch (UserExistsException exception) { throw new ServiceException(exception.getMessage()); } }
LoginController package com.intetm.web.login; import com.intetm.db.entity.User; import com.intetm.service.login.LoginService; import com.intetm.service.login.UserExistsException; import com.intetm.web.exception.ServiceException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import static com.intetm.db.entity.Authority.ROLE_USER; @Controller public class LoginController { private static final Logger logger = LoggerFactory.getLogger(LoginController.class); private static final String HELLO_VIEW = "hello"; @Autowired private LoginService loginService; @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello(Model model) { logger.debug("hello page"); model.addAttribute("subject", "world"); return HELLO_VIEW; } @RequestMapping(value = "/createUser", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST) @ResponseBody public UserDetails createUser(@RequestParam String username, @RequestParam String password) throws ServiceException { try { User user = loginService.createUser(username, password, ROLE_USER); return new UserDetails(user); } catch (UserExistsException exception) { throw new ServiceException(exception.getMessage()); } } }
JSONに変換される戻りオブジェクト。ユーザー詳細 package com.intetm.web.login; import com.intetm.db.entity.Authority; import com.intetm.db.entity.User; import java.util.List; class UserDetails { private String userName; private List<Authority> authorities; public UserDetails(User user) { this.userName = user.getUserName(); this.authorities = user.getAuthorities(); } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public List<Authority> getAuthorities() { return authorities; } public void setAuthorities(List<Authority> authorities) { this.authorities = authorities; } }
エラーをキャッチして正しく処理するためだけに残ります。これを行うには、アシスタントクラスを作成します。@ControllerAdviceアノテーションでタグ付けされています。
要求処理中にエラーが発生した場合、SpringはExceptionHandlerアノテーションを使用して元のコントローラーとヘルパーでメソッドを探します。エラークラスが一致する場合、このメソッドが呼び出され、メソッドの結果がクライアントに返されます。これにより、エラーが発生して正しくログに記録された場合でも、意味のある結果を返すことができます。ExceptionController package com.intetm.web.exception; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @ControllerAdvice public class ExceptionController extends ResponseEntityExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(ExceptionController.class); @ExceptionHandler(ServiceException.class) @ResponseBody @ResponseStatus(code = HttpStatus.BAD_REQUEST) public String handleServiceException(ServiceException ex) { if (ex.isNeedLogging()) { logger.error(ex.getMessage(), ex); } return ex.getMessage(); } @ExceptionHandler(RuntimeException.class) @ResponseBody @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR) public String handleException(RuntimeException ex) { logger.error(ex.getMessage(), ex); return ex.getMessage(); } }
コントローラーによってスローされた例外。デフォルトでは、これはデータエラーであるため、ログに記録すべきではないと見なされます。ServiceException package com.intetm.web.exception; public class ServiceException extends Exception { private boolean needLogging = false; public ServiceException() { super(); } public ServiceException(String message) { super(message); } public ServiceException(boolean needLogging) { super(); this.needLogging = needLogging; } public ServiceException(String message, boolean needLogging) { super(message); this.needLogging = needLogging; } public boolean isNeedLogging() { return needLogging; } public void setNeedLogging(boolean needLogging) { this.needLogging = needLogging; } }
javascriptに送信コードを追加します。login.js $(function () { $("#tabs").tabs(); }); $(document).ready(function () { var frm = $("#formCreate") frm.submit(function (event) { event.preventDefault(); $.ajax({ type: frm.attr('method'), url: frm.attr('action'), data: frm.serialize(), success: function (data) { alert('Username:' + data.userName + "\nrole:" + data.authorities[0]); }, error: function (xhr, str) { alert('User exist!'); } }); return false; }); });
いくつかの小さなことをすることが残っています。JavaオブジェクトをJSONに変換するBeanを追加します。 <bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="jacksonMessageConverter"/> </list> </property> </bean>
ライブラリ接続build.gradle java json.
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: gradle.jacksonVersion compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: gradle.jacksonVersion
buildscript { repositories { mavenLocal() mavenCentral() jcenter() } dependencies { classpath group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } } plugins { id "org.akhikhl.gretty" version "1.2.4" id 'org.liquibase.gradle' version '1.1.1' id "com.eriwen.gradle.js" version "1.12.1" id "com.eriwen.gradle.css" version "1.11.1" } group 'com.intetm' version '0.1' apply plugin: 'java' apply plugin: 'war' apply from: 'database.gradle' //noinspection GroovyUnusedAssignment sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile group: 'javax', name: 'javaee-api', version: gradle.javaxVersion runtime group: 'org.springframework', name: 'spring-jdbc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-webmvc', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-orm', version: gradle.springVersion compile group: 'org.springframework', name: 'spring-tx', version: gradle.springVersion compile group: 'org.springframework.security', name: 'spring-security-web', version: gradle.springSecurityVersion runtime group: 'org.springframework.security', name: 'spring-security-config', version: gradle.springSecurityVersion runtime group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-2.1-api', version: gradle.hibernateJpaVersion runtime group: 'org.hibernate', name: 'hibernate-core', version: gradle.hibernateVersion runtime group: 'org.hibernate', name: 'hibernate-entitymanager', version: gradle.hibernateVersion compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: gradle.jacksonVersion compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: gradle.jacksonVersion compile group: 'org.slf4j', name: 'slf4j-api', version: gradle.slf4jVersion runtime group: 'ch.qos.logback', name: 'logback-classic', version: gradle.logbackVersion runtime group: 'org.apache.taglibs', name: 'taglibs-standard-impl', version: gradle.jstlVersion gretty group: 'org.hsqldb', name: 'hsqldb', version: gradle.hsqldbVersion } def serverHttpPort = project.properties['serverHttpPort'] ?: gradle.serverHttpPort def serverResourcesPath = project.properties['serverResourcesPath'] ?: gradle.serverResourcesPath def serverContextFile = project.properties['serverContextFile'] ?: gradle.serverContextFile def serverClassPath = [project.properties['serverClassPath'] ?: gradle.serverClassPath] as Set def dbUser = project.properties['dbUser'] ?: gradle.dbUser def dbPassword = project.properties['dbPassword'] ?: gradle.dbPassword def dbUrl = project.properties['dbUrl'] ?: gradle.dbUrl gretty { httpPort = serverHttpPort serverConfigFile = serverContextFile classPath = serverClassPath inplaceMode = "hard" } task copyEnvironment(type: Copy) { group = 'develop' from 'src/test/resources/environment' into serverResourcesPath } liquibase { activities { //noinspection GroovyAssignabilityCheck main { changeLogFile 'src/sql/main/changelog.xml' url dbUrl username dbUser password dbPassword } dev { changeLogFile 'src/sql/dev/changelog.xml' url dbUrl username dbUser password dbPassword } } } task updateDbMain(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main' tasks.update.execute() } } task updateDbDev(dependsOn: startDatabase) { group = 'develop' doLast { liquibase.runList = 'main, dev' tasks.update.execute() } } javascript.source { login { js { srcDir "src/main/js/login" include "*.js" } } } combineJs { source = javascript.source.login.js.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/combine/login.js") } minifyJs { source = combineJs //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/js/min/login.js") //sourceMap = file("${buildDir}/js/min/login.sourcemap.json") closure { warningLevel = 'QUIET' } } css.source { login { css { srcDir "src/main/css/login" include "*.css" } } } combineCss { source = css.source.login.css.files //noinspection GrReassignedInClosureLocalVar dest = file("${buildDir}/css/combine/login.css") } minifyCss { source = combineCss dest = file("${buildDir}/css/min/login.css") yuicompressor { // Optional lineBreakPos = -1 } } def dev = true; task copyJs(type: Copy) { group = 'develop' from(dev ? combineJs : minifyJs) as String into "src/main/webapp/js" } task copyCss(type: Copy) { group = 'develop' from(dev ? combineCss : minifyCss) as String into "src/main/webapp/css" } compileJava.dependsOn.add(copyJs) compileJava.dependsOn.add(copyCss)
settings.gradle
gradle.ext.jacksonVersion = '2.3.0'
rootProject.name = 'gull' //lib version gradle.ext.springVersion = '4.2.2.RELEASE' gradle.ext.springSecurityVersion = '4.0.2.RELEASE' gradle.ext.javaxVersion = '7.0' gradle.ext.hibernateVersion = '5.0.2.Final' gradle.ext.hibernateJpaVersion = '1.0.0.Final' gradle.ext.slf4jVersion = '1.7.13' gradle.ext.logbackVersion = '1.1.3' gradle.ext.hsqldbVersion = '2.3.2' gradle.ext.jacksonVersion = '2.3.0' gradle.ext.jstlVersion = '1.2.5' //default server config gradle.ext.serverHttpPort = 8080 gradle.ext.serverResourcesPath = "dev/resources" gradle.ext.serverContextFile = "src/test/resources/environment/jetty-context.xml" gradle.ext.serverClassPath = "src/test/resources/environment/classpath" //default database config gradle.ext.dbName = "xdb" gradle.ext.dbFile = "dev/database/devDB" gradle.ext.dbUser = "SA" gradle.ext.dbPassword = "password" gradle.ext.dbPort = 9001 gradle.ext.dbHost = "localhost" gradle.ext.dbUrl = "jdbc:hsqldb:hsql://${gradle.dbHost}:${gradle.dbPort}/${gradle.dbName}" gradle.ext.dbDrive = "org.hsqldb.jdbc.JDBCDriver"
トランザクションアノテーションを有効にします。 <tx:annotation-driven transaction-manager="transactionManager"/>
dispatcher-servlet.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:view-controller path="/" view-name="index"/> <mvc:view-controller path="/login" view-name="login"/> <mvc:resources mapping="/js/**" location="/js/"/> <mvc:resources mapping="/css/**" location="/css/"/> <mvc:annotation-driven/> <context:component-scan base-package="com.intetm.web"/> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="loginService" class="com.intetm.service.login.LoginService"/> <bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <ref bean="jacksonMessageConverter"/> </list> </property> </bean> </beans>
多くのクラスが作成されていることに気付くかもしれません。これは、クラスの責任と責任を分離する試みです。この分離は完全ではなく、単なる例です。これが意味するクラス契約は次のとおりです。1)Dao-データベースの比較的低レベルの作業。データの保存、読み込み、更新。実行時に、ランタイム例外をスローして、リクエストの処理を完全に中断する場合があります。2)サービス-仕事の主な論理。エントリポイントであるパブリックメソッドは、トランザクションとしてマークされます。要求処理中に、Daoメソッドを繰り返し呼び出すことがありますが、呼び出しは同じトランザクション内で発生します。リクエストの処理全体に対する単一のトランザクションにより、エラーが発生したときに行われたすべての変更を簡単にロールバックできます。注釈の結果をよりよく理解するには、作業の原則を知る必要があります。彼に隠されているものは何もありません。このトランザクションを確認すると、SpringはパッケージBeanをプロキシでラップします。プロキシコードは、次のように概略的に表すことができます public void someMethod(){ transaction.open(); try{ subject.someMethod(); } catch(Exception exception){ transaction.setRollbackOnly(true); } finally { transaction.close(); } }
つまり、メソッドが呼び出されると、プロキシは最初にプロキシに入ります。プロキシは、トランザクションを開き、エラーが発生した場合に元のメソッドを呼び出し、トランザクションをロールバックする必要があることをマークし、呼び出しクラスに制御を戻す前にトランザクションを閉じます。おおよその作業方法を理解すると、2つの重要な結論を導き出すことができます。注釈が付けられたメソッドの実行が完了すると、トランザクションは自動的に閉じられ、変更をロールバックできなくなります。したがって、トランザクションを開いたり閉じたりする必要があるのはServiceメソッドです。一部のトレーニング例では、トランザクションはDaoレベルで開かれますが、これは根本的に間違っています。 Serviceメソッド内で2つの連続したDao呼び出しが行われた場合、2番目の呼び出しでエラーが発生した場合、最初の呼び出しの結果はデータベースに残ります!実際、2回目の呼び出しまでに、最初のトランザクションはすでに正常に終了していました!2番目の結論はもっと簡単です。トランザクションを開くためにプロキシが使用されるため、呼び出しは別のBeanから行われる必要があります。プロキシは異なるBean間の呼び出しに埋め込まれ、クラス内のメソッド呼び出しをインターセプトする機能はありません。エラー処理でクラスの責任に戻ると、サービスの動作は次のようになります。すべてがエラーなしで終了した場合、クラスは単に結果を返します。 Daoメソッド呼び出しの入力でエラーが発生すると、エラーがスローされ、変更がロールバックされます。ランタイムエラーに加えて、サービスはチェック例外をスローする場合があります。クライアントのリクエストが正しくない場合は破棄され、その理由がわかります。たとえば、一致する名前を持つユーザーを作成しようとします。これはアプリケーションエラーではないため、特別な方法で処理する必要があります。そのようなクエリは、プロセスのロジックに応じて、データベースを変更することもできます。注意!デフォルトでは、ロールバックはランタイム例外の場合にのみ発生します。変更がロールバックされ、チェックされた例外が発生するようにするには、rollbackForパラメーターを使用して変更を追加で指定する必要があります。コード例: @Transactional(rollbackFor = Exception.class) public void someMethod() throws Exception { ... }
次に、例外を除き、変更のロールバックが行われます。3)コントローラー-要求を受け入れます。最小値はそれを処理し、サービスに渡します。サービスから正常な応答を受信した場合-応答を生成します。チェックされたエラーは、必要に応じて適切なコンテナに再パッケージ化され、残りは次のレベルに転送されます。その役割は重要ではないように見えますが、サービスとマージしようとしないでください。最初の段階では、エントリポイントは1つですが、その数は増える可能性があります。たとえば、APIを使用してモバイルクライアントを追加します。4)コントローラーのアドバイス-コントローラーの追加クラスは、エラーの処理に役立ちます。すべてのランタイム例外をインターセプトし、ログに記録を作成し、クライアントに必要な最小限の情報のみを提供します。チェック例外の場合、状況は異なります。これらもすべてインターセプトされますが、特別なフラグの付いたエラーのみがログに記録されます。同様の違いは、エラーの性質によるものです。前者は実際にはアプリケーションエラーであり、ログインする必要があります。制御された例外は、ユーザーが入力したデータの不正確さのみを表示し、ログに記載する必要はありません。おわりに
結果の例は小さな「Hello、world!」とあまり似ていませんが、すべてではなく基本的なコンポーネントのみが含まれています。テストやローカライズなどの船外のものは取り残されました。それらがなければ、アプリケーションを完了したと見なすことはできません。アプリケーションのソースコードはgithubで入手できます。打ち上げは2行で行われます。 gradlew updateDbDev gradlew jettyStart
必要なのは、JDKのインストールだけです。他のすべての依存関係はその場で起動します。しかし、同時に、アプリケーションはモノリシックではありません。必要に応じて、データベースをローカルで外部データベースに置き換え、外部サーブレットコンテナを使用し、VCSと競合することなくロギングポリシーを設定できます。