はじめてのSpring MVCアプリケーション -------------------------------------------------------------- .. only:: html .. contents:: 目次 :depth: 3 :local: Spring MVCの、詳細な使い方の解説に入る前に、実際にSpring MVCに触れることで、Spring MVCを用いたWebアプリケーションの開発に対するイメージをつかむ。 | 検証環境 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本節の説明では、次の環境で動作検証している。(他の環境で実施する際は、本書をベースに適宜読み替えて設定していくこと。) .. tabularcolumns:: |p{0.25\linewidth}|p{0.75\linewidth}| .. list-table:: :header-rows: 1 :widths: 25 75 * - 種別 - プロダクト * - OS - Windows 10 * - JVM - \ `Java `_\ 17 * - IDE - \ `Spring Tool Suite `_\ 4.17.1.RELEASE (以降「STS」と呼ぶ。設定方法は\ :doc:`../Appendix/SpringToolSuite4`\ を参照されたい。) * - Build Tool - \ `Apache Maven `_\ 3.8.6 (以降「Maven」と呼ぶ) * - Application Server - \ `Apache Tomcat `_\ 10.1.15 * - Web Browser - \ `Google Chrome `_\ 117 .. note:: インターネット接続するためにプロキシサーバーを介する必要がある場合は、\ :ref:`STSのProxy設定`\ と\ `MavenのProxy設定 `_\ が必要である。 | 新規プロジェクト作成 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ インターネットから\ ``mvn archetype:generate``\ を利用して、プロジェクトを作成する。 .. tabs:: .. group-tab:: Java Config .. tabs:: .. group-tab:: JSP .. code-block:: console mvn archetype:generate -B^ -DarchetypeGroupId=org.terasoluna.gfw.blank^ -DarchetypeArtifactId=terasoluna-gfw-web-blank-jsp-archetype^ -DarchetypeVersion=5.9.0.RELEASE^ -DgroupId=com.example.helloworld^ -DartifactId=helloworld^ -Dversion=1.0.0-SNAPSHOT ここではWindows上にプロジェクトの元を作成する。 .. code-block:: console C:\work>mvn archetype:generate -B^ More? -DarchetypeGroupId=org.terasoluna.gfw.blank^ More? -DarchetypeArtifactId=terasoluna-gfw-web-blank-jsp-archetype^ More? -DarchetypeVersion=5.9.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 [org.terasoluna.gfw.blank:terasoluna-gfw-web-blank-jsp-archetype:5.9.0.RELEASE] found in catalog local [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating project from Archetype: terasoluna-gfw-web-blank-jsp-archetype:5.9.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] ------------------------------------------------------------------------ .. group-tab:: Thymeleaf .. code-block:: console mvn archetype:generate -B^ -DarchetypeGroupId=org.terasoluna.gfw.blank^ -DarchetypeArtifactId=terasoluna-gfw-web-blank-thymeleaf-archetype^ -DarchetypeVersion=5.9.0.RELEASE^ -DgroupId=com.example.helloworld^ -DartifactId=helloworld^ -Dversion=1.0.0-SNAPSHOT ここではWindows上にプロジェクトの元を作成する。 .. code-block:: console C:\work>mvn archetype:generate -B^ More? -DarchetypeGroupId=org.terasoluna.gfw.blank^ More? -DarchetypeArtifactId=terasoluna-gfw-web-blank-thymeleaf-archetype^ More? -DarchetypeVersion=5.9.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 [org.terasoluna.gfw.blank:terasoluna-gfw-web-blank-thymeleaf-archetype:5.9.0.RELEASE] found in catalog local [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating project from Archetype: terasoluna-gfw-web-blank-thymeleaf-archetype:5.9.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] ------------------------------------------------------------------------ .. group-tab:: XML Config .. tabs:: .. group-tab:: JSP .. code-block:: console mvn archetype:generate -B^ -DarchetypeGroupId=org.terasoluna.gfw.blank^ -DarchetypeArtifactId=terasoluna-gfw-web-blank-xmlconfig-jsp-archetype^ -DarchetypeVersion=5.9.0.RELEASE^ -DgroupId=com.example.helloworld^ -DartifactId=helloworld^ -Dversion=1.0.0-SNAPSHOT ここではWindows上にプロジェクトの元を作成する。 .. code-block:: console C:\work>mvn archetype:generate -B^ More? -DarchetypeGroupId=org.terasoluna.gfw.blank^ More? -DarchetypeArtifactId=terasoluna-gfw-web-blank-xmlconfig-jsp-archetype^ More? -DarchetypeVersion=5.9.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 [org.terasoluna.gfw.blank:terasoluna-gfw-web-blank-xmlconfig-jsp-archetype:5.9.0.RELEASE] found in catalog remote [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating project from Archetype: terasoluna-gfw-web-blank-xmlconfig-jsp-archetype:5.9.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] ------------------------------------------------------------------------ .. group-tab:: Thymeleaf .. code-block:: console mvn archetype:generate -B^ -DarchetypeGroupId=org.terasoluna.gfw.blank^ -DarchetypeArtifactId=terasoluna-gfw-web-blank-xmlconfig-thymeleaf-archetype^ -DarchetypeVersion=5.9.0.RELEASE^ -DgroupId=com.example.helloworld^ -DartifactId=helloworld^ -Dversion=1.0.0-SNAPSHOT ここではWindows上にプロジェクトの元を作成する。 .. code-block:: console C:\work>mvn archetype:generate -B^ More? -DarchetypeGroupId=org.terasoluna.gfw.blank^ More? -DarchetypeArtifactId=terasoluna-gfw-web-blank-xmlconfig-thymeleaf-archetype^ More? -DarchetypeVersion=5.9.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 [org.terasoluna.gfw.blank:terasoluna-gfw-web-blank-xmlconfig-thymeleaf-archetype:5.9.0.RELEASE] found in catalog remote [INFO] ---------------------------------------------------------------------------- [INFO] Using following parameters for creating project from Archetype: terasoluna-gfw-web-blank-xmlconfig-thymeleaf-archetype:5.9.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で作成したプロジェクトを選択する。 .. figure:: images_FirstApplication/NewMVCProjectImport.png :alt: New MVC Project Import :width: 60% Root Directoryに \ ``C:\work\helloworld``\ を設定し、Projectsにhelloworldのpom.xmlが選択された状態で、 [Finish] を押下する。 .. figure:: images_FirstApplication/NewMVCProjectCreate.png :alt: New MVC Project Import :width: 60% Package Explorerに、次のようなプロジェクトが生成される。 .. figure:: images_FirstApplication/HelloWorldWorkspace.png :alt: workspace Spring MVCの設定方法を理解するために、生成されたSpring MVCの設定ファイルについて、簡単に説明する。 .. tabs:: .. group-tab:: Java Config .. tabs:: .. group-tab:: JSP * \ ``src/main/com/example/helloworld/config/web/SpringMvcConfig.java``\ .. code-block:: java :emphasize-lines: 49, 51, 197 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; /* REMOVE THIS LINE IF YOU USE JPA import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor; REMOVE THIS LINE IF YOU USE JPA */ import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver; import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor; /* REMOVE THIS LINE IF YOU USE JPA import org.springframework.web.context.request.WebRequestInterceptor; REMOVE THIS LINE IF YOU USE JPA */ 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 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()); /* REMOVE THIS LINE IF YOU USE JPA addWebRequestInterceptor(registry, openEntityManagerInViewInterceptor()); REMOVE THIS LINE IF YOU USE JPA */ } /** * 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/**"); } /* REMOVE THIS LINE IF YOU USE JPA /** * Common processes used in #addInterceptors. * @param registry {@link InterceptorRegistry} * @param interceptor {@link WebRequestInterceptor} *REMOVE THIS COMMENT IF YOU USE JPA/ private void addWebRequestInterceptor(InterceptorRegistry registry, WebRequestInterceptor interceptor) { registry.addWebRequestInterceptor(interceptor).addPathPatterns("/**") .excludePathPatterns("/resources/**"); } REMOVE THIS LINE IF YOU USE JPA */ /** * 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; } /* REMOVE THIS LINE IF YOU USE JPA /** * Configure {@link OpenEntityManagerInViewInterceptor} bean. * @return Bean of configured {@link OpenEntityManagerInViewInterceptor} *REMOVE THIS COMMENT IF YOU USE JPA/ @Bean public OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() { return new OpenEntityManagerInViewInterceptor(); } REMOVE THIS LINE IF YOU USE JPA */ /** * {@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); } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``@EnableWebMvc``\ アノテーションを定義することにより、Spring MVCのデフォルト設定が行われる。デフォルトの設定については、\ `Spring Framework Documentation -Enable MVC Configuration- `_\ を参照されたい。 * - | (2) - Spring MVCで使用するコンポーネントを探すパッケージを定義する。 * - | (3) - JSP用の\ ``ViewResolver``\ を指定し、JSPファイルの配置場所を定義する。 .. group-tab:: Thymeleaf * \ ``src/main/com/example/helloworld/config/web/SpringMvcConfig.java``\ .. code-block:: java :emphasize-lines: 57, 59, 205, 212-213, 226-227, 240-241 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; /* REMOVE THIS LINE IF YOU USE JPA import org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor; REMOVE THIS LINE IF YOU USE JPA */ import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver; import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor; /* REMOVE THIS LINE IF YOU USE JPA import org.springframework.web.context.request.WebRequestInterceptor; REMOVE THIS LINE IF YOU USE JPA */ 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 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()); /* REMOVE THIS LINE IF YOU USE JPA addWebRequestInterceptor(registry, openEntityManagerInViewInterceptor()); REMOVE THIS LINE IF YOU USE JPA */ } /** * 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/**"); } /* REMOVE THIS LINE IF YOU USE JPA /** * Common processes used in #addInterceptors. * @param registry {@link InterceptorRegistry} * @param interceptor {@link WebRequestInterceptor} *REMOVE THIS COMMENT IF YOU USE JPA/ private void addWebRequestInterceptor(InterceptorRegistry registry, WebRequestInterceptor interceptor) { registry.addWebRequestInterceptor(interceptor).addPathPatterns("/**") .excludePathPatterns("/resources/**"); } REMOVE THIS LINE IF YOU USE JPA */ /** * 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; } /* REMOVE THIS LINE IF YOU USE JPA /** * Configure {@link OpenEntityManagerInViewInterceptor} bean. * @return Bean of configured {@link OpenEntityManagerInViewInterceptor} *REMOVE THIS COMMENT IF YOU USE JPA/ @Bean public OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() { return new OpenEntityManagerInViewInterceptor(); } REMOVE THIS LINE IF YOU USE JPA */ /** * {@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 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); } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ``@EnableWebMvc``\ アノテーションを定義することにより、Spring MVCのデフォルト設定が行われる。デフォルトの設定については、\ `Spring Framework Documentation -Enable MVC Configuration- `_\ を参照されたい。 * - | (2) - Spring MVCで使用するコンポーネントを探すパッケージを定義する。 * - | (3) - Thymeleaf用の\ ``ViewResolver``\ を指定する。TemplateEngineには、idが\ ``templateEngine``\ のbeanである(5)を参照している。 * - | (4) - Viewファイルの拡張子と配置場所を定義する。 * - | (5) - Springを用いたThymeleafの実装を定義する。TemplateResolverには、idが\ ``templateResolver``\ のbeanである(4)を参照している。 .. group-tab:: XML Config .. tabs:: .. group-tab:: JSP * \ ``src/main/resources/META-INF/spring/spring-mvc.xml``\ .. code-block:: xml :emphasize-lines: 18-19, 30-31, 69-70 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ````\ 要素を定義することにより、Spring MVCのデフォルト設定が行われる。デフォルトの設定については、\ `Spring Framework Documentation -Enable MVC Configuration- `_\ を参照されたい。 * - | (2) - Spring MVCで使用するコンポーネントを探すパッケージを定義する。 * - | (3) - JSP用の\ ``ViewResolver``\ を指定し、JSPファイルの配置場所を定義する。 .. group-tab:: Thymeleaf * \ ``src/main/resources/META-INF/spring/spring-mvc.xml``\ .. code-block:: xml :emphasize-lines: 18-19, 30-31, 69-75, 78-81, 88-90 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - \ ````\ 要素を定義することにより、Spring MVCのデフォルト設定が行われる。デフォルトの設定については、\ `Spring Framework Documentation -Enable MVC Configuration- `_\ を参照されたい。 * - | (2) - Spring MVCで使用するコンポーネントを探すパッケージを定義する。 * - | (3) - Thymeleaf用の\ ``ViewResolver``\ を指定する。TemplateEngineには、idが\ ``templateEngine``\ のbeanである(5)を参照している。 * - | (4) - Viewファイルの拡張子と配置場所を定義する。 * - | (5) - Springを用いたThymeleafの実装を定義する。TemplateResolverには、idが\ ``templateResolver``\ のbeanである(4)を参照している。 | 次に、Welcomeページを表示するためのControllerについて、簡単に説明する。 .. tabs:: .. group-tab:: JSP * \ ``com.example.helloworld.app.welcome.HelloController``\ .. code-block:: java 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) } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (4) - \ ``@Controller``\ アノテーションを付けることで、DIコンテナにより、コントローラクラスが自動で読み込まれる。前述「Spring MVCの設定ファイルの説明(2)」の設定により、component-scanの対象となっている。 * - | (5) - HTTPメソッドがGETで、"\ ``/``\ "というResource(もしくはRequest URL)にアクセスする際に実行される。 * - | (6) - Viewに渡したいオブジェクトを\ ``Model``\ に設定する。 * - | (7) - View名を返却する。前述「Spring MVCの設定ファイルの説明(3)」の設定により、\ ``WEB-INF/views/welcome/home.jsp``\ がレンダリングされる。 .. group-tab:: Thymeleaf * \ ``com.example.helloworld.app.welcome.HelloController``\ .. code-block:: java 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) } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (6) - \ ``@Controller``\ アノテーションを付けることで、DIコンテナにより、コントローラクラスが自動で読み込まれる。前述「Spring MVCの設定ファイルの説明(2)」の設定により、component-scanの対象となっている。 * - | (7) - HTTPメソッドがGETで、"\ ``/``\ "というResource(もしくはRequest URL)にアクセスする際に実行される。 * - | (8) - Viewに渡したいオブジェクトを\ ``Model``\ に設定する。 * - | (9) - View名を返却する。前述「Spring MVCの設定ファイルの説明(4)」の設定により、\ ``WEB-INF/views/welcome/home.html``\ がレンダリングされる。 | 最後に、Welcomeページを表示するためのViewについて簡単に説明する。 .. tabs:: .. group-tab:: JSP * \ ``src/main/webapp/WEB-INF/views/welcome/home.jsp``\ .. code-block:: jsp Home

Hello world!

The time on the server is ${serverTime}.

.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (8) - | 前述の「Controllerの説明(6)」でModelに設定したオブジェクト(serverTime)は、HttpServletRequestに格納される。 | そのため、JSPで\ ``${serverTime}``\ と記述することで、Controllerで設定した値を画面に出力することができる。 .. warning:: \ **${XXX}の記述は、XSS対象になる可能性があるので、文字列を出力する場合はHTMLエスケープする必要がある。**\ 詳細は\ :doc:`../Security/XSS`\ を参照されたい。 .. group-tab:: Thymeleaf * \ ``src/main/webapp/WEB-INF/views/welcome/home.html``\ .. code-block:: html Home

Hello world!

The time on the server is 2018/01/01 00:00:00 JST.

.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (10) - | スタンダードダイアレクトが提供する属性を使用したとき、EclipseなどのIDEでの警告を抑止するため、ネームスペースを付与する。 * - | (11) - | 前述の「Controllerの説明(8)」でModelに設定したオブジェクト(serverTime)は、HttpServletRequestに格納される。 | そのため、テンプレートHTMLで\ ``${serverTime}``\ と記述し、Thymeleafの\ ``th:text``\ 属性を使用することで、Controllerで設定した値を画面に出力することができる。 | | \ ``th:text``\ 属性はHTMLエスケープをして出力を行うため、自動的にXSS対策をとることができる。 | 詳細については\ :ref:`xss_how_to_use_ouput_escaping`\ を参照されたい。 | サーバーを起動する ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | STSで、"helloworld"プロジェクトを右クリックして、Run As -> Run On Server -> localhost -> Tomcat v10.1 Server at localhost -> Finishを実行し、helloworldプロジェクトを起動する。 | ブラウザに\ ``http://localhost:8080/helloworld/``\ を入力し、実行すると下記の画面が表示される。 .. figure:: images_FirstApplication/AppHelloWorldIndex.png :alt: Hello World | .. _first-application-create-an-echo-application: エコーアプリケーションの作成 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 続いて、簡単なアプリケーションを作成する。作成するのは、次の図のようなテキストフィールドに、名前を入力すると メッセージを表示する、いわゆるエコーアプリケーションである。 .. figure:: images_FirstApplication/AppEchoIndex.png :alt: Form of Echo Application .. figure:: images_FirstApplication/AppEchoHello.png :alt: Output of Echo Application | フォームオブジェクトの作成 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | まずは、テキストフィールドの値を受け取るための、フォームオブジェクトを作成する。 | \ ``com.example.helloworld.app.echo``\ パッケージに\ ``EchoForm``\ クラスを作成する。プロパティを1つだけ持つ、単純なJavaBeanである。 .. code-block:: java 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; } } | Controllerの作成 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 次に、Controller(\ ``com.example.helloworld.app.echo.EchoController``\ )クラスを作成する。 .. code-block:: java 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"; } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``@ModelAttribute``\ というアノテーションを、メソッドに付加する。このアノテーションがついたメソッドの返り値は、自動でModelに追加される。 | Modelの属性名を、\ ``@ModelAttribute``\ で指定することもできるが、デフォルトでは、クラス名の先頭を小文字にした値が、属性名になる。この場合は、\ ``echoForm``\ である。フォームの属性名は、次に説明する\ ``form:form タグ``\ の\ ``modelAttribute``\ 属性の値に一致している必要がある。 * - | (2) - | メソッドに付加した\ ``@GetMapping``\ アノテーションの\ ``value``\ 属性に何も指定しない場合、クラスに付加した\ ``@RequestMapping``\ のルートにマッピングされる。この場合、\ ``/echo``\ にGETメソッドを使用してアクセスすると、\ ``index``\ メソッドが呼ばれる。 * - | (3) - | View名で\ ``echo/index``\ を返すので、ViewResolverにより、\ ``WEB-INF/views/echo/index.jsp``\ または\ ``WEB-INF/views/echo/index.html``\ がレンダリングされる。 * - | (4) - | メソッドに付加した\ ``@PostMapping``\ アノテーションの\ ``value``\ 属性に\ ``hello``\ を指定しているので、この場合、\ ``/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``\ アノテーション を指定している。 | Viewの作成 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 最後に、入力画面と、出力画面のViewを作成する。それぞれのファイルパスは、View名に合わせて、次のようになる。 .. tabs:: .. group-tab:: JSP 入力画面 (\ ``src/main/webapp/WEB-INF/views/echo/index.jsp``\ ) を作成する。 .. code-block:: jsp Echo Application <%-- (1) --%> Input Your Name: .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | タグライブラリを利用し、HTMLフォームを構築している。\ ``modelAttribute``\ 属性に、Controllerで用意したフォームオブジェクトの名前を指定する。 | タグライブラリは\ `Spring Framework Documentation -The Form Tag- `_\ を参照されたい。 .. note:: \ ````\ タグの\ ``method``\ 属性を省略した場合は、POSTメソッドが使用される。 出力されるHTMLは、 .. code-block:: html Echo Application
となる。 | 出力画面 (\ ``src/main/webapp/WEB-INF/views/echo/hello.jsp``\ ) を作成する。 .. code-block:: jsp Echo Application

Hello <%-- (2) --%>

.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (2) - | Controllerから渡された\ ``name``\ を出力する。\ ``c:out``\ タグにより、XSS対策を行っている。 .. note:: ここではXSS対策を標準タグの\ ``c:out``\ で実現したが、より容易に使用できる\ ``f:h()``\ 関数を共通ライブラリで用意している。 詳細は、\ :doc:`../Security/XSS`\ を参照されたい。 .. group-tab:: Thymeleaf 入力画面 (\ ``src/main/webapp/WEB-INF/views/echo/index.html``\ ) を作成する。 .. code-block:: html Echo Application
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (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``\ 属性の詳細については、\ :doc:`アプリケーション層の実装 <../ImplementationAtEachLayer/ApplicationLayer>`\ を参照されたい。 .. note:: \ ``
``\ タグの\ ``method``\ 属性を省略した場合は、\ **GETメソッドが使用される。**\ 出力されるHTMLは、 .. code-block:: html Echo Application
となる。 | 出力画面 (\ ``src/main/webapp/WEB-INF/views/echo/hello.html``\ ) を作成する。 .. code-block:: html Echo Application

.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (4) - | Controllerから渡された\ ``name``\ を出力する。\ ``th:text``\ 属性により、XSS対策を行っている。 | これでエコーアプリケーションの実装は完了である。 | サーバーを起動し、\ ``http://localhost:8080/helloworld/echo``\ にアクセスするとフォームが表示される。 | 入力チェックの実装 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ここまでのアプリケーションでは、入力チェックを行っていない。 | Spring MVCでは、\ `Bean Validation `_\ をサポートしており、アノテーションベースな入力チェックを、簡単に実装することができる。 | 例として、エコーアプリケーションで名前の入力チェックを行う。 \ ``EchoForm``\ の\ ``name``\ フィールドに、入力チェックルールを指定するアノテーションを付与する。 .. code-block:: java 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; } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | \ ``@NotNull``\ アノテーションをつけることで、HTTPリクエスト中に\ ``name``\ パラメータがあることを確認する。 * - | (2) - | \ ``@Size(min = 1, max = 5)``\ をつけることで、\ ``name``\ のサイズが、1以上5以下であることを確認する。 | 入力チェックが実行されるように修正し、入力チェックでエラーが発生した場合の処理を実装する。 .. code-block:: java 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"; } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | コントローラー側には、Validation対象の引数に\ ``@Validated``\ アノテーションを付加し、\ ``BindingResult``\ オブジェクトを引数に追加する。 | Bean Validationによる入力チェックは、自動で行われる。結果は、\ ``BindingResult``\ オブジェクトに渡される。 * - | (2) - | \ ``hasErrors``\ メソッドを実行して、エラーがあるかどうかを確認する。入力エラーがある場合は、入力画面を表示するためのView名を返却する。 | 入力画面に、入力エラーのメッセージを表示するための実装を追加する。 .. tabs:: .. group-tab:: JSP * \ ``src/main/webapp/WEB-INF/views/echo/index.jsp``\ .. code-block:: jsp Echo Application Input Your Name: <%-- (1) --%> .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 入力画面には、エラーがあった場合に、エラーメッセージを表示するため、\ ``form:errors``\ タグを追加する。 出力されるHTMLは、 .. code-block:: html Echo Application
size must be between 1 and 5
となる。 .. group-tab:: Thymeleaf * \ ``src/main/webapp/WEB-INF/views/echo/index.html``\ .. code-block:: html Echo Application
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 入力画面には、エラーがあった場合に、エラーメッセージを表示するため、 ``th:errors`` 属性を追加する。 出力されるHTMLは、 .. code-block:: html Echo Application
size must be between 1 and 5
となる。 | 以上で、入力チェックの実装は完了である。 | 実際に、次のような場合、エラーメッセージが表示される。 * 名前を空にして送信した場合 * 5文字より大きいサイズで送信した場合 .. figure:: images_FirstApplication/AppValidationEmpty.png :alt: Validation Error (name is empty) .. figure:: images_FirstApplication/AppValidationSizeOver.png :alt: Validation Error (name's size is over 5) | まとめ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ この章では、 #. \ ``mvn archetype:generate``\ を利用したブランクプロジェクトの作成方法 #. Spring MVCの基本的な設定方法 #. 最も簡易な、画面遷移方法 #. 画面間での値の引き渡し方法 #. シンプルな入力チェック方法 を学んだ。 上記の内容が理解できていない場合は、もう一度、本節を読み、環境構築から始めて、進めていくことで理解が深まる。 .. raw:: latex \newpage