目前JAVA实现HTTP请求的方法用的最多的有两种:一种是通过HTTPClient这种第三方的开源框架去实现。HTTPClient对HTTP的封装性比较不错,通过它基本上能够满足我们大部分的需求,HttpClient3.1 是 org.apache.commons.httpclient下操作远程 url的工具包,虽然已不再更新,但实现工作中使用httpClient3.1的代码还是很多,HttpClient4.5是org.apache.http.client下操作远程 url的工具包,最新的;另一种则是通过HttpURLConnection去实现,HttpURLConnection是JAVA的标准类,是JAVA比较原生的一种实现方式。 自己在工作中三种方式都用到过,总结一下分享给大家,也方便自己以后使用,话不多说上代码。 第一种方式:java原生HttpURLConnection
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
package com.powerX.httpClient; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class HttpClient { public static String doGet(String httpurl) { HttpURLConnection connection = null; InputStream is = null; BufferedReader br = null; String result = null;// 返回结果字符串 try { // 创建远程url连接对象 URL url = new URL(httpurl); // 通过远程url连接对象打开一个连接,强转成httpURLConnection类 connection = (HttpURLConnection) url.openConnection(); // 设置连接方式:get connection.setRequestMethod("GET"); // 设置连接主机服务器的超时时间:15000毫秒 connection.setConnectTimeout(15000); // 设置读取远程返回的数据时间:60000毫秒 connection.setReadTimeout(60000); // 发送请求 connection.connect(); // 通过connection连接,获取输入流 if (connection.getResponseCode() == 200) { is = connection.getInputStream(); // 封装输入流is,并指定字符集 br = new BufferedReader(new InputStreamReader(is, "UTF-8")); // 存放数据 StringBuffer sbf = new StringBuffer(); String temp = null; while ((temp = br.readLine()) != null) { sbf.append(temp); sbf.append("\r\n"); } result = sbf.toString(); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源 if (null != br) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != is) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } connection.disconnect();// 关闭远程连接 } return result; } public static String doPost(String httpUrl, String param) { HttpURLConnection connection = null; InputStream is = null; OutputStream os = null; BufferedReader br = null; String result = null; try { URL url = new URL(httpUrl); // 通过远程url连接对象打开连接 connection = (HttpURLConnection) url.openConnection(); // 设置连接请求方式 connection.setRequestMethod("POST"); // 设置连接主机服务器超时时间:15000毫秒 connection.setConnectTimeout(15000); // 设置读取主机服务器返回数据超时时间:60000毫秒 connection.setReadTimeout(60000); // 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true connection.setDoOutput(true); // 默认值为:true,当前向远程服务读取数据时,设置为true,该参数可有可无 connection.setDoInput(true); // 设置传入参数的格式:请求参数应该是 name1=value1&name2=value2 的形式。 connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // 设置鉴权信息:Authorization: Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0 connection.setRequestProperty("Authorization", "Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0"); // 通过连接对象获取一个输出流 os = connection.getOutputStream(); // 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的 os.write(param.getBytes()); // 通过连接对象获取一个输入流,向远程读取 if (connection.getResponseCode() == 200) { is = connection.getInputStream(); // 对输入流对象进行包装:charset根据工作项目组的要求来设置 br = new BufferedReader(new InputStreamReader(is, "UTF-8")); StringBuffer sbf = new StringBuffer(); String temp = null; // 循环遍历一行一行读取数据 while ((temp = br.readLine()) != null) { sbf.append(temp); sbf.append("\r\n"); } result = sbf.toString(); } } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源 if (null != br) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != os) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != is) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } // 断开与远程地址url的连接 connection.disconnect(); } return result; } } |
第二种方式:apache HttpClient3.1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
package com.powerX.httpClient; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.NameValuePair; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.params.HttpMethodParams; public class HttpClient3 { public static String doGet(String url) { // 输入流 InputStream is = null; BufferedReader br = null; String result = null; // 创建httpClient实例 HttpClient httpClient = new HttpClient(); // 设置http连接主机服务超时时间:15000毫秒 // 先获取连接管理器对象,再获取参数对象,再进行参数的赋值 httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000); // 创建一个Get方法实例对象 GetMethod getMethod = new GetMethod(url); // 设置get请求超时为60000毫秒 getMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 60000); // 设置请求重试机制,默认重试次数:3次,参数设置为true,重试机制可用,false相反 getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, true)); try { // 执行Get方法 int statusCode = httpClient.executeMethod(getMethod); // 判断返回码 if (statusCode != HttpStatus.SC_OK) { // 如果状态码返回的不是ok,说明失败了,打印错误信息 System.err.println("Method faild: " + getMethod.getStatusLine()); } else { // 通过getMethod实例,获取远程的一个输入流 is = getMethod.getResponseBodyAsStream(); // 包装输入流 br = new BufferedReader(new InputStreamReader(is, "UTF-8")); StringBuffer sbf = new StringBuffer(); // 读取封装的输入流 String temp = null; while ((temp = br.readLine()) != null) { sbf.append(temp).append("\r\n"); } result = sbf.toString(); } } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源 if (null != br) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != is) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } // 释放连接 getMethod.releaseConnection(); } return result; } public static String doPost(String url, Map<String, Object> paramMap) { // 获取输入流 InputStream is = null; BufferedReader br = null; String result = null; // 创建httpClient实例对象 HttpClient httpClient = new HttpClient(); // 设置httpClient连接主机服务器超时时间:15000毫秒 httpClient.getHttpConnectionManager().getParams().setConnectionTimeout(15000); // 创建post请求方法实例对象 PostMethod postMethod = new PostMethod(url); // 设置post请求超时时间 postMethod.getParams().setParameter(HttpMethodParams.SO_TIMEOUT, 60000); NameValuePair[] nvp = null; // 判断参数map集合paramMap是否为空 if (null != paramMap && paramMap.size() > 0) {// 不为空 // 创建键值参数对象数组,大小为参数的个数 nvp = new NameValuePair[paramMap.size()]; // 循环遍历参数集合map Set<Entry<String, Object>> entrySet = paramMap.entrySet(); // 获取迭代器 Iterator<Entry<String, Object>> iterator = entrySet.iterator(); int index = 0; while (iterator.hasNext()) { Entry<String, Object> mapEntry = iterator.next(); // 从mapEntry中获取key和value创建键值对象存放到数组中 try { nvp[index] = new NameValuePair(mapEntry.getKey(), new String(mapEntry.getValue().toString().getBytes("UTF-8"), "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } index++; } } // 判断nvp数组是否为空 if (null != nvp && nvp.length > 0) { // 将参数存放到requestBody对象中 postMethod.setRequestBody(nvp); } // 执行POST方法 try { int statusCode = httpClient.executeMethod(postMethod); // 判断是否成功 if (statusCode != HttpStatus.SC_OK) { System.err.println("Method faild: " + postMethod.getStatusLine()); } // 获取远程返回的数据 is = postMethod.getResponseBodyAsStream(); // 封装输入流 br = new BufferedReader(new InputStreamReader(is, "UTF-8")); StringBuffer sbf = new StringBuffer(); String temp = null; while ((temp = br.readLine()) != null) { sbf.append(temp).append("\r\n"); } result = sbf.toString(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源 if (null != br) { try { br.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != is) { try { is.close(); } catch (IOException e) { e.printStackTrace(); } } // 释放连接 postMethod.releaseConnection(); } return result; } } |
第三种方式:apache httpClient4.5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
package com.powerX.httpClient; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; public class HttpClient4 { public static String doGet(String url) { CloseableHttpClient httpClient = null; CloseableHttpResponse response = null; String result = ""; try { // 通过址默认配置创建一个httpClient实例 httpClient = HttpClients.createDefault(); // 创建httpGet远程连接实例 HttpGet httpGet = new HttpGet(url); // 设置请求头信息,鉴权 httpGet.setHeader("Authorization", "Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0"); // 设置配置请求参数 RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000)// 连接主机服务超时时间 .setConnectionRequestTimeout(35000)// 请求超时时间 .setSocketTimeout(60000)// 数据读取超时时间 .build(); // 为httpGet实例设置配置 httpGet.setConfig(requestConfig); // 执行get请求得到返回对象 response = httpClient.execute(httpGet); // 通过返回对象获取返回数据 HttpEntity entity = response.getEntity(); // 通过EntityUtils中的toString方法将结果转换为字符串 result = EntityUtils.toString(entity); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源 if (null != response) { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != httpClient) { try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } } return result; } public static String doPost(String url, Map<String, Object> paramMap) { CloseableHttpClient httpClient = null; CloseableHttpResponse httpResponse = null; String result = ""; // 创建httpClient实例 httpClient = HttpClients.createDefault(); // 创建httpPost远程连接实例 HttpPost httpPost = new HttpPost(url); // 配置请求参数实例 RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000)// 设置连接主机服务超时时间 .setConnectionRequestTimeout(35000)// 设置连接请求超时时间 .setSocketTimeout(60000)// 设置读取数据连接超时时间 .build(); // 为httpPost实例设置配置 httpPost.setConfig(requestConfig); // 设置请求头 httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded"); // 封装post请求参数 if (null != paramMap && paramMap.size() > 0) { List<NameValuePair> nvps = new ArrayList<NameValuePair>(); // 通过map集成entrySet方法获取entity Set<Entry<String, Object>> entrySet = paramMap.entrySet(); // 循环遍历,获取迭代器 Iterator<Entry<String, Object>> iterator = entrySet.iterator(); while (iterator.hasNext()) { Entry<String, Object> mapEntry = iterator.next(); nvps.add(new BasicNameValuePair(mapEntry.getKey(), mapEntry.getValue().toString())); } // 为httpPost设置封装好的请求参数 try { httpPost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } try { // httpClient对象执行post请求,并返回响应参数对象 httpResponse = httpClient.execute(httpPost); // 从响应对象中获取响应内容 HttpEntity entity = httpResponse.getEntity(); result = EntityUtils.toString(entity); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { // 关闭资源 if (null != httpResponse) { try { httpResponse.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != httpClient) { try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } } return result; } } |
有时候我们在使用post请求时,可能传入的参数是json或者其他格式,此时我们则需要更改请求头及参数的设置信息,以httpClient4.5为例,更改下面两列配置:httpPost.setEntity(new StringEntity("你的json串")); httpPost.addHeader("Content-Type", "application/json")。 from:https://www.cnblogs.com/hhhshct/p/8523697.html
View Details本文来自:https://www.sojson.com/blog/350.html 以前我们创建一个Http请求,很复杂,要写很多代码,而且请求还有各种兼容问题。而用 RestTemplate 的话优雅的几行代码就可以解决,并且是可以直接返回对象。 RestTemplate 是 Spring 用于同步请求client端的核心类,简化了与 HTTP 的通信,并满足RestFul原则,RestTemplate默认依赖 JDK 的HTTP连接工具。当然你也可以 通过setRequestFactory属性切换到不同的HTTP 数据源,比如Apache HttpComponents、Netty和OkHttp,都是支持的。 HTTP Get 请求 我们先做一个普通的Http请求,直接上源码。
1 2 3 4 5 6 7 8 9 10 11 12 |
try { HttpClient client = new HttpClient(); //创建一个Get请求 GetMethod method = new GetMethod("http://t.weather.sojson.com/api/weather/city/"+101010100); client.executeMethod(method); //获取String类型的返回值 String res = method.getResponseBodyAsString(); //使用gson转换为对象 WeatherDto dto = new Gson().fromJson(res,WeatherDto.class); } catch (IOException e) { e.printStackTrace(); } |
这是一个最简单的Http请求,把返回值使用 Gson 来转换成对象。 使用RestTemplate HTTP Get 请求
1 2 |
RestTemplate restTemplate = new RestTemplate(); WeatherDto dto = restTemplate.getForObject("http://t.weather.sojson.com/api/weather/city/"+101010100 , WeatherDto.class); |
上面其实是一个简单的带参数请求,用“{1}”、“{2}”、“{3}”… 方式传参,如果是地址拼接的方式,可以N个。 上一篇博客采用这个方式,模拟的Http请求,请求天气接口数据:https://www.sojson.com/blog/349.html 。 2.RestTemplate 多个参数请求 因为是Get请求,其实就是问号的方式带参数请求
1 2 3 4 |
Map<String,String> map = new HashMap(); map.put("id",101010100); RestTemplate restTemplate = new RestTemplate(); Details detail = restTemplate.getForObject("http://example.sojson.com/detail.html" , Details.class,map); |
3.RestTemplate getForEntity 请求 其实上面的1和2算是简单的请求,就是直接返回了Object 实例对象。而我们要获取详细的详细,如返回status、Header信息等。那就得用 getForEntity 。 看看源码里的参数描述,其实是和 getForObject 一致,我这里网络不行没下载下来源码包,凑合看看。
1 2 3 4 5 |
<T> ResponseEntity<T> getForEntity(String var1, Class<T> var2, Object... var3) throws RestClientException; <T> ResponseEntity<T> getForEntity(String var1, Class<T> var2, Map<String, ?> var3) throws RestClientException; <T> ResponseEntity<T> getForEntity(URI var1, Class<T> var2) throws RestClientException; |
HTTP 实例讲解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
RestTemplate restTemplate = new RestTemplate(); String url = "http://example.sojson.com/detail.html"; //添加请求头 HttpHeaders headers = new HttpHeaders(); //form表单提交 application/x-www-form-urlencoded headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); //组装参数 MultiValueMap<String, String> map= new LinkedMultiValueMap<>(); map.add("id", "101010100"); HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers); ResponseEntity<WeatherDto> response = restTemplate.postForEntity( url, request , WeatherDto.class ); //返回对象 WeatherDto dto = response.getBody(); //HTTP状态 int status = response.getStatusCodeValue(); //Spring 封装的 HttpStatus statusCode = response.getStatusCode(); //封装的对应状态请求,返回来都是 Boolean 类型 statusCode.is2xxSuccessful();//2开头的成功状态 statusCode.is3xxRedirection();//3开头重定向 statusCode.is4xxClientError();//4开头客户端错误 statusCode.is5xxServerError();//5开头服务端异常 |
具体可以自行测试下。 我们看源码知道还有 Post请求 方法。
1 2 |
restTemplate.postForEntity( ... ) restTemplate.postForObject(... ) |
方法传参是和上面讲解的 Get请求 的使用方式一模一样。 有兴趣的可以测试下我们的在线 HTTP模拟请求 工具 ,就是采用 restTemplate 实现的。 HTTP在线模拟请求 from:https://cloud.tencent.com/developer/article/1554561
View Details1.用户登录请求登录接口时,验证用户名密码等,验证成功会返回给前端一个token,这个token就是之后鉴权的唯一凭证。
2.后台可能将token存储在redis或者数据库中。
3.之后前端的请求,需要在header中携带token,后端取出token去redis或者数据库中进行验证,如果验证通过则放行,如果不通过则拒绝操作。
改单个项目 在项目的build.gradle文件中,修改repositories配置,将mavenCentral()改为 maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}, 如:
1 2 3 |
repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } } |
更改所有项目 如果想一次更改所有的仓库地址,可以在 USER_HOME/.gradle/文件夹下添加 init.gradle 文件来配置,如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
allprojects{ repositories { def REPOSITORY_URL = 'http://maven.aliyun.com/nexus/content/groups/public/' all { ArtifactRepository repo -> if(repo instanceof MavenArtifactRepository){ def url = repo.url.toString() if (url.startsWith('https://repo1.maven.org/maven2') || url.startsWith('https://jcenter.bintray.com/')) { remove repo } } } maven { url REPOSITORY_URL } } } |
作者:xiaolyuh 链接:https://www.jianshu.com/p/471227b2b7e8 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
View Details问题描述 使用swagger生成接口文档后,访问http://localhost:8888/swagger-ui.html#/,显示如下: 有些强迫症的我,感觉看起来很不舒服,结果百度了好久,找到解决方案,刚接触spring boot对于很多api还不是很熟悉,先mark再说 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
package com.course.config; import com.google.common.base.Predicates; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .pathMapping("/") .select() // 选择那些路径和api会生成document .apis(RequestHandlerSelectors.any())// 对所有api进行监控 //不显示错误的接口地址 .paths(Predicates.not(PathSelectors.regex("/error.*")))//错误路径不监控 .paths(PathSelectors.regex("/.*"))// 对根下所有路径进行监控 .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder().title("这是我的接口文档") .contact(new Contact("rongrong", "", "emai@qq.com")) .description("这是SWAGGER_2生成的接口文档") .termsOfServiceUrl("NO terms of service") .license("The Apache License, Version 2.0") .licenseUrl("http://www.apache.org/licenses/LICENSE-2.0.html") .version("v1.0") .build(); } } |
重启服务:再次访问如下 from:https://www.cnblogs.com/longronglang/p/9045559.html
View Details综合概述 spring-boot作为当前最为流行的Java web开发脚手架,越来越多的开发者选择用其来构建企业级的RESTFul API接口。这些接口不但会服务于传统的web端(b/s),也会服务于移动端。在实际开发过程中,这些接口还要提供给开发测试进行相关的白盒测试,那么势必存在如何在多人协作中共享和及时更新API开发接口文档的问题。 假如你已经对传统的wiki文档共享方式所带来的弊端深恶痛绝,那么尝试一下Swagger2 方式,一定会让你有不一样的开发体验。 使用 Swagger 集成文档具有以下几个优势: 功能丰富 :支持多种注解,自动生成接口文档界面,支持在界面测试API接口功能; 及时更新 :开发过程中花一点写注释的时间,就可以及时的更新API文档,省心省力; 整合简单 :通过添加pom依赖和简单配置,内嵌于应用中就可同时发布API接口文档界面,不需要部署独立服务。 实现案例 接下来,我们就通过Spring Boot 来整合Swagger实现在线API文档的功能。 生成项目模板 为方便我们初始化项目,Spring Boot给我们提供一个项目模板生成网站。 1. 打开浏览器,访问:https://start.spring.io/ 2. 根据页面提示,选择构建工具,开发语言,项目信息等。 3. 点击 Generate the project,生成项目模板,生成之后会将压缩包下载到本地。 4. 使用IDE导入项目,我这里使用Eclipse,通过导入Maven项目的方式导入。 添加相关依赖 添加 Maven 相关依赖,这里需要添加上WEB和SWAGGER依赖。 WEB依赖
1 2 3 4 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> |
swagger依赖,这里选择 2.9.2 版本。
1 2 3 4 5 6 7 8 9 10 11 |
<!-- swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> |
添加配置类 添加一个swagger 配置类,在工程下新建 config 包并添加一个 SwaggerConfig 配置类。 SwaggerConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi(){ return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()).build(); } private ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("Kitty API Doc") .description("This is a restful api document of Kitty.") .version("1.0") .build(); } } |
添加控制器 添加一个控制器,在工程下新建 controller包并添加一个 HelloController控制器。 HelloController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package com.louis.springboot.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; /* 类注解 */ @Api(value = "desc of class") @RestController public class HelloController { /* 方法注解 */ @ApiOperation(value = "desc of method", notes = "") @GetMapping(value="/hello") public Object hello( /* 参数注解 */ @ApiParam(value = "desc of param" , required=true ) @RequestParam String name) { return "Hello " + name + "!"; } } |
编译运行测试 1. 右键项目 -> Run as -> Maven install,开始执行Maven构建,第一次会下载Maven依赖,可能需要点时间,如果出现如下信息,就说明项目编译打包成功了。 2. 右键文件 DemoApplication.java -> Run as -> Java Application,开始启动应用,当出现如下信息的时候,就说明应用启动成功了,默认启动端口是8080。 3. 打开浏览器,访问:http://localhost:8080/swagger-ui.html,进入swagger接口文档界面。 4. 展开hello-controller的hello接口,输入参数并点击执行,就可以看到接口测试结果了。 常用注解说明 swagger 通过注解接口生成文档,包括接口名,请求方法,参数,返回信息等。 @Api: 修饰整个类,用于controller类上 @ApiOperation: 描述一个接口,用户controller方法上 @ApiParam: 单个参数描述 @ApiModel: […]
View Details综合概述 Spring Security 是 Spring 社区的一个顶级项目,也是 Spring Boot 官方推荐使用的安全框架。除了常规的认证(Authentication)和授权(Authorization)之外,Spring Security还提供了诸如ACLs,LDAP,JAAS,CAS等高级特性以满足复杂场景下的安全需求。另外,就目前而言,Spring Security和Shiro也是当前广大应用使用比较广泛的两个安全框架。 Spring Security 应用级别的安全主要包含两个主要部分,即登录认证(Authentication)和访问授权(Authorization),首先用户登录的时候传入登录信息,登录验证器完成登录认证并将登录认证好的信息存储到请求上下文,然后再进行其他操作,如在进行接口访问、方法调用时,权限认证器从上下文中获取登录认证信息,然后根据认证信息获取权限信息,通过权限信息和特定的授权策略决定是否授权。 本教程将首先给出一个完整的案例实现,然后再分别对登录认证和访问授权的执行流程进行剖析,希望大家可以通过实现案例和流程分析,充分理解Spring Security的登录认证和访问授权的执行原理,并且能够在理解原理的基础上熟练自主的使用Spring Security实现相关的需求。 实现案例 接下来,我们就通过一个具体的案例,来讲解如何进行Spring Security的整合,然后借助Spring Security实现登录认证和访问控制。 生成项目模板 为方便我们初始化项目,Spring Boot给我们提供一个项目模板生成网站。 1. 打开浏览器,访问:https://start.spring.io/ 2. 根据页面提示,选择构建工具,开发语言,项目信息等。 3. 点击 Generate the project,生成项目模板,生成之后会将压缩包下载到本地。 4. 使用IDE导入项目,我这里使用Eclipse,通过导入Maven项目的方式导入。 添加相关依赖 清理掉不需要的测试类及测试依赖,添加 Maven 相关依赖,这里需要添加上web、swagger、spring security、jwt和fastjson的依赖,Swagge和fastjson的添加是为了方便接口测试。 pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.louis.springboot</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!-- web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- swagger --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <!-- spring security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- jwt --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.58</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> <!-- 打包时拷贝MyBatis的映射文件 --> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/sqlmap/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.*</include> </includes> <filtering>true</filtering> </resource> </resources> </build> </project> |
添加相关配置 1.添加swagger 配置 添加一个swagger 配置类,在工程下新建 config 包并添加一个 SwaggerConfig 配置类,除了常规配置外,加了一个令牌属性,可以在接口调用的时候传递令牌。 SwaggerConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
package com.louis.springboot.demo.config; import java.util.ArrayList; import java.util.List; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ParameterBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.schema.ModelRef; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Parameter; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket createRestApi(){ // 添加请求参数,我们这里把token作为请求头部参数传入后端 ParameterBuilder parameterBuilder = new ParameterBuilder(); List<Parameter> parameters = new ArrayList<Parameter>(); parameterBuilder.name("Authorization").description("令牌").modelRef(new ModelRef("string")).parameterType("header") .required(false).build(); parameters.add(parameterBuilder.build()); return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select().apis(RequestHandlerSelectors.any()) .paths(PathSelectors.any()).build().globalOperationParameters(parameters); } private ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("SpringBoot API Doc") .description("This is a restful api document of Spring Boot.") .version("1.0") .build(); } } |
加了令牌属性后的 Swagger 接口调用界面,会多出一个令牌参数,在发起请求的时候一起发送令牌。 2.添加跨域 配置 添加一个CORS跨域配置类,在工程下新建 config 包并添加一个 CorsConfig配置类。 CorsConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.louis.springboot.demo.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 允许跨域访问的路径 .allowedOrigins("*") // 允许跨域访问的源 .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") // 允许请求方法 .maxAge(168000) // 预检间隔时间 .allowedHeaders("*") // 允许头部设置 .allowCredentials(true); // 是否发送cookie } } |
安全配置类 下面这个配置类是Spring Security的关键配置。 在这个配置类中,我们主要做了以下几个配置: 1. 访问路径URL的授权策略,如登录、Swagger访问免登录认证等 2. 指定了登录认证流程过滤器 JwtLoginFilter,由它来触发登录认证 3. 指定了自定义身份认证组件 JwtAuthenticationProvider,并注入 UserDetailsService 4. 指定了访问控制过滤器 JwtAuthenticationFilter,在授权时解析令牌和设置登录状态 5. 指定了退出登录处理器,因为是前后端分离,防止内置的登录处理器在后台进行跳转 WebSecurityConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
package com.louis.springboot.demo.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; import com.louis.springboot.demo.security.JwtAuthenticationFilter; import com.louis.springboot.demo.security.JwtAuthenticationProvider; import com.louis.springboot.demo.security.JwtLoginFilter; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { // 使用自定义登录身份认证组件 auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService)); } @Override protected void configure(HttpSecurity http) throws Exception { // 禁用 csrf, 由于使用的是JWT,我们这里不需要csrf http.cors().and().csrf().disable() .authorizeRequests() // 跨域预检请求 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 登录URL .antMatchers("/login").permitAll() // swagger .antMatchers("/swagger**/**").permitAll() .antMatchers("/webjars/**").permitAll() .antMatchers("/v2/**").permitAll() // 其他所有请求需要身份认证 .anyRequest().authenticated(); // 退出登录处理器 http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()); // 开启登录认证流程过滤器 http.addFilterBefore(new JwtLoginFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); // 访问控制时登录状态检查过滤器 http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } } |
登录认证触发过滤器 JwtLoginFilter 是在通过访问 /login 的POST请求是被首先被触发的过滤器,默认实现是 UsernamePasswordAuthenticationFilter,它继承了 AbstractAuthenticationProcessingFilter,抽象父类的 doFilter 定义了登录认证的大致操作流程,这里我们的 JwtLoginFilter 继承了 UsernamePasswordAuthenticationFilter,并进行了两个主要内容的定制。 1. […]
View Details捕获全局异常是在项目运行期间如果调用的某一个方法出现了运行时异常,则会捕获,并且给出回馈。 首先需要建一个包,包里新建一个捕获异常类GlobalExceptionHandler。前提是springboot的启动类的扫描注解ComponentScan()要扫描到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/** * 用于捕获全局异常 */ @ControllerAdvice//控制器切面 public class GlobalExceptionHandler { @ExceptionHandler(RuntimeException.class)//捕获运行时异常 @ResponseBody public Map<String,Object> exceptionHandler(){//处理异常方法 Map<String,Object> map=new HashMap<String, Object>(); map.put("errorCode","101"); map.put("errorMsg","系统错误!"); return map; } } |
这个捕获异常类可以捕获到全局的运行时异常,例如商城购物车的控制层一个方法出现异常,黄色背景的是重点,会发生异常。
1 2 3 4 5 6 7 |
@RequestMapping("getShoppingCar") public String getShoppingCar(HttpSession session,Model model){ Map<Integer,Cart> cartMap =(Map<Integer,Cart>)session.getAttribute("cartMap"); model.addAttribute("carList",cartMap); int i=1/0; return "udai_shopcart"; } |
然后启动项目,进入主页,当点击购物车按钮时也就是调用了getShoppingCar这个方法,发生了异常,运行结果是跳转页面失败,直接给出异常信息,也就是全局捕获异常类中设置的信息。 配置多数据源# 以前是在applicationContext.xml中配置的,现在springboot通过注解来配置数据源。 首先在application.properties配置文件中加入多数据源配置。
1 2 3 4 5 6 7 8 9 |
spring.datasource.test1.url=jdbc:mysql://127.0.0.1:3306/test1?serverTimezone=UTC spring.datasource.test1.username=root spring.datasource.test1.password=1234 spring.datasource.test1.driverClassName=com.mysql.cj.jdbc.Driver spring.datasource.test2.url=jdbc:mysql://127.0.0.1:3306/test2?serverTimezone=UTC spring.datasource.test2.username=root spring.datasource.test2.password=1234 spring.datasource.test2.driverClassName=com.mysql.cj.jdbc.Driver |
在例子开始之前,首先去创建两个用于测试的数据库test1和test2,并且新建两张空表。 写两个数据源的配置类。# 在java文件夹下新建dataSource包,新建数据源配置类DataSource01和DataSource02。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
@Configuration//注解到springboot容器中 @MapperScan(basePackages="com.gyf.test1.mapper",sqlSessionFactoryRef="test1SqlSessionFactory") public class DataSource01 { /** * @return 返回test1数据库的数据源 */ @Bean(name="test1DataSource") @Primary//主数据源,一个应用只能有一个主数据源 @ConfigurationProperties(prefix="spring.datasource.test1") public DataSource dateSource(){ return DataSourceBuilder.create().build(); } /** * @return 返回test1数据库的会话工厂 */ @Bean(name = "test1SqlSessionFactory") public SqlSessionFactory sqlSessionFactory(@Qualifier("test1DataSource") DataSource ds) throws Exception{ SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(ds); return bean.getObject(); } /** * @return 返回test1数据库的事务 */ @Bean(name = "test1TransactionManager") @Primary//主事务 public DataSourceTransactionManager transactionManager(@Qualifier("test1DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } /** * @return 返回test1数据库的会话模版 */ @Bean(name = "test1SqlSessionTemplate") public SqlSessionTemplate sqlSessionTemplate( @Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
@Configuration//注解到springboot容器中 @MapperScan(basePackages="com.gyf.test2.mapper",sqlSessionFactoryRef="test2SqlSessionFactory") public class DataSource02 { /** * @return 返回test2数据库的数据源 */ @Bean(name="test2DataSource") @ConfigurationProperties(prefix="spring.datasource.test2") public DataSource dateSource(){ return DataSourceBuilder.create().build(); } /** * @return 返回test2数据库的会话工厂 */ @Bean(name = "test2SqlSessionFactory") public SqlSessionFactory sqlSessionFactory(@Qualifier("test2DataSource") DataSource ds) throws Exception{ SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(ds); return bean.getObject(); } /** * @return 返回test2数据库的事务 */ @Bean(name = "test2TransactionManager") public DataSourceTransactionManager transactionManager(@Qualifier("test2DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } /** * @return 返回test2数据库的会话模版 */ @Bean(name = "test2SqlSessionTemplate") public SqlSessionTemplate sqlSessionTemplate( @Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } |
在java文件夹下分别创建两个文件夹test1和test2,分别对这两个数据库进行操作,启动类都需要先扫描到test1和test2的包
1 2 3 4 5 6 7 8 9 10 |
@EnableAutoConfiguration @ComponentScan(basePackages = {"com.gyf.datasource","com.gyf.web","com.gyf.test1.service","com.gyf.test2.service"}) public class App { public static void main( String[] args ) { //启动springboot项目 SpringApplication.run(App.class,args); } } |
Test1的userMapper
1 2 3 4 |
public interface UserMapper { @Insert("insert user (username,password) values (#{username},#{password})") public int save(@Param("username") String username, @Param("password") String password); } |
Test1的UserServiceImpl
1 2 3 4 5 6 7 8 9 |
@Service @Transactional public class UserServiceImpl{ @Autowired private UserMapper userMapper; public void register(String username, String password) { userMapper.save(username,password); } } |
Test2的CustomerMapper
1 2 3 4 5 |
public interface CustomerMapper { @Insert("insert customer (name,tel) values (#{name},#{tel})") public int save(@Param("name") String name, @Param("tel") String tel); } |
Test2的CustomerServiceImpl
1 2 3 4 5 6 7 8 9 |
@Service @Transactional public class CustomerServiceImpl { @Autowired private CustomerMapper customerMapper; public void save(String name, String tel) { customerMapper.save(name,tel); } } |
最后写一个controller类,调用这两个service
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@RestController //声明Rest风格的控制器 //@EnableAutoConfiguration //自动配置,相当于写了spring的配置文件 @RequestMapping("user") public class UserController { @Autowired private UserServiceImpl userService; @Autowired private CustomerServiceImpl customerService; @RequestMapping("register") @ResponseBody public String register(String username,String password){ //把数据保存到test1数据库 userService.register(username,password); //把数据保存到test2数据库 customerService.save(username,"120"); return "success"; } } |
启动项目,看数据库表内容,两张表都插入了信息。 from:https://www.cnblogs.com/fantongxue/p/12443377.html
View Details总归上述问题Rt,其实今天开发刚遇到,当时找了半天为啥前台传参后台却接收不到,原来是返回的时候返回小写,但是前台依旧大写传参。 查了很多后发现其实是json返回的时候把首字母变小写了,也就是Spring Boot中Jackson的功劳 百度后得@JsonProperty注解完美解决。但与此同时会出现两个问题 如果注解放到属性上,则返回的时候既有大写也有小写,
1 2 |
@JsonProperty("Ao") private Integer Ao; |
Result:{Ao:xxx,ao:xxx} 所以注解放在getter上完美解决,返回只有大写不再自动变为小写的问题。
1 2 3 4 |
@JsonProperty("Ao") public Integer getAo() { return Ao; } |
Result:{Ao:xxx} from:https://blog.csdn.net/github_36887863/article/details/81807088
View Details控制台报错: java.lang.NoSuchMethodException:tk.mybatis.mapper.provider.base.BaseSelectProvider.<init>() 浏览器访问:http://localhost:8081/category/list?pid=0 解决办法: 应该导入import tk.mybatis.spring.annotation.MapperScan这个包。 from:https://blog.csdn.net/qq_37495786/article/details/83448614
View Details