0%

RESTful

介绍

REST:表现层状态转化(Representational State Transfer)

RESTful是一种软件设计风格,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便。

Spring 对 RESTful 风格的接口有着天然的支持。

什么是REST

REST(RepresentationalState Transfer)是Roy Fielding 提出的一个描述互联系统架构风格的名词。REST定义了一组体系架构原则,您可以根据这些原则设计以系统资源为中心的Web 服务,包括使用不同语言编写的客户端如何通过 HTTP处理和传输资源状态。

为什么称为 REST?Web本质上由各种各样的资源组成,资源由URI 唯一标识。浏览器(或者任何其它类似于浏览器的应用程序)将展示出该资源的一种表现方式,或者一种表现状态。如果用户在该页面中定向到指向其它资源的链接,则将访问该资源,并表现出它的状态。这意味着客户端应用程序随着每个资源表现状态的不同而发生状态转移,也即所谓REST。

附:REST定义RESTSOAP的比较

REST成熟度的四个层次

第一个层次(Level0)的Web 服务只是使用 HTTP 作为传输方式,实际上只是远程方法调用(RPC)的一种具体形 式。SOAP和 XML-RPC都属于此类。

第二个层次(Level1)的Web 服务引入了资源的概念。每个资源有对应的标识符和表达。

第三个层次(Level2)的Web 服务使用不同的 HTTP 方法来进行不同的操作,并且使用HTTP 状态码来表示不同的结果。如 HTTPGET 方法来获取资源,HTTPDELETE 方法来删除资源。

第四个层次(Level3)的Web 服务使用 HATEOAS。在资源的表达中包含了链接信息。客户端可以根据链接来发现可以执行的动作。

其中第三个层次建立了创建、读取、更新和删除(create,read, update, and delete,CRUD)操作与 HTTP方法之间的一对一映射。根据此映射:

(1)若要在服务器上创建资源,应该使用POST 方法。

(2)若要检索某个资源,应该使用GET 方法。

(3)若要更改资源状态或对其进行更新,应该使用PUT 方法。

(4)若要删除某个资源,应该使用DELETE 方法。

HTTP请求的方法

(1)GET:通过请求URI得到资源
(2)POST:用于添加新的内容
(3)PUT:用于修改某个内容,若不存在则添加
(4)DELETE:删除某个内容
(5)OPTIONS :询问可以执行哪些方法
(6)HEAD :类似于GET, 但是不返回body信息,用于检查对象是否存在,以及得到对象的元数据
(7)CONNECT :用于代理进行传输,如使用SSL
(8)TRACE:用于远程诊断服务器

HTTP请求的状态码

(1)成功Successful2xx:此类状态码标识客户端的请求被成功接收、理解并接受。常见如200(OK)、204(NoContent)。
(2)重定向Redirection3xx:这个类别的状态码标识用户代理要做出进一步的动作来完成请求。常见如301(MovedPermanently)、302(MovedTemprarily)。
(3)客户端错误Client Error 4xx:4xx类别的状态码是当客户端象是出错的时使用的。常见如400(BadRequest)、401(Unauthorized)、403(Forbidden)、404(NotFound)。
(4)服务器错误Server Error 5xx:响应状态码以5开头表示服务器知道自己出错或者没有能力执行请求。常见如500(InternalServer Error)、502(BadGateway)、504(GatewayTimeout)。

附:HTTP1.1的标准简介

接口中的注解

  1. @Controller:修饰class,用来创建处理http请求的对象
  2. @RestController:Spring4之后加入的注解,原来在@Controller中返回实体需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回实体格式。
  3. @RequestMapping:配置 url 映射
  4. @PostMapping: 这个是@RequestMapping+POST方法的简写
  5. @RequestHeader: 请求Header参数
  6. @PathVariable: URL路径参数,比如/{id}中的id参数
  7. @RequestParam: URL请求参数
  8. @RequestBody: 请求Body参数

RESTful架构有一些典型的设计误区,就是URI包含动词。因为”资源”表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中。 上面设计的API的URI中都是名词。(此句引用)

RESTful 测试类

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

@AutoConfigureMockMvc
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ApplicationTests {
@Autowired
private MockMvc mvc;

@Test
public void testUserController() throws Exception {
// 测试UserController
RequestBuilder request;

// 1、get查一下user列表,应该为空
request = get("/users/");
MvcResult result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
String content = result.getResponse().getContentAsString();
BaseResponse<List<User>> response = JacksonUtil.json2Bean(content, new TypeReference<BaseResponse<List<User>>>() {});
assertThat(response.isSuccess(), is(true));
assertThat(response.getMsg(), is("查询列表成功"));
assertThat(((List) response.getData()).size(), is(0));

// 2、post提交一个user
request = post("/users/")
.param("id", "1")
.param("name", "测试大师")
.param("age", "20");
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString();
BaseResponse<String> response1 = JacksonUtil.json2Bean(content, new TypeReference<BaseResponse<String>>() {});
assertThat(response1.isSuccess(), is(true));
assertThat(response1.getMsg(), is("新增成功"));

// 3、get获取user列表,应该有刚才插入的数据
request = get("/users/");
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString();
BaseResponse<List<User>> response2 = JacksonUtil.json2Bean(content, new TypeReference<BaseResponse<List<User>>>() {});
assertThat(response2.isSuccess(), is(true));
assertThat(response2.getMsg(), is("查询列表成功"));
assertThat((response2.getData()).size(), is(1));

// 4、put修改id为1的user
request = put("/users/1")
.param("name", "测试终极大师")
.param("age", "30");
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString();
BaseResponse<String> response3 = JacksonUtil.json2Bean(content, new TypeReference<BaseResponse<String>>() {});
assertThat(response3.isSuccess(), is(true));
assertThat(response3.getMsg(), is("更新成功"));

// 5、get一个id为1的user
request = get("/users/1");
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString();
BaseResponse<User> response4 = JacksonUtil.json2Bean(content, new TypeReference<BaseResponse<User>>() {});
assertThat(response4.isSuccess(), is(true));
assertThat(response4.getMsg(), is("查询成功"));
User user = response4.getData();
assertThat(user.getId(), is(1L));
assertThat(user.getName(), is("测试终极大师"));

// 6、del删除id为1的user
request = delete("/users/1");
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString();
BaseResponse<String> response5 = JacksonUtil.json2Bean(content, new TypeReference<BaseResponse<String>>() {});
assertThat(response5.isSuccess(), is(true));
assertThat(response5.getMsg(), is("删除成功"));

// 7、get查一下user列表,应该为空
request = get("/users/");
result = mvc.perform(request)
.andExpect(status().isOk())
.andReturn();
content = result.getResponse().getContentAsString();
BaseResponse<List<User>> response6 = JacksonUtil.json2Bean(content, new TypeReference<BaseResponse<List<User>>>() {});
assertThat(response6.isSuccess(), is(true));
assertThat(response6.getMsg(), is("查询列表成功"));
assertThat((response6.getData()).size(), is(0));
}

}

RestTemplate

介绍

用于同步客户端HTTP访问的Spring scentral类。它简化了与httpserver的通信,并实施了RESTful原则。它处理HTTP连接,让应用程序代码提供url(带有可能的模板变量)并提取结果。(简化了发起HTTP请求以及处理响应的过程,并且支持REST。)

RestTemplate默认依赖JDK提供http连接的能力(HttpURLConnection),如果有需要的话也可以通过setRequestFactory方法替换为例如 Apache HttpComponents、Netty或OkHttp等其它HTTP library。

实现逻辑

参考https://www.xncoding.com/2017/07/06/spring/sb-restclient.html

API地址

不同的 HTTP 请求实现方式

RestTemplate 默认依赖 JDK 提供 http 连接的能力(HttpURLConnection),如果有需要的话也可以通过 setRequestFactory 方法替换为例如 Apache HttpComponents、Netty 或 OkHttp 等其它HTTP library。

需要引入依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<!--<version>4.5.3</version>-->
</dependency>

URLConnection 方式的请求

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
String result= "";
BufferedReaderin = null;
try {
String urlNameString= url +"?" + param;
URL realUrl= new URL(urlNameString);
// 打开和URL之间的连接
URLConnectionconnection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept","*/*");
connection.setRequestProperty("connection","Keep-Alive");
connection.setRequestProperty("user-agent",
"Mozilla/4.0(compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String,List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for(String key : map.keySet()) {
System.out.println(key+ "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in =new BufferedReader(newInputStreamReader(
connection.getInputStream()));
String line;
while ((line = in.readLine())!= null) {
result += line;
}
} catch (Exception e) {

}
// 使用finally块来关闭输入流
finally{
// 关闭流
}

RestTemplate 方式的请求

1
ResponseEntity<SsoUrlPrm>result = restTemplate.getForEntity(requestPathUrl,SsoUrlPrm.class);  

参考1https://www.cnblogs.com/zhaoyan001/p/8442602.html

参考2https://blog.csdn.net/weixin_39986856/article/details/83018655

使用 RestTemplate

直接使用方式很简单:

1
2
3
4
5
6
7
public class RestTemplateTest {	public static void main(String[] args) {
RestTemplate restT = new RestTemplate();
//通过Jackson JSON processing library直接将返回值绑定到对象
Quote quote = restT.getForObject("http://gturnquist-quoters.cfapps.io/api/random", Quote.class);
String quoteString = restT.getForObject("http://gturnquist-quoters.cfapps.io/api/random", String.class);
System.out.println(quoteString);
}}

发送GET请求

1
2
3
4
5
6
// 1-getForObject()
User user1 = this.restTemplate.getForObject(uri, User.class);
// 2-getForEntity()
ResponseEntity<User> responseEntity1 = this.restTemplate.getForEntity(uri, User.class);HttpStatus statusCode = responseEntity1.getStatusCode();HttpHeaders header = responseEntity1.getHeaders();User user2 = responseEntity1.getBody();
// 3-exchange()
RequestEntity requestEntity = RequestEntity.get(new URI(uri)).build();ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);User user3 = responseEntity2.getBody();

发送POST请求

1
2
3
4
5
6
7
8
9
// 1-postForObject()
User user1 = this.restTemplate.postForObject(uri, user, User.class);
// 2-postForEntity()
ResponseEntity<User> responseEntity1 = this.restTemplate.postForEntity(uri, user, User.class);
// 3-exchange()
RequestEntity<User> requestEntity = RequestEntity
.post(new URI(uri))
.body(user);
ResponseEntity<User> responseEntity2 = this.restTemplate.exchange(requestEntity, User.class);

设置HTTP Header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1-Content-TypeRequest
Entity<User> requestEntity = RequestEntity
.post(new URI(uri))
.contentType(MediaType.APPLICATION_JSON)
.body(user);
// 2-AcceptRequest
Entity<User> requestEntity = RequestEntity
.post(new URI(uri))
.accept(MediaType.APPLICATION_JSON)
.body(user);
// 3-OtherRequest
Entity<User> requestEntity = RequestEntity
.post(new URI(uri))
.header("Authorization", "Basic " + base64Credentials)
.body(user);

捕获异常

捕获HttpServerErrorException

1
2
3
4
try {    
responseEntity = restTemplate.exchange(requestEntity, String.class);} catch (HttpServerErrorException e) {
// log error
}

自定义异常处理器

1
2
3
4
5
public class CustomErrorHandler extends DefaultResponseErrorHandler {    
@Override
public void handleError(ClientHttpResponse response) throws IOException {
// todo
}}

然后设置下异常处理器:

1
2
3
4
5
6
7
8
@Configurationpublic class RestClientConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new CustomErrorHandler());
return restTemplate;
}
}

配置类

创建HttpClientConfig类,设置连接池大小、超时时间、重试机制等。配置如下:

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
/**
* - Supports both HTTP and HTTPS
* - Uses a connection pool to re-use connections and save overhead of creating connections.
* - Has a custom connection keep-alive strategy (to apply a default keep-alive if one isn't specified)
* - Starts an idle connection monitor to continuously clean up stale connections.
*/
@Configuration
@EnableScheduling
public class HttpClientConfig {

private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientConfig.class);

@Resource
private HttpClientProperties p;

@Bean
public PoolingHttpClientConnectionManager poolingConnectionManager() {
SSLContextBuilder builder = new SSLContextBuilder();
try {
builder.loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] arg0, String arg1) {
return true;
}
});
} catch (NoSuchAlgorithmException | KeyStoreException e) {
LOGGER.error("Pooling Connection Manager Initialisation failure because of " + e.getMessage(), e);
}

SSLConnectionSocketFactory sslsf = null;
try {
sslsf = new SSLConnectionSocketFactory(builder.build());
} catch (KeyManagementException | NoSuchAlgorithmException e) {
LOGGER.error("Pooling Connection Manager Initialisation failure because of " + e.getMessage(), e);
}

Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
.<ConnectionSocketFactory>create()
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory())
.build();

PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
poolingConnectionManager.setMaxTotal(p.getMaxTotalConnections()); //最大连接数
poolingConnectionManager.setDefaultMaxPerRoute(p.getDefaultMaxPerRoute()); //同路由并发数
return poolingConnectionManager;
}

@Bean
public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
return new ConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext httpContext) {
HeaderElementIterator it = new BasicHeaderElementIterator
(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
return Long.parseLong(value) * 1000;
}
}
return p.getDefaultKeepAliveTimeMillis();
}
};
}

@Bean
public CloseableHttpClient httpClient() {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(p.getRequestTimeout())
.setConnectTimeout(p.getConnectTimeout())
.setSocketTimeout(p.getSocketTimeout()).build();

return HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(poolingConnectionManager())
.setKeepAliveStrategy(connectionKeepAliveStrategy())
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)) // 重试次数
.build();
}

@Bean
public Runnable idleConnectionMonitor(final PoolingHttpClientConnectionManager connectionManager) {
return new Runnable() {
@Override
@Scheduled(fixedDelay = 10000)
public void run() {
try {
if (connectionManager != null) {
LOGGER.trace("run IdleConnectionMonitor - Closing expired and idle connections...");
connectionManager.closeExpiredConnections();
connectionManager.closeIdleConnections(p.getCloseIdleConnectionWaitTimeSecs(), TimeUnit.SECONDS);
} else {
LOGGER.trace("run IdleConnectionMonitor - Http Client Connection manager is not initialised");
}
} catch (Exception e) {
LOGGER.error("run IdleConnectionMonitor - Exception occurred. msg={}, e={}", e.getMessage(), e);
}
}
};
}

@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("poolScheduler");
scheduler.setPoolSize(50);
return scheduler;
}
}

然后再配置RestTemplateConfig类,使用之前配置好的CloseableHttpClient类注入,同时配置一些默认的消息转换器:

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
/**
* RestTemplate客户端连接池配置
*/
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class RestTemplateConfig {

@Resource
private CloseableHttpClient httpClient;

@Bean
public RestTemplate restTemplate(MappingJackson2HttpMessageConverter jackson2HttpMessageConverter) {
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());

List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charset.forName("utf-8"));
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(jackson2HttpMessageConverter);
restTemplate.setMessageConverters(messageConverters);

return restTemplate;
}

@Bean
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory rf = new HttpComponentsClientHttpRequestFactory();
rf.setHttpClient(httpClient);
return rf;
}

}

发送文件

1
2
3
4
5
6
MultiValueMap<String, Object> multiPartBody = new LinkedMultiValueMap<>();
multiPartBody.add("file", new ClassPathResource("/tmp/user.txt"));
RequestEntity<MultiValueMap<String, Object>> requestEntity = RequestEntity
.post(uri)
.contentType(MediaType.MULTIPART_FORM_DATA)
.body(multiPartBody);

下载文件

1
2
3
4
5
6
7
8
9
10
11
12
// 小文件
RequestEntity requestEntity = RequestEntity.get(uri).build();ResponseEntity<byte[]> responseEntity = restTemplate.exchange(requestEntity, byte[].class);byte[] downloadContent = responseEntity.getBody();
// 大文件
ResponseExtractor<ResponseEntity<File>> responseExtractor = new ResponseExtractor<ResponseEntity<File>>() {
@Override
public ResponseEntity<File> extractData(ClientHttpResponse response) throws IOException {
File rcvFile = File.createTempFile("rcvFile", "zip");
FileCopyUtils.copy(response.getBody(), new FileOutputStream(rcvFile));
return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()).body(rcvFile);
}
};
File getFile = this.restTemplate.execute(targetUri, HttpMethod.GET, null, responseExtractor);

Service注入

1
2
3
4
5
@Servicepublic
class DeviceService {
@Resource
private RestTemplate restTemplate;
}

实际使用例子

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
// 开始推送消息logger.info("解绑成功后推送消息给对应的POS机");
LoginParam loginParam = new LoginParam();
loginParam.setUsername(managerInfo.getUsername());
loginParam.setPassword(managerInfo.getPassword());HttpBaseResponse r = restTemplate.postForObject(
p.getPosapiUrlPrefix() + "/notifyLogin", loginParam, HttpBaseResponse.class);
if (r.isSuccess()) {
logger.info("推送消息登录认证成功");
String token = (String) r.getData();
UnbindParam unbindParam = new UnbindParam();
unbindParam.setImei(pos.getImei());
unbindParam.setLocation(location);
// 设置HTTP Header信息
URI uri;
try {
uri = new URI(p.getPosapiUrlPrefix() + "/notify/unbind");
} catch (URISyntaxException e) {
logger.error("URI构建失败", e);
return 1;
}
RequestEntity<UnbindParam> requestEntity = RequestEntity .post(uri)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", token)
.body(unbindParam);
ResponseEntity<HttpBaseResponse> responseEntity = restTemplate.exchange(requestEntity, HttpBaseResponse.class);
HttpBaseResponse r2 = responseEntity.getBody();
if (r2.isSuccess()) {
logger.info("推送消息解绑网点成功");
} else {
logger.error("推送消息解绑网点失败,errmsg = " + r2.getMsg());
}
} else {
logger.error("推送消息登录认证失败");
}

本文地址: SpringBoot实现RESTful与RestTemplate

推荐

JAVA自定义注解

本文地址: https://github.com/maxzhao-it/blog/post/40132/

日常遇到就收集啦,时间长了版本可能就旧了.

仅仅是收集

commons-codec是Apache开源组织提供的用于摘要运算、编码解码的包。常见的编码解码工具Base64、MD5、Hex、SHA1、DES等。

1
2
3
4
5
6
<!--   commons-codec是Apache开源组织提供的用于摘要运算、编码解码的包。常见的编码解码工具Base64、MD5、Hex、SHA1、DES等。     -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- junit 中没有引入打测试框架 -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
</dependencies>

代码性能测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<dependencies>
<!-- JMH 与插件配套使用-->
<!-- 想准确的知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性;-->
<!-- 对比接口不同实现在给定条件下的吞吐量,找到最优实现-->
<!-- 查看多少百分比的请求在多长时间内完成-->
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>

JWT+shiro验证

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

<dependencies>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<!-- shiro 权限控制 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- shiro ehcache (shiro缓存)-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

加载 XML 或 properties 配置依赖

1
2
3
4
5
6
<!-- @ConfigurationProperties annotation processing (metadata for IDEs) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

SpringBoot 外化配置和自动配置

1
2
3
4
5

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

JSON

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

<dependencies>
<!-- Json libs-->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.4</version>
</dependency>
</dependencies>

连接池(看爱好)

1
2
3
4
5
6

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>

SpringBoot - plugins

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

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<!--<proc>none</proc>-->
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
</executions>
</plugin>
</plugins>
</build>

MAVEN 配置资源目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>

本文地址: https://github.com/maxzhao-it/blog/post/52100/

前言

Java中的 Cloneable并没有具体的方法,那为什么我们需要实现Cloneable这个空接口才能使用clone方法呢?

这里有两方面的原因:

  • 首先,可能有一个上溯造型句柄指向一个基础类型,而且不知道它是否真的能克隆那个对象。在这种情况下,可以用instanceof关键字来判断句柄是否确实实现同一个能克隆的对象连接。
  • 第二个原因是考虑到我们可能不愿所有的对象都能克隆。所以Object.clone()会验证一个类是否真的是实现了Cloneable接口。若没有实现,则抛出CloneNotSupportedException异常。

成功的克隆

首先对象中的clone()方法必须要是public的,其次要实现Cloneable接口,我们最好用一个try-catch块把super.clone()代码封装起来,试图捕获一个应当永不出现的异常。

super.clone()Object.clone()操作,之所以能调用,因为Object中的clone()方法是protected的,所以衍生类都可以访问(Object是顶级父类)。

Object.clone()操作会检查原来对象有多大,再为新的对象腾出足够多的内容,将所有二进制位从原来的对象,复制到新的对象爱嗯。这叫“按位复制”。

简单的示例:

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
public class CloneDemo {

@Test
public void t() {
Cat cat = new Cat();
Object catClone = cat.clone();
if (Objects.nonNull(catClone)) {
System.out.println("catClone:" + catClone.toString());
((Cat) catClone).name = "cat";
System.out.println("cat:" + cat.toString());
System.out.println("catClone:" + catClone.toString());

}
}
}

class Cat implements Cloneable {
String name = "keep";

static {
System.out.println("load Cat");
}

@Override
protected Object clone() {
try {
return super.clone();
} catch (Exception e) {
System.err.println(e.getMessage());
}
return null;
}

@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}

对象中包含其它对象,其它对象也会被Clone吗?

结论,Object.clone()不会自动克隆所有句柄指向的目标,是“浅层复制”。

对象中的对象没有实现Cloneable

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
public class CloneDemo {
@Test
public void t() {
Cat cat = new Cat();
cat.dog = new Dog();
Object catClone = cat.clone();
if (Objects.nonNull(catClone)) {
System.out.println("catClone:" + catClone.toString());
((Cat) catClone).name = "cat";
((Cat) catClone).dog.name = "xiao dog";
System.out.println("改变Clone后的对象值=>");
System.out.println("cat:" + cat.toString());
System.out.println("catClone:" + catClone.toString());
}
}
}

class Cat implements Cloneable {
String name = "keep";
Dog dog;

static {
System.out.println("load Cat");
}

@Override
protected Object clone() {
try {
return super.clone();
} catch (Exception e) {
System.err.println(e.getMessage());
}
return null;
}

@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", dog=" + dog +
'}';
}
}

class Dog {
String name = "dog";

static {
System.out.println("load Dog");
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}

输出结果

1
2
3
4
5
6
load Cat
load Dog
catClone:Cat{name='keep', dog=Dog{name='dog'}}
改变Clone后的对象值=>
cat:Cat{name='keep', dog=Dog{name='xiao dog'}}
catClone:Cat{name='cat', dog=Dog{name='xiao dog'}}

这里我们发现,Dog对象还是引用,并没有被Clone成功。

对象中的对象实现了Cloneable接口

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
public class CloneDemo {
@Test
public void t() {
Cat cat = new Cat();
cat.dog = new Dog();
Object catClone = cat.clone();
if (Objects.nonNull(catClone)) {
System.out.println("catClone:" + catClone.toString());
((Cat) catClone).name = "cat";
((Cat) catClone).dog.name = "xiao dog";
System.out.println("改变Clone后的对象值=>");
System.out.println("cat:" + cat.toString());
System.out.println("catClone:" + catClone.toString());

}
}
}

class Cat implements Cloneable {
String name = "keep";
Dog dog;

static {
System.out.println("load Cat");
}

@Override
protected Object clone() {
try {
return super.clone();
} catch (Exception e) {
System.err.println(e.getMessage());
}
return null;
}

@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", dog=" + dog +
'}';
}
}

class Dog implements Cloneable {
String name = "dog";

static {
System.out.println("load Dog");
}

@Override
protected Object clone() {
System.out.println(" Dog clone ");
try {
return super.clone();
} catch (Exception e) {
System.err.println(e.getMessage());
}
return null;
}

@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}

输出结果

1
2
3
4
5
6
load Cat
load Dog
catClone:Cat{name='keep', dog=Dog{name='dog'}}
改变Clone后的对象值=>
cat:Cat{name='keep', dog=Dog{name='xiao dog'}}
catClone:Cat{name='cat', dog=Dog{name='xiao dog'}}

在这里我们发现,并没有任何区别,实际上上面已经说道“按位复制”,那么Dog对象在Cat对象中只是引用,不存在Dog内存,所以就不存在复制Dog内存。

本文地址: https://github.com/maxzhao-it/blog/post/4368/

SpringBoot使用RabbitMQ

RabbitMQ 是

  • 消息队列
  • 实现AMQP(高级消息队列协议Advanced Message Queuing Protocol)的消息中间件的一种

作用:主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲,消息分发的作用。

流程:

一般消息队列都是生产者将消息发送到队列,消费者监听队列进行消费。

rabbitmq中一个虚拟主机(vhost默认 /)持有一个或者多个交换机(Exchange)。 用户只能在虚拟主机的粒度进行权限控制,交换机根据一定的策略(RoutingKey)绑定(Binding)到队列(Queue)上, 这样生产者和队列就没有直接联系,而是将消息发送的交换机,交换机再把消息转发到对应绑定的队列上。

交换机(Exchange)为rabbitmq独特的概念,用到的最常见的是4中类型:

  1. Direct: 先匹配, 再投送。即在绑定时设定一个routing_key, 消息的routing_key匹配时, 才会被交换器投送到绑定的队列中去. 交换机跟队列必须是精确的对应关系,这种最为简单。
  2. Topic: 转发消息主要是根据通配符。在这种交换机下,队列和交换机的绑定会定义一种路由模式,那么,通配符就要在这种路由模式和路由键之间匹配后交换机才能转发消息 这种可以认为是Direct 的灵活版
  3. Headers: 也是根据规则匹配, 相较于 direct 和 topic 固定地使用 routingkey , headers则是一个自定义匹配规则的类型, 在队列与交换器绑定时会设定一组键值对规则,消息中也包括一组键值对( headers属性),当这些键值对有一对或全部匹配时,消息被投送到对应队列
  4. Fanout : 消息广播模式,不管路由键或者是路由模式,会把消息发给绑定给它的全部队列,如果配置了routingkey会被忽略

举例说明

创建 2 个交换机directExchangefanoutExchange,3个 队列 queueA queueB queueC

队列directExchange作为定点发送,包含队列 A B

队列fanoutExchange作为广播发送,包含队列 A B C

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
@Configuration
public class RabbitConfig {
/**
* 声明 Direct 交换机 支持持久化.
*
* @return the exchange
*/
@Bean("directExchange")
public Exchange directExchange() {
return ExchangeBuilder.directExchange("DIRECT_EXCHANGE").durable(true).build();
}

/**
* 声明 fanout 交换机.
*
* @return the exchange
*/
@Bean("fanoutExchange")
public FanoutExchange fanoutExchange() {
return (FanoutExchange) ExchangeBuilder.fanoutExchange("FANOUT_EXCHANGE").durable(true).build();
}

/**
* 声明一个队列 支持持久化.
*
* @return the queue
*/
@Bean("queueA")
public Queue directQueue() {
return QueueBuilder.durable("QUEUE_A").build();
}

@Bean("queueB")
public Queue directQueue2() {
return QueueBuilder.durable("QUEUE_B").build();
}

@Bean("queueC")
public Queue directQueue3() {
return QueueBuilder.durable("QUEUE_C").build();
}

/**
* 绑定队列A 到 direct 交换机.
*
* @param queue the queue
* @param directExchange the exchange
* @return the binding
*/
@Bean
public Binding bindingA(@Qualifier("queueA") Queue queue,
@Qualifier("directExchange") Exchange directExchange) {
return BindingBuilder.bind(queue).to(directExchange).with("DIRECT_ROUTING_KEY_A").noargs();
}

@Bean
public Binding bindingB(@Qualifier("queueB") Queue queue,
@Qualifier("directExchange") Exchange directExchange) {
return BindingBuilder.bind(queue).to(directExchange).with("DIRECT_ROUTING_KEY_B").noargs();
}

/**
* 绑定队列A 到 fanout 交换机.
*
* @param queue the queue
* @param fanoutExchange the exchange
* @return the binding
*/
@Bean
public Binding bindingA1(@Qualifier("queueA") Queue queue,
@Qualifier("fanoutExchange") Exchange fanoutExchange) {
return BindingBuilder.bind(queue).to(fanoutExchange).with("FANOUT_ROUTING_KEY_A").noargs();
}

@Bean
public Binding bindingA2(@Qualifier("queueB") Queue queue,
@Qualifier("fanoutExchange") Exchange fanoutExchange) {
return BindingBuilder.bind(queue).to(fanoutExchange).with("FANOUT_ROUTING_KEY_B").noargs();
}

@Bean
public Binding bindingA3(@Qualifier("queueC") Queue queue,
@Qualifier("fanoutExchange") Exchange fanoutExchange) {
return BindingBuilder.bind(queue).to(fanoutExchange).with("FANOUT_ROUTING_KEY_C").noargs();
}

}

消息发送类

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
@Service
public class SenderService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Resource
private RabbitTemplate rabbitTemplate;

/**
* 测试广播模式.
*
* @param p the p
* @return the response entity
*/
public void broadcast(String p) {
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("FANOUT_EXCHANGE", "", p, correlationData);
}

/**
* 测试 Direct 模式.
*
* @param p the p
* @return the response entity
*/
public void directA(String p) {
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("DIRECT_EXCHANGE", "DIRECT_ROUTING_KEY_A", p, correlationData);
}
public void directB(String p) {
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("DIRECT_EXCHANGE", "DIRECT_ROUTING_KEY_B", p, correlationData);
}
public void directNull(String p) {
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
rabbitTemplate.convertAndSend("DIRECT_EXCHANGE", "", p, correlationData);
}

}

消息接收类

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
@Component
public class Receiver {
private static final Logger log = LoggerFactory.getLogger(Receiver.class);

/**
* FANOUT广播队列监听一.
*
* @param message the message
* @param channel the channel
* @throws IOException the io exception 这里异常需要处理
*/
@RabbitListener(queues = {"QUEUE_A"})
public void on(Message message, Channel channel) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
log.debug("FANOUT_QUEUE_A " + new String(message.getBody()));
}
@RabbitListener(queues = {"QUEUE_B"})
public void t(Message message, Channel channel) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
log.debug("FANOUT_QUEUE_B " + new String(message.getBody()));
}
@RabbitListener(queues = {"QUEUE_C"})
public void t(Message message, Channel channel) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
log.debug("FANOUT_QUEUE_C " + new String(message.getBody()));
}

/**
* DIRECT模式.
*
* @param message the message
* @param channel the channel
* @throws IOException the io exception 这里异常需要处理
*/
@RabbitListener(queues = {"DIRECT_QUEUE"})
public void message(Message message, Channel channel) throws IOException {
channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
log.debug("DIRECT_QUEUE " + new String(message.getBody()));
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class SenderServiceTest {
@Autowired
private SenderService senderService;

@Test
public void testCache() throws InterruptedException {
// 测试广播模式
senderService.broadcast(" Test 同学们集合啦!");
// 测试Direct模式
senderService.directA(" Test 定点消息 A ");
senderService.directB(" Test 定点消息 B ");
senderService.directNull(" Test 定点消息 null key ");
Thread.sleep(5000L);
}
}

结果

1
2
3
4
5
DIRECT_QUEUE_A "   Test    同学们集合啦!"
FANOUT_QUEUE_B " Test 同学们集合啦!"
FANOUT_QUEUE_C " Test 同学们集合啦!"
DIRECT_QUEUE_A" Test 定点消息 A "
DIRECT_QUEUE_B" Test 定点消息 B "

null key的并没有出现,所以在 directExchange中没有可以广播的队列(都绑定了routingkey)。

Maven 依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: maxzhao
password: maxzhao
#支持发布确认
publisher-confirms: true
#支持发布返回
publisher-returns: true
# 默认 /
virtual-host: maxzhao_vhost
listener:
simple:
acknowledge-mode: manual #采用手动应答
concurrency: 1 #指定最小的消费者数量
max-concurrency: 1 #指定最大的消费者数量
retry:
enabled: true #是否支持重试

其他参考

JMS介绍和使用场景

简介:

讲解什么是消息队列,JMS的基础知识和使用场景

  1. 什么是JMS: Java消息服务(Java Message Service),Java平台中关于面向消息中间件的接口
  2. JMS是一种与厂商无关的 API,用来访问消息收发系统消息,它类似于JDBC(Java Database Connectivity)。这里,JDBC 是可以用来访问许多不同关系数据库的 API
  3. 使用场景:
    1. 跨平台
    2. 多语言
    3. 多项目
    4. 解耦
    5. 分布式事务
    6. 流量控制
    7. 最终一致性
    8. RPC调用
    9. 上下游对接,数据源变动->通知下属

概念

  1. JMS提供者:Apache ActiveMQ、RabbitMQ、Kafka、Notify、MetaQ、RocketMQ
  2. JMS生产者(Message Producer)
  3. JMS消费者(Message Consumer)
  4. JMS消息
  5. JMS队列
  6. JMS主题
  7. JMS消息通常有两种类型:点对点(Point-to-Point)、发布/订阅(Publish/Subscribe

术语

  1. Broker - 简单来说就是消息队列服务器的实体。
  2. Exchange - 消息路由器,转发消息到绑定的队列上,指定消息按什么规则,路由到哪个队列。
  3. Queue - 消息队列,用来存储消息,每个消息都会被投入到一个或多个队列。
  4. Binding - 绑定,它的作用就是把 Exchange 和 Queue 按照路由规则绑定起来。
  5. RoutingKey - 路由关键字,Exchange 根据这个关键字进行消息投递。
  6. Producter - 消息生产者,产生消息的程序。
  7. Consumer - 消息消费者,接收消息的程序。
  8. Channel - 消息通道,在客户端的每个连接里可建立多个Channel,每个channel代表一个会话。

编程模型

  1. ConnectionFactory :连接工厂,JMS 用它创建连接
  2. Connection :JMS 客户端到JMS Provider 的连接
  3. Session: 一个发送或接收消息的线程
  4. Destination :消息的目的地;消息发送给谁.
  5. MessageConsumer / MessageProducer: 消息接收者,消费者

RabbitMQ

RabbitMQ是一个出色的消息代理中间件(Message Broker):接受和转发消息。你可以将它看作是一个邮局, 你把自己的信件写上收件人地址,然后放到邮筒里面就不用管了,由邮局负责将这个信件送到目的地。来自

一、安装

我这里使用的是 ArchLInux

1
2
sudo pacman -S rabbitmq
# 会自动安装 socat erlang-nox rabbitmq

RPM 安装

1
2
3
4
5
6
7
8
9
10
11
12
13
# 先安装erlang
rpm -Uvh https://mirrors.ustc.edu.cn/epel/7/x86_64/Packages/e/epel-release-7-11.noarch.rpm
# 下面可以 epel-release 是软件库
yum install epel-release
yum install erlang
#安装RabbitMQ
rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc
wget http://www.rabbitmq.com/releases/rabbitmq-server/v3.6.15/rabbitmq-server-3.6.15-1.el7.noarch.rpm
ll
yum install rabbitmq-server-3.6.15-1.el7.noarch.rpm
# 或者下面安装 mq
# wget ftp://195.220.108.108/linux/centos/7.4.1708/os/x86_64/Packages/socat-1.7.3.2-2.el7.x86_64.rpm
# yum localinstall -C -y --disablerepo=* *.rpm

安装web管理界面

1
2
3
sudo rabbitmq-plugins enable rabbitmq_management
# 查看所有的插件
sudo rabbitmq-plugins list

启动

1
2
3
4
5
6
# 设置开机自启服务
systemctl enable rabbitmq-server
# 启动RabbitMQ
systemctl start rabbitmq-server
# 重启
systemctl restart rabbitmq-server

配置

1
vim /etc/rabbitmq/rabbitmq.config

配置端口,备注:消息端口5672,则web访问端口为 15672

1
2
3
4
5
6
7
8
[
{rabbit,
[
{loopback_users, []},
{tcp_listeners, [5672]}
]
}
]

用户管理

1
2
3
4
5
6
7
8
9
10
11
12
# 修改guest的密码
sudo rabbitmqctl list_users
sudo rabbitmqctl change_password guest guest

# 创建其他管理员账号比如test/test:
sudo rabbitmqctl add_user maxzhao maxzhao
#
sudo rabbitmqctl set_user_tags maxzhao administrator
# /是 vhost的目录 Configure regexp Write regexp Read regexp
sudo rabbitmqctl set_permissions -p / maxzhao ".*" ".*" ".*"
# Sets user topic permissions for an exchange,默认使用 AMQP default 的exchange
# sudo rabbitmqctl set_topic_permissions

添加vhost

1
2
3
4
5
6
7
8
9
10
11
# 查看帮助
sudo rabbitmqctl --help
# 查看创建 vhost 的帮助
sudo rabbitmqctl add_vhost --help
# 创建
sudo rabbitmqctl add_vhost maxzhao_vhost
# 查看
sudo rabbitmqctl list_vhosts
# 赋权 注意 /maxzhao_vhost 与 maxzhao_vhost 不一样
sudo rabbitmqctl set_permissions -p /maxzhao_vhost maxzhao ".*" ".*" ".*"

删除 vhost

1
2
sudo rabbitmqctl add_vhost maxzhaoTest
sudo rabbitmqctl delete_vhost maxzhaoTest

二、任务队列等引用

本文地址:SpringBoot使用RabbitMQ及RabbitMQ介绍

推荐

官方示例

下面2017年写的,但是比较全面的新手教材。

RabbitMQ简易教程 - 任务队列

RabbitMQ简易教程 - 发布订阅

RabbitMQ简易教程 - 路由

RabbitMQ简易教程 - 主题

RabbitMQ简易教程 - RPC

RabbitMQ简易教程 - WebSocket

RabbitMQ简易教程 - 并发调度

本文地址: https://github.com/maxzhao-it/blog/post/2922/

一、JMS介绍和使用场景及基础编程模型

简介:讲解什么是小写队列,JMS的基础知识和使用场景

  1. 什么是JMS: Java消息服务(Java Message Service),Java平台中关于面向消息中间件的接口

  2. JMS是一种与厂商无关的 API,用来访问消息收发系统消息,它类似于JDBC(Java Database Connectivity)。这里,JDBC 是可以用来访问许多不同关系数据库的 API

  3. 使用场景:

    1. 跨平台
    2. 多语言
    3. 多项目
    4. 解耦
    5. 分布式事务
    6. 流量控制
    7. 最终一致性
    8. RPC调用
    9. 上下游对接,数据源变动->通知下属
  4. 概念

  • JMS提供者:Apache ActiveMQ、RabbitMQ、Kafka、Notify、MetaQ、RocketMQ

  • JMS生产者(Message Producer)

  • JMS消费者(Message Consumer)

  • JMS消息

  • JMS队列

  • JMS主题

  • JMS消息通常有两种类型:点对点(Point-to-Point)、发布/订阅(Publish/Subscribe)

  1. 编程模型

MQ中需要用的一些类

  • ConnectionFactory :连接工厂,JMS 用它创建连接

  • Connection :JMS 客户端到JMS Provider 的连接

  • Session: 一个发送或接收消息的线程

  • Destination :消息的目的地;消息发送给谁.

  • MessageConsumer / MessageProducer: 消息接收者,消费者

二、ActiveMQ5.x消息队列基础介绍和安装

简介:介绍ActiveMQ5.x消息队列基础特性和本地快速安装

特点:

  • 1)支持来自Java,C,C ++,C#,Ruby,Perl,Python,PHP的各种跨语言客户端和协议

  • 2)支持许多高级功能,如消息组,虚拟目标,通配符和复合目标

    1. 完全支持JMS 1.1和J2EE 1.4,支持瞬态,持久,事务和XA消息
    1. Spring支持,ActiveMQ可以轻松嵌入到Spring应用程序中,并使用Spring的XML配置机制进行配置
    1. 支持在流行的J2EE服务器(如TomEE,Geronimo,JBoss,GlassFish和WebLogic)中进行测试
    1. 使用JDBC和高性能日志支持非常快速的持久化

安装

面板:

  • Name:队列名称。

  • Number Of Pending Messages:等待消费的消息个数。

  • Number Of Consumers:当前连接的消费者数目

  • Messages Enqueued:进入队列的消息总个数,包括出队列的和待消费的,这个数量只增不减。

  • Messages Dequeued:已经消费的消息数量。

三、SpringBoot2整合ActiveMQ实战之点对点消息

简介:SpringBoot2.x整合ActiveMQ实战之点对点消息

1、官网地址

2、加入依赖

1
2
3
4
5
6
7
8
9
10
<!-- 整合消息队列ActiveMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<!-- 如果配置线程池则加入 -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
</dependency>

3、application.properties配置文件配置

1
2
3
4
5
6
7
8
9
#整合jms测试,安装在别的机器,防火墙和端口号记得开放
spring.activemq.broker-url=tcp://127.0.0.1:61616
#集群配置
#spring.activemq.broker-url=failover:(tcp://localhost:61616,tcp://localhost:61617)
spring.activemq.user=admin
spring.activemq.password=admin
#下列配置要增加依赖
spring.activemq.pool.enabled=true
spring.activemq.pool.max-connections=100

4、springboot启动类 @EnableJms,开启支持jms

5、模拟请求

localhost:8080/api/v1/order?msg=12312321321312

6、消费者:实时监听对应的队列

@JmsListener(destination = “order.queue”)

四、SpringBoot2整合ActiveMQ实战之发布订阅模式

简介:SpringBoot整合ActiveMQ实战之发布订阅模式(pub/sub),及同时支持点对点和发布订阅模型

1、需要加入配置文件,支持发布订阅模型,默认只支持点对点

1
2
#default point to point
spring.jms.pub-sub-domain=true

注意点:

1、默认消费者并不会消费订阅发布类型的消息,这是由于springboot默认采用的是p2p模式进行消息的监听

修改配置:spring.jms.pub-sub-domain=true

2、@JmsListener如果不指定独立的containerFactory的话是只能消费queue消息

修改订阅者container:containerFactory=”jmsListenerContainerTopic”

//需要给topic定义独立的JmsListenerContainer

1
2
3
4
5
6
7
@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerTopic(ConnectionFactory activeMQConnectionFactory) {
DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
bean.setPubSubDomain(true);
bean.setConnectionFactory(activeMQConnectionFactory);
return bean;
}

在配置文件里面,注释掉 #spring.jms.pub-sub-domain=true

五、RocketMQ4.x消息队列介绍

简介:阿里开源消息队列 RocketMQ4.x介绍和新概念讲解

1、Apache RocketMQ作为阿里开源的一款高性能、高吞吐量的分布式消息中间件

2、特点

1)在高压下1毫秒内响应延迟超过99.6%。

2)适合金融类业务,高可用性跟踪和审计功能。

3)支持发布订阅模型,和点对点

4)支持拉pull和推push两种消息模式

5)单一队列百万消息

6)支持单master节点,多master节点,多master多slave节点

3、概念

Producer:消息生产者

Producer Group:消息生产者组,发送同类消息的一个消息生产组

Consumer:消费者

Consumer Group:消费同个消息的多个实例

Tag:标签,子主题(二级分类),用于区分同一个主题下的不同业务的消息

Topic:主题

Message:消息

Broker:MQ程序,接收生产的消息,提供给消费者消费的程序

Name Server:给生产和消费者提供路由信息,提供轻量级的服务发现和路由

3、官网地址:http://rocketmq.apache.org/

学习资源:

1)http://jm.taobao.org/<st1:chsdate year=”2017” month=”1” day=”12” islunardate=”False” isrocdate=”False” w:st=”on”>2017/01/12/rocketmq-quick-start-in-10-minutes/

2)https://www.jianshu.com/p/453c6e7ff81c

六、RocketMQ4.x本地快速部署

简介:RocketMQ4.x本地快速部署

1、安装前提条件(推荐)

64bit OS, Linux/Unix/Mac

64bit JDK 1.8+;

2、快速开始 http://rocketmq.apache.org/docs/quick-start/

下载安装包:https://www.apache.org/dyn/closer.cgi?path=rocketmq/<st1:chsdate year=”1899” month=”12” day=”30” islunardate=”False” isrocdate=”False” w:st=”on”>4.2.0/rocketmq-all-4.2.0-bin-release.zip

路径:/Users/jack/Desktop/person/springboot/资料/第13章/第5课/rocketmq-all-<st1:chsdate year=”1899” month=”12” day=”30” islunardate=”False” isrocdate=”False” w:st=”on”>4.2.0-bin-release/bin

3、解压压缩包

1)进入bin目录,启动namesrv

nohup sh mqnamesrv &

  1. 查看日志 tail -f nohup.out

结尾:The Name Server boot success. serializeType=JSON 表示启动成功

3、启动broker

nohup sh mqbroker -n 127.0.0.1:9876 &

4)、关闭nameserver broker执行的命令

sh mqshutdown namesrv

sh mqshutdown broker

七、RoekerMQ4.x可视化控制台讲解

简介:RoekerMQ4.x可视化控制台讲解

1、下载 https://github.com/apache/rocketmq-externals

2、编译打包 mvn clean package -Dmaven.test.skip=true

3、target目录 通过java -jar的方式运行

4、无法连接获取broker信息

1)修改配置文件,名称路由地址为 namesrvAddr,例如我本机为

2)src/main/resources/application.properties

rocketmq.config.namesrvAddr=192.168.0.101:9876

5、默认端口 localhost:8080

6、注意:

在阿里云,腾讯云或者虚拟机,记得检查端口号和防火墙是否启动

八、Springboot2整合RocketMQ4.x实战上集

简介:Springboot2.x整合RocketMQ4.x实战,加入相关依赖,开发生产者代码

启动nameser和broker

1、加入相关依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>${rocketmq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-common</artifactId>
<version>${rocketmq.version}</version>
</dependency>

2、application.properties加入配置文件

1
2
3
4
5
6
# 消费者的组名
apache.rocketmq.consumer.PushConsumer=orderConsumer
# 生产者的组名
apache.rocketmq.producer.producerGroup=Producer
# NameServer地址
apache.rocketmq.namesrvAddr=127.0.0.1:9876

3、开发MsgProducer

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
 /**
- 生产者的组名
*/
@Value("${apache.rocketmq.producer.producerGroup}")
private String producerGroup;
/**
- NameServer 地址
*/
@Value("${apache.rocketmq.namesrvAddr}")
private String namesrvAddr;
private DefaultMQProducer producer ;
public DefaultMQProducer getProducer(){
return this.producer;
}

@PostConstruct
public void defaultMQProducer() {
//生产者的组名
producer = new DefaultMQProducer(producerGroup);
//指定NameServer地址,多个地址以 ; 隔开
//如 producer.setNamesrvAddr("192.168.100.141:9876;192.168.100.142:9876;192.168.100.149:9876");
producer.setNamesrvAddr(namesrvAddr);
producer.setVipChannelEnabled(false);
try {

/**
- Producer对象在使用之前必须要调用start初始化,只能初始化一次
*/
producer.start();
} catch (Exception e) {
e.printStackTrace();
}

// producer.shutdown(); 一般在应用上下文,关闭的时候进行关闭,用上下文监听器

}

九、Springboot2整合RocketMQ4.x实战下集

简介:Springboot2.x整合RocketMQ4.x实战,开发消费者代码,常见问题处理

1、创建消费者

问题:

1、Caused by: org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to <172.17.42.1:10911> failed

2、com.alibaba.rocketmq.client.exception.MQClientException: Send [1] times, still failed, cost [1647]ms, Topic: TopicTest1, BrokersSent: [broker-a, null, null]

3、org.apache.rocketmq.client.exception.MQClientException: Send [3] times, still failed, cost [497]ms, Topic: TopicTest, BrokersSent: [chenyaowudeMacBook-Air.local, chenyaowudeMacBook-Air.local, chenyaowudeMacBook-Air.local]

解决:多网卡问题处理

1、设置producer: producer.setVipChannelEnabled(false);

2、编辑ROCKETMQ 配置文件:broker.conf(下列ip为自己的ip)

namesrvAddr = 192.168.0.101:9876

brokerIP1 = 192.168.0.101

4、DESC: service not available now, maybe disk full, CL:

解决:修改启动脚本runbroker.sh,在里面增加一句话即可:

JAVA_OPT=”${JAVA_OPT} -Drocketmq.broker.diskSpaceWarningLevelRatio=0.98”

(磁盘保护的百分比设置成98%,只有磁盘空间使用率达到98%时才拒绝接收producer消息)

常见问题处理:

https://blog.csdn.net/sqzhao/article/details/54834761

https://blog.csdn.net/mayifan0/article/details/67633729

https://blog.csdn.net/a906423355/article/details/78192828

十、SpringBoot多环境配置介绍和项目实战(核心知识)

简介:SpringBoot介绍多环境配置和使用场景

1、不同环境使用不同配置

例如数据库配置,在开发的时候,我们一般用开发数据库,而在生产环境的时候,我们是用正式的数据

2、配置文件存放路径

classpath根目录的“/config”包下

classpath的根目录下

3、spring boot允许通过命名约定按照一定的格式(application-{profile}.properties)来定义多个配置文件

十一、SprinBoot2.x响应式编程简介

简介:讲解什么是reactive响应式编程和使用的好处

1、基础理解:

依赖于事件,事件驱动(Event-driven)

一系列事件称为“流”

异步

非阻塞

观察者模式

网上的一个例子:

int b= 2;

int c=3

int a = b+c //命令式编程后续b和c变化,都不影响a

b=5;

int b= 2;

int c= 3

int a = b+c //响应式编程中,a的变化,会和b、c的变化而变化(事件驱动)

b=5;

2、官网:https://docs.spring.io/spring-boot/docs/<st1:chsdate year=”1899” month=”12” day=”30” islunardate=”False” isrocdate=”False” w:st=”on”>2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-webflux

SpingBoot2底层是用spring5,开始支持响应式编程,Spring又是基于Reactor试下响应式。

学习资料

1、reactive-streams学习资料:http://www.reactive-streams.org/

2、web-flux相关资料:https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#spring-webflux

十二、SpringBoot2.x响应式编程webflux介绍

简介:讲解SpringBoot2.x响应式编程介绍 Mono、Flux对象和优缺点

1、Spring WebFlux是Spring Framework 5.0中引入的新的反应式Web框架

与Spring MVC不同,它不需要Servlet API,完全异步和非阻塞,并 通过Reactor项目实现Reactive Streams规范。

RxJava

2、Flux和Mono User List

1)简单业务而言:和其他普通对象差别不大,复杂请求业务,就可以提升性能

2)通俗理解:

Mono 表示的是包含 0 或者 1 个元素的异步序列

mono->单一对象 User redis->用户ID-》唯一的用户Mono

Flux 表示的是包含 0 到 N 个元素的异步序列

flux->数组列表对象 List redis->男性用户->Flux

Flux 和 Mono 之间可以进行转换

3、Spring WebFlux有两种风格:基于功能和基于注解的。基于注解非常接近Spring MVC模型,如以下示例所示:

第一种:

@RestController

@RequestMapping(“/ users”)

public class MyRestController {

@GetMapping(“/ {user}”)

public Mono getUser( @PathVariable Long user){

// …

}

@GetMapping(“/ {user} / customers”)

public Flux getUserCustomers( @PathVariable Long user){

// …

}

@DeleteMapping(“/ {user}”)

public Mono deleteUser( @PathVariable Long user){

// …

}

}

第二种: 路由配置与请求的实际处理分开

@Configuration

public class RoutingConfiguration {

@Bean

public RouterFunction monoRouterFunction(UserHandler userHandler){

return route(GET( “/ {user}”).and(accept(APPLICATION_JSON)),userHandler :: getUser)

.andRoute(GET(“/ {user} / customers”).and(accept(APPLICATION_JSON)),userHandler :: getUserCustomers)

.andRoute(DELETE(“/ {user}”).and(accept(APPLICATION_JSON)),userHandler :: deleteUser);

}

}

@Component

public class UserHandler {

公共 Mono getUser(ServerRequest请求){

// …

}

public Mono getUserCustomers(ServerRequest request){

// …

}

公共 Mono deleteUser(ServerRequest请求){

// …

}

}

4、Spring WebFlux应用程序不严格依赖于Servlet API,因此它们不能作为war文件部署,也不能使用src/main/webapp目录

5、可以整合多个模板引擎

除了REST Web服务外,您还可以使用Spring WebFlux提供动态HTML内容。Spring WebFlux支持各种模板技术,包括Thymeleaf,FreeMarker

十三、SpringBoot2.x webflux实战

简介:webflux响应式编程实战

1、WebFlux中,请求和响应不再是WebMVC中的ServletRequest和ServletResponse,而是ServerRequest和ServerResponse

2、加入依赖,如果同时存在spring-boot-starter-web,则会优先用spring-boot-starter-web

org.springframework.boot

spring-boot-starter-webflux

测试

localhost:8080/api/v1/user/test

3、启动方式默认是Netty,8080端口

4、参考:https://spring.io/blog/2016/04/19/understanding-reactive-types

十四、服务端推送常用技术介绍

简介:服务端常用推送技术介绍,如websocket,sse轮询等

1、客户端轮询:ajax定时拉取

2、服务端主动推送:WebSocket

全双工的,本质上是一个额外的tcp连接,建立和关闭时握手使用http协议,其他数据传输不使用http协议

更加复杂一些,适用于需要进行复杂双向数据通讯的场景

3、服务端主动推送:SSE (Server Send Event)

html5新标准,用来从服务端实时推送数据到浏览器端,

直接建立在当前http连接上,本质上是保持一个http长连接,轻量协议

简单的服务器数据推送的场景,使用服务器推送事件

学习资料:http://www.w3school.com.cn/html5/html_5_serversentevents.asp

十五、高级篇幅之云服务器介绍和部署生产环境实战

1、阿里云服务器介绍和使用讲解

简介:阿里云服务器介绍和使用讲解

2、阿里云Linux服务器部署JDK8实战

简介:在阿里云服务器上安装JDK8和配置环境变量

lnux下使用wget下载jdk8:

进到目录/usr/local/software

配置环境变量

vim /etc/profile

加入

export JAVA_HOME=/usr/local/software/jdk8

export PATH=$PATH:$JAVA_HOME/bin

export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

export JAVA_HOME PATH CLASSPATH

使用 source /etc/profile 让配置立刻生效

3、阿里云服务器SpringBoot2.x生产环境部署实战

简介:讲解SpringBoot生产环境部署和常见注意事项

1、去除相关生产环境没用的jar

比如热部署dev-tool

2、本地maven打包成jar包

mvn clean package -Dmaven.test.skip=true 跳过测试

3、服务器安装jdk,上传Jar包

上传工具:

windows:

winscp

securtyCRT

mac:

filezilla

ssh root@120.79.160.143

访问路径 http://120.79.160.143:8080/api/v1/user/find

java -jar xxxx.jar

守护进程、系统服务、shell脚本

打包指定配置文件

1、使用maven的profiles

2、使用springboot的profile=active

访问不了

1、阿里云防火墙是否开启,可以选择关闭,关闭是不安全的,可以选择开放端口

2、阿里云的安全访问组,开启对应的端口,如果应用是以80端口启动,则默认可以访问

4、成熟的互联网公司应该有的架构

本地提交生产代码->gitlab仓库->Jenkins自动化构建->运维或者开发人员发布

十六、SpringBoot2.x监控Actuator实战上集

简介:讲解SpringBoot使用actuator监控配置和使用

可用性:100%,99.9%

1、介绍什么是actuator

官方介绍:

Spring Boot包含许多附加功能,可帮助您在将应用程序投入生产时监视和管理应用程序。 可以选择使用HTTP端点或JMX来管理和监控您的应用程序,自动应用于审计,健康和指标收集;

一句话:springboot提供用于监控和管理生产环境的模块

官方文档:https://docs.spring.io/spring-boot/docs/<st1:chsdate year=”1899” month=”12” day=”30” islunardate=”False” isrocdate=”False” w:st=”on”>2.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#production-ready

2、加入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

3、加入上述依赖后,访问几个url

/actuator/health
/actuator/info
/actuator

十七、SpringBoot2监控Actuator下集及生产环境建议

简介:SpringBoot2.x监控Actuator实战下集及生产环境建议,SpringBoot新旧版本区别

注意点: 网上的资料大多数没有讲到访问的前缀

端点基础路径由 / 调整到 /actuator

如:/info调整为/actuator/info

/actuator/xxx

1、只能访问几个url

1)需要在配置文件中加入下列配置

management.endpoints.web.exposure.include=*

2)官网说明:https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-security-actuator

原因:

出于安全考虑,除/ health和/ info之外的所有执行器默认都是禁用的。 management.endpoints.web.exposure.include属性可用于启用执行器

2、建议

在设置management.endpoints.web.exposure.include之前,请确保暴露的执行器不包含敏感信息和/

或通过将其放置在防火墙进行控制,不对外进行使用

禁用的端点将从应用程序上下文中完全删除。如果您只想更改端点所暴露的技术,请改用 include和exclude属性 。

例子:

开启全部:management.endpoints.web.exposure.include=*

开启某个:management.endpoints.web.exposure.include=metrics

关闭某个:management.endpoints.web.exposure.exclude=metrics

或者用springadmin进行管理

相关资料:https://www.cnblogs.com/ityouknow/p/8440455.html

或者用自己编写脚本监控

CPU、内存、磁盘、nginx的http响应状态码200,404,5xx

3、介绍常用的几个

/health 查看应用健康指标

/actuator/metrics 查看应用基本指标列表

/actuator/metrics/{name} 通过上述列表,查看具体 查看具体指标

/actuator/env 显示来自Spring的 ConfigurableEnvironment的属性

本文地址: https://github.com/maxzhao-it/blog/post/2921/

IDEA使用技巧

我这里的快捷键方式是GNOME

快捷键查询

菜单栏HELP—–>keymap reference

Help—–>find action (ctrl+shift+a)

跳转

  1. 项目跳转:菜单栏window—->next project window(ctrl+alt+])

  2. 文件跳转:action搜索recent files(ctrl+e),recently changed files(ctrl+shift+e)

  3. 类跳转:go to class(ctrl+n)

  4. 万能搜索:search everywhere(连续按两次shift)

  5. 上次编辑:菜单栏navigate—->last edit location(ctrl+shift+backspace)

  6. 浏览跳转:菜单栏navigate—->back(ctrl+alt+左箭头or→)

  7. 标签跳转:select next tab(alt+左箭头or→)

  8. 书签跳转:

    • toggle bookmark(F11),

    • toggle bookmark with mnemonic(ctrl+F11) 就是添加快捷键,按了会出现快捷标识,

    • go to bookmark 2(ctrl+2)跳转到有助记符的书签

  9. 收藏:favorites(alt+2),add to favorites(alt+shift+f)

  10. 跳转到任何一个单词:安装插件IdeaVim或emacsIDEAs(ctrl + alt + ; 然后按想要跳转的字符,会提示快捷跳转方式)

  11. 项目区与工作区跳转:(alt+1,ESC)

  12. 打开run控制台:(alt + 4)

  13. 打开debug控制台:(alt + 5)

    • 设置debug (ctrl+f8)

    • 到下一个断点(f9)

    • 到下一行(f8)

    • 不同的案件方式,有差别

  14. 查看待做注解(alt+6)

    注释中标有todoorTODO的代码。

列操作

选中需要操作的字符

ctrl + alt +shift + J

搜索

  1. 类:(ctrl+n)

  2. 文件:(ctrl+shift+n) 文件名字前加/可搜目录

  3. 函数:symbol(ctrl+shift+alt+n)

    如果版本高的话会出现这个查询框

  4. 字符串:

  • 全局查询 find in path(ctrl+shift+f)
  • 文件中查询 find (ctrl+f)
  • 全局替换 replace in path(ctrl+shift+r)
  • 文件中替换 replace (ctrl+r)
  1. 万能查询:(shift+shift)

live templates 代码模板

  1. 插入自己创建的模板:(ctrl+j)

  2. surround with live template:(ctrl+shift+j) 会减少代码行数,自己尝试一下效果

  3. 创建自定义模板

    $END$ 是插入当前模板之后的光标的位置。

    需要配置模板可以使用的位置

    1
    2
    3
    4
    5
    6
    /**
    * $NAME$
    * $END$
    * @author $USER$
    * @since $DATE$
    */

    变量配置 Edit variables

    1
    2
    3
    NAME className()
    USER user()
    DATE date('yyyy-MM-dd HH:mm:ss')
  4. 插入自定义模板

    直接输入自己的模板名称,就会出现自定义模板内容了。

    这种注释建议直接设置 修改注释模板,下面有介绍。

postfix

1.fori

before
1
foo.fori
after
1
2
for(int i=0;i<foo; i++){
}

2.field

before
1
2
3
4
5
public class Foo {
public Foo(int arg) {
arg.field
}
}
after
1
2
3
4
5
6
public class Foo {
private int foo;

public Foo(int arg) {
foo = arg;
}
3.try
before
1
m().try
after
1
2
3
4
5
try{
m();
}catch(CheckedException e){
e.printStackTrace();
}

alter+enter

  1. 自动补全代码
  2. 单词拼写
  3. 导包
  4. 实现接口
  5. list replace
  6. 字符串format或build
  7. inject language(如JSON)

git

1.annotate

2.revert(ctrl+alt+z)

3.右键—->local history—->show history或put label

File

1.copy(F5)

2.MOVE(F6)

3.剪贴板(ctrl+shift+v)

4.file structure(ctrl+F12)

5.查看类继承关系图(ctrl+shift+alt+u,ctrl+alt+u)

6.type hierarchy(ctrl+h)

函数调用层次关系call hierarchy(ctrl+alt+h)

隐藏.idea文件夹和.iml等文件

在File->Settings->Editor->File Types下的”Ignore files and folders”一栏添加 .idea;.iml;等配置如下图所示 ,不建议使用,你想隐藏的文件都还是有用的。

修改注释模板

File->Settings->Editor->File and Code Templates下分别修改Class,Interface,Enum等注释模板

1
2
3
4
5
6
7
8
9
10
11
#if(${PACKAGE_NAME}&&${PACKAGE_NAME}!="")package ${PACKAGE_NAME};#end
#parse("File Header.java")

/**
* ${NAME}
*
* @author ${USER}
* @date ${YEAR}-${MONTH}-${DAY} ${HOUR}:${MINUTE}
*/
public class $ {NAME} {
}

添加serialVersionUID

Atl+Enter键就会提示生成serialVersionUID了

快捷键操作

Ctrl相关

快捷键 介绍
Ctrl + B 进入光标所在的方法/变量的接口或是定义处,等效于Ctrl + 左键单击
Ctrl + D 复制光标所在行或复制选择内容,并把复制内容插入光标位置下面
Ctrl + F 在当前文件进行文本查找
Ctrl + H 查看类的继承结构
Ctrl + N 通过类名定位文件
Ctrl + O 快速重写父类方法
Ctrl + P 方法参数提示
Ctrl + Y 删除光标所在行或删除选中的行
Ctrl + W 递进式选择代码块
Ctrl + Z 撤销
Ctrl + 1,2,3…9 定位到对应数值的书签位置 结合Ctrl + Shift + 1,2,3…9使用
Ctrl + F1 在光标所在的错误代码出显示错误信息
Ctrl + F12 弹出当前文件结构层,可以在弹出的层上直接输入进行筛选
Ctrl + Space 基础代码补全默认在Windows系统上被输入法占用,需要进行修改,建议修改为Ctrl + 逗号
Ctrl + / 注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号

Alt相关

快捷键 介绍
Alt + Q 弹出一个提示,显示当前类的声明/上下文信息
Alt + Enter 根据光标所在问题,提供快速修复选择

Shift相关

快捷键 介绍
Shift + F3 在查找模式下,定位到上一个匹配处

Ctrl+Alt相关

快捷键 介绍
Ctrl + Alt + B 在某个调用的方法名上使用会跳到具体的实现处
Ctrl + Alt + L 格式化代码 可以对当前文件和整个包目录使用
Ctrl + Alt + M 快速抽取方法
Ctrl + Alt + O 优化导入的类和包 可以对当前文件和整个包目录使用
Ctrl + Alt + T 对选中的代码弹出环绕选项弹出层
Ctrl + Alt + V 快速引进变量
Ctrl + Alt + F7 寻找类或是变量被调用的地方,以弹出框的方式显示
Ctrl + Alt + 左方向键 退回到上一个操作的地方
Ctrl + Alt + 右方向键 前进到上一个操作的地方
Ctrl + Alt +空格 列出最近使用的对象

Ctrl+Shift相关

快捷键 介绍
Ctrl + Shift + F 根据输入内容查找整个项目或指定目录内文件
Ctrl + Shift + H 查看方法的继承结构
Ctrl + Shift + J 自动将下一行合并到当前行末尾
Ctrl + Shift + N 通过文件名定位打开文件/目录,打开目录需要在输入的内容后面多加一个正斜杠
Ctrl + Shift + R 根据输入内容替换对应内容,范围为整个项目或指定目录内文件
Ctrl + Shift + U 对选中的代码进行大/小写轮流转换
Ctrl + Shift + W 递进式取消选择代码块
Ctrl + Shift + Z 取消撤销
Ctrl + Shift + / 代码块注释
Ctrl + Shift + + 展开所有代码
Ctrl + Shift + - 折叠所有代码
Ctrl + Shift + 1,2,3…9 快速添加指定数值的书签
Ctrl + Shift + F7 高亮显示所有该选中文本,按Esc高亮消失
Ctrl + Shift + Space 智能代码提示
Ctrl + Shift + Enter 自动结束代码,行末自动添加分号
Ctrl + Shift + 方向键左/右 切换多个字符串的顺序

Alt+Shift相关

快捷键 介绍

Alt+Shift + 鼠标左键 选中多个光标定位并编辑

Alt+Shift + 7 查询当前对象使用的地方(Usages of xxx in project and libraries)

Ctrl+Alt+Shift相关

快捷键 介绍

其他

快捷键 介绍
F2 跳转到下一个高亮错误或警告位置
F3 在查找模式下,定位到下一个匹配处
F4 编辑源

本文地址:IDEA使用技巧和快捷键
推荐:
IDEA好用的插件

本文地址: https://github.com/maxzhao-it/blog/post/5058/

常用工具支持

Java日常开发需要接触到很多常用的工具,为了便于使用,很多工具也有IDEA插件供开发使用,其中大部分已经在IDEA中默认集成了。例如maven、git、svn、tomcat、jetty、jrebel、Gradle等。

框架集成

集成框架主要是为了提供框架定制的代码和配置的生成,以及快速的访问框架提供的功能。例如集成Spring框架,Mybatis框架等。

UI 定制化及优化

UI定制化相关的插件主要提供一下个性化需求定制,例如修改编辑区的背景图片插件、修改代码颜色等。

其他编程语言支持

IDEA主要支持Java,为了使用其他语言,可以使用一些支持其他语言的插件,通过这些插件可以实现语法分析,配色主题,代码格式化和提示等功能。例如Go语言的支持的插件。

IDEA插件安装

IDEA的插件安装非常简单,对于很多插件来说,只要你知道插件的名字就可以在IDEA里面直接安装。

Preferences—>Plugins—>查找所需插件—>Install

或者

Preferences—>Plugins—>Install plug from disk —>选择下载好的插件安装

安装之后重启IDEA即可生效

IDEA插件仓库

IntelliJ
IDEA激发了许多Java开发人员编写插件,IntelliJ IDEA Plugins中目前包含1597个插件并且还在不断增长,可以到这里查看IDEA插件。

实用插件介绍

这里简单介绍一些日常开发中使用到的插件。

Maven Helper

我一般用这款插件来查看maven的依赖树。 在不使用此插件的情况下,要想查看maven的依赖树就要使用Maven命令maven dependency:tree 来查看依赖。
想要查看是否有依赖冲突也可以使用mvn dependency:tree -Dverbose -Dincludes=<groupId>:<artifactId>
只查看关心的jar包,但是这样还是需要我执行命令,并且当项目比较复杂的时候,这个过程是比较漫长的。maven helper就能很好的解决这个问题。

一旦安装了Maven Helper插件,只要打开pom文件,就可以打开该pom文件的Dependency Analyzer视图
(在 文件打开之后,文件下面会多出这样一个tab),进入Dependency Analyzer视图之后有三个查看选项,分别是Conflicts(冲突)、All Dependencies as List(列表形式查看所有依赖)、All
Dependencies as Tree(树结构查看所有依赖)。并且这个页面还支持搜索。很方便!并且使用该插件还能快速的执行maven命令。

来一张maven helper提供的图片感受一下:

FindBugs-IDEA

FindBugs很多人都并不陌生,Eclipse中有插件可以帮助查找代码中隐藏的bug,IDEA中也有这款插件。

分析完之后会有一个视图进行提示,详细的说明是哪种问题。

按照提示解决完问题之后再执行findbug查看情况即可。

使用方法很简单,就是可以对多种级别的内容进行finbugs

Alibaba java coding guidelines

阿里巴巴的代码规范

CheckStyle-IDEA

通过检查对代码编码格式,命名约定,Javadoc,类设计等方面进行代码规范和风格的检查,从而有效约束开发人员更好地遵循代码编写规范。 软件安装成功之后,首先要设置规则。可以通过

Preferences—>Other Settings —>CheckStyles

进行设置,可以直接将文件添加进来,然后就可以对具体的文件进行检查了。

同样,该插件也有个单独的视图,该视图可以展示检查结果。

GsonFormat

Java开发中,经常有把json格式的内容转成Object的需求,GsonFormat这款插件可以实现该功能。

Jrebel

JRebel for IntelliJ
是一款热部署
插件。由于我们团队有内部的热部署方案,所以该插件我没用过,但是这个插件和我们内部的插件差不多,所以在这也推荐一下。

AceJump

AceJump其实是一款能够代替鼠标的软件,只要安装了这款插件,可以在代码中跳转到任意位置。按快捷键进入 AceJump 模式后(默认是
Ctrl+J),再按任一个字符,插件就会在屏幕中这个字符的所有出现位置都打上标签,你只要再按一下标签的字符,就能把光标移到该位置上。换言之,你要 移动光标时,眼睛一直看着目标位置就行了,根本不用管光标的当前位置。

这个自己安装一下实践起来就知道了,我平时其实不怎么用,可能是没用习惯吧。

markdown

安装这个插件之后,打开.md文件就可以通过一个支持md的视图查看和编辑内容。一般用于写README.md文件。但是这个插件我不太用,因为他对md语法支持的并不是很好。还是习惯用macdown这款软件。

Key promoter

很多开发都是从Eclipse转到Idea的。用习惯了Eclipse的快捷键之后在使用IDEA真的非常不习惯。Key promoter
这款插件适合新手使用。当你点击鼠标一个功能的时候,可以提示你这个功能快捷键是什么。

ignore

经常使用git的同学对于ignore一定不会陌生,我们可以在该文件中设置一些忽略提交的规则。

通过该插件可以生成各种ignore文件,一键创建git ignore文件的模板,解决了手动去配置的麻烦。

lombok

当我们创建一个实体时,通常对每个字段去生成GET/SET方法,但是万一后面需要增加或者减少字段时,又要重新的去生成GET/SET方法,非常麻烦。

可以通过该插件,通过注解的形式去解决这些麻烦,同时还可以通过注解去完成构造函数等;

VisualVM Launcher

运行java程序的时候启动visualvm,方便查看jvm的情况 比如堆内存大小的分配

某个对象占用了多大的内存,jvm调优必备工具;

GenerateAllSetter

一键调用一个对象的所有set方法并且赋予默认值 在对象字段多的时候非常方便;

MyBatisX

当前是收费的,可以用 MybatisX代替

mybatis代码自动生成插件,大部分单表操作的代码可自动生成 减少重复劳动 大幅提升效率;

Translation

最好用的翻译插件,功能很强大,界面很漂亮;


翻译更推荐 ECtranslation,因为上面这个需要 AppKey 初识者不容易弄.

SonarLint(Sonar) 代码质量管理

Rainbow Brackets

彩虹颜色的括号 看着很舒服 敲代码效率变高。

Statistic统计代码

代码行数、注释量、空行量统计插件。

Background Image Plus 背景图片插件

CodeGlance 预览插件

类似 Sublime 的右侧导航栏。

CamelCase 坨峰命名神器

SHIFT + ALT + U

log插件三连

  1. ANSI Highlighter

    log文件高亮支持

  2. Catdea

  3. Ideolog

    快速跳转到日志文件中Error位置等

流式编程调试插件 Java Stream Debugger

对Stream API 的调试IDEA 官方开发了一个Plugin──Java Stream Debugger来扩展IDEA中的Debug工具。安装完该插件后在Debug的工具栏上增加了Trace Current Stream
Chain按钮

Flutter Dart VUE

CMD Support

CMD文件支持

Batch Scripts Support

Windows批处理脚本支持

RestfulToolkit

Restful工具集,支持SpringMVC下的URL导航到对应方法等。

Mongo Plugin

MongoDB客户端

Easy Code

基于IntelliJ IDEA开发的代码生成插件,支持自定义任意模板(Java,html,js,xml)。

IDEA Mind Map

IDEA 思维导图工具

SequenceDiagram

生成方法调用的时序图

Material Theme UI

眼睛舒适的主题

String Manipulation

字符串处理,提供驼峰、下划线,base64、md5

JUnit Generator V2.0

快捷生成单元测试类

快速配置:

Output Path:${SOURCEPATH}/../../test/java/${PACKAGE}/test/${FILENAME}

Default Template:Junit 4

big data tools

本文地址:IDEA好用的插件

推荐:
IDEA使用技巧

ASM Bytecode Outline

字节码

Alibaba Cloud AI Coding Assistant

cosy - https://toolkit.aliyun.com/idea/cosy-intellij-beta-latest-all-in-one.zip

本文地址: https://github.com/maxzhao-it/blog/post/58264/

git基本操作整理

之前用的svn,很少使用命令,如今要使用gitlab,所以整理一下 git 的简单用法。

基本快照

Git 的工作就是创建和保存你的项目的快照及与之后的快照进行对比。

查看git配置信息

1
git config --list

git status 查看状态

1
2
3
4
5
6
7
8
9
10
# git-status - Show the working tree status
# git status --help 帮助
# git status -s 简约查看
git status -s
$ A .gitignore
$ AM README.md
$ A pom.xml
$ ?? root.md
$ ?? .attach_pid24604
# A 是新添加的文件 AM 是添加之后又有改动 M 是commit 之后有修改 ??是未操作的文件

git add 添加文件进缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
git add root.md
git status -s
$ A .gitignore
$ AM README.md
$ A pom.xml
$ A root.md
$ ?? .attach_pid24604

git add README.md
$ A .gitignore
$ A README.md
$ A pom.xml
$ A root.md
$ ?? .attach_pid24604

git reset HEAD 取消 git add 添加的文件

git commit 将缓存内容添加到仓库中

如果新安装git,需要添加名称和邮箱

1
2
git config --global user.name "maxzhao"
git config --global user.email "1441439636@qq.com"
1
2
3
git commit -m 'first commit';
git status -s
$ ?? .attach_pid24604

修改 README.md

1
2
3
echo  'xxx' > README.md
git status -s
$ M README.md

git commit -a 直接提交缓存

上面修改了文件,想直接添加到仓库

1
git commit -am 'modify readme.md'

git rm 删除文件

1
2
3
4
5
6
# 简单删除,文件就没有了
git rm <file>
# 如果把文件从暂存区域移除,文件还在
git rm --cached <file>
# 递归删除
git rm –r *

查看提交历史

1
2
3
4
5
6
7
8
9
git log
## 简约查看
git log --oneline
## --graph 选项,查看历史中什么时候出现了分支、合并。
git log --oneline --graph
## --reverse 参数来逆向显示所有日志。
## --author 参数来查看指定用户所有日志。
git log --oneline --author=maxzhao
## --before={3.weeks.ago} --after={2010-04-18} 三周前且在四月十八日之后的所有提交 --no-merges 隐藏合并提交

查看提交记录详情

  • 查看commitId git log --oneline

  • 查看最新的commit git show

  • 查看指定commit hashID的所有修改:git show commitId

  • 查看某次commit中具体某个文件的修改: git show commitId fileName

远程仓库

添加远程仓库

1
git remote add [shortname] [url]

设置公钥

添加之前,需要设置公钥:

需要3次回车,两次是输入密码。

1
ssh-keygen -t rsa -C "1441439636@qq.com"

查看公钥

1
cat ~/.ssh/id_rsa.pub

然后根据不同网站的设置公钥的方式,设置好公钥。

添加仓库

1
2
3
4
5
git remote add origin git@gitee.com:<名称>/<项目名称>.git
git pull origin master --allow-unrelated-histories
git push -u origin master
# -f 覆盖
git push -f origin master

查看仓库

1
2
git remote
git remote -v

获取远程仓库代码

1、从远程仓库下载新分支与数据:

1
git fetch

该命令执行完后需要执行git merge 远程分支到你所在的分支。

2、从远端仓库提取数据并尝试合并到当前分支:

1
git merge

该命令就是在执行 git fetch 之后紧接着执行 git merge 远程分支到你所在的任意分支。

假设你配置好了一个远程仓库,并且你想要提取更新的数据,你可以首先执行 git fetch [alias] 告诉 Git 去获取它有你没有的数据,然后你可以执行 git merge [alias]/[branch]
以将服务器上的任何更新(假设有人这时候推送到服务器了)合并到你的当前分支。

推送到远程仓库

1
2
3
git push [alias] [branch]
# 推送到origin 的 master 分支
git push origin master

推送之前:

1
2
3
3ceeb5a (HEAD -> master) modify readme.md
e66d82e create readme.md
bc5a5d8 (origin/master) first commit

推送之后:

1
2
3
3ceeb5a (HEAD -> master, origin/master) modify readme.md
e66d82e create readme.md
bc5a5d8 first commit

删除远程仓库

1
git remote rm xxx

协作开发

  • 创建开发分支
  • fork项目
  • clone 项目(注意要有自己的地址和团队地址,自己的地址用来提交自己的修改,团队地址用来获取更新 git remote add 自己库名 仓库地址)
  • 使用 git branch 命令查看本地分支
  • 使用 git branch -a 查看项目所有分支
  • 创建并 clone 仓库开发分支
  • git checkout -b dev origin/dev 创建(-b)dev分支,clone 远程dev分支(origin/dev)的内容。切换到该分支(checkout)
  • 切换分支 git checkout master
  • 获取最新版本git fetch origin
  • 合并 git merge origin/dev
  • 解决冲突

强制解决合并冲突

1
rm -rf .git/MERGE*

退出账号

清除配置中纪录的用户名和密码,下次提交代码时会让重新输入账号密码:

1
git config --system --unset credential.helper

执行命令之后,再次pull或push时会缓存输入的用户名和密码:

1
git config --global credential.helper store

清除git缓存中的用户名的密码

1
git credential-manager uninstall

本文地址: git基本操作整理

本文地址: https://github.com/maxzhao-it/blog/post/44004/

下载地址:https://maven.apache.org/download.cgi

1.在/usr/local下面下载maven安装包:

1
wget http://apache.opencas.org/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz

2.解压maven安装包

1
2
mkdir /usr/local/maven
tar -zxvf apache-maven-3.3.9-bin.tar.gz -C /usr/local/maven/

3.配置环境变量

1
2
3
4
5
6
vi /etc/profile

MAVEN_HOME=/usr/local/maven/apache-maven-3.3.9
export MAVEN_HOME
PATH=$MAVEN_HOME/bin:$PATH
export PATH

4.编译环境变量

1
source /etc/profile

5.验证是否成功

1
mvn -version

本文地址 Maven安装(Linux)

本文地址: https://github.com/maxzhao-it/blog/post/58808/

定时任务

  • 使用注解@EnableScheduling 启动定时任务

  • 定义Component让任务被扫描到

  • @Scheduled(fixedRate = 5000) :上一次开始执行时间点之后5秒再执行

  • @Scheduled(fixedDelay = 5000) :上一次执行完毕时间点之后5秒再执行

  • @Scheduled(initialDelay=1000, fixedRate=5000) :第一次延迟1秒后执行,之后按fixedRate的规则每5秒执行一次

  • @Scheduled(cron="*/5 * * * * *") :通过 cron 表达式定义规则

异步任务

  • 使用注解@EnableAsync
  • 定义Component让异步任务被扫描到,@Async定义在方法上,作为异步方法。
  • 异步任务的调用是同时执行的。

异步任务使用场景

  • 发送短信、邮件
  • App消息推送

异步使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 @Async
public void dealNoReturnTask() {
logger.info("返回值为void的异步调用开始" + Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("返回值为void的异步调用结束" + Thread.currentThread().getName());
}
// Future<String> 回调函数
@Async
public Future<String> dealHaveReturnTask(int i) {
logger.info("asyncInvokeReturnFuture, parementer=" + i);
Future<String> future;
try {
Thread.sleep(1000 * i);
future = new AsyncResult<String>("success:" + i);
} catch (InterruptedException e) {
future = new AsyncResult<String>("error");
}
return future;
}

判断是否执行结束

1
2
Future<String> f = Future<String>(1);
f.isDone()

也可以使用多线程和MQ实现异步任务。

最后

一般来说,实际项目中,为了提高服务的响应能力,我们一般会通过负载均衡的方式,或者反向代理多个节点的方式来进行。 如果我们将定时任务写在我们的项目中,那么在同一个时间点,定时任务会一起执行,也就是会执行多次,这样很可能会导致我们的业务出现错误。

建议使用逻辑分离的方式来解决这个问题。就是我们将真正要定时任务处理的逻辑,写成rest或者rpc服务, 然后我们可以新建一个单独的定时任务项目,这个项目应该是没有任何的业务代码的,他纯粹只有定时任务功能, 几点启动,或者每隔多少时间启动。启动后,通过rest或者rpc的方式,调用真正处理逻辑的服务。 摘自:SpringBoot系列 - 定时任务

本文地址:https://www.jianshu.com/p/f45bac4131ce

本文地址: https://github.com/maxzhao-it/blog/post/29880/