# 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.27.0.RELEASE (以降「STS」と呼ぶ。設定方法はSTS4の設定手順を参照されたい。)
Build Tool | Apache Maven 3.9.9 (以降「Maven」と呼ぶ)
Application Server | Apache Tomcat 10.1.33
Web Browser | Google Chrome 132

Note

インターネット接続するためにプロキシサーバーを介する必要がある場合は、STSのProxy設定とMavenのProxy設定が必要である。

## 2.3.2. 新規プロジェクト作成

インターネットから`mvn archetype:generate`を利用して、プロジェクトを作成する。

    mvn archetype:generate -B^
     -DarchetypeGroupId=org.terasoluna.gfw.blank^
     -DarchetypeArtifactId=terasoluna-gfw-web-blank-jsp-archetype^
     -DarchetypeVersion=5.10.0.RELEASE^
     -DgroupId=com.example.helloworld^
     -DartifactId=helloworld^
     -Dversion=1.0.0-SNAPSHOT

ここではWindows上にプロジェクトの元を作成する。

    C:\work>mvn archetype:generate -B^
    More?  -DarchetypeGroupId=org.terasoluna.gfw.blank^
    More?  -DarchetypeArtifactId=terasoluna-gfw-web-blank-jsp-archetype^
    More?  -DarchetypeVersion=5.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] >>> archetype:3.3.1:generate (default-cli) > generate-sources @ standalone-pom >>>
    [INFO]
    [INFO] <<< archetype:3.3.1:generate (default-cli) < generate-sources @ standalone-pom <<<
    [INFO]
    [INFO]
    [INFO] --- archetype:3.3.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.10.0.RELEASE] found in catalog local
    [INFO] ----------------------------------------------------------------------------
    [INFO] Using following parameters for creating project from Archetype: terasoluna-gfw-web-blank-jsp-archetype:5.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:  1.174 s
    [INFO] Finished at: 2025-02-10T16:40:44+09:00
    [INFO] ------------------------------------------------------------------------

    mvn archetype:generate -B^
     -DarchetypeGroupId=org.terasoluna.gfw.blank^
     -DarchetypeArtifactId=terasoluna-gfw-web-blank-thymeleaf-archetype^
     -DarchetypeVersion=5.10.0.RELEASE^
     -DgroupId=com.example.helloworld^
     -DartifactId=helloworld^
     -Dversion=1.0.0-SNAPSHOT

ここではWindows上にプロジェクトの元を作成する。

    C:\work>mvn archetype:generate -B^
    More?  -DarchetypeGroupId=org.terasoluna.gfw.blank^
    More?  -DarchetypeArtifactId=terasoluna-gfw-web-blank-thymeleaf-archetype^
    More?  -DarchetypeVersion=5.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] >>> archetype:3.3.1:generate (default-cli) > generate-sources @ standalone-pom >>>
    [INFO]
    [INFO] <<< archetype:3.3.1:generate (default-cli) < generate-sources @ standalone-pom <<<
    [INFO]
    [INFO]
    [INFO] --- archetype:3.3.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.10.0.RELEASE] found in catalog local
    [INFO] ----------------------------------------------------------------------------
    [INFO] Using following parameters for creating project from Archetype: terasoluna-gfw-web-blank-thymeleaf-archetype:5.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:  1.233 s
    [INFO] Finished at: 2025-02-10T16:51:04+09:00
    [INFO] ------------------------------------------------------------------------

    mvn archetype:generate -B^
     -DarchetypeGroupId=org.terasoluna.gfw.blank^
     -DarchetypeArtifactId=terasoluna-gfw-web-blank-xmlconfig-jsp-archetype^
     -DarchetypeVersion=5.10.0.RELEASE^
     -DgroupId=com.example.helloworld^
     -DartifactId=helloworld^
     -Dversion=1.0.0-SNAPSHOT

ここではWindows上にプロジェクトの元を作成する。

    C:\work>mvn archetype:generate -B^
    More?  -DarchetypeGroupId=org.terasoluna.gfw.blank^
    More?  -DarchetypeArtifactId=terasoluna-gfw-web-blank-xmlconfig-jsp-archetype^
    More?  -DarchetypeVersion=5.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] >>> archetype:3.3.1:generate (default-cli) > generate-sources @ standalone-pom >>>
    [INFO]
    [INFO] <<< archetype:3.3.1:generate (default-cli) < generate-sources @ standalone-pom <<<
    [INFO]
    [INFO]
    [INFO] --- archetype:3.3.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.10.0.RELEASE] found in catalog local
    [INFO] ----------------------------------------------------------------------------
    [INFO] Using following parameters for creating project from Archetype: terasoluna-gfw-web-blank-xmlconfig-jsp-archetype:5.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:  1.167 s
    [INFO] Finished at: 2025-02-10T16:53:55+09:00
    [INFO] ------------------------------------------------------------------------

    mvn archetype:generate -B^
     -DarchetypeGroupId=org.terasoluna.gfw.blank^
     -DarchetypeArtifactId=terasoluna-gfw-web-blank-xmlconfig-thymeleaf-archetype^
     -DarchetypeVersion=5.10.0.RELEASE^
     -DgroupId=com.example.helloworld^
     -DartifactId=helloworld^
     -Dversion=1.0.0-SNAPSHOT

ここではWindows上にプロジェクトの元を作成する。

    C:\work>mvn archetype:generate -B^
    More?  -DarchetypeGroupId=org.terasoluna.gfw.blank^
    More?  -DarchetypeArtifactId=terasoluna-gfw-web-blank-xmlconfig-thymeleaf-archetype^
    More?  -DarchetypeVersion=5.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] >>> archetype:3.3.1:generate (default-cli) > generate-sources @ standalone-pom >>>
    [INFO]
    [INFO] <<< archetype:3.3.1:generate (default-cli) < generate-sources @ standalone-pom <<<
    [INFO]
    [INFO]
    [INFO] --- archetype:3.3.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.10.0.RELEASE] found in catalog local
    [INFO] ----------------------------------------------------------------------------
    [INFO] Using following parameters for creating project from Archetype: terasoluna-gfw-web-blank-xmlconfig-thymeleaf-archetype:5.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:  1.190 s
    [INFO] Finished at: 2025-02-10T17:17:07+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;
    /* 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<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());
            /* 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);
        }
    }

項番 | 説明
--- | ---
(1) | `@EnableWebMvc`アノテーションを定義することにより、Spring MVCのデフォルト設定が行われる。デフォルトの設定については、Spring Framework Documentation -Enable MVC Configuration-を参照されたい。
(2) | Spring MVCで使用するコンポーネントを探すパッケージを定義する。
(3) | JSP用の`ViewResolver`を指定し、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;
    /* 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<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());
            /* 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<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) | `@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)を参照している。

- `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>
            <!--  REMOVE THIS LINE IF YOU USE JPA
            <mvc:interceptor>
                <mvc:mapping path="/**" />
                <mvc:exclude-mapping path="/resources/**" />
                <bean
                    class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor" />
            </mvc:interceptor>
                REMOVE THIS LINE IF YOU USE JPA  -->
        </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) | `<mvc:annotation-driven>`要素を定義することにより、Spring MVCのデフォルト設定が行われる。デフォルトの設定については、Spring Framework Documentation -Enable MVC Configuration-を参照されたい。
(2) | Spring MVCで使用するコンポーネントを探すパッケージを定義する。
(3) | JSP用の`ViewResolver`を指定し、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>
            <!--  REMOVE THIS LINE IF YOU USE JPA
            <mvc:interceptor>
                <mvc:mapping path="/**" />
                <mvc:exclude-mapping path="/resources/**" />
                <bean
                    class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor" />
            </mvc:interceptor>
                REMOVE THIS LINE IF YOU USE JPA  -->
        </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) | `<mvc:annotation-driven>`要素を定義することにより、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について、簡単に説明する。

- `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) | `@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`がレンダリングされる。

- `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) | `@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について簡単に説明する。

- `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で設定した値を画面に出力することができる。 Warning ${XXX}の記述は、XSS対象になる可能性があるので、文字列を出力する場合はHTMLエスケープする必要がある。 詳細はXSS対策を参照されたい。

- `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. サーバーを起動する

STSで、”helloworld”プロジェクトを右クリックして、Run As -> Run On Server -> localhost -> Tomcat v10.1 Server at localhost -> Finishを実行し、helloworldプロジェクトを起動する。
ブラウザに`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") // (1)
    public class EchoController {

        @ModelAttribute // (2)
        public EchoForm setUpEchoForm() {
            EchoForm form = new EchoForm();
            return form;
        }

        @GetMapping // (3)
        public String index(Model model) {
            return "echo/index"; // (4)
        }

        @PostMapping(value = "hello") // (5)
        public String hello(EchoForm form, Model model) {// (6)
            model.addAttribute("name", form.getName()); // (7)
            return "echo/hello";
        }
    }

項番 | 説明
--- | ---
(1) | Controllerクラスに`@RequestMapping`を付与した場合、該当のクラスのルートパスは”`<contextPath> + / + value属性の値`“となる。 本実装例では`<contextPath>/echo`がControllerクラスのルートパスとなる。 Controllerクラスに`@RequestMapping`を付与していない場合、該当のクラスのルートパスは”`<contextPath>`“となる。
(2) | `@ModelAttribute`というアノテーションを、メソッドに付加する。このアノテーションがついたメソッドの返り値は、自動でModelに追加される。 Modelの属性名を、`@ModelAttribute`で指定することもできるが、デフォルトでは、クラス名の先頭を小文字にした値が、属性名になる。この場合は、`echoForm`である。フォームの属性名は、次に説明する`form:form タグ`の`modelAttribute`属性の値に一致している必要がある。
(3) | メソッドに`@GetMapping`を付与した場合、メソッドへのマッピングは”`クラスのルートパス + / + value属性の値`“となる。 本実装例では`value`属性に何も指定していないため、”`クラスのルートパス（<contextPath>/echo）`“にGETメソッドでアクセスすると`index`メソッドが呼ばれるようになる。
(4) | View名で”`echo/index`“を返すので、ViewResolverにより、”`WEB-INF/views/echo/index.jsp`“がレンダリングされる。
(5) | メソッドに`@PostMapping`を付与した場合、メソッドへのマッピングは”`クラスのルートパス + / + value属性の値`“となる。 本実装例では`value`属性の値に`hello`を指定しているため、”`クラスのルートパス + value属性の値（<contextPath>/echo/hello）`“にPOSTメソッドでアクセスすると`hello`メソッドが呼ばれるようになる。
(6) | 引数に、EchoFormには(1)によりModelに追加されたEchoFormオブジェクトが渡される。
(7) | フォームで入力された`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対策を行っている。

Note

ここではXSS対策を標準タグの`c:out`で実現したが、より容易に使用できる`f:h()`関数を共通ライブラリで用意している。

詳細は、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. 入力チェックの実装

ここまでのアプリケーションでは、入力チェックを行っていない。
Spring MVCでは、Bean Validationをサポートしており、アノテーションベースな入力チェックを、簡単に実装することができる。
例として、エコーアプリケーションで名前の入力チェックを行う。

`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" />
                <div>
                    <input type="hidden" name="_csrf" value="6e94a78d-4a2c-4a41-a514-0a60f0dbedaf" />
                </div>
            </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. まとめ

この章では、

1. `mvn archetype:generate`を利用したブランクプロジェクトの作成方法

2. Spring MVCの基本的な設定方法

3. 最も簡易な、画面遷移方法

4. 画面間での値の引き渡し方法

5. シンプルな入力チェック方法

を学んだ。

上記の内容が理解できていない場合は、もう一度、本節を読み、環境構築から始めて、進めていくことで理解が深まる。