美文网首页
spring集成Thymeleaf通过注解直接从视图链接到控制器

spring集成Thymeleaf通过注解直接从视图链接到控制器

作者: 爱的旋转体 | 来源:发表于2019-12-25 09:48 被阅读0次

在Spring4.1之后的版本中,Spring允许通过注解直接从视图链接到控制器,而不需要知道这些控制器映射的URI.
一.@RequestMapping的name属性:

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.bind.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

/**
 * Annotation for mapping web requests onto methods in request-handling classes
 * with flexible method signatures.
 *
 * <p>Both Spring MVC and Spring WebFlux support this annotation through a
 * {@code RequestMappingHandlerMapping} and {@code RequestMappingHandlerAdapter}
 * in their respective modules and package structure. For the exact list of
 * supported handler method arguments and return types in each, please use the
 * reference documentation links below:
 * <ul>
 * <li>Spring MVC
 * <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-arguments">Method Arguments</a>
 * and
 * <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-return-types">Return Values</a>
 * </li>
 * <li>Spring WebFlux
 * <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-ann-arguments">Method Arguments</a>
 * and
 * <a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-ann-return-types">Return Values</a>
 * </li>
 * </ul>
 *
 * <p><strong>Note:</strong> This annotation can be used both at the class and
 * at the method level. In most cases, at the method level applications will
 * prefer to use one of the HTTP method specific variants
 * {@link GetMapping @GetMapping}, {@link PostMapping @PostMapping},
 * {@link PutMapping @PutMapping}, {@link DeleteMapping @DeleteMapping}, or
 * {@link PatchMapping @PatchMapping}.</p>
 *
 * <p><b>NOTE:</b> When using controller interfaces (e.g. for AOP proxying),
 * make sure to consistently put <i>all</i> your mapping annotations - such as
 * {@code @RequestMapping} and {@code @SessionAttributes} - on
 * the controller <i>interface</i> rather than on the implementation class.
 *
 * @author Juergen Hoeller
 * @author Arjen Poutsma
 * @author Sam Brannen
 * @since 2.5
 * @see GetMapping
 * @see PostMapping
 * @see PutMapping
 * @see DeleteMapping
 * @see PatchMapping
 * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
 * @see org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {

    /**
     * Assign a name to this mapping.
     * <p><b>Supported at the type level as well as at the method level!</b>
     * When used on both levels, a combined name is derived by concatenation
     * with "#" as separator.
     * @see org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder
     * @see org.springframework.web.servlet.handler.HandlerMethodMappingNamingStrategy
     */
    String name() default "";

    /**
     * The primary mapping expressed by this annotation.
     * <p>This is an alias for {@link #path}. For example
     * {@code @RequestMapping("/foo")} is equivalent to
     * {@code @RequestMapping(path="/foo")}.
     * <p><b>Supported at the type level as well as at the method level!</b>
     * When used at the type level, all method-level mappings inherit
     * this primary mapping, narrowing it for a specific handler method.
     */
    @AliasFor("path")
    String[] value() default {};

    /**
     * The path mapping URIs (e.g. "/myPath.do").
     * Ant-style path patterns are also supported (e.g. "/myPath/*.do").
     * At the method level, relative paths (e.g. "edit.do") are supported
     * within the primary mapping expressed at the type level.
     * Path mapping URIs may contain placeholders (e.g. "/${connect}").
     * <p><b>Supported at the type level as well as at the method level!</b>
     * When used at the type level, all method-level mappings inherit
     * this primary mapping, narrowing it for a specific handler method.
     * @see org.springframework.web.bind.annotation.ValueConstants#DEFAULT_NONE
     * @since 4.2
     */
    @AliasFor("value")
    String[] path() default {};

    /**
     * The HTTP request methods to map to, narrowing the primary mapping:
     * GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
     * <p><b>Supported at the type level as well as at the method level!</b>
     * When used at the type level, all method-level mappings inherit
     * this HTTP method restriction (i.e. the type-level restriction
     * gets checked before the handler method is even resolved).
     */
    RequestMethod[] method() default {};

    /**
     * The parameters of the mapped request, narrowing the primary mapping.
     * <p>Same format for any environment: a sequence of "myParam=myValue" style
     * expressions, with a request only mapped if each such parameter is found
     * to have the given value. Expressions can be negated by using the "!=" operator,
     * as in "myParam!=myValue". "myParam" style expressions are also supported,
     * with such parameters having to be present in the request (allowed to have
     * any value). Finally, "!myParam" style expressions indicate that the
     * specified parameter is <i>not</i> supposed to be present in the request.
     * <p><b>Supported at the type level as well as at the method level!</b>
     * When used at the type level, all method-level mappings inherit
     * this parameter restriction (i.e. the type-level restriction
     * gets checked before the handler method is even resolved).
     * <p>Parameter mappings are considered as restrictions that are enforced at
     * the type level. The primary path mapping (i.e. the specified URI value)
     * still has to uniquely identify the target handler, with parameter mappings
     * simply expressing preconditions for invoking the handler.
     */
    String[] params() default {};

    /**
     * The headers of the mapped request, narrowing the primary mapping.
     * <p>Same format for any environment: a sequence of "My-Header=myValue" style
     * expressions, with a request only mapped if each such header is found
     * to have the given value. Expressions can be negated by using the "!=" operator,
     * as in "My-Header!=myValue". "My-Header" style expressions are also supported,
     * with such headers having to be present in the request (allowed to have
     * any value). Finally, "!My-Header" style expressions indicate that the
     * specified header is <i>not</i> supposed to be present in the request.
     * <p>Also supports media type wildcards (*), for headers such as Accept
     * and Content-Type. For instance,
     * <pre class="code">
     * &#064;RequestMapping(value = "/something", headers = "content-type=text/*")
     * </pre>
     * will match requests with a Content-Type of "text/html", "text/plain", etc.
     * <p><b>Supported at the type level as well as at the method level!</b>
     * When used at the type level, all method-level mappings inherit
     * this header restriction (i.e. the type-level restriction
     * gets checked before the handler method is even resolved).
     * @see org.springframework.http.MediaType
     */
    String[] headers() default {};

    /**
     * The consumable media types of the mapped request, narrowing the primary mapping.
     * <p>The format is a single media type or a sequence of media types,
     * with a request only mapped if the {@code Content-Type} matches one of these media types.
     * Examples:
     * <pre class="code">
     * consumes = "text/plain"
     * consumes = {"text/plain", "application/*"}
     * </pre>
     * Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
     * all requests with a {@code Content-Type} other than "text/plain".
     * <p><b>Supported at the type level as well as at the method level!</b>
     * When used at the type level, all method-level mappings override
     * this consumes restriction.
     * @see org.springframework.http.MediaType
     * @see javax.servlet.http.HttpServletRequest#getContentType()
     */
    String[] consumes() default {};

    /**
     * The producible media types of the mapped request, narrowing the primary mapping.
     * <p>The format is a single media type or a sequence of media types,
     * with a request only mapped if the {@code Accept} matches one of these media types.
     * Examples:
     * <pre class="code">
     * produces = "text/plain"
     * produces = {"text/plain", "application/*"}
     * produces = MediaType.APPLICATION_JSON_UTF8_VALUE
     * </pre>
     * <p>It affects the actual content type written, for example to produce a JSON response
     * with UTF-8 encoding, {@link org.springframework.http.MediaType#APPLICATION_JSON_UTF8_VALUE} should be used.
     * <p>Expressions can be negated by using the "!" operator, as in "!text/plain", which matches
     * all requests with a {@code Accept} other than "text/plain".
     * <p><b>Supported at the type level as well as at the method level!</b>
     * When used at the type level, all method-level mappings override
     * this produces restriction.
     * @see org.springframework.http.MediaType
     */
    String[] produces() default {};

}

javadoc描述:为此映射分配名称,它可以使用在类上,也可以标注在方法上。
在分析RequestMappingHandlerMapping源码的时候指出过:它的createRequestMappingInfo()方法会把注解的name封装到RequestMappingInfo.name属性里。因为它既可以在类上又可以在方法上,因此一样的它需要combine,但是它的combine逻辑稍微特殊些,此处展示如下:

RequestMappingInfo:

    // 此方法来自接口:RequestCondition
    @Override
    public RequestMappingInfo combine(RequestMappingInfo other) {
        String name = combineNames(other);
        PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition);
        RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
        ...
        return new RequestMappingInfo( ... );
    }

    @Nullable
    private String combineNames(RequestMappingInfo other) {
        if (this.name != null && other.name != null) {
            String separator = RequestMappingInfoHandlerMethodMappingNamingStrategy.SEPARATOR;
            return this.name + separator + other.name;
        } else if (this.name != null) {
            return this.name;
        } else {
            return other.name;
        }
    }

逻辑不难,就是类+"#"+方法的拼接,但是我们知道其实绝大部分情况下我们都从来没有指定过name属性,那么此处就不得不提这个策略接口:HandlerMethodMappingNamingStrategy了,它用于缺省的名字生成策略器。
1.HandlerMethodMappingNamingStrategy:
HandlerMethodMappingNamingStrategy为处理程序HandlerMethod方法分配名称的策略接口。此接口可以在AbstractHandlerMethodMapping里配置生成name,然后这个name可以用于AbstractHandlerMethodMapping#getHandlerMethodsForMappingName(String)方法进行查询:

// @since 4.1
@FunctionalInterface // 函数式接口
public interface HandlerMethodMappingNamingStrategy<T> {
    
    // Determine the name for the given HandlerMethod and mapping.
    // 根据HandlerMethod 和 Mapping拿到一个name
    String getName(HandlerMethod handlerMethod, T mapping);
}

它的唯一实现类是:RequestMappingInfoHandlerMethodMappingNamingStrategy(目前而言RequestMappingInfo的唯一实现只有@RequestMapping,但设计上是没有强制绑定必须是这个注解。
2.RequestMappingInfoHandlerMethodMappingNamingStrategy:
RequestMappingInfoHandlerMethodMappingNamingStrategy
此类提供name的默认的生成规则(若没指定的话)的实现:

// @since 4.1
public class RequestMappingInfoHandlerMethodMappingNamingStrategy implements HandlerMethodMappingNamingStrategy<RequestMappingInfo> {
    // 类级别到方法级别的分隔符(当然你也是可以改的)
    public static final String SEPARATOR = "#";

    @Override
    public String getName(HandlerMethod handlerMethod, RequestMappingInfo mapping) {
        if (mapping.getName() != null) { // 若使用者自己指定了,那就以指定的为准
            return mapping.getName();
        }
        // 自动生成的策略
        StringBuilder sb = new StringBuilder();
        
        // 1、拿到类名
        // 2、遍历每个字母,拿到所有的大写字母
        // 3、用拿到的大写字母拼接 # 拼接方法名。如:TestController#getFoo()最终结果是:TC#getFoo
        String simpleTypeName = handlerMethod.getBeanType().getSimpleName();
        for (int i = 0; i < simpleTypeName.length(); i++) {
            if (Character.isUpperCase(simpleTypeName.charAt(i))) {
                sb.append(simpleTypeName.charAt(i));
            }
        }
        sb.append(SEPARATOR).append(handlerMethod.getMethod().getName());
        return sb.toString();
    }

}

简单总结这部分逻辑如下:
1)类上的name值 + '#' + 方法的name值
2)类上若没指定,默认值是:类名所有大写字母拼装
3)方法上若没指定,默认值是:方法名
3.name属性有什么用?
JavaDoc里有提到了它的作用:应用程序可以在下面这个静态方法的帮助下按名称构建控制器方法的URL,它借助的是MvcUriComponentsBuilder的fromMappingName方法实现:

MvcUriComponentsBuilder:
    
    // 静态方法:根据mappingName,获取到一个MethodArgumentBuilder
    public static MethodArgumentBuilder fromMappingName(String mappingName) {
        return fromMappingName(null, mappingName);
    }
    public static MethodArgumentBuilder fromMappingName(@Nullable UriComponentsBuilder builder, String name) {
        ...
        Map<String, RequestMappingInfoHandlerMapping> map = wac.getBeansOfType(RequestMappingInfoHandlerMapping.class);
        ...
        for (RequestMappingInfoHandlerMapping mapping : map.values()) {
            // 重点:根据名称找到List<HandlerMethod> handlerMethods
            // 依赖的是getHandlerMethodsForMappingName()这个方法,它是从MappingRegistry里查找
            handlerMethods = mapping.getHandlerMethodsForMappingName(name);
        }
        ...
        HandlerMethod handlerMethod = handlerMethods.get(0);
        Class<?> controllerType = handlerMethod.getBeanType();
        Method method = handlerMethod.getMethod();
        // 构建一个MethodArgumentBuilder
        return new MethodArgumentBuilder(builder, controllerType, method);
    }

MethodArgumentBuilder是MvcUriComponentsBuilder的一个public静态内部类,持有controllerType、method、argumentValues、baseUrl等属性。
使用场景,参考spring官方文档

1577238071(1).png
二:Thymeleaf
在Thymeleaf中可以通过#mvc.url(...)表达式方法调用Controller中符合驼峰命名规则的方法(get,set),调用的方式为方法的名字,即相当于jsp的spring:mvcUrl(...)自定义方法。比如:
public class ExampleController {
    @RequestMapping("/data")
    public String getData(Model model) { ... return "template" }
    @RequestMapping("/data")
    public String getDataParam(@RequestParam String type) { ... return "template" }
}
<a th:href="${(#mvc.url('EC#getData')).build()}">获取Data参数</a>
<a th:href="${(#mvc.url('EC#getDataParam').arg(0,'internal')).build()}">获取Data参数</a>
/*
 * =============================================================================
 *
 *   Copyright (c) 2011-2018, The THYMELEAF team (http://www.thymeleaf.org)
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 * =============================================================================
 */
package org.thymeleaf.spring5.expression;

import org.thymeleaf.exceptions.ConfigurationException;
import org.thymeleaf.spring5.util.SpringVersionUtils;
import org.thymeleaf.util.ClassLoaderUtils;

/**
 * <p>
 *   Expression object in charge of the creation of URLs using the controller-based mechanism in Spring MVC 4.1.
 * </p>
 * <p>
 *   This mimics the {@code s:mvcUrl} behaviour explained at
 *   http://docs.spring.io/spring/docs/4.1.0.RELEASE/spring-framework-reference/html/mvc.html#mvc-links-to-controllers-from-views
 *   using the same method (function) names in the Spring JSP tag library.
 * </p>
 * 
 * @author Daniel Fern&aacute;ndez
 * 
 * @since 3.0.3
 *
 */
public class Mvc {

    private static final MvcUriComponentsBuilderDelegate mvcUriComponentsBuilderDelegate;
    private static final String SPRING41_MVC_URI_COMPONENTS_BUILDER_DELEGATE_CLASS_NAME = Mvc.class.getName() + "$Spring41MvcUriComponentsBuilderDelegate";
    private static final String NON_SPRING41_MVC_URI_COMPONENTS_BUILDER_DELEGATE_CLASS_NAME = Mvc.class.getName() + "$NonSpring41MvcUriComponentsBuilderDelegate";


    static {

        final String delegateClassName =
                (SpringVersionUtils.isSpring41AtLeast()?
                        SPRING41_MVC_URI_COMPONENTS_BUILDER_DELEGATE_CLASS_NAME :
                        NON_SPRING41_MVC_URI_COMPONENTS_BUILDER_DELEGATE_CLASS_NAME);

        try {
            final Class<?> implClass = ClassLoaderUtils.loadClass(delegateClassName);
            mvcUriComponentsBuilderDelegate = (MvcUriComponentsBuilderDelegate) implClass.newInstance();
        } catch (final Exception e) {
            throw new ExceptionInInitializerError(
                    new ConfigurationException(
                        "Thymeleaf could not initialize a delegate of class \"" + delegateClassName + "\" for taking " +
                        "care of the " + SpringStandardExpressionObjectFactory.MVC_EXPRESSION_OBJECT_NAME + " expression utility object", e));
        }

    }


    public MethodArgumentBuilderWrapper url(final String mappingName) {
        return mvcUriComponentsBuilderDelegate.fromMappingName(mappingName);
    }



    static interface MvcUriComponentsBuilderDelegate {

        public MethodArgumentBuilderWrapper fromMappingName(String mappingName);

    }


    static class Spring41MvcUriComponentsBuilderDelegate implements MvcUriComponentsBuilderDelegate {

        Spring41MvcUriComponentsBuilderDelegate() {
            super();
        }

        public MethodArgumentBuilderWrapper fromMappingName(final String mappingName) {
            return new Spring41MethodArgumentBuilderWrapper(org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.fromMappingName(mappingName));
        }

    }


    static class NonSpring41MvcUriComponentsBuilderDelegate implements MvcUriComponentsBuilderDelegate {

        NonSpring41MvcUriComponentsBuilderDelegate() {
            super();
        }

        public MethodArgumentBuilderWrapper fromMappingName(final String mappingName) {
            throw new UnsupportedOperationException(
                    "MVC URI component building is only supported in Spring versions 4.1 or newer");
        }

    }



    public static interface MethodArgumentBuilderWrapper {

        public MethodArgumentBuilderWrapper arg(int index, Object value);
        public String build();
        public String buildAndExpand(Object... uriVariables);

    }


    static class Spring41MethodArgumentBuilderWrapper implements MethodArgumentBuilderWrapper {

        private final org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.MethodArgumentBuilder builder;


        private Spring41MethodArgumentBuilderWrapper(
                final org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.MethodArgumentBuilder builder) {
            super();
            this.builder = builder;
        }

        public MethodArgumentBuilderWrapper arg(final int index, final Object value) {
            return new Spring41MethodArgumentBuilderWrapper(this.builder.arg(index, value));
        }

        public String build() {
            return this.builder.build();
        }

        public String buildAndExpand(final Object... uriVariables) {
            return this.builder.buildAndExpand(uriVariables);
        }

    }


}

参考:1.https://www.cnblogs.com/fangshixiang/p/11491517.html
2.https://www.cnblogs.com/jiangchao226/p/5937458.html
3.https://docs.spring.io/spring/docs/4.1.0.RELEASE/spring-framework-reference/html/mvc.html#mvc-links-to-controllers-from-views

相关文章

网友评论

      本文标题:spring集成Thymeleaf通过注解直接从视图链接到控制器

      本文链接:https://www.haomeiwen.com/subject/mpwqoctx.html