以spring官方文档为基础,官方地址:Spring Boot_Web
Spring Boot非常适合web应用程序的开发。可以使用嵌入的Tomcat、Jetty、Undertow或Netty创建一个自包含的HTTP服务器。大多数web应用程序使用spring-boot-starter-web模块来快速启动和运行。也可以选择使用spring-boot-starter-webflux模块来构建响应式web应用程序。
如果想构建基于servlet的web应用程序,可以利用Spring Boot对Spring MVC或Jersey的自动配置。
Spring Web MVC框架(通常称为“Spring MVC”)是一个丰富的“模型-视图-控制器”Web框架。Spring MVC允许使用@Controller或@RestController bean来处理传入的HTTP请求, controller控制器中的方法通过@RequestMapping注释映射到HTTP。
下面是一个典型的@RestController服务示例:
import java.util.List;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/users")
public class MyRestController {
private final UserRepository userRepository;
private final CustomerRepository customerRepository;
public MyRestController(UserRepository userRepository, CustomerRepository customerRepository) {
this.userRepository = userRepository;
this.customerRepository = customerRepository;
}
@GetMapping("/{user}")
public User getUser(@PathVariable Long userId) {
return this.userRepository.findById(userId).get();
}
@GetMapping("/{user}/customers")
public List<Customer> getUserCustomers(@PathVariable Long userId) {
return this.userRepository.findById(userId).map(this.customerRepository::findByUser).get();
}
@DeleteMapping("/{user}")
public void deleteUser(@PathVariable Long userId) {
this.userRepository.deleteById(userId);
}
}
Spring MVC是核心Spring框架的一部分,Spring参考文档中提供了详细的信息。
Spring Boot为大多数应用提供了Spring MVC的自动配置。它的自动配置在Spring的默认值之上添加了以下特性:
如果想保留那些Spring Boot的MVC定制,并做更多的MVC定制(拦截器、格式化器、视图控制器和其他特性),可以添加自己的WebMvcConfigurer类型的@Configuration类,但不要添加@EnableWebMvc。
如果想提供自定义的实例RequestMappingHandlerMapping,RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver,并且依旧保留Spring Boot MVC的定制,可以声明一个WebMvcRegistrations,利用它来提供这些组件的自定义实例。
如果想完全控制Spring MVC,可以添加被@EnableWebMvc注释过的@Configuration配置,或者也可以添加被DelegatingWebMvcConfiguration注释过的@Configuration配置,具体可参见@EnableWebMvc的Javadoc中所述。
Spring MVC使用HttpMessageConverter接口来转换HTTP请求和响应。对象可以自动转换为JSON(通过使用Jackson库)或XML(如果Jackson XML扩展可用,则使用Jackson XML扩展;如果Jackson XML扩展不可用,则使用JAXB)。默认情况下,字符串采用UTF-8编码。Jackson库)是默认解析json的库。
如果需要添加或自定义转换器,可以使用Spring Boot的HttpMessageConverters类,如下所示:
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
@Configuration(proxyBeanMethods = false)
public class MyHttpMessageConvertersConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = new AdditionalHttpMessageConverter();
HttpMessageConverter<?> another = new AnotherHttpMessageConverter();
return new HttpMessageConverters(additional, another);
}
}
上述自定义的HttpMessageConverter bean都将被添加到转换器列表中(这是添加配置类来注入Bean)。也可以用同样的方法重写默认转换器(启动类继承extends WebMvcConfigurerAdapter,然后覆盖方法configureMessageConverters)
在使用Jackson来序列化/反序列化JSON数据时,可能需要编写自己的JsonSerializer和JsonDeserializer类。自定义序列化器通常通过一个模块注册到Jackson,但是Spring Boot其实提供了一个@JsonComponent注释,它使得直接注册Spring bean变得更简单。
可以直接在JsonSerializer、JsonDeserializer或KeyDeserializer实现上使用@JsonComponent注释。也可以在包含序列化器/反序列化器作为内部类的类上使用它,如下面的例子所示:
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.jackson.JsonComponent;
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonSerializer<MyObject> {
@Override
public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
}
}
public static class Deserializer extends JsonDeserializer<MyObject> {
@Override
public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
ObjectCodec codec = jsonParser.getCodec();
JsonNode tree = codec.readTree(jsonParser);
String name = tree.get("name").textValue();
int age = tree.get("age").intValue();
return new MyObject(name, age);
}
}
}
ApplicationContext中的所有@JsonComponent bean都会自动注册到Jackson。因为@JsonComponent是用@Component元注解的,所以通常的组件扫描规则也适用。
Spring Boot还提供了JsonObjectSerializer和JsonObjectDeserializer基类,它们在序列化对象时为标准Jackson版本提供了有用的替代方案。详见Javadoc中的JsonObjectSerializer和JsonObjectDeserializer。
上面的例子可以重写为使用JsonObjectSerializer/JsonObjectDeserializer,如下所示:
import java.io.IOException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JsonObjectDeserializer;
import org.springframework.boot.jackson.JsonObjectSerializer;
@JsonComponent
public class MyJsonComponent {
public static class Serializer extends JsonObjectSerializer<MyObject> {
@Override
protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
throws IOException {
jgen.writeStringField("name", value.getName());
jgen.writeNumberField("age", value.getAge());
}
}
public static class Deserializer extends JsonObjectDeserializer<MyObject> {
@Override
protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
JsonNode tree) throws IOException {
String name = nullSafeValue(tree.get("name"), String.class);
int age = nullSafeValue(tree.get("age"), Integer.class);
return new MyObject(name, age);
}
}
}
Spring MVC有一个从绑定错误中生成错误代码的策略:MessageCodesResolver。如果已经设置了spring.mvc.message-codes-resolver-format属性,比如PREFIX_ERROR_CODE或POSTFIX_ERROR_CODE,那么Spring Boot会为其创建一个MessageCodesResolver(参见DefaultMessageCodesResolver.Format中的枚举)。
默认情况下,Spring Boot服务的静态内容来自类路径中的/static(或/public或/resources或/META-INF/resources)目录,或者来自ServletContext的根目录。它使用了来自Spring MVC的ResourceHttpRequestHandler,这样就可以通过添加自己的WebMvcConfigurer和覆盖addResourceHandlers方法来修改该行为。
在一个独立的web应用程序中,容器的默认servlet也会被启用,并作为一个备用,如果Spring决定不处理它,它会从ServletContext的根目录提供内容。大多数时候,这不会发生(除非修改默认的MVC配置),因为Spring总是可以通过DispatcherServlet处理请求。
默认情况下,资源映射到/** 上,但是你可以使用spring.mvc.static-path-pattern对其进行调整。例如,重新定位所有资源到/resources/** 可以实现如下:
spring.mvc.static-path-pattern=/resources/**
还可以使用spring.web.resources.static-locations属性自定义静态资源位置(用目录位置列表替换默认值)。根servlet上下文路径“/”也会被自动添加为一个位置。
针对被打成jar包的应用,不要使用src/main/webapp目录。虽然这个目录是一个通用的标准,但它只适用于war包。而生成的jar文件,则会被大多数构建工具默默忽略。
Spring MVC可以通过查看请求路径并将其匹配到应用程序中定义的映射(例如,Controller方法上的@GetMapping注释),将传入的HTTP请求映射到处理程序。
默认情况下,Spring Boot选择禁用后缀模式匹配,这意味着像“GET /projects/spring-boot.json"将无法匹配@GetMapping("/projects/spring-boot"))的映射。这被认为是Spring MVC应用程序的最佳实践。这个特性主要是针对过去有些HTTP客户端没有发送适当的“Accept”请求报头;而又需要确保发送正确的内容类型给客户端。如今,内容协商机制更加可靠。
还有其他方法来处理HTTP客户端不一致地发送正确的“Accept”请求报头。除了使用后缀匹配,还可以使用查询参数来确保像“GET /projects/spring-boot?”format=json"将被映射到@GetMapping("/projects/spring-boot"):
spring.mvc.contentnegotiation.favor-parameter=true
或者这样:
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.parameter-name=myparam
后缀模式匹配已弃用,并将在未来的版本中删除。如果理解了这些注意事项,但仍希望应用程序使用后缀模式匹配,以下配置是必需的:
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-suffix-pattern=true
另外,比起打开所有后缀模式,只支持注册后缀模式更安全:
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.pathmatch.use-registered-suffix-pattern=true
从Spring Framework 5.3开始,Spring MVC支持若干种将请求路径匹配到控制器处理程序的实现策略。它以前只支持AntPathMatcher策略,但现在它还提供PathPatternParser。Spring Boot现在提供了一个配置属性来在新策略中进行选择:
spring.mvc.pathmatch.matching-strategy=path-pattern-parser
PathPatternParser是一个经过优化的实现,但是限制了一些路径模式变体的使用,并且不兼容后缀模式匹配(spring.mvc.pathmatch.use-suffix-pattern, spring.mvc.pathmatch.use-registered-suffix-pattern)或将DispatcherServlet映射为servlet前缀(spring.mvc.servlet.path)。
Spring MVC使用WebBindingInitializer为特定的请求初始化一个WebDataBinder。如果你创建了自己的ConfigurableWebBindingInitializer @Bean, Spring Boot会自动配置Spring MVC来使用它。
除REST web服务之外,还可以使用Spring MVC来提供动态HTML内容。Spring MVC支持多种模板技术,包括Thymeleaf、FreeMarker和jsp。此外,许多其他模板引擎也包含了它们自己的Spring MVC集成。
Spring Boot包括对以下模板引擎的自动配置支持:
当使用以上模板引擎的默认配置时,模板会自动从src/main/resources/templates中选取。
根据运行应用程序的方式,IDE可能会对类路径进行不同的排序。在IDE中从主方法运行应用程序,与使用Maven或Gradle或从其打包的jar运行应用程序时的顺序不同。这可能会导致Spring Boot无法找到预期的模板。如果出现这个问题,可以在IDE中重新排序类路径,以优先放置模块的类和资源。
默认情况下,Spring Boot提供了一个/error映射,以一种合理的方式处理所有错误,并且它在servlet容器中被注册为一个“全局”错误页面。针对机器客户端,它会生成一个JSON响应,其中包含错误、HTTP状态和异常消息的详细信息。对于浏览器客户端,有一个“白标签”错误视图,它以HTML格式呈现相同的数据(自定义它,添加一个解决错误的视图)。
有一些服务器。如果要自定义默认错误处理行为,可以设置的错误属性。请参阅附录中的“服务器属性”部分。
要完全替换默认行为,可以实现ErrorController并注册一个该类型的bean定义,或者添加一个ErrorAttributes类型的bean来使用现有的机制但替换其中的内容。
也可以定义一个带有@ControllerAdvice注释的类来定制JSON文档,以返回特定的控制器和/或异常类型,如下例所示:
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice(basePackageClasses = SomeController.class)
public class MyControllerAdvice extends ResponseEntityExceptionHandler {
@ResponseBody
@ExceptionHandler(MyException.class)
public ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new MyErrorBody(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer code = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
HttpStatus status = HttpStatus.resolve(code);
return (status != null) ? status : HttpStatus.INTERNAL_SERVER_ERROR;
}
}
在前面的例子中,如果YourException是由与SomeController定义在同一个包中的控制器抛出的,则使用CustomErrorType POJO的JSON表示而不是ErrorAttributes表示。
在某些情况下,在控制器级别处理的错误不会被度量基础设施记录下来。通过将处理的异常设置为请求属性,应用程序可以确保这些异常被记录在请求度量中:
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
@Controller
public class MyController {
@ExceptionHandler(CustomException.class)
String handleCustomException(HttpServletRequest request, CustomException ex) {
request.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, ex);
return "errorView";
}
}
如果希望为给定的状态码显示自定义HTML错误页面,可以向/error目录中添加一个文件。错误页面可以是静态HTML(也就是说,添加到任何静态资源目录下),也可以通过使用模板来构建。文件的名称应该是确切的状态码或系列掩码。
例如,将404映射到一个静态HTML文件,目录结构如下:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- public/
+- error/
| +- 404.html
+- <other public assets>
使用FreeMarker模板映射所有5xx错误,目录结构如下:
src/
+- main/
+- java/
| + <source code>
+- resources/
+- templates/
+- error/
| +- 5xx.ftlh
+- <other templates>
对于更复杂的映射,也可以添加实现ErrorViewResolver接口的bean,如下例所示:
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.ModelAndView;
public class MyErrorViewResolver implements ErrorViewResolver {
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
// Use the request or status to optionally return a ModelAndView
if (status == HttpStatus.INSUFFICIENT_STORAGE) {
// We could add custom model values here
new ModelAndView("myview");
}
return null;
}
}
还可以使用常规的Spring MVC特性,如@ExceptionHandler方法和@ControllerAdvice。然后ErrorController会获取任何未处理的异常。
对于不使用Spring MVC的应用程序,可以使用ErrorPageRegistrar接口直接注册ErrorPages。这个抽象直接与底层嵌入式servlet容器一起工作,即使没有Spring MVC DispatcherServlet也可以工作。
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
@Configuration(proxyBeanMethods = false)
public class MyErrorPagesConfiguration {
@Bean
public ErrorPageRegistrar errorPageRegistrar() {
return this::registerErrorPages;
}
private void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.BAD_REQUEST, "/400"));
}
}
如果注册了一个ErrorPage的路径,该路径最终会被Filter处理(这在一些非spring的web框架中很常见,比如Jersey和Wicket),那么Filter必须被显式地注册为一个ERROR dispatcher,如下所示:
import java.util.EnumSet;
import javax.servlet.DispatcherType;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MyFilterConfiguration {
@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>(new MyFilter());
// ...
registration.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
return registration;
}
}
需要注意的是,默认的FilterRegistrationBean不包括ERROR调度程序类型。
跨源资源共享(CORS)是由大多数浏览器实现的W3C规范,它允许您以灵活的方式指定哪种跨域请求得到授权,而不是使用一些不太安全、功能不太强大的方法,如IFRAME或JSONP。
从4.2版开始,Spring MVC支持CORS。在Spring Boot应用程序中使用带有@CrossOrigin注释的控制器方法CORS配置不需要任何特定的配置。全局CORS配置可以通过使用自定义的addcorsm(CorsRegistry)方法注册WebMvcConfigurer bean来定义,如下所示:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration(proxyBeanMethods = false)
public class MyCorsConfiguration {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**");
}
};
}
}
对于servlet应用,Spring Boot支持嵌入Tomcat、Jetty和Undertow服务器。大多数开发人员使用适当的“Starter”来获得完全配置的实例。默认情况下,嵌入式服务器在端口8080上侦听HTTP请求。
当使用嵌入式servlet容器时,可以通过使用Spring bean或扫描servlet组件,从servlet规范中注册servlet、过滤器和监听器(如HttpSessionListener)。
任何Servlet、Filter或Servlet *Listener实例,只要是一个Spring bean,就会注册到嵌入的容器中。这使得在配置属性阶段,如果想引用application.properties的值,就会变得非常方便。
默认情况下,如果上下文只包含一个Servlet,它会被映射到/。在有多个servlet bean的情况下,bean名被用作路径前缀。过滤器映射到/*。
如果基于约定的映射不够灵活,可以使用servleregistrationbean、FilterRegistrationBean和ServletListenerRegistrationBean类来进行完全控制。
嵌入式servlet容器不会直接执行servlet 3.0+ javax.servlet.ServletContainerInitializer接口或Spring的org.springframework.web.WebApplicationInitializer接口。这是一个有意的设计决策,目的是为了减少在war中运行的第三方库可能破坏Spring Boot应用程序的风险。
如果需要在Spring Boot应用程序中执行servlet上下文初始化,应该注册一个实现org.springframework.boot.web.servlet.ServletContextInitializer接口的bean。onStartup方法提供了对ServletContext的访问,如果需要的话,可以很容易地用作现有WebApplicationInitializer的适配器。
当使用嵌入式容器时,使用@WebServlet, @WebFilter和@WebListener注解的类的自动注册可以通过使用@ServletComponentScan来启用。
关于自定义内嵌Servlet容器:可以使用Spring Environment属性配置常见的servlet容器设置。通常,可以在application.properties或application.yaml文件中定义属性。
常用服务器设置包括:
Spring Boot尽可能地公开通用的设置,但这并不总是可能的。对于这些情况,可以看一下特定于服务器的专用名称空间(参见server.omcat和server.undertow)。例如,可以使用嵌入式servlet容器的特定特性来配置访问日志。
关于SameSite Cookies:web浏览器可以使用SameSite cookie属性来控制是否以及如何在跨站点请求中提交cookie。这个属性对于现代的web浏览器来说是非常重要的,因为当属性丢失时,浏览器会改变默认值。
如果想要更改会话cookie的samsite属性,可以使用server.servlet.session.cookie.same-site属性。自动配置的Tomcat、Jetty和Undertow服务器支持此属性。它也被用来配置基于SessionRepository bean的Spring Session servlet。
例如,如果希望会话cookie具有None的SameSite属性,可以将以下内容添加到application.properties 或 application.yaml文件:
server.servlet.session.cookie.same-site=none
如果想要更改添加到您的HttpServletResponse的其他cookie的SameSite属性,可以使用一个cookesamesitessupplier。传递给cookesamesitesupplier一个Cookie,可能返回一个SameSite值,或者null。
有许多方便的工厂方法和过滤器方法,可以使用它们来快速匹配特定的cookie。例如,添加以下bean将自动为所有名称与正则表达式myapp.*匹配的cookie应用Lax的samsite。
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
public class MySameSiteConfiguration {
@Bean
public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
return CookieSameSiteSupplier.ofLax().whenHasNameMatching("myapp.*");
}
}
关于程序定制化Servlet容器:
如果需要以编程方式配置嵌入式servlet容器,可以注册一个实现WebServerFactoryCustomizer接口的Spring bean。WebServerFactoryCustomizer提供了对ConfigurableServletWebServerFactory的访问,其中包括许多定制setter方法。下面的示例显示了以编程方式设置端口:
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.stereotype.Component;
@Component
public class MyWebServerFactoryCustomizer implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
@Override
public void customize(ConfigurableServletWebServerFactory server) {
server.setPort(9000);
}
}
TomcatServletWebServerFactory、JettyServletWebServerFactory和UndertowServletWebServerFactory是ConfigurableServletWebServerFactory的专用变体,它们分别为Tomcat、Jetty和Undertow提供了额外的定制setter方法。下面的例子展示了如何定制TomcatServletWebServerFactory,它提供了对特定于tomcat的配置选项的访问:
import java.time.Duration;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
@Component
public class MyTomcatWebServerFactoryCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Override
public void customize(TomcatServletWebServerFactory server) {
server.addConnectorCustomizers((connector) -> connector.setAsyncTimeout(Duration.ofSeconds(20).toMillis()));
}
}
关于JSP限制:
当运行使用嵌入式servlet容器(并打包为可执行文件)的Spring Boot应用程序时,JSP支持中存在一些限制。