开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

SpringBoot中的常用注解

发表于 2021-11-26

「这是我参与11月更文挑战的第24天,活动详情查看:2021最后一次更文挑战」。

前言

在使用 SpringBoot 开发时,我们已经习惯了使用注解去引入依赖,但是其实很多注解的功能是一样的,所以在合适的地方使用合适的注解,可以提高代码的可读性,帮助我们更好的理解项目结构。

常用注解

1. @Component、@Controller、@Service、@Repository 的区别

一句话,除了名字不同,作用完全等价。@Controller、@Service、@Repository 就相当于三件衣服,穿衣服的人就是 @Component
源码
image.png

这几个注解的本质都是一样的,往容器中添加 bean,之所以准备这么多套衣服,是为了帮助我们更好的划分代码结构。@Controller 控制层类,@Service 业务层类,@Repository 持久层类,@Component 无法归类到前 3 种时就称为组件。

2. @ComponentScan、@Import

@ComponentScan

@ComponentScan 指定包扫描 等同于原先的 <context:component-scan> , 当我们加上 @ComponentScan(value = "com.xxx.xxx") 时会扫描该路径下的所有组件配置类。

  • basePackages与value: 用于指定包的路径,进行扫描
  • basePackageClasses: 用于指定某个类的包的路径进行扫描
  • nameGenerator: bean 的名称的生成器
  • useDefaultFilters: 是否开启对 @Component,@Repository,@Service,@Controller 的类进行检测
  • includeFilters: 包含的过滤条件
    • FilterType.ANNOTATION:按照注解过滤
    • FilterType.ASSIGNABLE_TYPE:按照给定的类型
    • FilterType.ASPECTJ:使用 ASPECTJ 表达式
    • FilterType.REGEX:正则
    • FilterType.CUSTOM:自定义规则
    • excludeFilters: 排除的过滤条件,用法和 includeFilters 一样

@Import

和组件配合使用,给容器中导入我们指定的类型的 bean

1
java复制代码@Import({User.class, DBHelper.class})

当加上这个注解,会调用指定类的无参构造器,创建出对于的实例对象,注入到容器中。

3. @Conditional

这个注解的作用是按条件装配,当满足 Conditional 指定的条件时,才会进行组件注入。
Ctrl + H 打开打开继承树
image.png
这是一组注解,满足对应得条件,才会进行组件注入,比如一下几个:

  • @ConditionalOnBean: 当容器中存在指定 bean 时才会做下面的操作
  • @ConditionalOnMissingBean: 当容器中不存在指定 bean 时才会做下面的操作
  • @ConditionalOnClass: 当容器中存在指定类时,执行操作
  • @ConditionalOnMissingClass: 当容器中不存在指定类时,执行操作

这里就不一一列举了,等到使用得时候再详细了解。

4. @ImportResource

@ImportResource(value = "classpath:bean.xml") 在配置类上加上该注解,相当于将类路径下 bean.xml 配置文件里得信息重新解析,放到容器里。

5. @ConfigurationProperties

当类加上这个注解,属性的值会从配置文件中加载,比如 @ConfigurationProperties(prefix = "user") 会在配置文件中找到前缀为user 的配置,后面的属性一一对应上。

注意:

  • 只有这个类是容器中的组件,该注解才会生效,所以一般会和 @Component 一起出现。
  • 避免使用关键字,比如,如果你使用 user , Sprig 会优先取系统的环境变量值,这时你会发现你配置的没有生效。
  • 前缀不支持驼峰式命名
  • MyUser*
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
java复制代码@Component
@ConfigurationProperties(prefix = "myuser")
public class MyUser {
private String name;
private String age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getAge() {
return age;
}

public void setAge(String age) {
this.age = age;
}

@Override
public String toString() {
return "MyUser{" +
"name='" + name + ''' +
", age='" + age + ''' +
'}';
}
}

application.yml

1
2
3
yml复制代码myuser:
name: zahngsan
age: 12

获取 bean 打印输出

1
ini复制代码MyUser{name='zhangsan', age='12'}

6. @EnableConfigurationProperties

将指定的类注入到容器中,使配置了 @ConfigurationProperties 注解的类完成属性绑定。如果一个配置类只配置 @ConfigurationProperties 注解,而没有使用 @Component ,那么在IOC容器中是获取不到 properties 配置文件转化的 bean。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

re的使用(二)

发表于 2021-11-26

王器([\s\S]*?)的使用

在想要获取所有的文本信息的时候,并且使用其他的解析工具相对比较复杂的时候,使用([\s\S]*?)绝对能解决问题

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
python复制代码import selenium
import selenium.webdriver
import re

def getnumberbyspace(space):
url = "https://search.51job.com/list/" + space + ",000000,0000,00,9,99,python,2,1.html?lang=c&stype=&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&providesalary=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare="
driver = selenium.webdriver.Chrome(r"C:\Users\xxxx\Desktop\chromedriver.exe")
driver.get(url)
pagesource = driver.page_source
# 下面的部分不使用u会发生报错
restr = '<div class="rt">([\s\S]*?)</div>' # 如果正则抓取失败,一般都是由于空白字符导致的
"""
([\s\S]*?) 的作用就是将给定的字符串的
深圳
共4497条职位

上海
共6804条职位

北京
共3516条职位

"""
regex = re.compile(restr, re.IGNORECASE)
mylist = regex.findall(pagesource)
newpagesource = mylist[0].strip() # 去除前后的空白符
newstr = "(\d+)"
newgex = re.compile(newstr, re.IGNORECASE)
mylist = newgex.findall(newpagesource)
driver.close()
# 在我们得到的是字典或者列表的时候,我们需要保证其数据的正确性,在后面取数据的时候才不会出错
if len(mylist) == 0:
return "失败"
else:
return mylist[0]


spacedict = {"010000": "北京", "020000": "上海", "040000": "深圳"}
print(getnumberbyspace("010000"))
# for space in spacedict:
# print spacedict[space], getnumberbyspace(space)
# break


"""
对上面的方法进行解析:
1.首先他并没有解决抓取中存在中文的问题,只是通过两次抓取的方式避开了
2.学习的点:strip()方法去掉空格(多余字符的处理),([\s\S]*?)使用非贪婪的模式进行匹配给定的字符串中所有的东西,包括多余字符
"""

常见的使用正则匹配的结构格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
python复制代码import selenium
import selenium.webdriver
import re


def getnumberbyspace(space):
url = "https://search.51job.com/list/"+space+",000000,0000,00,9,99,python,2,1.html?lang=c&stype=&postchannel=0000&workyear=99&cotype=99&degreefrom=99&jobterm=99&companysize=99&providesalary=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare="
driver = selenium.webdriver.Chrome(r"C:\Users\xxxx\Desktop\chromedriver.exe")
driver.get(url)
pagesource = driver.page_source
# 下面的部分不使用u会发生报错
restr = u"共(\d+)条职位"
regex = re.compile(restr,re.IGNORECASE)
mylist = regex.findall(pagesource)
driver.close()
# 在我们得到的是字典或者列表的时候,我们需要保证其数据的正确性,在后面取数据的时候才不会出错
if len(mylist) == 0:
return "失败"
else:
return mylist[0]

spacedict = {"010000":"北京","020000":"上海","040000":"深圳"}
for space in spacedict:
print(spacedict[space],getnumberbyspace(space))

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

如何更新GO Lang版本 5总结

发表于 2021-11-26

案例系统:CentOS Linux release 7.5.1804 (Core). 其他linux系统也应该可以。

1.卸载现在已有的版本

正如这里提到的,要更新一个运行版本,你首先需要卸载原始版本。
查找之前go的地址,这个里面可能yb’y

1
go复制代码whereis go  或者 which go

要卸载,删除之前的go安装目录,注意这里是删掉go目录,而不单单是go文件,比如/usr/local/go目录 :

1
shell复制代码$ sudo rm -rf /usr/local/go

2.下载新版本

转到下载页面并下载适合您的系统的二进制版本。

1
arduino复制代码wget https://golang.google.cn/dl/go1.16.10.linux-amd64.tar.gz

3.解压下载的go文件

解压存档文件,其实就是~ 解压到你刚才删除的那个目录:

1
bash复制代码$ sudo tar -C /usr/local(你的go安装目录) -xzvf /你的下载目录/go1.16.10.linux-amd64.tar.gz

4.确保你的go命令在环境变量中

测试一下

1
go复制代码go version

不行的话,查看go是否已经在环境变量中,看看环境变量哪错了!

1
shell复制代码$ echo $PATH | grep "/usr/local/go/bin"

5.总结

go版本更新。

1.删掉原来的go目录,注意是目录不是文件

2.下载新版本

3.解压到原来的go安装目录

4.测试,不行的话,修复go的环境变量

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

如何让你的消费者保证最终一致性?

发表于 2021-11-26

1.前言

不知道你在开发中是否遇到过如下类似场景:消息消费了,但是由于代码问题,导致业务未正确执行。补偿吧,不知道要补偿哪些数据;不补偿吧,又要挨骂;找生产方重新推送吧,人家不大乐意,甚至不愿鸟你。此类问题,可以归结为最终一致性问题。什么是最终一致性呢?就是我不管你采用什么方式、什么手段,只要保证业务最终执行成功即可。如果是你,你会怎么保证消息消费的最终一致性呢?本文将带着你了解rabbitmq消息队列实现最终一致性的方案。

  1. rabbtimq

2.1 默认ack方式

如你知道的那样,在springboot框架中使用rabbitmq,其ack方式默认为AUTO,从代码中也可以证实这一点AbstractRabbitListenerContainerFactory中setAcknowledgeMode()声明如下

1
2
3
4
5
6
7
java复制代码/**
* @param acknowledgeMode the acknowledge mode to set. Defaults to {@link AcknowledgeMode#AUTO}
* @see AbstractMessageListenerContainer#setAcknowledgeMode(AcknowledgeMode)
*/
public void setAcknowledgeMode(AcknowledgeMode acknowledgeMode) {
this.acknowledgeMode = acknowledgeMode;
}

此AUTO的含义和你从rabbitmq官方文档中了解的自动ack是不一样的,它表示的是由框架本身自动帮你执行ack和nack,而不需要你自己手动去执行

2.2 AUTO逻辑代码

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
java复制代码private boolean doReceiveAndExecute(BlockingQueueConsumer consumer) throws Exception { //NOSONAR

Channel channel = consumer.getChannel();

List<Message> messages = null;
long deliveryTag = 0;

for (int i = 0; i < this.batchSize; i++) {
Message message = consumer.nextMessage(this.receiveTimeout);
if (message == null) {
break;
}
if (this.consumerBatchEnabled) {

}
else {
messages = debatch(message);
if (messages != null) {
break;
}
try {
executeListener(channel, message);
}
catch (ImmediateAcknowledgeAmqpException e) {
}
catch (Exception ex) {
if (causeChainHasImmediateAcknowledgeAmqpException(ex)) {}
else {
// 1.消息执行异常进行回滚
consumer.rollbackOnExceptionIfNecessary(ex);
throw ex;
}
}
}
}
if (messages != null) {
executeWithList(channel, messages, deliveryTag, consumer);
}
// 2.消息执行正常进行提交
return consumer.commitIfNecessary(isChannelLocallyTransacted());

}

2.3 消息提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码public boolean commitIfNecessary(boolean localTx) throws IOException {
try {
// 1.ack方式为auto
boolean ackRequired = !this.acknowledgeMode.isAutoAck() && !this.acknowledgeMode.isManual();
if (ackRequired && (!this.transactional || isLocallyTransacted)) {
long deliveryTag = new ArrayList<Long>(this.deliveryTags).get(this.deliveryTags.size() - 1);
// 2.确认成功消费消息
this.channel.basicAck(deliveryTag, true);
}
}
finally {
this.deliveryTags.clear();
}

return true;
}

2.4 消息回滚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码public void rollbackOnExceptionIfNecessary(Throwable ex) {

// 1.ack方式为auto
boolean ackRequired = !this.acknowledgeMode.isAutoAck()
&& (!this.acknowledgeMode.isManual() || ContainerUtils.isRejectManual(ex));
try {
if (this.transactional) {}
if (ackRequired) {
OptionalLong deliveryTag = this.deliveryTags.stream().mapToLong(l -> l).max();
if (deliveryTag.isPresent()) {
// 2.将消息重新放入队列
this.channel.basicNack(deliveryTag.getAsLong(), true,
ContainerUtils.shouldRequeue(this.defaultRequeueRejected, ex, logger));
}
}
}
catch (Exception e) {

}
finally {
this.deliveryTags.clear();
}
}

2.5 小结

通过以上分析,想必你已经知道,框架会使用try catch包裹我们写的消费者逻辑,如果消费者逻辑执行异常,则会将消息重新放入消息队列中;如果消息执行成功,则会进行ack操作。有了这些理论基础后,一起来看个消费者示例代码。

2.6 消息消费示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码@RabbitHandler
public void helloConsumer(String content) throws IOException {
try {
System.out.println("【receive message】:" + content);
// 模拟业务异常
if (Objects.equals(content, "hello rabbitMQ")) {
throw new NullPointerException();
}
System.out.println("执行操作数据库逻辑");
// ...后续业务
} catch (Exception e) {

}
}

如上示例在没有触发异常的情况下,业务会正常执行;在触发异常的情况下,业务会执行失败,并且没法实现最终一致性。那么怎么做可以达到实现最终一致性的目的呢?

2.7 解决方案

一番分析过后,你不难发现如上示例无法实现最终一致性的原因其实是由开发者自身造成的,开发者使用了try catch块,导致消费者逻辑不会抛出异常,在没有异常的场景下框架会自动进行ack。因此,要想实现最终一致性,只需要去除try catch即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码@Component
@RabbitListener(queues = RabbitmqConfig.QUEUE_NAME)
public class HelloConsumer {

int number = 0;

@RabbitHandler
public void helloConsumer(String content) throws IOException {
number++;
System.out.println("【receive message】:" + content);
// 模拟网络抖动、接口调用失败偶发业务异常
if (number < 5) {
throw new NullPointerException();
}
System.out.println("执行操作数据库逻辑");
// ...后续业务
}
}

2.8 执行结果

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
java复制代码【receive message】:hello rabbitMQ
2021-11-18 20:12:49.795 WARN 70173 --- [ntContainer#0-1] s.a.r.l.ConditionalRejectingErrorHandler : Execution of Rabbit message listener failed.

org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener method 'public void com.boot.example.HelloConsumer.helloConsumer(java.lang.String) throws java.io.IOException' threw exception
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:252) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandlerAndProcessResult(MessagingMessageListenerAdapter.java:194) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:137) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1654) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1573) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1561) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1552) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1496) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:968) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:914) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:83) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1289) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1195) [spring-rabbit-2.3.9.jar:2.3.9]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]
Caused by: java.lang.NullPointerException: null
at com.boot.example.HelloConsumer.helloConsumer(HelloConsumer.java:27) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_161]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_161]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_161]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_161]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:171) ~[spring-messaging-5.3.8.jar:5.3.8]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:120) ~[spring-messaging-5.3.8.jar:5.3.8]
at org.springframework.amqp.rabbit.listener.adapter.DelegatingInvocableHandler.invoke(DelegatingInvocableHandler.java:164) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:81) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:244) ~[spring-rabbit-2.3.9.jar:2.3.9]
... 13 common frames omitted

【receive message】:hello rabbitMQ
2021-11-18 20:12:49.804 WARN 70173 --- [ntContainer#0-1] s.a.r.l.ConditionalRejectingErrorHandler : Execution of Rabbit message listener failed.

org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener method 'public void com.boot.example.HelloConsumer.helloConsumer(java.lang.String) throws java.io.IOException' threw exception
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:252) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandlerAndProcessResult(MessagingMessageListenerAdapter.java:194) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:137) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1654) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1573) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1561) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1552) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1496) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:968) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:914) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:83) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1289) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1195) [spring-rabbit-2.3.9.jar:2.3.9]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]
Caused by: java.lang.NullPointerException: null
at com.boot.example.HelloConsumer.helloConsumer(HelloConsumer.java:27) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_161]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_161]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_161]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_161]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:171) ~[spring-messaging-5.3.8.jar:5.3.8]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:120) ~[spring-messaging-5.3.8.jar:5.3.8]
at org.springframework.amqp.rabbit.listener.adapter.DelegatingInvocableHandler.invoke(DelegatingInvocableHandler.java:164) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:81) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:244) ~[spring-rabbit-2.3.9.jar:2.3.9]
... 13 common frames omitted

【receive message】:hello rabbitMQ
2021-11-18 20:12:49.813 WARN 70173 --- [ntContainer#0-1] s.a.r.l.ConditionalRejectingErrorHandler : Execution of Rabbit message listener failed.

org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener method 'public void com.boot.example.HelloConsumer.helloConsumer(java.lang.String) throws java.io.IOException' threw exception
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:252) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandlerAndProcessResult(MessagingMessageListenerAdapter.java:194) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:137) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1654) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1573) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1561) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1552) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1496) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:968) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:914) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:83) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1289) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1195) [spring-rabbit-2.3.9.jar:2.3.9]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]
Caused by: java.lang.NullPointerException: null
at com.boot.example.HelloConsumer.helloConsumer(HelloConsumer.java:27) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_161]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_161]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_161]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_161]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:171) ~[spring-messaging-5.3.8.jar:5.3.8]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:120) ~[spring-messaging-5.3.8.jar:5.3.8]
at org.springframework.amqp.rabbit.listener.adapter.DelegatingInvocableHandler.invoke(DelegatingInvocableHandler.java:164) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:81) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:244) ~[spring-rabbit-2.3.9.jar:2.3.9]
... 13 common frames omitted

【receive message】:hello rabbitMQ
2021-11-18 20:12:49.818 WARN 70173 --- [ntContainer#0-1] s.a.r.l.ConditionalRejectingErrorHandler : Execution of Rabbit message listener failed.

org.springframework.amqp.rabbit.support.ListenerExecutionFailedException: Listener method 'public void com.boot.example.HelloConsumer.helloConsumer(java.lang.String) throws java.io.IOException' threw exception
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:252) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandlerAndProcessResult(MessagingMessageListenerAdapter.java:194) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:137) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:1654) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.actualInvokeListener(AbstractMessageListenerContainer.java:1573) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:1561) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:1552) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:1496) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:968) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:914) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$1600(SimpleMessageListenerContainer.java:83) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.mainLoop(SimpleMessageListenerContainer.java:1289) [spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1195) [spring-rabbit-2.3.9.jar:2.3.9]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]
Caused by: java.lang.NullPointerException: null
at com.boot.example.HelloConsumer.helloConsumer(HelloConsumer.java:27) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_161]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_161]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_161]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_161]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:171) ~[spring-messaging-5.3.8.jar:5.3.8]
at org.springframework.messaging.handler.invocation.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:120) ~[spring-messaging-5.3.8.jar:5.3.8]
at org.springframework.amqp.rabbit.listener.adapter.DelegatingInvocableHandler.invoke(DelegatingInvocableHandler.java:164) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.HandlerAdapter.invoke(HandlerAdapter.java:81) ~[spring-rabbit-2.3.9.jar:2.3.9]
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:244) ~[spring-rabbit-2.3.9.jar:2.3.9]
... 13 common frames omitted

【receive message】:hello rabbitMQ
执行操作数据库逻辑

从执行结果可以看到虽然中间出现了偶发异常,但是通过重试实现了业务数据的最终一致性

通过去除try catch块的确实现了最终一致性,那么此方案会不会存在其它问题呢?

2.9 重试带来的问题

假如你一味地信任你的生产者,未对消息体内容进行校验,大部分场景下你的业务执行也都没有问题。但是你总有收到让你惊喜的消息体,此时你的业务会抛出异常,抛出异常后,框架自动进行nack,将消息从新放入队列,消费者继续消费消息,然后又被放入消息队列,如此往复,不仅会消耗你的服务器资源,还可能影响其他服务,比如日志服务(因为你打了日志,导致日志量会徒增)。

因此,在利用重试实现最终一致性的时候,你一定要留个心眼,使用消息重试机制时一定要考虑异常场景可以通过重试自动进行修复(比如网络偶发抖动、接口偶发异常)。

2.10 数据库解决方案

只要你执行业务逻辑,总有你想不到的异常会发生,需要考虑的东西太多,反而是一种负担。如果你想最大程度实现最终一致性,那么你的消费者就只需要做一件事情:把消息存入数据库,异步消费。数据库发生故障的概率小之又小,即便异常,通过重试也可以将消息写入数据库,基本可以满足你业务的需求。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

『Netty核心』细说IO模型 IO模型

发表于 2021-11-26

「这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战」。

点赞再看,养成习惯👏👏

IO模型

IO模型简单说就是用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式:BIO,NIO,AIO。

BIO(Blocking IO)

BIO是同步阻塞模型,一个客户端连接对应一个处理线程,典型的模型如下:

BIO服务端示例代码

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
java复制代码public class SocketServer {

static ExecutorService threadPool =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
while(true){
System.out.println("等待客户端的连接。。");
//阻塞方法,如果没有客户端连接就会一直阻塞在这里
Socket clientSocket = serverSocket.accept();
System.out.println("有客户端连接了。。。。。");
//单线程的方式
handler(clientSocket);

//多线程的方式
// threadPool.execute(() -> {
// try {
// handler(clientSocket);
// } catch (IOException e) {
// e.printStackTrace();
// }
//});
}
}

private static void handler(Socket clientSocket) throws IOException {
byte[] bytes = new byte[12];
System.out.println("准备read。。");
//接收客户端的数据,阻塞方法,没有数据可读时就阻塞
int read = clientSocket.getInputStream().read(bytes);
System.out.println("read完毕。。");
if (read != -1){
System.out.println("接收到客户端的数据:" + new String(bytes,0,read));
}
clientSocket.getOutputStream().write("HelloClient".getBytes());
clientSocket.getOutputStream().flush();
}
}

BIO客户端示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码public class SocketClient {

public static void main(String[] args) throws IOException {
//连接服务器的地址和端口
Socket socket = new Socket("localhost",8888);
//向服务器发送数据
socket.getOutputStream().write("bio客户端测试".getBytes());
socket.getOutputStream().flush();
System.out.println("向服务端发送数据结束!!!!!");
byte[] bytes = new byte[1024];
//接收服务端回传的数据
socket.getInputStream().read(bytes);
System.out.println("接收到服务端的数据:" + new String(bytes));
socket.close();
}
}

缺点

由于accept()是一个阻塞方法,如果没有客户端连接的话,将一直阻塞着,而且在单线程的情况下由于read也是一个阻塞的操作,如果连接不做数据的读写操作会导致线程阻塞,浪费资源,而且其它客户端也进不来,多线程可以解决这个问题,但是很多客户端如果仅仅只是连接下,又不做读写操作,那么会操作大量的资源浪费,如果线程很多,会导致服务器线程太多,压力太大,比如 C10K 问题。

应用场景

BIO方式适用于连接数目比较小并且一次发送大量数据的场景,这种方式对服务器资源要求比较高,并发局限于应用中。但程序简单易理解。

NIO(Non Blocking IO)

同步非阻塞,服务器实现模式为一个线程可以处理多个请求(连接) ,客户端发送的连接请求都会注册到多路复用器selector上,多路复用器轮询到连接有IO请求就进行处理,JDK1.4开始引入。

应用场景:

NIO方式适用于连接数目多且连接比较短(轻操作) 的架构, 比如聊天服务器, 弹幕系统, 服务器间通讯,编程比较复杂。

NIO服务端代码示例

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
java复制代码public class NioServer {
//保存客户端连接
static List<SocketChannel> channelList = new ArrayList<>();

public static void main(String[] args) throws IOException {
//创建NIO ServerSocketChannel,与BIO的serverSocket类似
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(8888));
//设置ServerSocketChannel为非阻塞
serverSocket.configureBlocking(false);
System.out.println("服务启动成功");
while(true){
//非阻塞模式accept方法不会阻塞,否则会阻塞
//NIO的非阻塞是由操作系统内部实现的,底层调用了linux内核的accept函数
SocketChannel socketChannel = serverSocket.accept();
if (socketChannel != null){
System.out.println("连接成功");
//设置SocketChannel为非阻塞
socketChannel.configureBlocking(false);
//保存客户端连接在List中
channelList.add(socketChannel);
}
//遍历连接进行数据读取
Iterator<SocketChannel> iterator = channelList.iterator();
while(iterator.hasNext()){
SocketChannel sc = iterator.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
//非阻塞模式read方法不会阻塞,否则会阻塞
int len = sc.read(byteBuffer);
//如果有数据,把数据打印出来
if (len > 0){
System.out.println("接收到消息:" + new String(byteBuffer.array()));
}else if (len == -1){ //如果客户端断开,把socket从集合中去掉
iterator.remove();
System.out.println("客户端断开连接");
}
}
}
}
}

总结:如果连接数太多的话,会有大量的无效遍历,假如有10000个连接,其中只有1000个连接有写数据,但是由于其他9000个连接并没有断开,我们还是要每次轮询遍历一万次,其中有十分之九的遍历都是无效的,这显然不是一个让人很满意的状态。

NIO引入多路复用器代码示例

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
java复制代码public class NioSelectorServer {

public static void main(String[] args) throws IOException {
//创建NIO ServerSocketChannel
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9000));
//设置ServerSocketChannel为非阻塞
serverSocket.configureBlocking(false);
//打开Selector处理channel,即创建epoll
Selector selector = Selector.open();
//把ServerSocketChannel注册到selector上,并且selector对客户端accept连接操作感兴趣
serverSocket.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务启动成功");
while(true){
//阻塞等待需要处理的事件发生
selector.select();
//获取selector中注册的全部事件的SelectionKey实例
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//遍历SelectionKey对事件进行处理
while(iterator.hasNext()){
SelectionKey key = iterator.next();
//如果是OP_ACCEPT事件,则进行连接获取贺事件注册
if (key.isAcceptable()){
ServerSocketChannel server = (ServerSocketChannel)key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
//这里只注册了读事件,如果需要给客户端发送数据可以注册写事件
socketChannel.register(selector,SelectionKey.OP_READ);
System.out.println("客户端连接成功");
}else if (key.isReadable()){ //如果是OP_READ事件,则进行读取贺打印
SocketChannel socketChannel = (SocketChannel)key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = socketChannel.read(byteBuffer);
//如果有数据,把数据打印出来
if (len > 0){
System.out.println("接收到消息:" + new String(byteBuffer.array()));
}else if(len == -1){ //如果客户端断开连接,关闭Socket
System.out.println("客户端断开连接");
socketChannel.close();
}
}
//从事件集合里删除本次处理的key,防止下次select重复处理
iterator.remove();
}
}
}
}

NIO 有三大核心组件: Channel(通道), Buffer(缓冲区),Selector(多路复用器)

1)、channel 类似于流,每个 channel 对应一个 buffer缓冲区,buffer 底层就是个数组。

2)、channel 会注册到 selector 上,由 selector 根据 channel 读写事件的发生将其交由某个空闲的线程处理。

3)、NIO 的 Buffer 和 channel 都是既可以读也可以写。

NIO底层在JDK1.4版本是用linux的内核函数 select() 或 poll() 来实现,跟上面的 NioServer 代码类似,selector 每次都会轮询所有的 sockchannel 看下哪个 channel 有读写事件,有的话就处理,没有就继续遍历,JDK1.5开始引入了 epoll 基于事件响应机制来优化NIO。

底层实现

NioSelectorServer 代码里如下几个方法非常重要,我们从Hotspot与Linux内核函数级别来理解下

1
2
3
4
5
java复制代码Selector.open() //创建多路复用器

socketChannel.register(selector, SelectionKey.OP_READ) //将channel注册到多路复用器上

selector.select() //阻塞等待需要处理的事件发生

image.png

总结:NIO整个调用流程就是Java调用了操作系统的内核函数来创建Socket,获取到Socket的文件描述符,再创建一个Selector对象,对应操作系统的Epoll描述符,将获取到的Socket连接的文件描述符的事件绑定到Selector对应的Epoll文件描述符上,进行事件的异步通知,这样就实现了使用一条线程,并且不需要太多的无效的遍历,将事件处理交给了操作系统内核(操作系统中断程序实现),大大提高了效率。

Epoll函数详解

(1) int epoll_create(int size);

创建一个epoll实例,并返回一个非负数作为文件描述符,用于对epoll接口的所有后续调用。

参数size代表可能会容纳size个描述符,但size不是一个最大值,只是提示操作系统它的数量级,现在这个参数基本上已经弃用了。

所以说调用Selector selector = Selector.open();执行这一句本质就是创建了一个epoll对象,是通过调用操作系统函数epoll_create创建的。

(2) int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

使用文件描述符epfd引用的epoll实例,对目标文件描述符fd执行op操作。

参数epfd表示epoll对应的文件描述符,参数fd表示socket对应的文件描述符。

参数op有以下几个值:

  • EPOLL_CTL_ADD:注册新的fd到epfd中,并关联事件event;
  • EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL:从epfd中移除fd,并且忽略掉绑定的event,这时event可以为null;

参数event是一个结构体

1
2
3
4
5
6
7
8
9
10
11
c复制代码struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};

typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;

events有很多可选值,这里只举例最常见的几个:

  • EPOLLIN :表示对应的文件描述符是可读的;
  • EPOLLOUT:表示对应的文件描述符是可写的;
  • EPOLLERR:表示对应的文件描述符发生了错误;

成功则返回0,失败返回-1

(3)int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

等待文件描述符epfd上的事件。

epfd是Epoll对应的文件描述符,events表示调用者所有可用事件的集合,maxevents表示最多等到多少个事件就返回,timeout是超时时间。

I/O多路复用底层主要用的Linux 内核函数(select,poll,epoll)来实现,windows不支持epoll实现,windows底层是基于winsock2的select函数实现的(不开源)。

select poll epoll(jdk 1.5及以上)
操作方式 遍历 遍历 回调
底层实现 数组 链表 哈希表
IO效率 每次调用都进行线性遍历,时间复杂度为O(n) 每次调用都进行线性遍历,时间复杂度为O(n) 事件通知方式,每当有IO事件就绪,系统注册的回调函数就会被调用,时间复杂度O(1)
最大连接 有上限 无上限 无上限

AIO(NIO 2.0)

异步非阻塞,由操作系统完成后回调通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。

应用场景

AIO方式适用于连接数目多且连接比较长(重操作)的架构,JDK7 开始支持。

AIO服务端代码示例

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
java复制代码public class AIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8888));
serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {

@Override
public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
try{
System.out.println("2--"+Thread.currentThread().getName());
//再此接收客户端连接,如果不写这行代码后面的客户端连接不上服务器
serverChannel.accept(attachment,this);
System.out.println(socketChannel.getRemoteAddress());
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("3--" + Thread.currentThread().getName());
buffer.flip();
System.out.println(new String(buffer.array(),0,result));
socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
}

@Override
public void failed(Throwable exc, ByteBuffer attachment) {
exc.printStackTrace();
}
});
}catch (IOException e){
e.printStackTrace();
}
}

@Override
public void failed(Throwable exc, Object attachment) {
exc.printStackTrace();
}
});
System.out.println("1--"+Thread.currentThread().getName());
Thread.sleep(Integer.MAX_VALUE);
}
}

AIO客户端代码示例

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public class AIOClient {
public static void main(String... args) throws Exception {
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000)).get();
socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes()));
ByteBuffer buffer = ByteBuffer.allocate(512);
Integer len = socketChannel.read(buffer).get();
if (len != -1) {
System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
}
}
}

BIO、 NIO、 AIO 对比

image.png

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

C++ STL萃取机制

发表于 2021-11-26

我将从定义、技术实现、设问形式、实例总结来阐述我对于萃取机制的理解。

1.定义

traits中文意思是特性,它通过提取不同类的共性,使得可以统一处理。

2.技术实现

traits运用显式模板特殊化将代码中因为类型不同而发生变化的片段提取出来,用统一的接口来包装,并通过traits模板类公开的接口间接访问相应的类。

3.设问形式

问题1:什么是显式模板特殊化呢?
答:模板特殊化又分了一个偏特化(意思就是没有完全的特化)我们来看一段代码,

1
ruby复制代码template<class T,class U>// 基础模板类class NumTraits{};//模板特化的格式template<class T> //偏特殊化class NumTraits<IntArray>{public:    typedef int resulttype;    typedef int inputargtype;};template<class T>class NumTraits{};//模板特化的格式template<> //特殊化class NumTraits<IntArray>{public:    typedef int resulttype;    typedef int inputargtype;};

问题2:用实例来展示一下为什么会使用萃取机制?

答:我会用(3步走)的代码来解释为什么需要使用萃取。

1
2
3
css复制代码#include<iostream>using namespace std;
//①基本类写法class IntArray{public: IntArray() { a = new int[10]; for (int i = 0; i < 10; ++i) { a[i] = i + 1; } } ~IntArray() { delete[] a; }
int GetSum(int times){ int sum = 0; for (int i = 0; i < 10; ++i) sum += a[i]; cout << "int sum=" << sum << endl; return sum * times; }private: int *a;};class FloatArray{public:

FloatArray() { f = new float[10]; for (int i = 1; i <= 10; ++i) { f[i - 1] = 1.0f / i; } } ~FloatArray() { delete[] f; } float GetSum(float times){ float sum = 0.0f; for (int i = 0; i < 10; i++) sum += f[i]; cout << “float sum=” << sum << endl; return sum * times; }private: float* f;};//②模板写法templateclass Apply{public: float GetSum(T& t, float inarg){ return t.GetSum(inarg); }};
//以上方法不能完全解决我们的问题(函数返回值固定,就会导致异常)//③采用萃取机制:模板特化templateclass NumTraits{};//模板特化的格式template<>class NumTraits{public: typedef int resulttype; typedef int inputargtype;};template<>class NumTraits{public: typedef float resulttype; typedef float inputargtype;};templateclass Apply2{public: NumTraits::resulttype GetSum(T& obj, NumTraits::inputargtype inputarg){ return obj.GetSum(inputarg); }};int main(){ IntArray intary; FloatArray floatary; Apply ai; //采用模板 Apply af; //采用模板 cout << “1整型数组的和3倍:” << ai.GetSum(intary, 3) << endl; cout << “1浮点数组的和3.2倍:” << af.GetSum(floatary, 3.2f) << endl; cout<<endl; cout<<endl; Apply2 ai2; //采用萃取 Apply2 af2; //采用萃取 cout << “2整型数组的和3倍:” <<ai2.GetSum(intary,3) << endl; cout << “2浮点数组的和3.2倍:” << af2.GetSum(floatary,3.2f) << endl; return 0;}

1
2


markdown复制代码4.实例总结


第①步:我们会发现代码冗余度很高,所以采用了第二种;
第②步:我们会发现在运用模板后,代码量是减少了,但是其类内部函数定义出现了固定形式的类型。若遇到复杂的问题,会导致数据的错误。
第③步:我们运用了traits机制,将根据不同类类型特化出相应的函数参数类型和返回值类型,这样就可以通过统一的接口,来实现不同的实例。
由此,萃取机制对我们编码的复用性,帮助还是很大的!!!!!!!



**本文转载自:** [掘金](https://juejin.cn/post/7034765632323190792)

*[开发者博客 – 和开发相关的 这里全都有](https://dev.newban.cn/)*

json的使用

发表于 2021-11-26

json字符串生成json对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
python复制代码import json

str = '''
[{
"name": "Bob",
"gender": "male",
"birthday": "1992-10-18"
}, {
"name": "Selina",
"gender": "female",
"birthday": "1995-10-18"
}]
'''
print(type(str))
data = json.loads(str)
print(data)
print(type(data))

json双引号,不能使用单引号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
python复制代码import json

str = '''
[{
'name': 'Bob',
'gender': 'male',
'birthday': '1992-10-18'
}]
'''
data = json.loads(str)

"""
json字符串的key-value都需要使用双引号,不能使用单引号,否则在loads的时候会报错
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 3 column 5 (char 8)
"""

文件读取json内容

1
2
3
4
5
6
python复制代码import json

with open('data.json', 'r') as file:
str = file.read()
data = json.loads(str)
print(data)

json写入文件

  • json.dumps()会自动将json串中的单引号变成双引号
1
2
3
4
5
6
7
8
9
python复制代码import json

data = [{
'name': 'Bob',
'gender': 'male',
'birthday': '1992-10-18'
}]
with open('data.json', 'w') as file:
file.write(json.dumps(data))

json写入文件,indent指定缩进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
python复制代码import json

data = [{
'name': 'Bob',
'gender': 'male',
'birthday': '1992-10-18'
}]
with open('data.json', 'w') as file:
file.write(json.dumps(data, indent=2))

"""
文件中的文本格式
[
{
"name": "Bob",
"gender": "male",
"birthday": "1992-10-18"
}
]
"""

中文解码问题

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
python复制代码import json

data = [{
'name': '李宁',
'gender': '男',
'birthday': '1992-10-18'
}]

with open('data.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(data, indent=2, ensure_ascii=False))

"""
存在中文
with open('data.json', 'w') as file:
file.write(json.dumps(data, indent=2))
[
{
"name": "\u738b\u4f1f",
"gender": "\u7537",
"birthday": "1992-10-18"
}
]

解决方式:
with open('data.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(data, indent=2, ensure_ascii=False))
"""

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

3《spring从零到壹》-认识AOP(三)(JAVA 小虚

发表于 2021-11-26

这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」

❤️作者简介:大家好,我是小虚竹。Java领域优质创作者🏆,CSDN博客专家认证🏆,华为云享专家认证🏆

❤️技术活,该赏

❤️点赞 👍 收藏 ⭐再看,养成习惯

回顾:

spring框架-认识spring框架(一)

spring框架-认识IOC(二)


AOP综述

AOP是什么

Aspect-oriented programming 面向切面(方面)编程

AOP有什么用

可以把业务逻辑和系统级的服务进行隔离

系统级的服务像系统的日志,事务,权限验证等

AOP怎么用

动态代理

AOP优点

1.降低了组件之间的耦合性 ,实现了软件各层之间的解耦

2、低侵入式设计,代码的污染极低

3、利用它很容易实现如权限拦截,运行期监控和系统日志等功能


探索AOP

如果没有接触过AOP(面向切面编程)的,可能一时间没办法理解,因为之前都是用JAVA的面向对象编程(OOP),没关系,一步步跟我来学习AOP。

当然如果您是技术大牛,既然您抽出时间查看了虚竹的文章,欢迎纠正文章中的不足和缺陷。

我们先来看下来这段《精通Spring4.x 企业应用开发实战》提供的代码片段

图上的业务代码被事务管理代码和性能监控代码所包围,而且学过JAVA的都知道,出现重复代码了。出现重复代码那就需要重构代码,代码的重构步骤一般有这两种:

1、抽成方法

2、抽成类

抽取成类的方式我们称之为:纵向抽取

  • 通过继承的方式实现纵向抽取

但是,虚竹发现这种抽取方式在图上的业务中不适用了,因为事务管理代码和性能监控代码是跟对应的业务代码是有逻辑关系的

现在纵向抽取的方式不行了,AOP希望将分散在各个业务逻辑代码中相同的代码通过横向切割的方式抽取到一个独立的模块中!请看下图

图上应该很好理解,把 重复性的非业务代码横向抽取出来,这个简单。但是如何把这些非业务代码融合到业务代码中,达到跟之前的代码同样的效果。这个才是难题,就是AOP要解决的难题。


AOP原理

AOP的底层原理就是动态代理(如果没听过代理模式,可以看看这位大神写的博客给女朋友讲解什么是代理模式)

来源《Spring 实战 (第4版)》一句话:

Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。

AOP的动态代理:

  1. jdk动态代理
  2. cglib动态代理

Spring在选择用JDK动态代理还是CGLiB动态代理的依据:

(1)当Bean实现接口时,Spring就会用JDK的动态代理

(2)当Bean没有实现接口时,Spring使用CGlib是实现

(3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=”true”/>)

JDK代理和CGLib代理我们该用哪个

在《精通Spring4.x 企业应用开发实战》给出了建议:

如果是单例的我们最好使用CGLib代理,如果是多例的我们最好使用JDK代理

原因:

JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。


如果是单例的代理,推荐使用CGLib

现在大家知道什么是AOP了吧:把非业务重复代码横向抽取出来,通过动态代理织入目标业务对象函数中,实现跟之前一样的代码


AOP术语

aop术语不好理解,《精通Spring4.x 企业应用开发实战》书上有提到这些术语,我尽量解释下这些术语

连接点(JointPoint)

一个类中的所有方法都可以被称为连接点

切入点(PointCut)

上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点。

用注解来说明,被@Log定义的方法就是切入点

增强/通知(Advice)

表示添加到切点的一段逻辑代码,并定位连接点的方位信息。

前置通知(MethodBeforeAdvice )

前置通知是在目标方法执行前执行

后置通知 (AfterReturningAdvice )

后置通知是在目标方法执行后才执行 ,可以得到目标方法返回的值 ,但不能改变返回值

环绕通知(MethodInterceptor)

环绕通知有在目标方法执行前的代码,也有在目标方法执行后的代码,可以得到目标方法的值,可以改变这个返回值!

扩展知识点:

spring中的拦截器分两种:

1、HandlerInterceptor

2、MethodInterceptor

HandlerInterceptor是springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行

MethodInterceptor是AOP项目中的拦截器,它拦截的目标是方法,即使不是controller中的方法。

异常通知(ThrowsAdvice)

最适合的场景就是事务管理

引介通知(IntroductionInterceptor)

引介通知比较特殊,上面的四种通知都是在目标方法上织入通知,引介通知是在目标类添加新方法或属性

  • 简单来说就定义了是干什么的,具体是在哪干
  • Spring AOP提供了5种Advice类型给我们:前置通知、后置通知、返回通知、异常通知、环绕通知给我们使用!

切面(Aspect)

切面由切入点和增强/通知组成

交叉在各个业务逻辑中的系统服务,类似于安全验证,事务处理,日志记录都可以理解为切面

织入(weaving)

就是将切面代码插入到目标对象某个方法的过程,相当于我们在jdk动态代理里面的 invocationHandler接口方法的内容

用注解解释:就是在目标对象某个方法上,打上切面注解

目标对象(target)

切入点和连接点所属的类

顾问(Advisor)

就是通知的一个封装和延伸,可以将通知以更为复杂的方式织入到某些方法中,是将通知包装为更复杂切面的装配器。


Spring对AOP的支持

Spring提供了3种类型的AOP支持:

  • 基于代理的经典SpringAOP
    • 需要实现接口,手动创建代理
  • 纯POJO切面
    • 使用XML配置,aop命名空间
  • @AspectJ注解驱动的切面
    • 使用注解的方式,这是最简洁和最方便的!

知识点

切面类型主要分成了三种

  • 一般切面
  • 切点切面
  • 引介/引入切面

一般切面,切点切面,引介/引入切面说明:

一般切面一般不会直接使用,切点切面都是直接用就可以了。

这里重点说明下引介/引入切面:

  • 引介/引入切面是引介/引入增强的封装器,通过引介/引入切面,可以更容易地为现有对象添加任何接口的实现!

继承关系图:

引介/引入切面有两个实现类:

  • DefaultIntroductionAdvisor:常用的实现类
  • DeclareParentsAdvisor:用于实现AspectJ语言的DeclareParent注解表示的引介/引入切面

实际上,我们使用AOP往往是Spring内部使用BeanPostProcessor帮我们创建代理。

这些代理的创建器可以分成三类:

  • 基于Bean配置名规则的自动代理创建器:BeanNameAutoProxyCreator
  • 基于Advisor匹配机制的自动代理创建器:它会对容器所有的Advisor进行扫描,实现类为DefaultAdvisorAutoProxyCreator
  • 基于Bean中的AspectJ注解标签的自动代理创建器:AnnotationAwareAspectJAutoProxyCreator

对应的类继承图:


使用引介/引入功能实现为Bean引入新方法

其实前置通知,后置通知,还是环绕通知,这些都很好理解。整个文章就引介/引入切面比较有趣,我们来实现试试吧

有个服务员的接口:

1
2
3
4
5
6
7
8
java复制代码public interface Waiter {

// 向客人打招呼
void greetTo(String clientName);

// 服务
void serveTo(String clientName);
}

对应的服务员接口实现类:

1
2
3
4
5
6
7
8
9
10
11
12
typescript复制代码@Service("Waiter")
public class NaiveWaiter implements Waiter {
@Override
public void greetTo(String clientName) {
System.out.println("NaiveWaiter:greet to " + clientName + "...");
}

@Override
public void serveTo(String clientName) {
System.out.println("NaiveWaiter:serve to " + clientName + "...");
}
}

现在我想做的就是:想这个服务员可以充当售货员的角色,可以卖东西!当然了,我肯定不会加一个卖东西的方法到Waiter接口上啦,因为这个是暂时的~

所以,我搞了一个售货员接口:

1
2
3
4
arduino复制代码public interface Seller {

int sell(String goods, String clientName);
}

售货员接口实现类:

1
2
3
4
5
6
7
typescript复制代码public class SmartSeller implements Seller {
@Override
public int sell(String goods, String clientName) {
System.out.println("SmartSeller: sell "+goods +" to "+clientName+"...");
return 100;
}
}

类图是这样子的:

现在我想干的就是:借助AOP的引入/引介切面,来让我们的服务员也可以卖东西!

我们的引入/引介切面具体是这样干的:

1
2
3
4
5
6
7
less复制代码@Component
@Aspect
public class EnableSellerAspect {
@DeclareParents(value = "com.yiyu.kata.util.aop.NaiveWaiter", // 指定服务员具体的实现
defaultImpl = SmartSeller.class) // 售货员具体的实现
public Seller seller; // 要实现的目标接口
}

写了这个切面类后,大家猜猜有什么效果?

答案是:切面技术将SmartSeller融合到NaiveWaiter中,这样**NaiveWaiter就实现了Seller接口**!!!!

很神奇是不是,我们来测试下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
java复制代码public class TestAop {

@Test
public void testAop(){
ApplicationContext ac = new ClassPathXmlApplicationContext("dispatcher-servlet.xml","applicationContext-datasource.xml","applicationContext.xml");
Waiter waiter = ac.getBean("Waiter",Waiter.class);

// 调用服务员原有的方法
waiter.greetTo("虚竹");
waiter.serveTo("虚竹");

// 通过引介/引入切面已经将waiter服务员实现了Seller接口,所以可以强制转换
Seller seller = (Seller) waiter;
seller.sell("东西", "虚竹");

}
}

结果是: 神奇吧,哈哈哈

具体的调用过程是:


总结

  1. AOP的底层实现是动态代理,动态代理包含JDK动态代理和CGLib代理。当Bean实现接口时,Spring就会用JDK的动态代理;当Bean没有实现接口时,Spring使用CGlib是实现;可以强制使用CGlib。
  2. 如果是单实例,推荐使用CGLib代理,如果是多例的我们最好使用JDK代理。因为CGLib代理对象运行速度要比JDK的代理对象要快
  3. AOP的层面是方法级别的,只能对方法进入拦截
  4. 无论是XML方式还是注解方式,原理都一样,我们用注解AOP就够了,简单好用
  5. 引介/引入切面是比较有趣的,具体实现上面举例说明了,它可以达到用某个接口的方法,又不入侵代码,低耦合的效果。具体妙处还待开发,后面有发现再补充说明

参考资料:

  • 《精通Spring4.x 企业应用开发实战》

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

注解和反射(全方面解析)

发表于 2021-11-26

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战」

前言:注解和反射是一切框架的底层
本文很多是学习狂神视频时做的笔记,感谢狂神,也有加入了自己的理解和思考,希望作为一个整理,也能对大家有所帮助。

注解(java.Annotation)

什么是注解

1.不是程序本身,可以对程序作出解释

2.可以被其他程序(如编译器等)读取

内置注解

@Override 重写

@Deprecated 指这段代码过时了

@SuppressWarning(“all”) 抑制编译时候的警告信息,需要一个参数

元注解

image.png
@Target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
less复制代码class learn extends Object{
@Override
public String toString() {
return "learn{}";
}
@MyAnnotation()
public void say(){

}
}

@Target(value = {ElementType.METHOD})
@interface MyAnnotation{

}

例如lombol的源码中标记了运行时才有效果。

Retention 美[rɪˈtenʃn] n.保持 维持;

Retention默认RUNTIME

image.png

自定义注解

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
less复制代码class Person{
@MyAnno(name="lcg",id = 1,schools = {"bjtu","cn"})
public void say(){
}
}

@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno{
String name() default "xixi";
int id() default -1;
String[] schools();
}

反射机制(java.Reflection)

Java是静态语言,因为有了反射机制变成了动态语言

反射:即在运行状态中,对于任意一个类,都能够知道这个类的所以属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性。这种动态获取信息及动态调用对象方法的功能叫Java的反射机制。

静态语言和动态语言

  1. 动态语言

是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。

主要动态语言:Object-C、JavaScript、PHP、Python、Erlang。

  1. 静态语言

与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++、C#。

PS:C#不是动态语言,但是MS有将.NET支持动态语言的趋势,3.0吸收了一定动态特征,比如 匿名函数,临时类型,临时变量等

动态语言示例:

1
2
3
4
5
6
7
8
9
10
ini复制代码function Person(name){
this.name=name;
}
Person.prototype.getName=function(){
return this.name;
}
var person=new Person("okok");
alert(person.getName());
person.getName=function(){return "nono"};
alert(person.getName());

Java反射机制概述

image.png

image.png

image.png

image.png

image.png

Demo

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
typescript复制代码package com.company;

public class learn1 {
public static void main(String[] args) throws ClassNotFoundException {
Class c1=Class.forName("com.company.Person");
System.out.println(c1);
Class c2=Class.forName("com.company.Person");
/**
* 一个类只有一个class对象
* 一个类被加载后,类的整个结构多会被封装在class对象中
*/
System.out.println("c1.hashCode"+c1.hashCode());
System.out.println("c2.hashCode"+c2.hashCode());
System.out.println("c1==c2: "+(c2.hashCode()==c1.hashCode()));//true
}
}
class User{
String username;
String password;
int number;

public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}

public void setNumber(int number) {
this.number = number;
}

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

public int getNumber() {
return number;
}

public User() {
}

public User(String username, String password, int number) {
this.username = username;
this.password = password;
this.number = number;
}
}

输出

1
2
3
4
5
6
vbnet复制代码class com.company.Person
c1.hashCode692404036
c2.hashCode692404036
c1==c2: true

Process finished with exit code 0

理解Class类并获取Class实例

image.png

image.png

image.png

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
csharp复制代码package com.company;

public class learn1 {
public static void main(String[] args) throws ClassNotFoundException {
Class c1=Class.forName("com.company.Person1");
System.out.println(c1);
Class c2=Class.forName("com.company.Person1");
/**
* 一个类只有一个class对象
* 一个类被加载后,类的整个结构多会被封装在class对象中
*/
System.out.println("c1.hashCode"+c1.hashCode());
System.out.println("c2.hashCode"+c2.hashCode());
System.out.println("c1==c2: "+(c2.hashCode()==c1.hashCode()));//true
User user=new User();
//方式一,通过对象获得
Class aClass1=user.getClass();
System.out.println(aClass1.hashCode());
//方式二,通过forname获得
Class aClass2=Class.forName("com.company.User");
System.out.println(aClass2.hashCode());
//方式三,通过类型.class
Class<User> aClass3 = User.class;
System.out.println(aClass3.hashCode());
//方法四:基本内置类型的包装类都有一个Type的属性
Class<Integer> integerClass = Integer.TYPE;
System.out.println(integerClass);
//方式五,获取父类类型
Class<AdminUser> adminUserClass = AdminUser.class;
Class aClass = adminUserClass.getSuperclass();
System.out.println(adminUserClass+" "+adminUserClass.hashCode());
System.out.println(aClass+" "+aClass.hashCode());
}
}
class AdminUser extends User{
@Override
public void login() {
System.out.println("Admin login...");

}
}

class User{
String username;
String password;
int number;
public void login(){
System.out.println("User login...");
}

public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}

public void setNumber(int number) {
this.number = number;
}

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

public int getNumber() {
return number;
}

public User() {
}

public User(String username, String password, int number) {
this.username = username;
this.password = password;
this.number = number;
}
}

image.png

image.png

image.png

image.png

image.png

类的加载原理与ClassLoader

image.png

image.png

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
csharp复制代码public class learn3 {
public static void main(String[] args){
A a=new A();
System.out.println(A.m);
}
}
class A{
static {
System.out.println("A类静态代码块初始化");
m=300;
}
static int m=100;//如果m在静态代码块前面的话,m就是300
public A(){
System.out.println("A类的无参构造初始化");
}
}

output:
A类静态代码块初始化
A类的无参构造初始化
100
Process finished with exit code 0

image.png

  1. 加载到内存,会产生一个类对应Class对象
  2. 链接,链接结束后m=0
  3. 初始化
1
2
3
4
5
ini复制代码<clinit>(){
System.out.println("A类静态代码块初始化");
m=300;
m=100;
}

分析类初始化

image.png
子类没有被加载

image.png

image.png

image.png

image.png

因为在链接阶段就已经设置类变量默认初始值了

常量在链接阶段就存入调用类的常量池中了

类加载器的作用(了解)

image.png

image.png

image.png

获取运行时类的结构

image.png

image.png

image.png

image.png

image.png

image.png

有了Class对象,能做什么

image.png

动态创建对象
  1. 直接用字节码文件获取对应实例
1
2
ini复制代码// 调用无参构造器 ,若是没有,则会报异常
Object o = clazz.newInstance();
  1. 有带参数的构造函数的类,先获取到其构造对象,再通过该构造方法类获取实例:
1
2
3
4
5
6
arduino复制代码//获取构造函数类的对象
Constroctor constroctor = clazz.getConstructor(String.class,Integer.class);

![image.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/275ddf5d2a8f40968a0c56805934c39e~tplv-k3u1fbpfcp-watermark.image?)
// 使用构造器对象的newInstance方法初始化对象
Object obj = constroctor.newInstance("龙哥", 29);

image.png

image.png

image.png

调用指定方法

image.png

image.png

image.png
Demo

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
csharp复制代码public class Calculator{ 

public double add(double score1,double score2){
return score1 + score2;
}

public void print(){
System.out.println("OK");
}

public static double mul(double score1,double score2){
return score1 * score2;
}
}


public class CalculatorTest {

public static void main(String[] args) throws Exception {
//通过类的.class属性获取
Class<Calculator> clz = Calculator.class;
//或者通过类的完整路径获取,这个方法由于不能确定传入的路径是否正确,这个方法会抛ClassNotFoundException
// Class<Calculator> clz = Class.forName("test.Calculator");
//或者new一个实例,然后通过实例的getClass()方法获取
// Calculator s = new Calculator();
// Class<Calculator> clz = s.getClass();
//1. 获取类中带有方法签名的mul方法,getMethod第一个参数为方法名,第二个参数为mul的参数类型数组
Method method = clz.getMethod("mul", new Class[]{double.class,double.class});
//invoke 方法的第一个参数是被调用的对象,这里是静态方法故为null,第二个参数为给将被调用的方法传入的参数
Object result = method.invoke(null, new Object[]{2.0,2.5});
//如果方法mul是私有的private方法,按照上面的方法去调用则会产生异常NoSuchMethodException,这时必须改变其访问属性
//method.setAccessible(true);//私有的方法通过发射可以修改其访问权限
System.out.println(result);//结果为5.0
//2. 获取类中的非静态方法
Method method_2 = clz.getMethod("add", new Class[]{double.class,double.class});
//这是实例方法必须在一个对象上执行
Object result_2 = method_2.invoke(new Calculator(), new Object[]{2.0,2.5});
System.out.println(result_2);//4.5
//3. 获取没有方法签名的方法print
Method method_3 = clz.getMethod("print", new Class[]{});
Object result_3 = method_3.invoke(new Calculator(), null);//result_3为null,该方法不返回结果
}
}
获取属性值
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
csharp复制代码package com.company.myp1;

import java.lang.reflect.Field;

public class learn1 {
public static void main(String[] args) throws IllegalAccessException {
Dog d1=new Dog("xixi",11);
Dog d2=new Dog("hehe",22);
//获取某属性
System.out.println(ClassUtil.getField(d1.getClass(),"name"));
//获取某属性值
Object o1=ClassUtil.getPropertyValue(d1,"name");
System.out.println(o1);
//获取全部属性值
Field[] declaredFields = d1.getClass().getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
System.out.println(declaredFields[i].getName()+" "+declaredFields[i].get(d2));
}
}

}
class Dog{
String name;
int age;

public Dog() {
}

public Dog(String name, int age) {
this.name = name;
this.age = age;
}
}
class ClassUtil {

public static Object getPropertyValue(Object obj, String propertyName) throws IllegalAccessException {
Class<?> Clazz = obj.getClass();
Field field;
if ((field = getField(Clazz, propertyName)) == null)
return null;
field.setAccessible(true);
return field.get(obj);
}

public static Field getField(Class<?> clazz, String propertyName) {
if (clazz == null)
return null;
try {
return clazz.getDeclaredField(propertyName);//getDeclaredFiled 仅能获取类本身的属性成员(包括私有、共有、保护)
} catch (NoSuchFieldException e) {
return getField(clazz.getSuperclass(), propertyName);//getField 仅能获取类(及其父类可以自己测试) public属性成员
}
}
}

性能对比分析

image.png

image.png

image.png

image.png

反射操作范型

image.png

image.png

image.png

image.png

反射操作注解

image.png

练习Demo,生成sql语句

image.png

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
java复制代码package com.company.com.learn1;

import java.lang.annotation.*;
import java.lang.reflect.Field;

public class Demo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class c1 = Class.forName("com.company.com.learn1.Student"); //根据包名获取Class
Annotation[] annotations=c1.getAnnotations();//方法返回此元素上存在的所有注解
for (int i = 0; i < annotations.length; i++) {
System.out.println(annotations[i]);//只获得了注解,想要注解里面的值
}
//获得注解value的值
System.out.println("===============");
TableName tableName = (TableName) c1.getAnnotation(TableName.class);
System.out.println(tableName.value());

//获得类指定的注解
System.out.println("===============");
Field f=c1.getDeclaredField("name");
FieldName fieldName=f.getAnnotation(FieldName.class);
System.out.println(fieldName.type());
System.out.println(fieldName.lengtn());
System.out.println(fieldName.columnName());
//获得所有字段的列名,类型和长度
System.out.println("===============");
Field[] fieldNames = c1.getDeclaredFields();
for (int i = 0; i < fieldNames.length; i++) {
FieldName annotation = fieldNames[i].getAnnotation(FieldName.class);
System.out.println(annotation.columnName()+" "+annotation.type()+" "+annotation.lengtn());
}
//获得所有字段的属性名,属性值和注解
System.out.println("===============");
Student s=new Student(1001,18,"小明");
//s.getClass().getAnnotation();
TableName annotation = s.getClass().getAnnotation(TableName.class);
String mytableName=annotation.value();
Field[] declaredFields = s.getClass().getDeclaredFields();
StringBuffer value=new StringBuffer();
StringBuffer column=new StringBuffer();
for (int i = 0; i < declaredFields.length; i++) {
declaredFields[i].setAccessible(true);
System.out.println("属性名:" + declaredFields[i].getName() + " 属性值:" + declaredFields[i].get(s)+" 注解columnname "+ declaredFields[i].getAnnotation(FieldName.class).columnName());
value.append(declaredFields[i].get(s));
column.append(declaredFields[i].getAnnotation(FieldName.class).columnName());
if(i!=declaredFields.length-1){
value.append(" , ");
column.append(" , ");
}
}
//模仿自己生成sql
System.out.println("===============");
System.out.println("利用反射和注解生成sql语句:");
String sql="INSERT INTO "+mytableName+" ( "+column+" ) VALUES "+" ( "+value+" )";
System.out.println(sql);

}
}

@TableName("db_student")
class Student{
@FieldName(columnName = "db_id",type = "int",lengtn = 10)
private int id;
@FieldName(columnName = "db_age",type = "int",lengtn = 10)
private int age;
@FieldName(columnName = "db_name",type = "varchar",lengtn = 30)
private String name;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Student(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}

public Student() {
}
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableName{
String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldName{
String columnName();
String type();
int lengtn();
}

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ini复制代码@com.company.com.learn1.TableName(value=db_student)
===============
db_student
===============
varchar
30
db_name
===============
db_id int 10
db_age int 10
db_name varchar 30
===============
属性名:id 属性值:1001 注解columnname db_id
属性名:age 属性值:18 注解columnname db_age
属性名:name 属性值:小明 注解columnname db_name
===============
利用反射和注解生成sql语句:
INSERT INTO db_student ( db_id , db_age , db_name ) VALUES ( 1001 , 18 , 小明 )

Process finished with exit code 0

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java并发编程之LockSupport类

发表于 2021-11-26

这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战

JDK中的rt.jar包中的LockSupport是个工具类,它的主要作用是挂起和唤醒线程,该工具类是创建锁和其他同步类的基础。

LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持有许可证的。LockSupport是使用Unsafe类实现,下面介绍LockSupport中的几个函数。

1.void park()方法

如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport关联的许可证,则调用LockSupport.park()时,会立即返回,否则调用线程会被禁止参与线程的调度,也就是被阻塞挂起。

1
2
3
4
5
java复制代码public static void main(String[] args) {
System.out.println("begin park");
LockSupport.park();
System.out.println("end park");
}

在其他线程调用unpark()方法并且将当前线程作为参数时,调用park方法而被阻塞的线程会返回。另外如果其他线程调用了阻塞线程的interrupt()方法,设置了中断标志或者线程被虚假唤醒,则阻塞线程也会返回。所以在调用park方法时,最好使用循环条件判断方式。因调用park()方法而被阻塞的线程被其他线程中断而返回时并不会抛出InterruptedException异常。

  1. void unpark(Thread thread)方法
    当一个线程调用unpark时,如果参数thread线程没有持有thread与LockSupport类关联的许可证,则让thread线程持有。如果thread之前因调用park()而被挂起,则调用unpark后,则该线程会被唤醒。如果thread之前没有调用park,则调用unpark方法后,再调用park方法,其会立即返回。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
java复制代码public static void main(String[] args) throws InterruptedException{
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("child thread begin park!");
LockSupport.park();
System.out.println("child thread unpark!");
}
});

Thread.sleep(1000);
System.out.println("main thread begin unpark!");
LockSupport.unpark(thread);
}

park方法返回时不会告诉你因何种原因返回,所以调用者需要根据之前调用park方法的原因,再次检查条件是否满足,如果不满足还需要再次调用park方法。
例如,根据调用park方法后的线程被中断后返回,我们修改上面的例子代码,删除LockSupport.Unpark(thread);然后添加thread.interrupt()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码public static void main(String[] args) throws InterruptedException{
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("child thread begin park!");
while (!Thread.currentThread().isInterrupted()) {
LockSupport.park();
}
System.out.println("child thread unpark!");
}
});

Thread.sleep(1000);
System.out.println("main thread begin unpark!");
thread.interrupt();
}

在如上代码中,只用中断子线程,子线程才会运行结束,如果子线程不被中断,即使调用unpark方法,子线程也不用结束。

  1. void parkNanos(long nanos)方法

与park方法类似,只不过是当调用park被挂起namos时间后,会自动返回。

  1. park (Object blocker)
1
2
3
4
5
6
scss复制代码public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}

park方法还支持带有blocker参数的方法,当线程在没有持有许可证的情况下调用park方法而被阻塞挂起时候,这个blocker对象会被记录到该线程内部。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…171172173…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%