2.3. はじめてのSpring MVCアプリケーション¶
Spring MVCの、詳細な使い方の解説に入る前に、実際にSpring MVCに触れることで、Spring MVCを用いたWebアプリケーションの開発に対するイメージをつかむ。
2.3.1. 検証環境¶
本節の説明では、次の環境で動作検証している。(他の環境で実施する際は、本書をベースに適宜読み替えて設定していくこと。)
種別 |
プロダクト |
---|---|
OS |
Windows 10 |
JVM |
Java 17 |
IDE |
Spring Tool Suite 4.17.1.RELEASE (以降「STS」と呼ぶ。設定方法はSTS4の設定手順を参照されたい。) |
Build Tool |
Apache Maven 3.8.6 (以降「Maven」と呼ぶ) |
Application Server |
Apache Tomcat 10.1.15 |
Web Browser |
Google Chrome 117 |
Note
インターネット接続するためにプロキシサーバーを介する必要がある場合は、STSのProxy設定とMavenのProxy設定が必要である。
2.3.2. 新規プロジェクト作成¶
インターネットからmvn archetype:generate
を利用して、プロジェクトを作成する。
mvn archetype:generate -B^
-DarchetypeGroupId=com.github.macchinetta.blank^
-DarchetypeArtifactId=macchinetta-web-blank-jsp-archetype^
-DarchetypeVersion=1.10.0.RELEASE^
-DgroupId=com.example.helloworld^
-DartifactId=helloworld^
-Dversion=1.0.0-SNAPSHOT
ここではWindows上にプロジェクトの元を作成する。
C:\work>mvn archetype:generate -B^
More? -DarchetypeGroupId=com.github.macchinetta.blank^
More? -DarchetypeArtifactId=macchinetta-web-blank-jsp-archetype^
More? -DarchetypeVersion=1.10.0.RELEASE^
More? -DgroupId=com.example.helloworld^
More? -DartifactId=helloworld^
More? -Dversion=1.0.0-SNAPSHOT
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.2.1:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.2.1:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.2.1:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] Archetype repository not defined. Using the one from [com.github.macchinetta.blank:macchinetta-web-blank-jsp-archetype:1.10.0.RELEASE] found in catalog local
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: macchinetta-web-blank-jsp-archetype:1.10.0.RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.example.helloworld
[INFO] Parameter: artifactId, Value: helloworld
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: package, Value: com.example.helloworld
[INFO] Parameter: packageInPathFormat, Value: com/example/helloworld
[INFO] Parameter: package, Value: com.example.helloworld
[INFO] Parameter: ProjectName, Value: Helloworld
[INFO] Parameter: groupId, Value: com.example.helloworld
[INFO] Parameter: artifactId, Value: helloworld
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Project created from Archetype in dir: C:\work\helloworld
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.859 s
[INFO] Finished at: 2023-12-27T09:05:15+09:00
[INFO] ------------------------------------------------------------------------
mvn archetype:generate -B^
-DarchetypeGroupId=com.github.macchinetta.blank^
-DarchetypeArtifactId=macchinetta-web-blank-thymeleaf-archetype^
-DarchetypeVersion=1.10.0.RELEASE^
-DgroupId=com.example.helloworld^
-DartifactId=helloworld^
-Dversion=1.0.0-SNAPSHOT
ここではWindows上にプロジェクトの元を作成する。
C:\work>mvn archetype:generate -B^
More? -DarchetypeGroupId=com.github.macchinetta.blank^
More? -DarchetypeArtifactId=macchinetta-web-blank-thymeleaf-archetype^
More? -DarchetypeVersion=1.10.0.RELEASE^
More? -DgroupId=com.example.helloworld^
More? -DartifactId=helloworld^
More? -Dversion=1.0.0-SNAPSHOT
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.2.1:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.2.1:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.2.1:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] Archetype repository not defined. Using the one from [com.github.macchinetta.blank:macchinetta-web-blank-thymeleaf-archetype:1.10.0.RELEASE] found in catalog local
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: macchinetta-web-blank-thymeleaf-archetype:1.10.0.RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.example.helloworld
[INFO] Parameter: artifactId, Value: helloworld
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: package, Value: com.example.helloworld
[INFO] Parameter: packageInPathFormat, Value: com/example/helloworld
[INFO] Parameter: package, Value: com.example.helloworld
[INFO] Parameter: ProjectName, Value: Helloworld
[INFO] Parameter: groupId, Value: com.example.helloworld
[INFO] Parameter: artifactId, Value: helloworld
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Project created from Archetype in dir: C:\work\helloworld
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.859 s
[INFO] Finished at: 2023-12-27T09:05:15+09:00
[INFO] ------------------------------------------------------------------------
mvn archetype:generate -B^
-DarchetypeGroupId=com.github.macchinetta.blank^
-DarchetypeArtifactId=macchinetta-web-blank-xmlconfig-jsp-archetype^
-DarchetypeVersion=1.10.0.RELEASE^
-DgroupId=com.example.helloworld^
-DartifactId=helloworld^
-Dversion=1.0.0-SNAPSHOT
ここではWindows上にプロジェクトの元を作成する。
C:\work>mvn archetype:generate -B^
More? -DarchetypeGroupId=com.github.macchinetta.blank^
More? -DarchetypeArtifactId=macchinetta-web-blank-xmlconfig-jsp-archetype^
More? -DarchetypeVersion=1.10.0.RELEASE^
More? -DgroupId=com.example.helloworld^
More? -DartifactId=helloworld^
More? -Dversion=1.0.0-SNAPSHOT
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.2.1:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.2.1:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.2.1:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] Archetype repository not defined. Using the one from [com.github.macchinetta.blank:macchinetta-web-blank-xmlconfig-jsp-archetype:1.10.0.RELEASE] found in catalog remote
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: macchinetta-web-blank-xmlconfig-jsp-archetype:1.10.0.RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.example.helloworld
[INFO] Parameter: artifactId, Value: helloworld
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: package, Value: com.example.helloworld
[INFO] Parameter: packageInPathFormat, Value: com/example/helloworld
[INFO] Parameter: package, Value: com.example.helloworld
[INFO] Parameter: groupId, Value: com.example.helloworld
[INFO] Parameter: artifactId, Value: helloworld
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Project created from Archetype in dir: C:\work\helloworld
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.859 s
[INFO] Finished at: 2023-12-27T09:05:15+09:00
[INFO] ------------------------------------------------------------------------
mvn archetype:generate -B^
-DarchetypeGroupId=com.github.macchinetta.blank^
-DarchetypeArtifactId=macchinetta-web-blank-xmlconfig-thymeleaf-archetype^
-DarchetypeVersion=1.10.0.RELEASE^
-DgroupId=com.example.helloworld^
-DartifactId=helloworld^
-Dversion=1.0.0-SNAPSHOT
ここではWindows上にプロジェクトの元を作成する。
C:\work>mvn archetype:generate -B^
More? -DarchetypeGroupId=com.github.macchinetta.blank^
More? -DarchetypeArtifactId=macchinetta-web-blank-xmlconfig-thymeleaf-archetype^
More? -DarchetypeVersion=1.10.0.RELEASE^
More? -DgroupId=com.example.helloworld^
More? -DartifactId=helloworld^
More? -Dversion=1.0.0-SNAPSHOT
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.2.1:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.2.1:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.2.1:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] Archetype repository not defined. Using the one from [com.github.macchinetta.blank:macchinetta-web-blank-xmlconfig-thymeleaf-archetype:1.10.0.RELEASE] found in catalog remote
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: macchinetta-web-blank-xmlconfig-thymeleaf-archetype:1.10.0.RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.example.helloworld
[INFO] Parameter: artifactId, Value: helloworld
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: package, Value: com.example.helloworld
[INFO] Parameter: packageInPathFormat, Value: com/example/helloworld
[INFO] Parameter: package, Value: com.example.helloworld
[INFO] Parameter: groupId, Value: com.example.helloworld
[INFO] Parameter: artifactId, Value: helloworld
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Project created from Archetype in dir: C:\work\helloworld
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.859 s
[INFO] Finished at: 2023-12-27T09:05:15+09:00
[INFO] ------------------------------------------------------------------------
STSのメニューから、[File] -> [Import] -> [Maven] -> [Existing Maven Projects] -> [Next]を選択し、archetypeで作成したプロジェクトを選択する。
Root Directoryに C:\work\helloworld
を設定し、Projectsにhelloworldのpom.xmlが選択された状態で、 [Finish] を押下する。
Package Explorerに、次のようなプロジェクトが生成される。
Spring MVCの設定方法を理解するために、生成されたSpring MVCの設定ファイルについて、簡単に説明する。
src/main/com/example/helloworld/config/web/SpringMvcConfig.java
package com.example.helloworld.config.web;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.Resource;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
import org.terasoluna.gfw.common.exception.ExceptionCodeResolver;
import org.terasoluna.gfw.common.exception.ExceptionLogger;
import org.terasoluna.gfw.web.codelist.CodeListInterceptor;
import org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor;
import org.terasoluna.gfw.web.exception.SystemExceptionResolver;
import org.terasoluna.gfw.web.logging.TraceLoggingInterceptor;
import org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessor;
import org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor;
import org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestDataValueProcessor;
/**
* Configure SpringMVC.
*/
@ComponentScan(basePackages = { "com.example.helloworld.app" }) // (2)
@EnableAspectJAutoProxy
@EnableWebMvc // (1)
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
/**
* Configure {@link PropertySourcesPlaceholderConfigurer} bean.
* @param properties Property files to be read
* @return Bean of configured {@link PropertySourcesPlaceholderConfigurer}
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(
@Value("classpath*:/META-INF/spring/*.properties") Resource... properties) {
PropertySourcesPlaceholderConfigurer bean = new PropertySourcesPlaceholderConfigurer();
bean.setLocations(properties);
return bean;
}
/**
* {@inheritDoc}
*/
@Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(pageableHandlerMethodArgumentResolver());
argumentResolvers.add(authenticationPrincipalArgumentResolver());
}
/**
* Configure {@link PageableHandlerMethodArgumentResolver} bean.
* @return Bean of configured {@link PageableHandlerMethodArgumentResolver}
*/
@Bean
public PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver() {
return new PageableHandlerMethodArgumentResolver();
}
/**
* Configure {@link AuthenticationPrincipalArgumentResolver} bean.
* @return Bean of configured {@link AuthenticationPrincipalArgumentResolver}
*/
@Bean
public AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver() {
return new AuthenticationPrincipalArgumentResolver();
}
/**
* {@inheritDoc}
*/
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* {@inheritDoc}
*/
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations(
"/resources/", "classpath:META-INF/resources/").setCachePeriod(
60 * 60);
}
/**
* {@inheritDoc}
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
addInterceptor(registry, traceLoggingInterceptor());
addInterceptor(registry, transactionTokenInterceptor());
addInterceptor(registry, codeListInterceptor());
}
/**
* Common processes used in #addInterceptors.
* @param registry {@link InterceptorRegistry}
* @param interceptor {@link HandlerInterceptor}
*/
private void addInterceptor(InterceptorRegistry registry,
HandlerInterceptor interceptor) {
registry.addInterceptor(interceptor).addPathPatterns("/**")
.excludePathPatterns("/resources/**");
}
/**
* Configure {@link TraceLoggingInterceptor} bean.
* @return Bean of configured {@link TraceLoggingInterceptor}
*/
@Bean
public TraceLoggingInterceptor traceLoggingInterceptor() {
return new TraceLoggingInterceptor();
}
/**
* Configure {@link TransactionTokenInterceptor} bean.
* @return Bean of configured {@link TransactionTokenInterceptor}
*/
@Bean
public TransactionTokenInterceptor transactionTokenInterceptor() {
return new TransactionTokenInterceptor();
}
/**
* Configure {@link CodeListInterceptor} bean.
* @return Bean of configured {@link CodeListInterceptor}
*/
@Bean
public CodeListInterceptor codeListInterceptor() {
CodeListInterceptor codeListInterceptor = new CodeListInterceptor();
codeListInterceptor.setCodeListIdPattern(Pattern.compile("CL_.+"));
return codeListInterceptor;
}
/**
* {@inheritDoc}
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp"); // (3)
}
/**
* Configure {@link RequestDataValueProcessor} bean.
* @return Bean of configured {@link CompositeRequestDataValueProcessor}
*/
@Bean("requestDataValueProcessor")
public RequestDataValueProcessor requestDataValueProcessor() {
return new CompositeRequestDataValueProcessor(csrfRequestDataValueProcessor(), transactionTokenRequestDataValueProcessor());
}
/**
* Configure {@link CsrfRequestDataValueProcessor} bean.
* @return Bean of configured {@link CsrfRequestDataValueProcessor}
*/
@Bean
public CsrfRequestDataValueProcessor csrfRequestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
/**
* Configure {@link TransactionTokenRequestDataValueProcessor} bean.
* @return Bean of configured {@link TransactionTokenRequestDataValueProcessor}
*/
@Bean
public TransactionTokenRequestDataValueProcessor transactionTokenRequestDataValueProcessor() {
return new TransactionTokenRequestDataValueProcessor();
}
/**
* Configure {@link SystemExceptionResolver} bean.
* @param exceptionCodeResolver Bean defined by ApplicationContext#exceptionCodeResolver
* @see com.example.helloworld.config.app.ApplicationContext#exceptionCodeResolver()
* @return Bean of configured {@link SystemExceptionResolver}
*/
@Bean("systemExceptionResolver")
public SystemExceptionResolver systemExceptionResolver(
ExceptionCodeResolver exceptionCodeResolver) {
SystemExceptionResolver bean = new SystemExceptionResolver();
bean.setExceptionCodeResolver(exceptionCodeResolver);
bean.setOrder(3);
Properties exceptionMappings = new Properties();
exceptionMappings.setProperty("ResourceNotFoundException",
"common/error/resourceNotFoundError");
exceptionMappings.setProperty("BusinessException",
"common/error/businessError");
exceptionMappings.setProperty("InvalidTransactionTokenException",
"common/error/transactionTokenError");
exceptionMappings.setProperty(".DataAccessException",
"common/error/dataAccessError");
bean.setExceptionMappings(exceptionMappings);
Properties statusCodes = new Properties();
statusCodes.setProperty("common/error/resourceNotFoundError", String
.valueOf(HttpStatus.NOT_FOUND.value()));
statusCodes.setProperty("common/error/businessError", String.valueOf(
HttpStatus.CONFLICT.value()));
statusCodes.setProperty("common/error/transactionTokenError", String
.valueOf(HttpStatus.CONFLICT.value()));
statusCodes.setProperty("common/error/dataAccessError", String.valueOf(
HttpStatus.INTERNAL_SERVER_ERROR.value()));
bean.setStatusCodes(statusCodes);
bean.setDefaultErrorView("common/error/systemError");
bean.setDefaultStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
return bean;
}
/**
* Configure messages logging AOP.
* @param exceptionLogger Bean defined by ApplicationContext#exceptionLogger
* @see com.example.helloworld.config.app.ApplicationContext#exceptionLogger()
* @return Bean of configured {@link HandlerExceptionResolverLoggingInterceptor}
*/
@Bean("handlerExceptionResolverLoggingInterceptor")
public HandlerExceptionResolverLoggingInterceptor handlerExceptionResolverLoggingInterceptor(
ExceptionLogger exceptionLogger) {
HandlerExceptionResolverLoggingInterceptor bean = new HandlerExceptionResolverLoggingInterceptor();
bean.setExceptionLogger(exceptionLogger);
return bean;
}
/**
* Configure messages logging AOP advisor.
* @param handlerExceptionResolverLoggingInterceptor Bean defined by #handlerExceptionResolverLoggingInterceptor
* @see #handlerExceptionResolverLoggingInterceptor(ExceptionLogger)
* @return Advisor configured for PointCut
*/
@Bean
public Advisor handlerExceptionResolverLoggingInterceptorAdvisor(
HandlerExceptionResolverLoggingInterceptor handlerExceptionResolverLoggingInterceptor) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(
"execution(* org.springframework.web.servlet.HandlerExceptionResolver.resolveException(..))");
return new DefaultPointcutAdvisor(pointcut, handlerExceptionResolverLoggingInterceptor);
}
}
項番 |
説明 |
---|---|
(1)
|
|
(2)
|
Spring MVCで使用するコンポーネントを探すパッケージを定義する。 |
(3)
|
JSP用の |
src/main/com/example/helloworld/config/web/SpringMvcConfig.java
package com.example.helloworld.config.web;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.Resource;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
import org.terasoluna.gfw.common.exception.ExceptionCodeResolver;
import org.terasoluna.gfw.common.exception.ExceptionLogger;
import org.terasoluna.gfw.web.codelist.CodeListInterceptor;
import org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor;
import org.terasoluna.gfw.web.exception.SystemExceptionResolver;
import org.terasoluna.gfw.web.logging.TraceLoggingInterceptor;
import org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessor;
import org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor;
import org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestDataValueProcessor;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect;
import org.thymeleaf.spring6.SpringTemplateEngine;
import org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring6.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
/**
* Configure SpringMVC.
*/
@ComponentScan(basePackages = { "com.example.helloworld.app" }) // (2)
@EnableAspectJAutoProxy
@EnableWebMvc // (1)
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
/**
* Configure {@link PropertySourcesPlaceholderConfigurer} bean.
* @param properties Property files to be read
* @return Bean of configured {@link PropertySourcesPlaceholderConfigurer}
*/
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(
@Value("classpath*:/META-INF/spring/*.properties") Resource... properties) {
PropertySourcesPlaceholderConfigurer bean = new PropertySourcesPlaceholderConfigurer();
bean.setLocations(properties);
return bean;
}
/**
* {@inheritDoc}
*/
@Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(pageableHandlerMethodArgumentResolver());
argumentResolvers.add(authenticationPrincipalArgumentResolver());
}
/**
* Configure {@link PageableHandlerMethodArgumentResolver} bean.
* @return Bean of configured {@link PageableHandlerMethodArgumentResolver}
*/
@Bean
public PageableHandlerMethodArgumentResolver pageableHandlerMethodArgumentResolver() {
return new PageableHandlerMethodArgumentResolver();
}
/**
* Configure {@link AuthenticationPrincipalArgumentResolver} bean.
* @return Bean of configured {@link AuthenticationPrincipalArgumentResolver}
*/
@Bean
public AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver() {
return new AuthenticationPrincipalArgumentResolver();
}
/**
* {@inheritDoc}
*/
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* {@inheritDoc}
*/
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations(
"/resources/", "classpath:META-INF/resources/").setCachePeriod(
60 * 60);
}
/**
* {@inheritDoc}
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
addInterceptor(registry, traceLoggingInterceptor());
addInterceptor(registry, transactionTokenInterceptor());
addInterceptor(registry, codeListInterceptor());
}
/**
* Common processes used in #addInterceptors.
* @param registry {@link InterceptorRegistry}
* @param interceptor {@link HandlerInterceptor}
*/
private void addInterceptor(InterceptorRegistry registry,
HandlerInterceptor interceptor) {
registry.addInterceptor(interceptor).addPathPatterns("/**")
.excludePathPatterns("/resources/**");
}
/**
* Configure {@link TraceLoggingInterceptor} bean.
* @return Bean of configured {@link TraceLoggingInterceptor}
*/
@Bean
public TraceLoggingInterceptor traceLoggingInterceptor() {
return new TraceLoggingInterceptor();
}
/**
* Configure {@link TransactionTokenInterceptor} bean.
* @return Bean of configured {@link TransactionTokenInterceptor}
*/
@Bean
public TransactionTokenInterceptor transactionTokenInterceptor() {
return new TransactionTokenInterceptor();
}
/**
* Configure {@link CodeListInterceptor} bean.
* @return Bean of configured {@link CodeListInterceptor}
*/
@Bean
public CodeListInterceptor codeListInterceptor() {
CodeListInterceptor codeListInterceptor = new CodeListInterceptor();
codeListInterceptor.setCodeListIdPattern(Pattern.compile("CL_.+"));
return codeListInterceptor;
}
/**
* {@inheritDoc}
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(thymeleafViewResolver()); // (3)
}
/**
* Configure Thymeleaf bean.
* @return Bean of configured ThymeleafViewResolver
*/
@Bean // (3)
public ThymeleafViewResolver thymeleafViewResolver() {
ThymeleafViewResolver bean = new ThymeleafViewResolver();
bean.setTemplateEngine(templateEngine());
bean.setCharacterEncoding("UTF-8");
bean.setForceContentType(true);
bean.setContentType("text/html;charset=UTF-8");
return bean;
}
/**
* Configure ITemplateResolver Bean.
* @return Bean of configured SpringResourceTemplateResolver
*/
@Bean("templateResolver") // (5)
public ITemplateResolver templateResolver() {
SpringResourceTemplateResolver bean = new SpringResourceTemplateResolver();
bean.setPrefix("/WEB-INF/views/");
bean.setSuffix(".html");
bean.setTemplateMode("HTML");
bean.setCharacterEncoding("UTF-8");
return bean;
}
/**
* Configure SpringTemplateEngine Bean.
* @return Bean of configured SpringTemplateEngine
*/
@Bean("templateEngine") // (4)
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine bean = new SpringTemplateEngine();
bean.setTemplateResolver(templateResolver());
bean.setEnableSpringELCompiler(true);
Set<IDialect> set = new HashSet<>();
set.add(new SpringSecurityDialect());
bean.setAdditionalDialects(set);
return bean;
}
/**
* Configure {@link RequestDataValueProcessor} bean.
* @return Bean of configured {@link CompositeRequestDataValueProcessor}
*/
@Bean("requestDataValueProcessor")
public RequestDataValueProcessor requestDataValueProcessor() {
return new CompositeRequestDataValueProcessor(csrfRequestDataValueProcessor(), transactionTokenRequestDataValueProcessor());
}
/**
* Configure {@link CsrfRequestDataValueProcessor} bean.
* @return Bean of configured {@link CsrfRequestDataValueProcessor}
*/
@Bean
public CsrfRequestDataValueProcessor csrfRequestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
/**
* Configure {@link TransactionTokenRequestDataValueProcessor} bean.
* @return Bean of configured {@link TransactionTokenRequestDataValueProcessor}
*/
@Bean
public TransactionTokenRequestDataValueProcessor transactionTokenRequestDataValueProcessor() {
return new TransactionTokenRequestDataValueProcessor();
}
/**
* Configure {@link SystemExceptionResolver} bean.
* @param exceptionCodeResolver Bean defined by ApplicationContext#exceptionCodeResolver
* @see com.example.helloworld.config.app.ApplicationContext#exceptionCodeResolver()
* @return Bean of configured {@link SystemExceptionResolver}
*/
@Bean("systemExceptionResolver")
public SystemExceptionResolver systemExceptionResolver(
ExceptionCodeResolver exceptionCodeResolver) {
SystemExceptionResolver bean = new SystemExceptionResolver();
bean.setExceptionCodeResolver(exceptionCodeResolver);
bean.setOrder(3);
Properties exceptionMappings = new Properties();
exceptionMappings.setProperty("ResourceNotFoundException",
"common/error/resourceNotFoundError");
exceptionMappings.setProperty("BusinessException",
"common/error/businessError");
exceptionMappings.setProperty("InvalidTransactionTokenException",
"common/error/transactionTokenError");
exceptionMappings.setProperty(".DataAccessException",
"common/error/dataAccessError");
bean.setExceptionMappings(exceptionMappings);
Properties statusCodes = new Properties();
statusCodes.setProperty("common/error/resourceNotFoundError", String
.valueOf(HttpStatus.NOT_FOUND.value()));
statusCodes.setProperty("common/error/businessError", String.valueOf(
HttpStatus.CONFLICT.value()));
statusCodes.setProperty("common/error/transactionTokenError", String
.valueOf(HttpStatus.CONFLICT.value()));
statusCodes.setProperty("common/error/dataAccessError", String.valueOf(
HttpStatus.INTERNAL_SERVER_ERROR.value()));
bean.setStatusCodes(statusCodes);
bean.setDefaultErrorView("common/error/systemError");
bean.setDefaultStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
return bean;
}
/**
* Configure messages logging AOP.
* @param exceptionLogger Bean defined by ApplicationContext#exceptionLogger
* @see com.example.helloworld.config.app.ApplicationContext#exceptionLogger()
* @return Bean of configured {@link HandlerExceptionResolverLoggingInterceptor}
*/
@Bean("handlerExceptionResolverLoggingInterceptor")
public HandlerExceptionResolverLoggingInterceptor handlerExceptionResolverLoggingInterceptor(
ExceptionLogger exceptionLogger) {
HandlerExceptionResolverLoggingInterceptor bean = new HandlerExceptionResolverLoggingInterceptor();
bean.setExceptionLogger(exceptionLogger);
return bean;
}
/**
* Configure messages logging AOP advisor.
* @param handlerExceptionResolverLoggingInterceptor Bean defined by #handlerExceptionResolverLoggingInterceptor
* @see #handlerExceptionResolverLoggingInterceptor(ExceptionLogger)
* @return Advisor configured for PointCut
*/
@Bean
public Advisor handlerExceptionResolverLoggingInterceptorAdvisor(
HandlerExceptionResolverLoggingInterceptor handlerExceptionResolverLoggingInterceptor) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(
"execution(* org.springframework.web.servlet.HandlerExceptionResolver.resolveException(..))");
return new DefaultPointcutAdvisor(pointcut, handlerExceptionResolverLoggingInterceptor);
}
}
項番 |
説明 |
---|---|
(1)
|
|
(2)
|
Spring MVCで使用するコンポーネントを探すパッケージを定義する。 |
(3)
|
Thymeleaf用の |
(4)
|
Viewファイルの拡張子と配置場所を定義する。 |
(5)
|
Springを用いたThymeleafの実装を定義する。TemplateResolverには、idが |
src/main/resources/META-INF/spring/spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
">
<context:property-placeholder
location="classpath*:/META-INF/spring/*.properties" />
<!-- (1) Enables the Spring MVC @Controller programming model -->
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean
class="org.springframework.data.web.PageableHandlerMethodArgumentResolver" />
<bean
class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
<mvc:default-servlet-handler />
<!-- (2) -->
<context:component-scan base-package="com.example.helloworld.app" />
<mvc:resources mapping="/resources/**"
location="/resources/,classpath:META-INF/resources/"
cache-period="#{60 * 60}" />
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<bean
class="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<bean class="org.terasoluna.gfw.web.codelist.CodeListInterceptor">
<property name="codeListIdPattern" value="CL_.+" />
</bean>
</mvc:interceptor>
</mvc:interceptors>
<!-- Settings View Resolver. -->
<mvc:view-resolvers>
<!-- (3) Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<mvc:jsp prefix="/WEB-INF/views/" />
</mvc:view-resolvers>
<bean id="requestDataValueProcessor"
class="org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessor">
<constructor-arg>
<util:list>
<bean
class="org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor" />
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestDataValueProcessor" />
</util:list>
</constructor-arg>
</bean>
<!-- Setting Exception Handling. -->
<!-- Exception Resolver. -->
<bean id="systemExceptionResolver"
class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
<property name="exceptionCodeResolver" ref="exceptionCodeResolver" />
<!-- Setting and Customization by project. -->
<property name="order" value="3" />
<property name="exceptionMappings">
<map>
<entry key="ResourceNotFoundException" value="common/error/resourceNotFoundError" />
<entry key="BusinessException" value="common/error/businessError" />
<entry key="InvalidTransactionTokenException" value="common/error/transactionTokenError" />
<entry key=".DataAccessException" value="common/error/dataAccessError" />
</map>
</property>
<property name="statusCodes">
<map>
<entry key="common/error/resourceNotFoundError" value="404" />
<entry key="common/error/businessError" value="409" />
<entry key="common/error/transactionTokenError" value="409" />
<entry key="common/error/dataAccessError" value="500" />
</map>
</property>
<property name="excludedExceptions">
<array>
</array>
</property>
<property name="defaultErrorView" value="common/error/systemError" />
<property name="defaultStatusCode" value="500" />
</bean>
<!-- Setting AOP. -->
<aop:aspectj-autoproxy/>
<bean id="handlerExceptionResolverLoggingInterceptor"
class="org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor">
<property name="exceptionLogger" ref="exceptionLogger" />
</bean>
<aop:config>
<aop:advisor advice-ref="handlerExceptionResolverLoggingInterceptor"
pointcut="execution(* org.springframework.web.servlet.HandlerExceptionResolver.resolveException(..))" />
</aop:config>
</beans>
項番 |
説明 |
---|---|
(1)
|
|
(2)
|
Spring MVCで使用するコンポーネントを探すパッケージを定義する。 |
(3)
|
JSP用の |
src/main/resources/META-INF/spring/spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
">
<context:property-placeholder
location="classpath*:/META-INF/spring/*.properties" />
<!-- (1) Enables the Spring MVC @Controller programming model -->
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean
class="org.springframework.data.web.PageableHandlerMethodArgumentResolver" />
<bean
class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
<mvc:default-servlet-handler />
<!-- (2) -->
<context:component-scan base-package="com.example.helloworld.app" />
<mvc:resources mapping="/resources/**"
location="/resources/,classpath:META-INF/resources/"
cache-period="#{60 * 60}" />
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<bean
class="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<bean class="org.terasoluna.gfw.web.codelist.CodeListInterceptor">
<property name="codeListIdPattern" value="CL_.+" />
</bean>
</mvc:interceptor>
</mvc:interceptors>
<!-- Settings View Resolver. -->
<mvc:view-resolvers>
<!-- (3) Resolves views selected for rendering by @Controllers -->
<bean class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
<property name="templateEngine" ref="templateEngine" />
<property name="characterEncoding" value="UTF-8" />
<property name="forceContentType" value="true" />
<property name="contentType" value="text/html;charset=UTF-8" />
</bean>
</mvc:view-resolvers>
<!-- (4) -->
<!-- TemplateResolver. -->
<bean id="templateResolver"
class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".html" />
<property name="templateMode" value="HTML" />
<property name="characterEncoding" value="UTF-8" />
</bean>
<!-- (5) -->
<!-- TemplateEngine. -->
<bean id="templateEngine" class="org.thymeleaf.spring6.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />
<property name="enableSpringELCompiler" value="true" />
<property name="additionalDialects">
<set>
<bean class="org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect" />
</set>
</property>
</bean>
<bean id="requestDataValueProcessor"
class="org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessor">
<constructor-arg>
<util:list>
<bean
class="org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor" />
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestDataValueProcessor" />
</util:list>
</constructor-arg>
</bean>
<!-- Setting Exception Handling. -->
<!-- Exception Resolver. -->
<bean id="systemExceptionResolver"
class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
<property name="exceptionCodeResolver" ref="exceptionCodeResolver" />
<!-- Setting and Customization by project. -->
<property name="order" value="3" />
<property name="exceptionMappings">
<map>
<entry key="ResourceNotFoundException" value="common/error/resourceNotFoundError" />
<entry key="BusinessException" value="common/error/businessError" />
<entry key="InvalidTransactionTokenException" value="common/error/transactionTokenError" />
<entry key=".DataAccessException" value="common/error/dataAccessError" />
</map>
</property>
<property name="statusCodes">
<map>
<entry key="common/error/resourceNotFoundError" value="404" />
<entry key="common/error/businessError" value="409" />
<entry key="common/error/transactionTokenError" value="409" />
<entry key="common/error/dataAccessError" value="500" />
</map>
</property>
<property name="excludedExceptions">
<array>
</array>
</property>
<property name="defaultErrorView" value="common/error/systemError" />
<property name="defaultStatusCode" value="500" />
</bean>
<!-- Setting AOP. -->
<aop:aspectj-autoproxy/>
<bean id="handlerExceptionResolverLoggingInterceptor"
class="org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor">
<property name="exceptionLogger" ref="exceptionLogger" />
</bean>
<aop:config>
<aop:advisor advice-ref="handlerExceptionResolverLoggingInterceptor"
pointcut="execution(* org.springframework.web.servlet.HandlerExceptionResolver.resolveException(..))" />
</aop:config>
</beans>
項番 |
説明 |
---|---|
(1)
|
|
(2)
|
Spring MVCで使用するコンポーネントを探すパッケージを定義する。 |
(3)
|
Thymeleaf用の |
(4)
|
Viewファイルの拡張子と配置場所を定義する。 |
(5)
|
Springを用いたThymeleafの実装を定義する。TemplateResolverには、idが |
次に、Welcomeページを表示するためのControllerについて、簡単に説明する。
com.example.helloworld.app.welcome.HelloController
package com.example.helloworld.app.welcome;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Handles requests for the application home page.
*/
@Controller // (4)
public class HelloController {
private static final Logger logger = LoggerFactory
.getLogger(HelloController.class);
/**
* Simply selects the home view to render by returning its name.
*/
@GetMapping(value = "/") // (5)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,
DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate); // (6)
return "welcome/home"; // (7)
}
}
項番 |
説明 |
---|---|
(4)
|
|
(5)
|
HTTPメソッドがGETで、” |
(6)
|
Viewに渡したいオブジェクトを |
(7)
|
View名を返却する。前述「Spring MVCの設定ファイルの説明(3)」の設定により、 |
com.example.helloworld.app.welcome.HelloController
package com.example.helloworld.app.welcome;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
/**
* Handles requests for the application home page.
*/
@Controller // (6)
public class HelloController {
private static final Logger logger = LoggerFactory
.getLogger(HelloController.class);
/**
* Simply selects the home view to render by returning its name.
*/
@GetMapping(value = "/") // (7)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG,
DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate); // (8)
return "welcome/home"; // (9)
}
}
項番 |
説明 |
---|---|
(6)
|
|
(7)
|
HTTPメソッドがGETで、” |
(8)
|
Viewに渡したいオブジェクトを |
(9)
|
View名を返却する。前述「Spring MVCの設定ファイルの説明(4)」の設定により、 |
最後に、Welcomeページを表示するためのViewについて簡単に説明する。
src/main/webapp/WEB-INF/views/welcome/home.jsp
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div class="container">
<div id="wrapper">
<h1 id="title">Hello world!</h1>
<p>The time on the server is ${serverTime}.</p>
</div>
<jsp:include page="../layout/footer.jsp" />
</div>
</body>
</html>
項番 |
説明 |
---|---|
(8)
|
前述の「Controllerの説明(6)」でModelに設定したオブジェクト(serverTime)は、HttpServletRequestに格納される。
そのため、JSPで
${serverTime} と記述することで、Controllerで設定した値を画面に出力することができる。 |
src/main/webapp/WEB-INF/views/welcome/home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> <!--/* (10) */-->
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
href="../../../resources/app/css/styles.css" th:href="@{/resources/app/css/styles.css}">
</head>
<body>
<div id="wrapper">
<h1 id="title">Hello world!</h1>
<p th:text="|The time on the server is ${serverTime}.|">The time on the server is 2018/01/01 00:00:00 JST.</p> <!--/* (11) */-->
</div>
</body>
</html>
項番 |
説明 |
---|---|
(10)
|
スタンダードダイアレクトが提供する属性を使用したとき、EclipseなどのIDEでの警告を抑止するため、ネームスペースを付与する。
|
(11)
|
前述の「Controllerの説明(8)」でModelに設定したオブジェクト(serverTime)は、HttpServletRequestに格納される。
そのため、テンプレートHTMLで
${serverTime} と記述し、Thymeleafのth:text 属性を使用することで、Controllerで設定した値を画面に出力することができる。th:text 属性はHTMLエスケープをして出力を行うため、自動的にXSS対策をとることができる。詳細についてはOutput Escapingを参照されたい。
|
2.3.3. サーバーを起動する¶
http://localhost:8080/helloworld/
を入力し、実行すると下記の画面が表示される。2.3.4. エコーアプリケーションの作成¶
続いて、簡単なアプリケーションを作成する。作成するのは、次の図のようなテキストフィールドに、名前を入力すると メッセージを表示する、いわゆるエコーアプリケーションである。
2.3.4.1. フォームオブジェクトの作成¶
com.example.helloworld.app.echo
パッケージにEchoForm
クラスを作成する。プロパティを1つだけ持つ、単純なJavaBeanである。package com.example.helloworld.app.echo;
import java.io.Serializable;
public class EchoForm implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
2.3.4.2. Controllerの作成¶
次に、Controller(com.example.helloworld.app.echo.EchoController
)クラスを作成する。
package com.example.helloworld.app.echo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("echo")
public class EchoController {
@ModelAttribute // (1)
public EchoForm setUpEchoForm() {
EchoForm form = new EchoForm();
return form;
}
@GetMapping // (2)
public String index(Model model) {
return "echo/index"; // (3)
}
@PostMapping(value = "hello") // (4)
public String hello(EchoForm form, Model model) {// (5)
model.addAttribute("name", form.getName()); // (6)
return "echo/hello";
}
}
項番 |
説明 |
---|---|
(1)
|
@ModelAttribute というアノテーションを、メソッドに付加する。このアノテーションがついたメソッドの返り値は、自動でModelに追加される。Modelの属性名を、
@ModelAttribute で指定することもできるが、デフォルトでは、クラス名の先頭を小文字にした値が、属性名になる。この場合は、echoForm である。フォームの属性名は、次に説明するform:form タグ のmodelAttribute 属性の値に一致している必要がある。 |
(2)
|
メソッドに付加した
@GetMapping アノテーションのvalue 属性に何も指定しない場合、クラスに付加した@RequestMapping のルートにマッピングされる。この場合、<contextPath>/echo にGETメソッドを使用してアクセスすると、index メソッドが呼ばれる。 |
(3)
|
View名で
echo/index を返すので、ViewResolverにより、WEB-INF/views/echo/index.jsp またはWEB-INF/views/echo/index.html がレンダリングされる。 |
(4)
|
メソッドに付加した
@PostMapping アノテーションのvalue 属性にhello を指定しているので、この場合、<contextPath>/echo/hello にPOSTメソッドを使用してアクセスするとhello メソッドが呼ばれる。 |
(5)
|
引数に、EchoFormには(1)によりModelに追加されたEchoFormオブジェクトが渡される。
|
(6)
|
フォームで入力された
name を、Viewにそのまま渡す。 |
Note
@GetMapping
アノテーションもしくは@PostMapping
アノテーションをメソッドに指定する場合は、クライアントから送信されたデータの扱い方によって変えるのが一般的である。
データをサーバに保存する場合(更新系の処理の場合)は、
@PostMapping
アノテーション(POSTメソッド)。データをサーバに保存しない場合(参照系の処理の場合)は、
@GetMapping
アノテーション(GETメソッド)。
エコーアプリケーションでは、
index
メソッドはデータをサーバに保存しない処理なのでGETメソッド@GetMapping
アノテーションhello
メソッドはデータをModel
オブジェクトに保存する処理なので@PostMapping
アノテーション
を指定している。
2.3.4.3. Viewの作成¶
最後に、入力画面と、出力画面のViewを作成する。それぞれのファイルパスは、View名に合わせて、次のようになる。
入力画面 (src/main/webapp/WEB-INF/views/echo/index.jsp
) を作成する。
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
<%-- (1) --%>
<form:form modelAttribute="echoForm" action="${pageContext.request.contextPath}/echo/hello">
<form:label path="name">Input Your Name:</form:label>
<form:input path="name" />
<input type="submit" />
</form:form>
</body>
</html>
項番 |
説明 |
---|---|
(1)
|
タグライブラリを利用し、HTMLフォームを構築している。
modelAttribute 属性に、Controllerで用意したフォームオブジェクトの名前を指定する。タグライブラリはSpring Framework Documentation -The Form Tag-を参照されたい。
|
Note
<form:form>
タグのmethod
属性を省略した場合は、POSTメソッドが使用される。
出力されるHTMLは、
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
<form id="echoForm" action="/helloworld/echo/hello" method="post">
<label for="name">Input Your Name:</label>
<input id="name" name="name" type="text" value=""/>
<input type="submit" />
<input type="hidden" name="_csrf" value="43595f38-3edd-4c08-843b-3c31a00d2b15" />
</form>
</body>
</html>
となる。
出力画面 (src/main/webapp/WEB-INF/views/echo/hello.jsp
) を作成する。
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
<p>
Hello <c:out value="${name}" /> <%-- (2) --%>
</p>
</body>
</html>
項番 |
説明 |
---|---|
(2)
|
Controllerから渡された
name を出力する。c:out タグにより、XSS対策を行っている。 |
入力画面 (src/main/webapp/WEB-INF/views/echo/index.html
) を作成する。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"> <!--/* (1) */-->
<head>
<title>Echo Application</title>
</head>
<body>
<!--/* (2) */-->
<form th:object="${echoForm}" th:action="@{/echo/hello}" method="post">
<label for="name">Input Your Name:</label>
<input th:field="*{name}"> <!--/* (3) */-->
<input type="submit">
</form>
</body>
</html>
項番 |
説明 |
---|---|
(1)
|
スタンダードダイアレクトが提供する属性を使用したとき、EclipseなどのIDEでの警告を抑止するため、ネームスペースを付与する。
|
(2)
|
Thymeleafの属性を利用し、HTMLフォームを構築している。
th:object 属性に、Controllerで用意したフォームオブジェクトの名前を指定する。また、ThymeleafのリンクURL式
@{} に “/ “から始まるパスを記述することでコンテキスト相対パスが生成され、th:action 属性に指定できる。これらの属性の詳細についてはTutorial: Thymeleaf + Spring -Creating a Form-を参照されたい。
|
(3)
|
Thymeleaf + Springで提供される
th:field 属性を用いて、特定のプロパティをHTML formにバインドすることができる。th:field 属性はid 属性、name 属性、value 属性をHTMLに出力し、id 属性、name 属性にはプロパティ名が出力される。th:field 属性の詳細については、アプリケーション層の実装を参照されたい。 |
Note
<form>
タグのmethod
属性を省略した場合は、GETメソッドが使用される。
出力されるHTMLは、
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
<form action="/helloworld/echo/hello" method="post">
<input type="hidden" name="_csrf" value="43595f38-3edd-4c08-843b-3c31a00d2b15">
<label for="name">Input Your Name:</label>
<input id="name" name="name" value="">
<input type="submit">
</form>
</body>
</html>
となる。
出力画面 (src/main/webapp/WEB-INF/views/echo/hello.html
) を作成する。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Echo Application</title>
</head>
<body>
<p th:text="|Hello ${name}|"></p> <!--/* (4) */-->
</body>
</html>
項番 |
説明 |
---|---|
(4)
|
Controllerから渡された
name を出力する。th:text 属性により、XSS対策を行っている。 |
http://localhost:8080/helloworld/echo
にアクセスするとフォームが表示される。2.3.4.4. 入力チェックの実装¶
EchoForm
のname
フィールドに、入力チェックルールを指定するアノテーションを付与する。
package com.example.helloworld.app.echo;
import java.io.Serializable;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
public class EchoForm implements Serializable {
private static final long serialVersionUID = 1L;
@NotNull // (1)
@Size(min = 1, max = 5) // (2)
private String name;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
項番 |
説明 |
---|---|
(1)
|
@NotNull アノテーションをつけることで、HTTPリクエスト中にname パラメータがあることを確認する。 |
(2)
|
@Size(min = 1, max = 5) をつけることで、name のサイズが、1以上5以下であることを確認する。 |
入力チェックが実行されるように修正し、入力チェックでエラーが発生した場合の処理を実装する。
package com.example.helloworld.app.echo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("echo")
public class EchoController {
@ModelAttribute
public EchoForm setUpEchoForm() {
EchoForm form = new EchoForm();
return form;
}
@GetMapping
public String index(Model model) {
return "echo/index";
}
@PostMapping(value = "hello")
public String hello(@Validated EchoForm form, BindingResult result, Model model) { // (1)
if (result.hasErrors()) { // (2)
return "echo/index";
}
model.addAttribute("name", form.getName());
return "echo/hello";
}
}
項番 |
説明 |
---|---|
(1)
|
コントローラー側には、Validation対象の引数に
@Validated アノテーションを付加し、BindingResult オブジェクトを引数に追加する。Bean Validationによる入力チェックは、自動で行われる。結果は、
BindingResult オブジェクトに渡される。 |
(2)
|
hasErrors メソッドを実行して、エラーがあるかどうかを確認する。入力エラーがある場合は、入力画面を表示するためのView名を返却する。 |
入力画面に、入力エラーのメッセージを表示するための実装を追加する。
src/main/webapp/WEB-INF/views/echo/index.jsp
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
<form:form modelAttribute="echoForm" action="${pageContext.request.contextPath}/echo/hello">
<form:label path="name">Input Your Name:</form:label>
<form:input path="name" />
<form:errors path="name" cssStyle="color:red" /><%-- (1) --%>
<input type="submit" />
</form:form>
</body>
</html>
項番 |
説明 |
---|---|
(1)
|
入力画面には、エラーがあった場合に、エラーメッセージを表示するため、
form:errors タグを追加する。 |
出力されるHTMLは、
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
<form id="echoForm" action="/helloworld/echo/hello" method="post">
<label for="name">Input Your Name:</label>
<input id="name" name="name" type="text" value=""/>
<span id="name.errors" style="color:red">size must be between 1 and 5</span>
<input type="submit" />
<input type="hidden" name="_csrf" value="6e94a78d-4a2c-4a41-a514-0a60f0dbedaf" />
</form>
</body>
</html>
となる。
src/main/webapp/WEB-INF/views/echo/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Echo Application</title>
</head>
<body>
<form th:object="${echoForm}" th:action="@{/echo/hello}" method="post">
<label for="name">Input Your Name:</label>
<input th:field="*{name}">
<span th:errors="*{name}" style="color:red"></span> <!--/* (1) */-->
<input type="submit">
</form>
</body>
</html>
項番 |
説明 |
---|---|
(1)
|
入力画面には、エラーがあった場合に、エラーメッセージを表示するため、
th:errors 属性を追加する。 |
出力されるHTMLは、
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
<form action="/helloworld/echo/hello" method="post">
<input type="hidden" name="_csrf" value="6e94a78d-4a2c-4a41-a514-0a60f0dbedaf">
<label for="name">Input Your Name:</label>
<input id="name" name="name" value="">
<span style="color:red">size must be between 1 and 5</span>
<input type="submit">
</form>
</body>
</html>
となる。
名前を空にして送信した場合
5文字より大きいサイズで送信した場合
2.3.4.5. まとめ¶
この章では、
mvn archetype:generate
を利用したブランクプロジェクトの作成方法Spring MVCの基本的な設定方法
最も簡易な、画面遷移方法
画面間での値の引き渡し方法
シンプルな入力チェック方法
を学んだ。
上記の内容が理解できていない場合は、もう一度、本節を読み、環境構築から始めて、進めていくことで理解が深まる。