SpringBoot学习笔记(三)——Spring MVC控制器、 Restful、Swagger
一、Spring MVC控制器
1.1、控制器
控制器提供访问应用程序的行为,通常通过服务接口定义或注解定义两种方法实现。 控制器解析用户的请求并将其转换为一个模型。在Spring MVC中一个控制器可以包含多个Action(动作、方法)。
注解有三种:
@Controller | 处理http请求 |
@RestController | spring4之后新加的注解,原来返回json需要@ResponseBody配合@Controller |
@RequestMapping | 配置url映射(从请求url(可能还包括请求方法、参数(pathvariable或parameter)等到控制器及对应方法的映射)) |
使用注解@Controller定义控制器。org.springframework.stereotype.Controller注解类型用于声明Spring类的实例是一个控制器(在讲IOC时还提到了另外3个注解);Spring可以使用扫描机制来找到应用程序中所有基于注解的控制器类,为了保证Spring能找到你的控制器,需要在配置文件中声明组件扫描。
创建一个名了Bar的类,定义为一个控制器,类的具体实现如下:
package com.zhangguo.springmvc02.controllers; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; /** * 定义控制器 */ //BarController类的实例是一个控制器,会自动添加到Spring上下文中 @Controller public class BarController { //映射访问路径 @RequestMapping("/bar") public String index(Model model){ //Spring MVC会自动实例化一个Model对象用于向视图中传值 model.addAttribute("message", "这是通过注解定义的一个控制器中的Action"); //返回视图位置 return "foo/index"; } }
1.2、@RequestMapping
@RequestMapping注释用于映射url到控制器类或一个特定的处理程序方法。可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。该注解共有8个属性,注解源码如下:
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 java.util.concurrent.Callable; import org.springframework.core.annotation.AliasFor; /** * 用于映射url到控制器类或一个特定的处理程序方法. */ //该注解只能用于方法或类型上 @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented @Mapping public @interface RequestMapping { /** * 指定映射的名称 */ String name() default ""; /** * 指定请求的路径映射,指定的地址可以是uri模板,别名为path */ @AliasFor("path") String[] value() default {}; /** 别名为value,使用path更加形象 * 只有用在一个Servlet环境:路径映射URI(例如“/myPath.do”)。 * Ant风格的路径模式,同时也支持(例如,“/myPath/*.do”)。在方法层面,在主要的映射在类型级别表示相对路径(例如,“edit.do”) * 的支持。路径映射的URI可能包含占位符(例如“/$ {}连接”) */ @AliasFor("value") String[] path() default {}; /** * 指定请求谓词的类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. 收窄请求范围 The * HTTP request methods to map to, narrowing the primary mapping: GET, POST, * HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE. */ RequestMethod[] method() default {}; /** * 映射请求的参数,收窄请求范围 The parameters of the mapped request, narrowing the * primary mapping. */ String[]params() default {}; /** * 映射请求头部,收窄请求范围 The headers of the mapped request, narrowing the primary * mapping. RequestMapping(value = "/something", headers = * "content-type=text/*") */ String[] headers() default {}; /** * 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html,收窄请求范围 The * consumable media types of the mapped request, narrowing the primary * mapping. */ String[] consumes() default {}; /** * 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回 The producible media types * of the mapped request, narrowing the primary mapping. produces = * "text/plain" produces = {"text/plain", "application/*"} produces = * "application/json; charset=UTF-8" */ String[] produces() default {}; }
从上面的源码可以发现除了name基本都是数组类型,在设置时我们可以指定单个值,如@RequestMapping(value="/foo");也可以同时指定多个值如:@RequestMapping(value={"/foo","/bar"})。
1.2.1、value 属性指定映射路径或URL模板
指定请求的实际地址,指定的地址可以是URL模板,正则表达式或路径占位,该属性与path互为别名关系,@RequestMapping("/foo")} 与 @RequestMapping(path="/foo")相同。该属性是使用最频繁,最重要的一个属性,如果只指定该属性时可以把value略去。Spring Framework 4.2引入了一流的支持声明和查找注释属性的别名。@AliasFor注解可用于声明一双别名属性,来给注解的属性起别名, 让使用注解时, 更加的容易理解(比如给value属性起别名, 更容易让人理解)。先看一个官网的示例:
@Controller @RequestMapping("/appointments") public class AppointmentsController { private final AppointmentBook appointmentBook; @Autowired public AppointmentsController(AppointmentBook appointmentBook) { this.appointmentBook = appointmentBook; } @RequestMapping(method = RequestMethod.GET) public Map<String, Appointment> get() { return appointmentBook.getAppointmentsForToday(); } @RequestMapping(value = "/{day}", method = RequestMethod.GET) public Map<String, Appointment> getForDay(@PathVariable @DateTimeFormat(iso = ISO.DATE) Date day, Model model) { return appointmentBook.getAppointmentsForDay(day); } @RequestMapping(value = "/new", method = RequestMethod.GET) public AppointmentForm getNewForm() { return new AppointmentForm(); } @RequestMapping(method = RequestMethod.POST) public String add(@Valid AppointmentForm appointment, BindingResult result) { if (result.hasErrors()) { return "appointments/new"; } appointmentBook.addAppointment(appointment); return "redirect:/appointments"; } }
1.2.2、指定具体路径字符
1.2.2.1 只注解方法
@Controller public class FooBarController { @RequestMapping("/action1") public String action1(){ return "foo/index"; } }
访问路径:http://localhost:8087/SpringMVC02/action1
1.2.2.2 同时注解类与方法
@Controller @RequestMapping("/foobar") public class FooBarController { @RequestMapping("/action1") public String action1(){ return "foo/index"; } }
访问路径:http://localhost:8087/SpringMVC02/foobar/action1
需要先指定类的路径再指定方法的路径
1.2.2.3 当value为空值
注解在方法上时,如果value为空则表示该方法为类下默认的Action。
@Controller @RequestMapping("/foobar") public class FooBarController { @RequestMapping("/action1") public String action1(Model model){ //在模型中添加属性message值为action1,渲染页面时使用 model.addAttribute("message", "action1"); return "foo/index"; } @RequestMapping public String action2(Model model){ //在模型中添加属性message值为action2,渲染页面时使用 model.addAttribute("message", "action2"); return "foo/index"; } }
访问action2的路径是:http://localhost:8087/SpringMVC02/foobar,如果加上action2就错误了。
注解在类上时,当value为空值则为默认的控制器,可以用于设置项目的起始页。
@Controller @RequestMapping public class FooBarController { @RequestMapping("/action1") public String action1(Model model){ //在模型中添加属性message值为action1,渲染页面时使用 model.addAttribute("message", "action1"); return "foo/index"; } @RequestMapping public String action2(Model model){ //在模型中添加属性message值为action2,渲染页面时使用 model.addAttribute("message", "action2"); return "foo/index"; } }
访问路径:http://localhost:8087/SpringMVC02/,同时省去了控制器名与Action名称,可用于欢迎页。
访问action1的路径是:http://localhost:8087/SpringMVC02/action1
1.2.3、路径变量占位,URI模板模式
在Spring MVC可以使用@PathVariable 注释方法参数的值绑定到一个URI模板变量。
@RequestMapping("/action3/{p1}/{p2}") public int action1(@PathVariable("p1") int n1, @PathVariable("p2")int n2){ return n1+n2; }
运行结果:
使用路径变量的好处:使路径变得更加简洁;获得参数更加方便,框架会自动进行类型转换。通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到action,如这里访问是的路径是/action3/1/a,则路径与方法不匹配,而不会是参数转换失败。
1.2.4、正则表达式模式的URI模板
@RequestMapping(value="/action4/{id:\\d{6}}-{name:[a-z]{3}}") public String action4(@PathVariable int id,@PathVariable String name){ }
正则要求id必须为6位的数字,而name必须为3位小写字母,访问结果如下:
1.2.5、Ant风格路径模式
@RequestMapping注解也支持ant风格的路径模式,如/myPath/*.do,/owners/*/pets/{petId},示例代码如下:
//Ant风格路径模式 @RequestMapping(value = "/action6/*.do") public String action6(){ }
运行结果:
当然还有关于路径匹配的规则,特殊的优先级高过一般的,更多规则可以参考官方帮助。
ANT通配符有三种:
1.2.6、@RequestMapping 来处理多个 URI
你可以将多个请求映射到一个方法上去,只需要添加一个带有请求路径值列表的 @RequestMapping 注解就行了。
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(value = { "", "/page", "page*", "view/*",
"**/msg" }) String indexMultipleMapping() { return "Hello from index multiple mapping."; } }
如你在这段代码中所看到的,@RequestMapping 支持统配符以及ANT风格的路径。前面这段代码中,如下的这些 URL 都会由 indexMultipleMapping() 来处理:
localhost:8080/home
localhost:8080/home/
localhost:8080/home/page
localhost:8080/home/pageabc
localhost:8080/home/view/
localhost:8080/home/view/view
1.2.7、method属性指定谓词类型
用于约束请求的谓词类型,可以收窄请求范围。指定请求谓词的类型如GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE,如下代码所示:
//谓词类型 @RequestMapping(value = "/action6",method={RequestMethod.POST,RequestMethod.DELETE}) public String action6(Model model) { }
要访问action7请求谓词类型必须是POST或者为DELETE,当我们从浏览器的URL栏中直接请求时为一个GET请求,则结果是405,如下所示:
如果将POST修改为GET则正常了,如下所示:
//谓词类型 @RequestMapping(value = "/action6",method=RequestMethod.GET) public String action6(Model model) { model.addAttribute("message", "请求谓词只能是GET"); return "foo/index"; }
Spring MVC 的 @RequestMapping 注解能够处理 HTTP 请求的方法, 比如 GET, PUT, POST, DELETE 以及 PATCH。
所有的请求默认都会是 HTTP GET 类型的。
为了能降一个请求映射到一个特定的 HTTP 方法,你需要在 @RequestMapping 中使用 method 来声明 HTTP 请求所使用的方法类型,如下所示:
@RestController @RequestMapping("/home") public class IndexController { @RequestMapping(method = RequestMethod.GET) String get() { return "Hello from get"; } @RequestMapping(method = RequestMethod.DELETE) String delete() { return "Hello from delete"; } @RequestMapping(method = RequestMethod.POST) String post() { return "Hello from post"; } @RequestMapping(method = RequestMethod.PUT) String put() { return "Hello from put"; } @RequestMapping(method = RequestMethod.PATCH) String patch() { return "Hello from patch"; } }
在上述这段代码中, @RequestMapping 注解中的 method 元素声明了 HTTP 请求的 HTTP 方法的类型。
所有的处理处理方法会处理从这同一个 URL( /home)进来的请求, 但要看指定的 HTTP 方法是什么来决定用哪个方法来处理。
例如,一个 POST 类型的请求 /home 会交给 post() 方法来处理,而一个 DELETE 类型的请求 /home 则会由 delete() 方法来处理。
你会看到 Spring MVC 将使用这样相同的逻辑来映射其它的方法。
1.2.8、consumes属性指定请求的Content-Type
@RequestMapping 注解的 produces 和 consumes 这两个元素来缩小请求映射类型的范围,达到处理生产和消费对象的目的。
指定处理请求的提交内容类型(Content-Type),例如application/json, text/html,收窄请求范围,如果用户发送的请求内容类型不匹配则方法不会响应请求,具体使用如下代码所示:
package com.zhangguo.springmvc02.controllers; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/home") public class HomeController { // 请求内容类型必须为text/html,注意浏览器默认没有指定Content-type @RequestMapping(value = "/action8",consumes="text/html") public String action8(Model model) { model.addAttribute("message", "请求的提交内容类型(Content-Type)是text/html"); return "foo/index"; } }
在action8的注解中约束发送到服务器的Content-Type必须是text/html类型,如果类型不一致则会报错(415),测试结果如下:
从两个图的对比可以看出当内容类型为text/plain时报客户端错误415,当内容类型为text/html时则响应正常,响应的结果如下:
请求的提交内容类型(Content-Type)是text/html
注意:可以使用!号,如consumes="!text/html"
1.2.9、produces属性指定响应的Content-Type,约束Accept类型
指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回,方法才处理客户端的请求否则会报406错误,常用设置如下:
produces = "text/plain" //客户端只接收纯文本
produces = {"text/plain", "application/*"} //客户端接收纯文本与application/*类型的内容
produces = "application/json; charset=UTF-8" //客户端接收json且编码为utf-8
//客户端接收json且编码为utf-8,多数浏览器Accept设置的为*/*,接收任意类型 @RequestMapping(value = "/action9",produces="application/json; charset=UTF-8") public String action9(Model model) { model.addAttribute("message", "客户端可以接收的类型是application/json; charset=UTF-8"); return "foo/index"; }
运行结果:
注意:可以使用!号,如produces="!text/html"
1.2.10、params属性指定请求中必须有特定参数与值
映射请求的参数,收窄请求范围。可以限制客户端发送到服务器的请求参数为某些特定值或不为某些值,如下代码所示:
//请求的参数必须包含id=215与name不等于abc @RequestMapping(value = "/action10",params={"id=215","name!=abc"}) public String action10(Model model) { model.addAttribute("message", "请求的参数必须包含id=215与name不等于abc"); return "foo/index"; }
运行结果如下:
name的值如没有指定也是通过的;可以使用不等于;
1.2.11、headers属性指定请求中必须有特定header值
映射请求头部,收窄请求范围。约束客户端发送的请求头部信息中必须包含某个特定的值或不包含某个值,作用范围明显大于前面讲过的几种,示例代码如下:
//请求头部信息中必须包含Host=localhost:8088 @RequestMapping(value = "/action11",headers="Host=localhost:8088") public String action11(Model model) { model.addAttribute("message", "请求头部信息中必须包含Host=localhost:8088"); return "foo/index"; }
运行结果:
修改Host为8087时运行就正常了:
这里同样可以使用!号;可以使用通配符如:Content-Type="application/*"
1.2.12、name属性指定名称
为当前映射指定一个名称,不常用,一般不会指定。
1.2.13、path属性指定路径
先看源码中的path与value,定义如下:
@AliasFor("path") String[] value() default {}; @AliasFor("value") String[] path() default {};
从Spring 4.2开始引入了@AliasFor注解,可以实现属性的别名,如value本身并没有特定的含义,而path会更加具体,能见名知义,通俗说可以认为两者在使用中是一样的如:@RequestMapping("/foo")} 与 @RequestMapping(path="/foo")相同。示例代码如下:
//映射访问路径为/action12或/myaction,指定映射名称为actionTest @RequestMapping(path ={"/action12","/myaction"},name="actionTest") public String action12(Model model) { model.addAttribute("message", "映射访问路径为/action12或/myaction,指定映射名称为actionTest"); return "foo/index"; }
运行结果:
1.2.14、@RequestMapping 快捷方式
Spring 4.3 引入了方法级注解的变体,也被叫做 @RequestMapping 的组合注解。组合注解可以更好的表达被注解方法的语义。它们所扮演的角色就是针对 @RequestMapping 的封装,而且成了定义端点的标准方法。
例如,@GetMapping 是一个组合注解,它所扮演的是 @RequestMapping(method =RequestMethod.GET) 的一个快捷方式。
方法级别的注解变体有如下几个:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMappin
如下面两个action就是基本等价的:
@RequestMapping(value = "/action3",method = RequestMethod.GET) public String action3(Model model){ model.addAttribute("msg","action3 get请求"); return "hi"; } @GetMapping("/action4") public String action4(Model model){ model.addAttribute("msg","action4 get请求"); return "hi"; }
action4的写法要简单一些,GetMapping与RequestMapping的具体用法一样,源码如下:
/* * Copyright 2002-2016 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 java.util.concurrent.Callable; import org.springframework.core.annotation.AliasFor; /** * Annotation for mapping web requests onto specific handler classes and/or * handler methods. Provides a consistent style between Servlet and Portlet * environments, with the semantics adapting to the concrete environment. * * <p><b>NOTE:</b> The set of features supported for Servlets is a superset * of the set of features supported for Portlets. The places where this applies * are marked with the label "Servlet-only" in this source file. For Servlet * environments there are some further distinctions depending on whether an * application is configured with {@literal "@MVC 3.0"} or * {@literal "@MVC 3.1"} support classes. The places where this applies are * marked with {@literal "@MVC 3.1-only"} in this source file. For more * details see the note on the new support classes added in Spring MVC 3.1 * further below. * * <p>Handler methods which are annotated with this annotation are allowed to * have very flexible signatures. They may have parameters of the following * types, in arbitrary order (except for validation results, which need to * follow right after the corresponding command object, if desired): * <ul> * <li>Request and/or response objects (Servlet API or Portlet API). * You may choose any specific request/response type, e.g. * {@link javax.servlet.ServletRequest} / {@link javax.servlet.http.HttpServletRequest} * or {@link javax.portlet.PortletRequest} / {@link javax.portlet.ActionRequest} / * {@link javax.portlet.RenderRequest}. Note that in the Portlet case, * an explicitly declared action/render argument is also used for mapping * specific request types onto a handler method (in case of no other * information given that differentiates between action and render requests). * <li>Session object (Servlet API or Portlet API): either * {@link javax.servlet.http.HttpSession} or {@link javax.portlet.PortletSession}. * An argument of this type will enforce the presence of a corresponding session. * As a consequence, such an argument will never be {@code null}. * <i>Note that session access may not be thread-safe, in particular in a * Servlet environment: Consider switching the * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#setSynchronizeOnSession * "synchronizeOnSession"} flag to "true" if multiple requests are allowed to * access a session concurrently.</i> * <li>{@link org.springframework.web.context.request.WebRequest} or * {@link org.springframework.web.context.request.NativeWebRequest}. * Allows for generic request parameter access as well as request/session * attribute access, without ties to the native Servlet/Portlet API. * <li>{@link java.util.Locale} for the current request locale * (determined by the most specific locale resolver available, * i.e. the configured {@link org.springframework.web.servlet.LocaleResolver} * in a Servlet environment and the portal locale in a Portlet environment). * <li>{@link java.io.InputStream} / {@link java.io.Reader} for access * to the request's content. This will be the raw InputStream/Reader as * exposed by the Servlet/Portlet API. * <li>{@link java.io.OutputStream} / {@link java.io.Writer} for generating * the response's content. This will be the raw OutputStream/Writer as * exposed by the Servlet/Portlet API. * <li>{@link org.springframework.http.HttpMethod} for the HTTP request method</li> * <li>{@link PathVariable @PathVariable} annotated parameters (Servlet-only) * for access to URI template values (i.e. /hotels/{hotel}). Variable values will be * converted to the declared method argument type. By default, the URI template * will match against the regular expression {@code [^\.]*} (i.e. any character * other than period), but this can be changed by specifying another regular * expression, like so: /hotels/{hotel:\d+}. * Additionally, {@code @PathVariable} can be used on a * {@link java.util.Map Map<String, String>} to gain access to all * URI template variables. * <li>{@link MatrixVariable @MatrixVariable} annotated parameters (Servlet-only) * for access to name-value pairs located in URI path segments. Matrix variables * must be represented with a URI template variable. For example /hotels/{hotel} * where the incoming URL may be "/hotels/42;q=1". * Additionally, {@code @MatrixVariable} can be used on a * {@link java.util.Map Map<String, String>} to gain access to all * matrix variables in the URL or to those in a specific path variable. * <li>{@link RequestParam @RequestParam} annotated parameters for access to * specific Servlet/Portlet request parameters. Parameter values will be * converted to the declared method argument type. Additionally, * {@code @RequestParam} can be used on a {@link java.util.Map Map<String, String>} or * {@link org.springframework.util.MultiValueMap MultiValueMap<String, String>} * method parameter to gain access to all request parameters. * <li>{@link RequestHeader @RequestHeader} annotated parameters for access to * specific Servlet/Portlet request HTTP headers. Parameter values will be * converted to the declared method argument type. Additionally, * {@code @RequestHeader} can be used on a {@link java.util.Map Map<String, String>}, * {@link org.springframework.util.MultiValueMap MultiValueMap<String, String>}, or * {@link org.springframework.http.HttpHeaders HttpHeaders} method parameter to * gain access to all request headers. * <li>{@link RequestBody @RequestBody} annotated parameters (Servlet-only) * for access to the Servlet request HTTP contents. The request stream will be * converted to the declared method argument type using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. Such parameters may optionally be annotated with {@code @Valid} * and also support access to validation results through an * {@link org.springframework.validation.Errors} argument. * Instead a {@link org.springframework.web.bind.MethodArgumentNotValidException} * exception is raised. * <li>{@link RequestPart @RequestPart} annotated parameters * (Servlet-only, {@literal @MVC 3.1-only}) * for access to the content * of a part of "multipart/form-data" request. The request part stream will be * converted to the declared method argument type using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. Such parameters may optionally be annotated with {@code @Valid} * and support access to validation results through a * {@link org.springframework.validation.Errors} argument. * Instead a {@link org.springframework.web.bind.MethodArgumentNotValidException} * exception is raised. * <li>{@link SessionAttribute @SessionAttribute} annotated parameters for access * to existing, permanent session attributes (e.g. user authentication object) * as opposed to model attributes temporarily stored in the session as part of * a controller workflow via {@link SessionAttributes}. * <li>{@link RequestAttribute @RequestAttribute} annotated parameters for access * to request attributes. * <li>{@link org.springframework.http.HttpEntity HttpEntity<?>} parameters * (Servlet-only) for access to the Servlet request HTTP headers and contents. * The request stream will be converted to the entity body using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. * <li>{@link java.util.Map} / {@link org.springframework.ui.Model} / * {@link org.springframework.ui.ModelMap} for enriching the implicit model * that will be exposed to the web view. * <li>{@link org.springframework.web.servlet.mvc.support.RedirectAttributes} * (Servlet-only, {@literal @MVC 3.1-only}) to specify the exact set of attributes * to use in case of a redirect and also to add flash attributes (attributes * stored temporarily on the server-side to make them available to the request * after the redirect). {@code RedirectAttributes} is used instead of the * implicit model if the method returns a "redirect:" prefixed view name or * {@code RedirectView}. * <li>Command/form objects to bind parameters to: as bean properties or fields, * with customizable type conversion, depending on {@link InitBinder} methods * and/or the HandlerAdapter configuration - see the "webBindingInitializer" * property on RequestMappingHandlerMethodAdapter. * Such command objects along with their validation results will be exposed * as model attributes, by default using the non-qualified command class name * in property notation (e.g. "orderAddress" for type "mypackage.OrderAddress"). * Specify a parameter-level {@link ModelAttribute @ModelAttribute} annotation for * declaring a specific model attribute name. * <li>{@link org.springframework.validation.Errors} / * {@link org.springframework.validation.BindingResult} validation results * for a preceding command/form object (the immediate preceding argument). * <li>{@link org.springframework.web.bind.support.SessionStatus} status handle * for marking form processing as complete (triggering the cleanup of session * attributes that have been indicated by the {@link SessionAttributes @SessionAttributes} * annotation at the handler type level). * <li>{@link org.springframework.web.util.UriComponentsBuilder} * (Servlet-only, {@literal @MVC 3.1-only}) * for preparing a URL relative to the current request's host, port, scheme, * context path, and the literal part of the servlet mapping. * </ul> * * <p><strong>Note:</strong> Java 8's {@code java.util.Optional} is supported * as a method parameter type with annotations that provide a {@code required} * attribute (e.g. {@code @RequestParam}, {@code @RequestHeader}, etc.). The use * of {@code java.util.Optional} in those cases is equivalent to having * {@code required=false}. * * <p>The following return types are supported for handler methods: * <ul> * <li>A {@code ModelAndView} object (Servlet MVC or Portlet MVC), * with the model implicitly enriched with command objects and the results * of {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * <li>A {@link org.springframework.ui.Model Model} object, with the view name implicitly * determined through a {@link org.springframework.web.servlet.RequestToViewNameTranslator} * and the model implicitly enriched with command objects and the results * of {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * <li>A {@link java.util.Map} object for exposing a model, * with the view name implicitly determined through a * {@link org.springframework.web.servlet.RequestToViewNameTranslator} * and the model implicitly enriched with command objects and the results * of {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * <li>A {@link org.springframework.web.servlet.View} object, with the * model implicitly determined through command objects and * {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * The handler method may also programmatically enrich the model by * declaring a {@link org.springframework.ui.Model} argument (see above). * <li>A {@link String} value which is interpreted as view name, * with the model implicitly determined through command objects and * {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * The handler method may also programmatically enrich the model by * declaring a {@link org.springframework.ui.ModelMap} argument * (see above). * <li>{@link ResponseBody @ResponseBody} annotated methods (Servlet-only) * for access to the Servlet response HTTP contents. The return value will * be converted to the response stream using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. * <li>An {@link org.springframework.http.HttpEntity HttpEntity<?>} or * {@link org.springframework.http.ResponseEntity ResponseEntity<?>} object * (Servlet-only) to access to the Servlet response HTTP headers and contents. * The entity body will be converted to the response stream using * {@linkplain org.springframework.http.converter.HttpMessageConverter message * converters}. * <li>An {@link org.springframework.http.HttpHeaders HttpHeaders} object to * return a response with no body.</li> * <li>A {@link Callable} which is used by Spring MVC to obtain the return * value asynchronously in a separate thread transparently managed by Spring MVC * on behalf of the application. * <li>A {@link org.springframework.web.context.request.async.DeferredResult} * which the application uses to produce a return value in a separate * thread of its own choosing, as an alternative to returning a Callable. * <li>A {@link org.springframework.util.concurrent.ListenableFuture} * which the application uses to produce a return value in a separate * thread of its own choosing, as an alternative to returning a Callable. * <li>A {@link java.util.concurrent.CompletionStage} (implemented by * {@link java.util.concurrent.CompletableFuture} for example) * which the application uses to produce a return value in a separate * thread of its own choosing, as an alternative to returning a Callable. * <li>A {@link org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter} * can be used to write multiple objects to the response asynchronously; * also supported as the body within {@code ResponseEntity}.</li> * <li>An {@link org.springframework.web.servlet.mvc.method.annotation.SseEmitter} * can be used to write Server-Sent Events to the response asynchronously; * also supported as the body within {@code ResponseEntity}.</li> * <li>A {@link org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody} * can be used to write to the response asynchronously; * also supported as the body within {@code ResponseEntity}.</li> * <li>{@code void} if the method handles the response itself (by * writing the response content directly, declaring an argument of type * {@link javax.servlet.ServletResponse} / {@link javax.servlet.http.HttpServletResponse} * / {@link javax.portlet.RenderResponse} for that purpose) * or if the view name is supposed to be implicitly determined through a * {@link org.springframework.web.servlet.RequestToViewNameTranslator} * (not declaring a response argument in the handler method signature; * only applicable in a Servlet environment). * <li>Any other return type will be considered as single model attribute * to be exposed to the view, using the attribute name specified through * {@link ModelAttribute @ModelAttribute} at the method level (or the default attribute * name based on the return type's class name otherwise). The model will be * implicitly enriched with command objects and the results of * {@link ModelAttribute @ModelAttribute} annotated reference data accessor methods. * </ul> * * <p><b>NOTE:</b> {@code @RequestMapping} will only be processed if an * an appropriate {@code HandlerMapping}-{@code HandlerAdapter} pair * is configured. This is the case by default in both the * {@code DispatcherServlet} and the {@code DispatcherPortlet}. * However, if you are defining custom {@code HandlerMappings} or * {@code HandlerAdapters}, then you need to add * {@code DefaultAnnotationHandlerMapping} and * {@code AnnotationMethodHandlerAdapter} to your configuration.</code>. * * <p><b>NOTE:</b> Spring 3.1 introduced a new set of support classes for * {@code @RequestMapping} methods in Servlet environments called * {@code RequestMappingHandlerMapping} and * {@code RequestMappingHandlerAdapter}. They are recommended for use and * even required to take advantage of new features in Spring MVC 3.1 (search * {@literal "@MVC 3.1-only"} in this source file) and going forward. * The new support classes are enabled by default from the MVC namespace and * with use of the MVC Java config ({@code @EnableWebMvc}) but must be * configured explicitly if using neither. * * <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 RequestParam * @see RequestAttribute * @see PathVariable * @see ModelAttribute * @see SessionAttribute * @see SessionAttributes * @see InitBinder * @see org.springframework.web.context.request.WebRequest * @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter * @see org.springframework.web.portlet.mvc.annotation.DefaultAnnotationHandlerMapping * @see org.springframework.web.portlet.mvc.annotation.AnnotationMethodHandlerAdapter */ @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>In a Servlet environment this is an alias for {@link #path}. * For example {@code @RequestMapping("/foo")} is equivalent to * {@code @RequestMapping(path="/foo")}. * <p>In a Portlet environment this is the mapped portlet modes * (i.e. "EDIT", "VIEW", "HELP" or any custom modes). * <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 {}; /** * In a Servlet environment only: 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). * <p>Supported for Servlet environments as well as Portlet 2.0 environments. */ 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>In a Servlet environment, 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. * <p>In a Portlet environment, parameters are taken into account as mapping * differentiators, i.e. the primary portlet mode mapping plus the parameter * conditions uniquely identify the target handler. Different handlers may be * mapped onto the same portlet mode, as long as their parameter mappings differ. */ 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"> * @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). * <p>Maps against HttpServletRequest headers in a Servlet environment, * and against PortletRequest properties in a Portlet 2.0 environment. * @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 = "application/json; charset=UTF-8" * </pre> * <p>It affects the actual content type written, for example to produce a JSON response * with UTF-8 encoding, {@code "application/json; charset=UTF-8"} 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 {}; }
1.3、@ResponseBody
@ResponseBody这个注解通常使用在控制层(controller)的方法上,其作用是将方法的返回值以特定的格式写入到response的body区域,进而将数据返回给客户端。当方法上面没有写ResponseBody,底层会将方法的返回值封装为ModelAndView对象。
假如是字符串则直接将字符串写到客户端,假如是一个对象,此时会将对象转化为json串然后写到客户端。这里需要注意的是,如果返回对象,按utf-8编码。如果返回String,默认按iso8859-1编码,页面可能出现乱码。因此在注解中我们可以手动修改编码格式,例如@RequestMapping(value="/cat/query",produces="text/html;charset=utf-8"),前面是请求的路径,后面是编码格式。
那么,控制层方法的返回值是如何转化为json格式的字符串的呢?其实是通过HttpMessageConverter中的方法实现的,因为它是一个接口,因此由其实现类完成转换。如果是bean对象,会调用对象的getXXX()方法获取属性值并且以键值对的形式进行封装,进而转化为json串。如果是map集合,采用get(key)方式获取value值,然后进行封装。
@ResponseBody是作用在方法上的,@ResponseBody 表示该方法的返回结果直接写入 HTTP response body 中,一般在异步获取数据时使用【也就是AJAX】,在使用 @RequestMapping后,返回值通常解析为跳转路径,但是加上 @ResponseBody 后返回结果不会被解析为跳转路径,而是直接写入 HTTP response body 中。 比如异步获取 json 数据,加上 @ResponseBody 后,会直接返回 json 数据。
User:
package com.zhangguo.autoconfigdemo; public class User { public User() { } public User(String name, int age) { this.name = name; this.age = age; } private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
UserController
package com.zhangguo.autoconfigdemo; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @Controller public class UserController { private static List<User> users=new ArrayList<>(); static { users.add(new User("jack",18)); users.add(new User("rose",19)); users.add(new User("lili",95)); } @RequestMapping("/users") @ResponseBody public List<User> findUsers() { return users; } }
测试
如果控制器中所有的方法都需要注解成@ResponseBody则可以直接注解在控制器上,省去了每一个都要注解的麻烦。
1.4、@RequestBody
@RequestBody 将 HTTP 请求正文插入方法中,使用适合的 HttpMessageConverter 将请求体写入某个对象。
@RequestMapping(value = "person/login") @ResponseBody public Person login(@RequestBody Person person) { // 将请求中的 datas 写入 Person 对象中 return person; // 不会被解析为跳转路径,而是直接写入 HTTP 响应正文中 }
后台 Controller类中对应的方法:
@RequestMapping("/login.do") @ResponseBody public Object login(String name, String password, HttpSession session) { user = userService.checkLogin(name, password); session.setAttribute("user", user); return new JsonResult(user); }
如上面的登录后台代码可以改为:
@RequestMapping("/login.do") @ResponseBody public Object login(@RequestBody User loginUuser, HttpSession session) { user = userService.checkLogin(loginUser); session.setAttribute("user", user); return new JsonResult(user); }
作用:该注解用于读取Request请求的body部分数据,使用系统默认配置的HttpMessageConverter进行解析,然后把相应的数据绑定到要返回的对象上;再把HttpMessageConverter返回的对象数据绑定到 controller中方法的参数上。
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="app"> <h2>用户管理</h2> <p> 姓名:<input v-model="user.name"/> </p> <p> 年龄:<input v-model="user.age"/> </p> <p> <button @click="add">保存</button> </p> </div> <script src="js/vue/vue.js"></script> <script src="js/axios/axios.min.js"></script> <script> var vm = new Vue({ el: "#app", data: { user: {name: "", age: ""} }, methods:{ add:function(){ axios .post("/add",vm.user) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); } } }); </script> </body> </html>
UserController
package com.zhangguo.autoconfigdemo; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @Controller public class UserController { private static List<User> users=new ArrayList<>(); static { users.add(new User("jack",18)); users.add(new User("rose",19)); users.add(new User("lili",95)); } @RequestMapping("/add") @ResponseBody public User getUser(@RequestBody User user){ return user; } }
运行结果:
注意:
这里axios发送请求时的Content-Type为:application/json,如果使用jQuery可其它AJAX工具这里需要注意。
1.5、封装页面返回结果的JSON
为了协调前后端接口,可以定义一个工具类用于封装后台数据,方便前端统一解析:
package com.zhangguo.ssm01.common.utils; import java.util.HashMap; import java.util.Map; /** * 返回数据封装 */ public class R extends HashMap<String, Object> { private static final long serialVersionUID = 1L; public R() { put("code", 1); put("msg", "success"); } //错误时 public static R error() { return error(500, "未知异常,请联系管理员"); } public static R error(String msg) { return error(500, msg); } public static R error(int code, String msg) { R r = new R(); r.put("code", code); r.put("msg", msg); return r; } //成功时 public static R ok(String msg) { R r = new R(); r.put("msg", msg); return r; } public static R ok(Map<String, Object> map) { R r = new R(); r.putAll(map); return r; } public static R ok() { return new R(); } public static R ok(Object data) { return new R().put("data",data); } @Override public R put(String key, Object value) { super.put(key, value); return this; } }
控制器:
package com.zhangguo.autoconfigdemo; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @Controller public class UserController { private static List<User> users=new ArrayList<>(); static { users.add(new User("jack",18)); users.add(new User("rose",19)); users.add(new User("lili",95)); } @RequestMapping("/users") @ResponseBody public R findUsers() { return R.ok(users); } }
运行结果:
二、Restful
2.1、REST概要
REST (英文:Representational State Transfer ,简称 REST )一种互联网软件架构设计的风格,但它并不是标准,它只是提出了一组客户端和服务器交互时的架构理念和设计原则,基于这种理念和原则设计的接口可以更简洁,更有层次,REST这个词,是 Roy Thomas Fielding 在他 2000 年的博士论文中提出的。任何的技术都可以实现这种理念,如果一个架构符合 REST 原则,就称它为 RESTful 架构。
- 资源(Resource)
- 资源的表述(Representation)
- 状态转移(State Transfer)
- 统一接口(Uniform Interface)
- 超文本驱动(Hypertext Driven)
- 面向资源(Resource Oriented)
- 可寻址(Addressability)
- 连通性(Connectedness)
- 无状态(Statelessness)
- 统一接口(Uniform Interface)
- 超文本驱动(Hypertext Driven)
2.2、Http请求类型(Method)
- GET 请求获取Request-URI所标识的资源
- POST 在Request-URI所标识的资源后附加新的数据
- HEAD 请求获取由Request-URI所标识的资源的响应消息报头
- PUT 请求服务器存储一个资源,并用Request-URI作为其标识
- DELETE 请求服务器删除Request-URI所标识的资源
- TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断
- CONNECT 保留将来使用
- OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求
2.3、简单示例
2.3.1、需要实现的功能
根据 ID,获取用户信息
GET http://127.0.0.1:8080/api/users/1
获取用户列表
GET http://127.0.0.1:8080/api/users
新增用户信息
POST http://127.0.0.1:8080/api/users
更新用户信息
PUT http://127.0.0.1:8080/api/users
删除用户信息
DELETE http://127.0.0.1:8080/api/users/2
完成后使用postman测试通过。
2.3.2、实体类
package com.zhangguo.restdemo.entity; /**用户*/ public class User { public User() { } public User(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } private int id; private String name; private int age; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
2.3.3、服务接口与实现
服务接口 UserService
package com.zhangguo.restdemo.service; import com.zhangguo.restdemo.entity.User; import java.util.List; /**用户服务接口*/ public interface UserService { /**根据用户编号获得用户对象*/ public User findUserById(int id); /**获取所有用户信息*/ public List<User> findAllUsers(); /**添加用户*/ public int addUser(User user); /**编辑用户*/ public int editUser(User user); /**删除用户通过编号*/ public int deleteUser(int id); }
服务实现
package com.zhangguo.restdemo.service.impl; import com.zhangguo.restdemo.entity.User; import com.zhangguo.restdemo.service.UserService; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; /**用户服务的实现*/ @Service public class UserServiceImpl implements UserService { /**模拟用户表*/ static List<User> users=new ArrayList<>(); static { users.add(new User(1,"rose",18)); users.add(new User(2,"lucy",26)); users.add(new User(3,"mark",17)); } /** * 根据用户编号获得用户对象 * * @param id */ @Override public User findUserById(int id) { User user=null; for (User u: users) { if(u.getId()==id){ user=u; break; } } return user; } /** * 获取所有用户信息 */ @Override public List<User> findAllUsers() { return users; } /** * 添加用户 * * @param user */ @Override public int addUser(User user) { users.add(user); return 1; } /** * 编辑用户 * * @param user */ @Override public int editUser(User user) { User u=findUserById(user.getId()); if(u!=null) { u.setName(user.getName()); u.setAge(user.getAge()); return 1; } return 0; } /** * 删除用户通过编号 * * @param id */ @Override public int deleteUser(int id) { User u=findUserById(id); if(u!=null){ users.remove(u); return 1; } return 0; } }
2.3.4、控制器
package com.zhangguo.restdemo.controller; import com.zhangguo.restdemo.entity.User; import com.zhangguo.restdemo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api") public class UserController { @Autowired UserService userService; @GetMapping(path = "/users/{id}") public User getUserById(@PathVariable int id){ return userService.findUserById(id); } @GetMapping(path = "/users") public List<User> getAllUser(){ return userService.findAllUsers(); } @PostMapping(path = "/users") public int addUser(@RequestBody User user){ return userService.addUser(user); } @PutMapping(path="/users") public int editUser(@RequestBody User user){ return userService.editUser(user); } @DeleteMapping(path = "/users/{id}") public int deleteUser(@PathVariable int id){ return userService.deleteUser(id); } }
@RequestMapping 处理请求地址映射。
- method – 指定请求的方法类型:POST/GET/DELETE/PUT 等
- value – 指定实际的请求地址
- consumes – 指定处理请求的提交内容类型,例如 Content-Type 头部设置application/json, text/html
- produces – 指定返回的内容类型
@PathVariable URL 映射时,用于绑定请求参数到方法参数
@RequestBody 这里注解用于读取请求体 boy 的数据,通过 HttpMessageConverter 解析绑定到对象中
2.3.2、测试结果
1.访问:http://localhost:8088/api/users/1
2.访问:http://localhost:8088/api/users
3.添加,http://localhost:8088/api/users
修改请求头部
指定数据,发送请求
4.更新http://localhost:8088/api/users
修改http头部信息
指定数据,发送请求
5.删除 http://localhost:8088/api/users/1
三、Swagger
3.1、Swagger概要
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法、参数和模型紧密集成到服务器端的代码,允许 API 来始终保持同步。Swagger 让部署管理和使用功能强大的 API 从未如此简单。
Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 Swagger 进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,Swagger 消除了调用服务时可能会有的猜测。
Swagger 的优势
- 支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。
- 提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试。参数和格式都定好了,直接在界面上输入参数对应的值即可在线测试接口。
3.2、Spring Boot 集成Swagger
3.2.1、Spring Boot 2 引入Swagger2
(1)、创建一个Spring Boot项目,模板建议选择2.5.6,最新版对2.9.2不兼容。
(2)、添加Maven依赖
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
(3)、添加一个控制器,用于测试
package com.example.swaggerdemo2.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello(){
return "Hello Swagger!";
}
}
(4)、添加配置类,启用Swagger
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class SwaggerConfig { }
(5)、浏览器访问http://localhost:8080/swagger-ui.html
3.2.2、SpringBoot 3.2.5 引入Swagger3(OpenApi)
(1)、说明
springdoc-openapi 和 swagger 都可以用,用其中一个就行,不用两个都引入。
springdoc-openapi(Java库)有助于使用 SpringBoot 项目 自动生成 API 文档。
SpringDoc注解的使用,它是基于OpenAPI 3和Swagger 3的现代化解决方案,相较于旧版的Swagger2(SpringFox),SpringDoc提供了更简洁、更直观的注解方式。
(2)、添加依赖
<!-- swagger-ui依赖 https://springdoc.org/ --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.5.0</version> </dependency>
(3)、配置信息
import io.swagger.v3.oas.models.ExternalDocumentation; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; //也有其他的扩展,有兴趣的可自行翻阅官网 @Configuration public class SpringDocConfig { //个人认为,以下括号里的内容,都可乱写,写啥都行,只要是字符串就行 @Bean public OpenAPI springShopOpenAPI() { return new OpenAPI() .info(new Info() .title("OpenApi的Swagger") .description("springdoc-openapi-starter-webmvc-ui") .version("springdoc-openapi v2.5.0") .license(new License() .name("SprigBoot3.2.5") //url,这里写的是SpringBoot的地址 .url("https://spring.io/projects/spring-boot")) ).externalDocs(new ExternalDocumentation() .description("springdoc-openapi v2.5.0") //url,写的是springdoc-openapi的地址 .url("https://springdoc.org/#google_vignette")); } }
(4)、控制层注解
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; @Validated @RestController @RequiredArgsConstructor @Tag(name = "Xxx名字", description = "这里可以多写几个字,用来描述xxx,描述的详细一点") @RequestMapping("/路径") public class XxxController { private final XxxService service; @Operation(summary = "删除", description = "根据 id 删除") @DeleteMapping("/delete/{id}") public Result<String> clean(@NotBlank(message = "id 不可为空") @PathVariable String id) { boolean b = service.removeById(id); return b ? Result.success("删除成功") : Result.error("删除失败"); } }
@Controller @RequestMapping("/test") @Tag(name = "测试接口") @Validated public class TestController { @Autowired private ArtifactService artifactService; @PostMapping("/v1/test") @Operation(summary = "设置制品库权限") @NoPermission public Result<Void> addArtifactPermission(@Validated @RequestBody AssetAuthDataDTO assetAuthData, @RequestHeader(value = "adminaction", defaultValue = "false") boolean adminAction) { return null; } @Operation(summary = "添加", description = "添加描述", security = { @SecurityRequirement(name = "sessionid")}, responses = { @ApiResponse(description = "返回信息", content = @Content(mediaType = "application/json")), @ApiResponse(responseCode = "400", description = "返回400时候错误的原因") } ) @Parameters({ @Parameter(name = "name", description = "名字", required = true), @Parameter(name = "typeId", description = "类型ID", required = true) }) @PutMapping("add") @NoPermission public Result<Void> add(String name, String typeId) { return null; } /** * 查询generic制品库下所有文件列表 * * @param quest 请求参数 * * @return * * @author wangsb9 * @data: 2023/4/6 14:54 */ @ApiOperation(value = "查询generic制品库下所有文件列表", httpMethod = "GET") @GetMapping("/v1/generic/item/list") @ResponseBody @RepoKeyPermission(permission = "read", param = "quest.repoKey") public Result<GenericItemListVO> getGenericItemList(GetGenericItemListQuest quest) { if (ArtifactTypes.GENERIC.equalsIgnoreCase(BusinessUtils.getArtifactTypeFromRepoKey(quest.getRepoKey()))) { GenericItemListVO vo = artifactService.geGenericItemList(quest); return Result.success("获取当前generic制品库包含的制品成功", vo); } else { return Result.failed("请求路径非generic库路径"); } } }
(5)、访问
启动成功后,可以直接从控制台点击这俩访问地址
http://localhost:8080/swagger-ui/index.html
http://localhost:8080/v3/api-docs
(6)、postman导入
3.3、配置接口信息
ApiInfo方法接收一个ApiInfo实例,实例的构造方法中给出来接口的描述参数,配置后的文件如下:
package com.example.swaggerdemo2; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.service.VendorExtension; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.util.ArrayList; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket getDocket() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(getApiInfo()); } private ApiInfo getApiInfo() { Contact DEFAULT_CONTACT = new Contact("", "", ""); return new ApiInfo("天狗商城Api文档", "接口描述信息", "2.0", "https://www.cnblogs.com/best/p/16158409.html", DEFAULT_CONTACT, "Apache 2.0", "http://www.apache.org/licenses/LICENSE-2.0", new ArrayList<VendorExtension>()); } }
查看运行效果:
3.4、指定扫描范围
默认情况Swagger会扫描所有的包,但实际应用中可能只需要为部分特定的服务生成Api文档,有多种指定方法,先看指定包的方法:
@Bean public Docket getDocket() { return new Docket(DocumentationType.SWAGGER_2).apiInfo(getApiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.example.swaggerdemo2.controller")) .build(); }
运行效果:
此时就仅剩下hello-controller了。
其它扫描范围
any() // 扫描所有,项目中的所有接口都会被扫描到 none() // 不扫描接口 // 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求 withMethodAnnotation(final Class<? extends Annotation> annotation) // 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口 withClassAnnotation(final Class<? extends Annotation> annotation) basePackage(final String basePackage) // 根据包路径扫描接口
通过路径过滤
@Bean public Docket docket() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select()// 通过.select()方法,去配置扫描接口,RequestHandlerSelectors配置如何扫描接口 .apis(RequestHandlerSelectors.basePackage("com.swagger.controller")) // 配置如何通过path过滤,即这里只扫描请求以/ang开头的接口 .paths(PathSelectors.ant("/ang/**")) .build(); }
其它方法
any() // 任何请求都扫描 none() // 任何请求都不扫描 regex(final String pathRegex) // 通过正则表达式控制 ant(final String antPattern) // 通过ant()控制
3.5、配置Swagger开关
通过enable()方法配置是否启用swagger,如果是false,swagger将不能在浏览器中访问了。
return new Docket(DocumentationType.SWAGGER_2) .apiInfo(getApiInfo()) .enable(false) .select() .apis(RequestHandlerSelectors.basePackage("com.example.swaggerdemo2.controller")) .build();
此时再访问就看不到接口信息了。
3.5、配置分组信息
如果没有配置分组,默认是default。通过groupName()方法即可配置分组名称:
return new Docket(DocumentationType.SWAGGER_2) .apiInfo(getApiInfo()) .groupName("第一组") .enable(true) .select() .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class)) .build();
查看结果:
生成多个分组信息:
@Bean public Docket docket2(){ return new Docket(DocumentationType.SWAGGER_2).groupName("第二组"); } @Bean public Docket docket3(){ return new Docket(DocumentationType.SWAGGER_2).groupName("第三组"); } @Bean public Docket docket4(){ return new Docket(DocumentationType.SWAGGER_2).groupName("第四组"); }
查看结果
3.6、注解实体信息
新建一个实体类并注解中文:
package com.example.swaggerdemo2.controller; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @ApiModel("用户") public class User { public User() { } public User(String name, int age) { this.name = name; this.age = age; } @ApiModelProperty("姓名") private String name; @ApiModelProperty("年龄") private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
在控制器中添加一个方法,方法引用了User
@RequestMapping("/getUser") public User getUser(){ return new User("tom",19); }
运行效果
注:并不是因为@ApiModel这个注解让实体显示在这里了,而是只要出现在接口方法的返回值上的实体都会显示在这里,而@ApiModel和@ApiModelProperty这两个注解只是为实体添加注释的。
@ApiModel为类添加注释
@ApiModelProperty为类属性添加注释
3.7、常用注解
Swagger的所有注解定义在io.swagger.annotations包下,下面列一些经常用到的,未列举出来的可以另行查阅说明:
(1)、 @Api(tags = "xxx模块说明") 作用在模块类上
@Api(tags={"用户接口"}) @RestController public class UserController { }
tags:接口说明,可以在页面中显示。可以配置多个,当配置多个的时候,在页面中会显示多个接口的信息。
(2)、@ApiOperation("xxx接口说明") 作用在接口方法上
@ApiOperation(value="新增用户", notes="详细描述") public UserDto addUser(@ApiParam(value = "新增用户参数", required = true) @RequestBody AddUserParam param) { }
(3)、@ApiModel("xxxPOJO说明")作用在模型类上:如VO、BO
@Data @ApiModel(value = "com.biancheng.auth.param.AddUserParam", description = "新增用户参数") public class AddUserParam { @ApiModelProperty(value = "ID") private String id; @ApiModelProperty(value = "名称") private String name; @ApiModelProperty(value = "年龄") private int age; }
(4)、@ApiModelProperty(value = "xxx属性说明",hidden = true)作用在类方法和属性上,hidden设置为true可以隐藏该属性
(5)、@ApiParam("xxx参数说明")作用在参数、方法和字段上,类似@ApiModelProperty
@PostMapping("/user") public UserDto addUser(@ApiParam(value = "新增用户参数", required = true) @RequestBody AddUserParam param) { System.err.println(param.getName()); return new UserDto(); }
这样的话,可以给一些比较难理解的属性或者接口,增加一些配置信息,让人更容易理解。
相较于传统的Postman或Curl方式测试接口,使用swagger简直就是傻瓜式操作,不需要额外说明文档(写得好本身就是文档)而且更不容易出错,只需要录入数据然后点击Execute,如果再配合自动化框架,可以说基本就不需要人为操作了。
Swagger是个优秀的工具,现在国内已经有很多的中小型互联网公司都在使用它,相较于传统的要先出Word接口文档再测试的方式,显然这样也更符合现在的快速迭代开发行情。当然了,提醒下大家在正式环境要记得关闭Swagger,一来出于安全考虑二来也可以节省运行时内存。
四、作业
1、完成上课的每一个示例
2、根据当前环境动态开启或关闭Swagger,如开发环境时开启Swagger,生产环境时关闭Swagger。
3、在我的任务作业中集成Swagger框架,使用Rest规范定义接口信息,中文描述每一个接口信息与模型,尝试在线调用接口。
4、请完成一个产品管理功能 ,产品实体包含如下属性
Product产品(id编号,title名称,price价格)
要求定义好完整的Rest服务,使用R.java返回结果,使用POSTMAN先测试后完成前端,实现CRUD功能,前端使用AXIOS+Vue消费服务。
五、视频
https://www.bilibili.com/video/BV1fi4y1S79P?share_source=copy_web
https://space.bilibili.com/87194917/video
作业解答:https://www.bilibili.com/video/BV1Hs411F71x?share_source=copy_web