NanYin的博客

记录生活点滴


  • Home

  • About

  • Tags

  • Categories

  • Archives

  • Search

ThreadLocal

Posted on 2020-10-01 | In 多线程
Words count in article: 572 | Reading time ≈ 2

ThreadLocal

ThreadLocal 被称为线程本地变量,每个线程都单独有一份 ThreadLocal 。各线程之间的 ThreadLocal 是不共享的。在[多线程与高并发](https://nanyiniu.github.io/2020/06/10/ 多线程与高并发 /) 中,谈到进程时,各个进程之间是相互独立的,拥有独立的运行空间、内存。而 ThreadLocal 完成的工作就是在线程层级,做了独立的空间。

ThreadLocal 结构

ThreadLocal 本身不具备什么数据结构,他的实现是依附于 ThreadLocalMap 。

每次 ThreadLocal 执行 set 方法后,实际上是使用 ThreadLocalMap 的 set 方法,将 ThreadLocal 作为 key,value 作为 value 放入到 ThreadLocalMap 中。

而实际上的 ThreadLocalMap 是由 Entry 来实现,而 Entry 中的 key 是 WeakReference 包裹的 ThreadLocal。

过程如下图:

图 1

结构引用:其中虚线表示弱引用

图 2

为什么要使用弱引用?

原因在于防止内存溢出,那为什么弱引用就能够够避免呢?再次之前需要先了解什么是弱引用 ->Java 的四种引用类型。

只要进行垃圾回收,那么弱引用就会被回收掉。那么,可以看图 2 中的结构。如果需要 ThreadLocal 引用被断开,也就是 TL ref 被设为 null,因为 ThreadLocal 与 Entry 中的 key 是弱引用关系,所以,只要这边的强引用(tl ref)断开,则下一次垃圾回收,就一定能把 ThreadLocal 回收掉。

如果不理解,那么举个反例,如果 ThreadLocal 与 Entry 中的 key 是强引用关系,此时 tl ref 和这个 threadlocal 断开。此时,还需要将 threadLocalMap 这边的引用断开,才能够将 threadlocal 进行回收,而 threadLocalMap 是线程 Thread 中的一部分,声明周期和 Thread 相同。所以,造成的结果就是,只要线程运行着,threadLocal 就不会被回收。

ThreadLocal 的内存溢出

除去上面分析的如果使用强引用会导致内存溢出外,还有一种情况会导致 ThreadLocal 的内存溢出

如果 tl ref 和 threadLocal 之间的强引用断开后,此时的 threadlocal 变为 null 存在于 Entry 中,也就是说内存中保存着 key 为 null 的 entry,同样无法回收,导致内存溢出。

解决方式是,用完 threadLocal 后,使用 remove 方法进行手动释放。

Java的四种引用类型

Posted on 2020-09-28 | In Java
Words count in article: 349 | Reading time ≈ 1

Java 的四种引用类型

强引用

任何时候声明一个对象使用=关联的引用,都是强引用,只要该强引用存在,就不会被垃圾回收器回收掉。当内存空间不足时,也不会随意清除任何一个强引用的对象。

软引用

软引用使用 SoftReference 进行声明,和强引用不同的是,如果堆内存空间不足时,会优先回收软引用。可以用于缓存数据。

1
2
SoftReference<String[]> softBean = 
new SoftReference<String[]>(new String[]{"a", "b", "c"});

弱引用

弱引用使用 WeakReference 进行声明,和软引用的区别是,只要发生垃圾回收,弱引用就一定会被回收掉。

1
2
WeakReference <String[]> weakBean = 
new WeakReference<String[]>(new String[]{"a", "b", "c"});

虚引用

虚引用使用 PhantomReference 进行声明,虚引用和其他引用不同,需要和一个ReferenceQueue一同绑定使用。如果再回收前,发现是虚引用,会现将虚引用放到ReferenceQueue中,执行完相关 hook 方法后,进行真正的回收。其目的是监测、跟踪垃圾回收器的行为。用于回收堆外内存。

  1. 当发现是虚引用,放到ReferenceQueue中。
  2. 另外一个线程,发现ReferenceQueue不为空,执行相关方法,清空堆外内存。
  3. 清空堆外内存后,对虚引用进行真正的回收。

Markdown语法入门

Posted on 2020-09-03 | In markdown
Words count in article: 865 | Reading time ≈ 3

Markdown语法总结

概述

其实markdown是一种标记型的语法,简洁好用,通过工具能够进行预览,不用担心格式问题,并且由很多工具支持markdown的预览。

正文

一个 Markdown 段落是由一个或多个连续的文本行组成,它的前后要有一个以上的空行(空行的定义是显示上看起来像是空的,便会被视为空行。比方说,若某一行只包含空格和制表符,则该行也会被视为空行)。普通段落不该用空格或制表符来缩进。

在编辑的过程当中每编辑一块的内容,就要空一行,否则可能出现渲染失败,出现格式乱掉的情况。

标题

markdown支持两种标题的语法,类 Setext 和类 atx 形式。

类 Setext 形式是用底线的形式,利用 = (最高阶标题)和 - (第二阶标题),例如:

This is an H1

=============

This is an H2

-————

效果

This is an H1

This is an H2

也能使用atx 使用 # 来进行1-6阶的划分 如:

# This is an H1

## This is an H2

效果

This is an H1

This is an H2

区块引用

1
2
> 区块应用 使用 > 符号进行标识 。
能每行都使用 > 进行标识,也能只在第一行标识,两种效果相同。

效果:

区块应用 使用 > 符号进行标识 。
能每行都使用 > 进行标识,也能只在第一行标识,两种效果相同。

列表

  1. 无序列表 可以使用*号或者+号或者-号都是一样的
  2. 有序列表 可以使用数字加英文.加空格使用有序列表

分割线

三个以上的*就是分割线,和三个---的效果相同。一般使用---


链接

Markdown 支持两种形式的链接语法: 行内式和参考式两种形式。

不管是哪一种,链接文字都是用 [方括号] 来标记。

要建立一个行内式的链接,只要在方块括号后面紧接着圆括号并插入网址链接即可,如果你还想要加上链接的 title 文字,只要在网址后面,用双引号把 title 文字包起来即可,例如:

This is [an example](http://example.com/ “Title”) inline link.

[This link](http://example.net/) has no title attribute.

效果

This is an example inline link.

This link has no title attribute.

下面是一个参考式链接的范例:

I get 10 times more traffic from [Google] [1] than from
[Yahoo] [2] or [MSN] [3].

[1]: http://google.com/ “Google”

[2]: http://search.yahoo.com/ “Yahoo Search”

[3]: http://search.msn.com/ “MSN Search”

I get 10 times more traffic from Google than from
Yahoo or MSN.

强调

使用两个*或者_表示强调 使用一个或者一个_表示斜体

如: helloworld helloworld

代码

使用`符号来使用代码块
行内代码使用`包起来 如 print()

可以使用三个`+语言名称 来标注是哪种语言,能够使用特定的语法高亮(前提是编辑器支持)

图片

同样的图片也有行内式和参考式
向链接一样只不过在前面多个 !

其他

大部分markdown编辑工具支持html语法,如常用的 <mark></mark> 使用起来效果就是这样.

同样加粗等语法可以使用对应的标签<b></b>

工具推荐

  • windows、mac、linux上推荐 typero,可以实现编辑时预览,所见即所得。
  • 其中mac还可以使用macdown工具,简洁好用。

IDEA设置代码注释模板和JavaDoc文档生成

Posted on 2020-09-01 | In 编程规约
Words count in article: 538 | Reading time ≈ 2

为了解决接口复用的问题,建议使用规范,从而能够让Java自动生成相关文档,有助于以后代码的二次开发。

主要分为两部分

  • 第一部分是如何利用ide帮助我们为方法、为类自动生成一套标准模板;
  • 第二部分是如何使用ide生成JavaDoc

设置注释模板

在设置注释模板之前,需要先自行了解有关于JavaDoc的标准的相关参数:JavaDoc

1. 设置类注释

进入到设置中,找到 file and code template 中的 include 中的 file header,点击在右侧添加内容:

1
2
3
4
5
6
7
/**
* 类<code>Doc</code>用于:TODO
*
* @author ${USER}
* @version 1.0
* @date ${YEAR}-${MONTH}-${DAY}
*/

设置方法注释

进入到设置中,找到 live template,点击 add 首先添加一个自定义组, 然后在左侧选中组后,再添加一个模板:

下面分为四步操作:

  1. 填写触发的前缀,这里使用 ** 作为前缀
  2. 选择触发方式,这里使用Enter,也就是回车键进行触发,当输入**后,再敲回车,则会自动生成③中的内容。
  3. 在此处填写模板内容
  4. 需要勾选为那些内容生成,这里需要勾选整个Java

在③中制作完模板内容后,需要点击 edit variables 设置其中参数的值;

模板参考

方法模板参考
1
2
3
4
5
6
7
8
**
* 方法<code>$methodName$</code>作用为:
*
* @author $author$
* $params$
* @throws $throws$
* @return $return$
*/

点击edit variables参考内容:

其中的params变量,如果要实现多参数分开展示,需要使用groovy脚本实现,脚本内容参考:

1
groovyScript("     def result='';     def params=\"${_1}\".replaceAll('[\\\\[|\\\\]|\\\\s]', '').split(',').toList();     for(i = 0; i < params.size(); i++) {         if(i!=0)result+= ' * ';         result+='@param ' + params[i] + ((i < (params.size() - 1)) ? '\\n' + '\\t' : '');     };     return result", methodParameters())
类模板参考
1
2
3
4
5
6
7
**
* 类<code>$className$</code>用于:TODO
*
* @author $USER$
* @version 1.0
* @date $date$
*/

点击edit variables参考内容:

生成JavaDoc

在Idea中的上方栏目中,找到 tools,打开菜单中的 Generate JavaDoc.. ,在弹出菜单中进行如下设置:

然后选择位置,直接导出即可。

基于Java的WebService实践

Posted on 2020-08-01 | In WebService
Words count in article: 1.4k | Reading time ≈ 6

背景

在很多Java项目中,仍然会使用基于WebService的数据传输接口,而webservice实际上就是http+xml。而在实践过程中,主要需要注意接收和发送的格式。

实践

被调用接口

如果处于被调用方,则需要声明出一个WebService服务,也就是生成、发布一个WSDL文件。

什么是WSDL?

WSDL – WebService Description Language – Web服务描述语言。

  • 通过XML形式说明服务在什么地方-地址。
  • 通过XML形式说明服务提供什么样的方法 – 如何调用。

将接口声明为一个ws服务

在Java中可以依靠javax.jws包中的相关注解来实现一个WebService接口

1
2
3
4
5
6
7
8
9
@WebService
@SOAPBinding(style = SOAPBinding.Style.RPC)
public interface testWebservice {

@WebMethod
public ResponseRoot test(@WebParam(name = "item") Item item,
@WebParam(name = "MessageHeader") MessageHeaderRoot header) ;

}
  1. 使用 @WebService 注解标注该接口为一个WebService接口,可以修改服务名、端口名、命名空间等信息,如
    @WebService(targetNamespace = "http://ujn.cn/",serviceName = "UserService", portName = "UserPort")
  2. 使用 @SOAPBinding 注解标注绑定的形式,根据绑定的不同的形式,客户端使用不同的调用约定进行调用。
    1. Document Wrapped(默认使用方式)
    2. Document Bare
    3. RPC
  3. 使用 @WebMethod 方法标注为一个WebService中的一个方法服务
  4. 最后实现接口中的方法即可,如果需要返回信息,直接封装为一个对象即可,如上面代码中的ResponseRoot对象。最后会解析出一个对象。

添加XML对象

在上面的代码中可以看到不管是接受还是返回,都是面向对象的,然而最后都需要形成为一个XML文档,那么,是如何将这些对象解析为有一整个XML对象的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@XmlRootElement(name = "MessageHeader")
public class MessageHeaderRoot implements Serializable {

private static final long serialVersionUID = 1848365930921924635L;

@XmlElement(name = "GUID")
private String guid;
@XmlElement(name = "SEND_DATE")
private String sendDate;
@XmlTransient
public String getGuid() {
return guid;
}

public void setGuid(String guid) {
this.guid = guid;
}

@XmlTransient
public String getSendDate() {
return sendDate;
}

public void setSendDate(String sendDate) {
this.sendDate = sendDate;
}
}
  1. 使用 @XmlRootElement 注解标注在对象上,说明此对象是一个可解析为XML节点的对象。
  2. 复杂对象必须继承 Serializable 接口,进行序列化。
  3. 使用 @XmlElement(name = "GUID") 注解,声明出其中的子节点和子节点的名称。
  4. 在get方法上添加 @XmlTransient 注解,否则会报对象节点重复的错误。
  5. 对对象通过以上的配置,可以完整的XML类型的数据映射到对象上,从而实现进一步的处理。

形成可接受的XML格式

1
2
3
4
5
6
7
8
9
10
11
12
13
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://webService.test.com/">
<soapenv:Header/>
<soapenv:Body>
<web:addPersonAndUnit>
<item>
<!--内部对象体:-->
</item>
<MessageHeader>
<!--内部对象体:-->
</MessageHeader>
</web:addPersonAndUnit>
</soapenv:Body>
</soapenv:Envelope>

可以使用SOAP-UI 这个工具进行生成,使用免费版,添加SOAP服务,后点击方法的request则会自动生成格式。

接口调用

在调用其他服务的接口时,对方法等没有特殊的要求,只需要遵守对方特定的接口格式即可。在接口调用时,同样有多种调用的方式,在此处我使用xml格式拼接,http服务直接调用的方式。

XML格式拼接

1
2
3
4
5
6
7
ResQuestionBean resQuestionBean = new ResQuestionBean();
resQuestionBean.setQuestionCode(questionCode);
resQuestionBean.setQuestionStatus(statusName);
resQuestionBean.setUploader(personCode);
resQuestionBean.setUploaderTime(DateUtil.format(new Date()));
StringBuilder resultXml = new StringBuilder();
resultXml.append(XmlUtils.objectToXML(resQuestionBean));
  1. 声明出一个返回对象 resQuestionBean ,对对象中的数据进行装载。
  2. 使用objectToXML方法将对象转换为XML字符串。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static String objectToXML(Object object) throws JAXBException {
JAXBContext context = JAXBContext.newInstance(object.getClass());
Marshaller m = context.createMarshaller();
// 设置格式化输出
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.setProperty(Marshaller.JAXB_FRAGMENT, true);
// 不进行转义字符的处理
m.setProperty(CharacterEscapeHandler.class.getName(), new CharacterEscapeHandler() {
@Override
public void escape(char[] ch, int start,int length, boolean isAttVal, Writer writer) throws IOException {
writer.write(ch, start, length);
}
});
Writer w = new StringWriter();
m.marshal(object, w);
return w.toString();
}

通过HTTP调用方法,推送数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private synchronized String doSend(String address, String invokeMethod, 
String guid, String recvSys,
String invokeDate, String dataXml) throws IOException {
//enum 选择系统,地址url http post方式
StringBuffer strBuf = null;
URL url = new URL(address);
HttpURLConnection huc = (HttpURLConnection) url.openConnection();
huc.setRequestMethod("POST");
//设置数据格式
huc.setRequestProperty("content-type", "text/xml;charset=utf-8");
huc.setConnectTimeout(10000);
huc.setUseCaches(false);
huc.setReadTimeout(10000);
huc.setDoInput(true);
huc.setDoOutput(true);
huc.setInstanceFollowRedirects(false);
DataOutputStream dos = new DataOutputStream(huc.getOutputStream());
dos.write(buildSendXml(invokeMethod, guid, recvSys, invokeDate, dataXml).getBytes("utf-8"));

dos.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(huc.getInputStream(), "utf-8"));
String line = null;
strBuf = new StringBuffer();
while ((line = reader.readLine()) != null) {
strBuf.append(line);
}
dos.close();
reader.close();
return strBuf.toString();
}
  1. 设置地址、调用方式等http的基本信息后,使用write方法将数据写入到请求中。
  2. 使用reader读取返回数据,并返回、解析,形成最终数据后返回给前台做最终的处理

最后XML整体拼接

在 buildSendXml 方法中,将第一步格式化为XML的Object进行拼接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private String buildSendXml(String invokeMethod, String guid, 
String invokeDate,
String data, String itemTag, String methodTag) {
StringBuffer sb = new StringBuffer();
sb.append("<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:urn=\"urn:sap-com:document:sap:rfc:functions\">" +
"<soapenv:Header/>" +
"<soapenv:Body>" +
"<" + methodTag + ":" + invokeMethod + ">" +
" <MessageHeader>" +
" <GUID>" + guid + "</GUID>" +
" <SEND_DATE>" + invokeDate + "</SEND_DATE>" +
" </MessageHeader>" +
" <" + itemTag + ">");
sb.append(data);
sb.append(" <" + itemTag + ">" +
"</urn:" + invokeMethod + "> " +
" </soapenv:Body>" +
"</soapenv:Envelope>");
return sb.toString();
}
  1. 接口无法联通后的再次重试问题
  2. 此篇文章写于WebService接口联合调试前,可能存在问题与风险未被发现。如果有请与我联系并进行规避。
  3. 如果有更好、更便捷的接口编写方案,欢迎一起讨论。

多线程实践

Posted on 2020-06-10 | In 多线程
Words count in article: 4.7k | Reading time ≈ 20

多线程

线程和进程的概念

什么是进程?

进程是程序的一次执行过程,是系统运行程序的基本单位。系统运行一个程序即是一个创建进程的过程。

进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。

什么是线程?

线程是比进程更小的执行单位,线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。

线程和进程的区别

  1. 一个程序执行时会至少会启动一个进程,但一个进程内会启动一个或多个线程
  2. 各个进程之间是相互独立的,拥有独立的运行空间、内存,但是线程不能够独立运行,多个线程依赖、共享进程中的资源。
  3. 线程相比于进程是更小的执行单位,但没有独立资源,所以在运行时线v程切换的开销小,但不利于资源的管理和保护;而进程正相反。

线程的生命周期

线程生命周期

线程生命周期

上图展示了线程从创建到结束的整个生命周期,下面从状态和控制两个方面分解图中的内容

线程状态

线程具有5中基本的状态:

  1. NEW(新建状态): 创建线程,还未启动

  2. RUNNABLE(就绪状态): 可运行状态,但还未获得时间片,等待执行

  3. RUNNING(执行状态): 运行状态,在就绪状态获得了时间片,进入运行状态

  4. BLOCKED(阻塞状态):当某些情况下,线程被阻止运行,进入阻塞状态,阻塞的状态可分为三种:

    • 第一种为执行了wait()后会进入放入线程等待队列中,这种情况叫等待阻塞.
      • 第二种为等待获取synchronized()同步锁时,会讲线程放入同步锁队列中,等待前一个线程执行完synchronized中的内容,这种情况叫同步阻塞.
      • 第三种为执行了sleep()或join()时,和wait()不同,它不会释放对象锁.
  5. TERMINATED(终止状态):当线程正常结束或异常退出时,会到达终止状态

线程方法

  • run/start 需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run() 方法,这是由Java的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。
  • wait 当前线程暂停执行并释放对象锁标志,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中
  • nodify/nodifyAll 唤醒等待(wait)的线程
  • sleep 休眠一段时间后,会自动唤醒。但它并不释放对象锁。也就是如果有 synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常
  • join 当前线程停下来等待,直至另一个调用join方法的线程终止,线程在被激活后不一定马上就运行,而是进入到可运行线程的队列中
  • yield 停止当前线程,让同等优先权的线程运行。如果没有同等优先权的线程,那么yield()方法将不会起作用

线程同步

synchronized关键字

前面讲过Synchronized关键字总结以及使用synchronized实现单例中的 double-check 实例。

  • 锁定方式,使用sync锁定的是对象,而锁定对象带来的是其他线程无法获取对象锁,而无法执行synchronized下面的代码。而不是锁定代码!!
    1. 使用synchronized修饰静态方法,锁定的是 .class 对象
    2. 使用synchronized修饰普通方法,锁定的是 this 实例对象
    3. 使用synchronized(obj),锁定特定obj对象
  • 使用sync的方法和非sync方法的调用时互不影响
  • 因为synchronized是可重入的,所以两个sync方法可相互调用,不会产生死锁。
  • 在sync中的代码出现异常时,锁会自动被释放,其他线程可拿到对象锁。
  • synchronized锁升级,在早起的java中,使用sync相当于向操作系统申请锁,这个锁是重量级锁,而在1.5之后,出现了锁升级的概念:
    1. 偏向锁,当使用sync时,会将对象中的头部信息中的markword中记录当前锁定线程的线程id。
    2. 自旋锁,如果多线程争用锁,则升级为自旋锁,占用CPU,通过线程自旋等待获取对象锁。
    3. 重量级锁,当自旋次数超过一定次数(10次),升级为重量级锁。

volatile

vaolaile关键字具有两个特性:

  • 保证线程可见性

多个线程读取写入对象时在非同步进行的时候是如图中的步骤一样

  1. 从内存中读取对象o,读取到线程2本地
  2. 然后在线程2中修改了o的值,再写回内存。
  3. 线程1从内存读取对象,得到最新的对象o。

Untitled

上面的步骤是正常,sync的情况下的情况下。然而在很多情况下,线程之间是同步进行的,在第一步线程2读取到内容,线程1也读取到了内容,线程2再写回内容,此时如果线程1再次修改和写回是,内容就出现了覆盖。

这种方式就是将对象使用volatile修饰。此时,保证了对象在所有线程间的可见性。

在这种情况下,最直接的方式就是在这个对象o被修改、写入到内存时,其他线程立刻能知道这个对象已经被修改了,进行再次获取最新的内容来保证最终的一致。

  • 禁止指令重排序

一个new语句可以创建一个对象,而创建这个对象虚拟机会分为多条指令让CPU进行处理,而处理的过程会发生指令的重新排序。

volatile关键字对于基本类型的修改可以在随后对多个线程的读保持一致,但是对于引用类型如数组,实体bean,仅仅保证引用的可见性,但并不保证引用内容的可见性。

CAS(Compare and Swap)

因为类似count++这种操作时非原子性的,所以每次在需要同步使用时必须要在count++处添加synchronized,保证此处只能有一个线程操作。

此时出现了Atomicxxx类,该类中的所有操作都是原子性的,从此区别于使用sync包裹count++,而是使用CAS乐观锁的方式。
在Atomic类中,实现”原子“这一性质的根源为UnSafe类中的CompareAndSwap相关方法,这些方法就是Java实现的CAS。

  • CAS分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
AtomicInteger integer = new AtomicInteger(2);
integer.incrementAndGet();
}
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

在incrementAndGet中使用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。

整个compareAndSwapInt是一个原子的操作,在含义上可以理解为:

1
2
3
4
5
6
7
8
compareAndSwapInt(this, valueOffset, expect, update){
if (this == expect) {
this = update
return true;
} else {
return false;
}
}
  • ABA问题

因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。

对于一个基本数据类型如Integer等,不会出现ABA问题,只要保证最终的一致就可以了。

Untitled 1

但是对于一个普通对象,ABA问题的影响就体现出来了。如右图中。在第2步中,已经将c的内容进行了变更,然而在CAS判断的时候,会判断B有没有变。

了解即可。。。

相关同步类

ReentrantLock

ReentrantLock 实现原理

由名字就可以得知这个 ReentrantLock 是可重入的锁,对比同样可重入的synchronized,它有什么优势有什么劣势呢?

  • 使用lock()和unlock()方法加锁
  • 使用tryLock() 尝试获得锁,能够限制获得锁的时间,更灵活
  • 设置公平锁,各线程公平竞争

CountDownLatch

latch是门闩的意思,所以countDownLatch的意思大致就为,每次线程运行后将count-1,直至减为0后,打开门闩,执行后面的程序。

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
private void useCountDownLatch(){
Thread[] threads = new Thread[10];
// set countDownLatch size
CountDownLatch countDownLatch = new CountDownLatch(10);

for (int i = 0; i < 10; i++) {
threads[i] = new Thread(){
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(getName());
countDownLatch.countDown();
}
}
};
threads[i].setName("thread-"+i);
// execute Thread run then countDown..
}

for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
System.out.println("Task Start!");

try{
// causes current thread wait until the countDownLatch down to zero
// effect all thread finish run()
countDownLatch.await();
}catch (Exception e){

}

System.out.println("All Task is Done!");
}

上面代码中,建立了10个线程,每个线程几乎同时开始,并且sleep1秒钟。输出的结果为:

1
2
3
4
5
6
7
8
9
10
11
12
Task Start!
thread-1
thread-3
thread-4
thread-0
thread-6
thread-2
thread-5
thread-9
thread-8
thread-7
All Task is Done!

可以看到,只有当所有线程执行完成后,才会执行后面的程序,否则所有线程就一直block在countDownLatch.await();方法处。

CyclicBarrier

多个线程互相等待,直到到达同一个同步点,再继续一起执行。

CyclicBarrier 和 CountDownLatch 不一样的地方在于

  1. 发展的方向不同:前者是递增发展,而后者为递减
  2. 使用次数不同:CountDownLatch只能使用一次,而CyclicBarrier可以多次使用
  3. 最重要的是概念、用法不同:CountDownLatch为一个或者多个线程,等待其他多个线程完成某件事情之后才能执行。而CyclicBarrier则为多个线程互相等待,直到到达同一个同步点,再继续一起执行。

ReadWriteLock

如名字一样,是读写锁,在ReadWriteLock中通过readLock()获取读锁,通过 writeLock() 获取写锁。

为什么要将写锁和读锁进行分离?这就好考虑应用场景。读写锁的应用场景通常是需要特别考虑效率的地方,并且读多写少的情况。

读的时候使用共享锁:在多线程读的时候,没有必要将方法锁住,多线程一块读效率更高。

写的时候使用排他锁:写的时候,其他线程不能同时读和写,以防止产生脏读的现象。

也就是说,读的时候是多线程并行读,写的时候只能单线程写。下面简单举个读写的例子。

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

static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

private Integer msg = 0;

public /*synchronized*/ void read(){
Lock read = readWriteLock.readLock();
try {
read.lock();
System.out.println("reading msg is "+msg);
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
read.unlock();
}
}

public /*synchronized*/ void write(){
Lock write = readWriteLock.writeLock();
try{
write.lock();
msg = new Random().nextInt();
System.out.println("write msg .."+ msg);
Thread.sleep(1000);
}catch (Exception e){

}finally {
write.unlock();
}
}

public static void main(String[] args) {
JUC03_ReadWriteLock juc03_readWriteLock = new JUC03_ReadWriteLock();

for (int i = 0; i < 9; i++) {
Thread readThread = new Thread(() -> {
juc03_readWriteLock.read();
});
readThread.start();
}

for (int i = 0; i < 2; i++) {
Thread writeThread = new Thread(()->{
juc03_readWriteLock.write();
});
writeThread.start();
}
}
}

这时,如果使用synchronized将read和write都上锁的话,那么每次读的时候都需要经过一秒的等待时间,所以总共需要等待11秒。但是,如果使用读写锁,总的读的时间可能也就一秒,总共也就3秒。

所以,在读多写少的情况下,使用读写锁,效率会非常高。

Semaphore

信号量,通过使用这个同步工具类,可以限制执行线程的数量。当特定数量的线程执行完,其他线程才可以执行。

类似买汽车票时去窗口排队一样,一共四个窗口,100个人,同时进行买票的只能是在四个窗口排队的那四个人。

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

Semaphore semaphore = new Semaphore(2);

Thread t1 = new Thread(() -> {
try {
semaphore.acquire();
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
System.out.println("t1.....end");
});

Thread t2 = new Thread(() -> {
try {
semaphore.acquire();
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
System.out.println("t2.....end");
});

Thread t3 = new Thread(() -> {
try {
semaphore.acquire();
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
System.out.println("t3.....end");
});

public void run(){
t1.start();
t2.start();
t3.start();
}

public static void main(String[] args) {
JUC03_Semaphore juc03_semaphore = new JUC03_Semaphore();
juc03_semaphore.run();
}
}

上面声明了3个线程,设置的限号量的permit数量为2,也就是说能够同时运行2个线程,每次线程运行时会acquire,也就是拿走一个信号量,执行完再放回。

所以结果也可想而知,t1和t2几乎同时被打印,t3过了两秒才被打印出来。

所以,如果需要对执行的线程数量需要控制,使用Semaphore,起到限流的作用。

线程通信

线程间通信,也就是线程a 通知到线程 b,可以有很多种方式,

  • wait/notify /nodifyAll
  • Lock/Condition
  • countDownLatch
  • LockSupport

根据实例代码来了解过程。

一、线程 b 监听线程 a 的工作过程

使用 wait 和 notify 实现
  1. 线程 b 先运行,进行等待
  2. 线程a 随后运行,当打印到 5 的时候,唤醒线程 b
  3. 唤醒线程 b 后,打印信息后,立刻唤醒线程a
  4. 线程 a 被唤醒后,继续打印

实现如图中的所显示的一样:当出现红色条时,证明当前线程为阻塞的。

Untitled

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

private int number = 0;

public synchronized void intercept() {
if (number != 5) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("print that message !!!");
this.notify();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("intercept finished!!");
}

public synchronized void increase() {
while (number <= 10) {
if (number == 5) {
this.notify();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println("current number size is :" + number);
}
this.notify();
}

public static void main(String[] args) {
JUC04_StopOtherThread01 stopOtherThread01 = new JUC04_StopOtherThread01();
new Thread(() -> {
stopOtherThread01.intercept();
}).start();

new Thread(() -> {
stopOtherThread01.increase();
}).start();
}
}
使用ReentrantLock和Condition实现

与 wait/notify 的结构相同

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
public class JUC04_StopOtherThread02 {
int number = 0;

private void increase() {
number++;
}

private int size() {
return number;
}

public static void main(String[] args) {
JUC04_StopOtherThread02 s = new JUC04_StopOtherThread02();

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

new Thread(() -> {
try {
lock.lock();
if (s.size() != 5) {
condition.await();
}
System.out.println("print that message !!!");
condition.signal();
condition.await();
System.out.println("all finished!!");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();

new Thread(() -> {
try {
lock.lock();
while (s.size() < 10) {
if (s.size() == 5) {
condition.signal();
condition.await();
}
s.increase();
System.out.println("current number size is :" + s.size());
}
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}

}).start();
}
}
CountDownLatch
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
public class JUC04_StopOtherThread03 {
int number = 0;

private void increase() {
number++;
}

private int size() {
return number;
}

public static void main(String[] args) {
JUC04_StopOtherThread03 s = new JUC04_StopOtherThread03();

CountDownLatch latch1 = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);

new Thread(() -> {
try {
if (s.size() != 5) {
latch1.await();
latch2.countDown();
}
System.out.println("print that message !!!");

System.out.println("all finished!!");
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}).start();

new Thread(() -> {
try {
while (s.size() < 10) {
if (s.size() == 5) {
latch1.countDown();

latch2.await();
}
s.increase();
System.out.println("current number size is :" + s.size());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}

}).start();
}
}
LockSupport
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
public class JUC04_StopOtherThread04 {
int number = 0;

Thread t1 = null;
Thread t2 = null;

private void increase() {
number++;
}

private int size() {
return number;
}

public static void main(String[] args) {

JUC04_StopOtherThread04 s = new JUC04_StopOtherThread04();

s.t1 = new Thread(() -> {
try {
if (s.size() != 5) {
LockSupport.park();
LockSupport.unpark(s.t2);
}
System.out.println("print that message !!!");

System.out.println("all finished!!");
} catch (Exception e) {
e.printStackTrace();
} finally {
}
});

s.t2 = new Thread(() -> {
try {
while (s.size() < 10) {
if (s.size() == 5) {
LockSupport.unpark(s.t1);
LockSupport.park();
}
s.increase();
System.out.println("current number size is :" + s.size());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}

});

s.t1.start();
s.t2.start();
}
}

除了监听,实现打断线程输出的这个例子,还有一个非常经典的就是生产者消费者的问题。

二、多线程中的生产者消费者问题

使用 wait 和 nodify 实现
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
public class JUC04_CustomerAndProducer01 {
List<Integer> repostory = new LinkedList<>();
private int size = 0;
private int MAX = 15;

public synchronized void add(Integer value) throws InterruptedException {
while (size == MAX) {
System.out.println(Thread.currentThread().getName() + " size:" + size + " block producer thread!!");
this.wait();
}
repostory.add(value);
size++;
System.out.println("current thread "+Thread.currentThread().getName()+" increase size , current size is :" + size);
this.notifyAll();
}

public synchronized void get() throws InterruptedException {
while (size <= 0) {
System.out.println(Thread.currentThread().getName() + " size:" + size + " block customer thread!!");
this.wait();
}
repostory.remove(0);
size--;
System.out.println("current thread "+Thread.currentThread().getName()+" increase size , current size is :" + size);
this.notifyAll();
}

public static void main(String[] args) {
JUC04_CustomerAndProducer01 c = new JUC04_CustomerAndProducer01();

for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {
try {
c.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "c_" + i).start();
}

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 25; j++) {
try {
c.add(new Random().nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "p_" + i).start();
}

}
}

使用 nodifyAll 会全部叫醒所有线程,如何实现单独叫醒某些线程?可以使用 ReentrantLock 来实现

使用ReentrantLock实现

ReentrantLock 通过使用多个 newCondition 来实现对不同类的线程单独唤醒。

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
public class JUC04_CustomerAndProducer02 {
List<Integer> repostory = new LinkedList<>();
private ReentrantLock lock = new ReentrantLock();
private Condition add = lock.newCondition();
private Condition get = lock.newCondition();

private int size = 0;
private int MAX = 15;

public void add(Integer value) throws InterruptedException {
try {
lock.lock();
while (size == MAX) {
System.out.println(Thread.currentThread().getName() + " size:" + size + " block producer thread!!");
add.await();
}
repostory.add(value);
size++;
System.out.println("current thread "+Thread.currentThread().getName()+" increase size , current size is :" + size);
get.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void get() throws InterruptedException {
try{
lock.lock();
while (size <= 0) {
System.out.println(Thread.currentThread().getName() + " size:" + size + " block customer thread!!");
get.await();
}
repostory.remove(0);
size--;
System.out.println("current thread "+Thread.currentThread().getName()+" increase size , current size is :" + size);
add.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}

}

public static void main(String[] args) {
JUC04_CustomerAndProducer02 c = new JUC04_CustomerAndProducer02();

for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) {
try {
c.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "c_" + i).start();
}

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 25; j++) {
try {
c.add(new Random().nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "p_" + i).start();
}
}
}

(四)AOP切面使用与原理

Posted on 2020-05-22
Words count in article: 4.2k | Reading time ≈ 18

SpringAOP的使用和源码分析

前言

现在来看(Aspect-oriented Programming) 面向切面编程aop,是和oop(Object-oriented Programming)是对编程结果的不同的思考的方向。在面向对象中,形成组合的组件是类(class)。而在面向切面,形成组合的组件是切面(Aspect),切面能够对关注的地方模块化,横切多种不同的类型和对象(比如事务管理)。而这个所谓的“关注的地方”,也可以在AOP中称为跨领域关注点。

Spring提供 配置文件 和 @AspectJ注解 方式编写AOP,两种方式都支持各种的 Advice(通知),如果使用@AspectJ注解,仍然会通过Spring的进行织入。

AOP中关键字的概念

1.Aspect

切面: 对多种不同的类的横切关注点,这个关注点被模块化为一个类,这个类就叫做切面(切面类),通过在普通类上标注 @Aspect注解来实现。

2.Join point

连接点:程序执行过程中能够执行通知(Advice)的点。

3.Advice

通知:通知指的是切面在指定的连接点处执行的操作,不同的通知类型包括:“around”, “before” and “after”

4.Pointcut

切点:切点会匹配通知所要织入的一个或者多个连接点,通知是与切点向关联的,并且在任何匹配到切点的地方执行在连接点上的操作。

5.Target object

目标对象:一个【被一个或多个切面进行通知】的对象叫做目标对象,也叫代理对象。

6.AOP proxy

AOP代理:在Spring中,一个是JDK动态代理,一个是 CGLIB 代理。

7.Weaving

织入:织入是将切面应用到目标对象来创建的代理对象过程

使用注解方式进行AOP的配置和使用

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
/**
* 1. 使用 @Aspect 注解创建切面类
* 2. 使用 @Pointcut 注解注明切点(使用Aspect表达式)
* 3. 声明通知(Before,After,Around,AfterReturning,AfterThrowing)具体含义可以参考Ref文档 5.4.4. Declaring Advice
* 只是以上三步是不行的,因为没有开启Spring对注解版AOP的支持
* 所以,需要使用 @EnableAspectJAutoProxy 开启对@Aspect注解的支持
**/
@Aspect
@Component
public class LogAspects {

@Pointcut(value="within(com.nanyin.service.*)")
// @Pointcut("execution(public * (..))")
public void pointCut(){
}

@Before("pointCut()")
public void logBefore(JoinPoint jp){
System.out.println(jp.getSignature().getName());
System.out.println("before...");
}

@After("pointCut()")
public void logAfter(){
System.out.println("after...");
}

@AfterReturning("pointCut()")
public void logAfterReturn(){
System.out.println("after return...");
}

@AfterThrowing(value = "pointCut()",throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint,Exception ex){
joinPoint.getSignature().getName();
System.out.println("after throwing...");
System.out.println(ex.getMessage());
}
// around在proceed 之前是在 before之前执行
// around在proceed 之前是在 after之前执行
// 可兼容before和after
@Around("pointCut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 方法开始
System.out.println("around methods begin ...");
Object object = joinPoint.proceed();
System.out.println("around methods end ...");
return object;
}
}

/**
* 进行测试
*
**/
@Test
public void test1(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(BasicConfig.class);
BasicService basicService = (BasicService) applicationContext.getBean("basicService");
// 因为切点包括这个basicService,所以执行hello就会触发AOP
basicService.hello();
}

// 结果
around methods begin ...
hello
before...
hello world!!
around methods end ...
after...
after return...

AOP代码原理分析

通过以上实践,可以看到如果需要使用Spring的AOP功能,需要先使用 @EnableAspectJAutoProxy 注解标注在配置类中,使 @Aspect 生效为一个切面类。

1. @EnableAspectJAutoProxy 开启springAOP支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Target(ElementType.TYPE) //标注在类上
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class) //引入 AspectJAutoProxyRegistrar
public @interface EnableAspectJAutoProxy {
/**
* 是否使用CGLIB进行代理,默认是false,即默认使用JDK动态代理
**/
boolean proxyTargetClass() default false;
/**
* 是否需要 代理应由AOP框架公开为{ThreadLocal},以便通过{AopContext}类进行检索。
**/
boolean exposeProxy() default false;
}

可以看到这里使用了Import注解,将AspectJAutoProxyRegistrar 引入到配置中。引入后,会通过继承自ImportBeanDefinitionRegistrar接口的方法 registerBeanDefinitions 做后续处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//如果需要 注册AspectJ注解自动代理创建类 internalAutoProxyCreator
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
// ...
}
@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
BeanDefinitionRegistry registry, @Nullable Object source) {
// 注册或升级AutoProxyCreator AnnotationAwareAspectJAutoProxyCreator 这个类是用来处理所有的AspectJ注解的切面
return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

最后通过 registerOrEscalateApcAsRequired 方法将 cls 也就是 AnnotationAwareAspectJAutoProxyCreator 注册到容器中,名称为 internalAutoProxyCreator。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(
Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
// cls=AnnotationAwareAspectJAutoProxyCreator
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
// 是否存在internalAutoProxyCreator
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
// ...
}
// 获得一个 AnnotationAwareAspectJAutoProxyCreator 的 beanDefinition
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 注册名为 org.springframework.aop.config.internalAutoProxyCreator 的类
// 实际对应的是 AnnotationAwareAspectJAutoProxyCreator 类,而这个类是用来使@Aspect起作用的
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}

经过上面的分析能够了解通过使用 @EnableAspectJAutoProxy 这个注解,能够将AnnotationAwareAspectJAutoProxyCreator引入到容器中,从而达到解析,使用AOP。

那么 AnnotationAwareAspectJAutoProxyCreator 是如何工作的呢?为何引入这个类,才能使用Spring的AOP功能呢?

2. @AnnotationAwareAspectJAutoProxyCreator

类结构初步分析

图1

对AnnotationAwareAspectJAutoProxyCreator 生成类结构图,可以看到它继承自有两个顶级接口,分别是 BeanPostProcesser和 Aware 接口。

  • Aware 接口主要用于标记超级接口,用于指示bean有资格通过回调样式方法由Spring容器通知特定框架对象。

  • BeanPostProcesser 可以通过实现 BeanPostProcesser的两个后置处理器来实现对Bean的前后置处理:1.postProcessBeforeInitialization(Object bean, String beanName) 2.postProcessAfterInitialization(Object bean, String beanName) 分别针对初始化前后的拦截处理。

a. AbstractAutoProxyCreator

在通过上面图片中所看到的 AbstractAutoProxyCreator 继承了 ProxyProcessorSupport 类

继承类/接口 作用 需要实现方法
ProxyConfig类 用于创建代理的配置的便利超类,保证所有的代理创建类有一致的属性
Ordered接口 表示继承类是有序的
BeanClassLoaderAware接口 使用指定的classLoader来加载Bean setBeanClassLoader(ClassLoader classLoader)
AopInfrastructureBean接口 标示该Bean是作为Spring AOP的基础组件进行使用,因此不会被代理,即使被PointCut指定到也不会被代理到
BeanFactoryAware 使用指定的 BeanFactory 来实例化Bean void setBeanFactory(BeanFactory beanFactory)
b. SmartInstantiationAwareBeanPostProcessor

AbstractAutoProxyCreator -> 实现自 SmartInstantiationAwareBeanPostProcessor -> 继承自 InstantiationAwareBeanPostProcessor -> 继承自 BeanPostProcessor.

  • InstantiationAwareBeanPostProcessor

在这个接口中,不仅仅有继承于 BeanPostProcessor 的 postProcessBeforeInitialization(Object bean, String beanName) 和 postProcessAfterInitialization(Object bean, String beanName) 两个Bean初始化前后的处理方法,本身还定义了 postProcessProperties 和 postProcessPropertyValues方法。作用是在针对Bean的初始化后对属性进行后置处理。

  • SmartInstantiationAwareBeanPostProcessor

在这个接口中定义两个方法,分别为 default Class<?> predictBeanType(Class<?> beanClass, String beanName) 用来预测使用BeanPostProcessor的postProcessBeforeInstantiation方法的返回类型,默认为null。default Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) 用来决定使用哪个候选的构造器。

此时,相比已经在前面了解到什么是AOP,同时可以在前面的类结构分析中得知了AnnotationAwareAspectJAutoProxyCreator 继承自InstantiationAwareBeanPostProcessor 从而实现了对Bean的前后处理。所以下面来着重找和 对Bean 前后置处理相关的方法。

AbstractAutoProxyCreator类源码分析

在 AbstractAutoProxyCreator 作为 AnnotationAwareAspectJAutoProxyCreator 的父类,起到了非常重要的作用。

在 AbstractAutoProxyCreator 中,实现了 BeanPostProcessor 的初始化前后处理方法,用来针对每次初始化Bean前后对切面的识别判断和对符合条件的Bean进行通知的代理。

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
// postProcessBeforeInitialization ,BeanPostProcessor的实现方法。
@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
Object cacheKey = getCacheKey(beanClass, beanName); //获得beanClass

if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
// 通过判断是否使用了@AspectJ注解定义的切面类还是
// 实现Advice、Pointcut、Advisor、AopInfrastructureBean
this.advisedBeans.put(cacheKey, Boolean.FALSE); //添加到advisedBeans里面,等待处理
return null;
}
}
//如果不是基础设施的类
TargetSource targetSource = getCustomTargetSource(beanClass, beanName); //获得targetSource
if (targetSource != null) {
if (StringUtils.hasLength(beanName)) {
this.targetSourcedBeans.add(beanName);
}
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource); //创建代理类
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}

return null;
}

在postProcessBeforeInstantiation实现类中,具体流程大致为

  • 如果是Aspect切面类,则将类添加到advisedBeans中等待进行Advice的组装
  • 如果不是切面类,则会先去判断是否自定义一个 customTargetSourceCreators 数组
    • 如果定义了,则组装成自定义的代理类进行返回。
1.是否为AspectJ配置类

如果是AspectJ定义的切面类,或者由Advice、Pointcut、Advisor、AopInfrastructureBean实现类,则添加到advisedBeans中,并制定值为FLASE。

在 AnnotationAwareAspectJAutoProxyCreator 中存在 的isInfrastructureClass 实现方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
protected boolean isInfrastructureClass(Class<?> beanClass) {
// 加上父类中的isInfrastructureClass判断规则
return (super.isInfrastructureClass(beanClass) ||
(this.aspectJAdvisorFactory != null && this.aspectJAdvisorFactory.isAspect(beanClass)));
}
public boolean isAspect(Class<?> clazz) {
return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}
// 在这里判断是否存在 Aspect 注解
private boolean hasAspectAnnotation(Class<?> clazz) {
return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);
}
// AbstractAutoProxyCreator 中的方法
protected boolean isInfrastructureClass(Class<?> beanClass) {
boolean retVal = Advice.class.isAssignableFrom(beanClass) || //是否实现 Advice
Pointcut.class.isAssignableFrom(beanClass) || //是否实现 Pointcut
Advisor.class.isAssignableFrom(beanClass) || //是否实现 Advisor
AopInfrastructureBean.class.isAssignableFrom(beanClass); //是否实现 AopInfrastructureBean
if (retVal && logger.isTraceEnabled()) {
logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]");
}
return retVal;
}
2.进行代理

真正的代理是发生在实现的 beanPostProcessor 的 postProcessAfterInitialization

在 AbstractAutoProxyCreator 中提供类一个初始化后置处理器 postProcessAfterInitialization 。

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
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) { // 如果不存在
// 如果需要进行代理的包装
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
//如有必要,包装给定的bean,即是否有资格被代理
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
// 如果在 postProcessBeforeInitialization 中定义了targetSourcedBeans,则直接返回
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
// 如果已经添加到advisedBeans中也直接返回
return bean;
}
// 如果是一个基本配置Aspect类,则放到advisedBeans中。
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

// 创建Advice
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
// Advice存在,则表示需要代理
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 创建代理
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
// 返回代理对象。
return proxy;
}

this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

创建Advice的Bean增强过程如下:

  1. 每个Bean在执行invokeInitMethods进行Init,然后进入到Init后置处理器中
  2. 通过在 AbstractAutoProxyCreator 的 postProcessAfterInitialization 对代理类的包装
    1. 通过getAdvicesAndAdvisorsForBean获取所有的可用的advice增强器。
    2. 通过 createProxy 创建代理Bean,并返回代理对象。
3.Advice获取过程

​ Advice是进行代理的重要的组成部分,代码中可以通过 @AspectJ 注解在切面类上, 同时使用Advice相关注解如 @After @Before 等进行标注,声明为一个 Advice。

​ 实际源码分析时会发现不管是 postProcessBeforeInstantiation 还是 postProcessAfterInstantiation,最后都会通过 buildAspectJAdvisors 方法寻找所有的候选的 advice。其中有两个比较重要的地方需要注意:

  • 为了避免重复,使用了对aspectBeanNames的判断,如果判断为空,才会进行Advice的查找
1
2
3
4
5
6
List<String> aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
synchronized (this) {
aspectNames = this.aspectBeanNames;
if (aspectNames == null) {
// ....
  • 通过getAdvices获取所有的备选的Advice
1
2
3
4
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);// 获取Aspect类中的Advice
if (this.beanFactory.isSingleton(beanName)) {// 如果是单例的
this.advisorsCache.put(beanName, classAdvisors);// advisorsCache 是以 BeanName为Key,List<Advice>为value的Map
}
  • 通过getAdvices中的getAdvisorMethods获取排除除去Pointcut 标注的所有方法。
1
2
3
4
5
6
7
8
9
10
11
private List<Method> getAdvisorMethods(Class<?> aspectClass) {
final List<Method> methods = new ArrayList<>();
ReflectionUtils.doWithMethods(aspectClass, method -> {
// Exclude pointcuts
if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) { // 除去标注了Pointcut注解的方法 添加到 "method"中
methods.add(method);
}
}, ReflectionUtils.USER_DECLARED_METHODS);
methods.sort(METHOD_COMPARATOR);
return methods;
}
  • 最后通过getAdvices中的getAdvisor中的getPointcut方法,通过 findAspectJAnnotationOnMethod对类中不存在 Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class进行过滤。
1
2
3
4
5
6
7
8
9
protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
if (foundAnnotation != null) {
return foundAnnotation;
}
}
return null;
}
  • 最后在buildAspectJAdvisors方法中放入到advisorsCache,作为缓存。
1
2
3
4
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);// 获取Aspect类中的Advice
if (this.beanFactory.isSingleton(beanName)) {// 如果是单例的
this.advisorsCache.put(beanName, classAdvisors);// advisorsCache 是以 BeanName为Key,List<Advice>为value的Map
}
4.方法的匹配

在进行代理的时候,如何知道对那些方法进行了代理。

  • 在后置处理器使用的时候,通过getAdvicesAndAdvisorsForBean获取缓存中的Advice
1
2
3
4
5
6
7
8
9
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
  • 通过其中的 findEligibleAdvisors方法,获取可用的Advice
  • 一步一步点进去后,发现最后使用canApply方法进行判断,是否可以对当前Bean进行代理
1
2
3
4
5
6
7
8
9
10
11
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
// 使用Matcher进行类级别的匹配验证
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true;
}

综上解析中,能够解决一下几个疑问:

  1. AnnotationAwareAspectJAutoProxyCreator 作用是什么

    最重要的是引入了AbstractAutoProxyCreator,实现对Bean的代理包装;同时在postProcessBeforeInstantiation时,在使用 shouldSkip 时,会获取所有的Advice,并加入到cache中。

  2. Spring在什么时候进行AOP代理?
    Spring在生成Bean单例时会执行Init方法初始化,通过初始化后postProcessor方法,先获取所有可用的Advice增强器,之后使用ProxyFactory创建Proxy并返回。

  3. 容器获取Advice的过程?

    在前置处理器中,获取@AspectJ中下的所有Advice方法,并添加到缓存中

  4. 哪些方法可以被匹配到可以进行AOP?
    在PointCut中执行范围中的Bean可以被AOP拦截使用

3. 创建代理的方式

SpringAOP创建代理有两种方式:

  • 第一种:使用JDK动态代理
  • 第二种:使用CGLIB代理。

下面来针对代理方式的选择方式,查看源码一探究竟。

在上一节中提到的在 AbstractAutoProxyCreator 中的在后置处理器中 postProcessAfterInitialization 的 wrapIfNecessary中进行了对Bean的代理包装处理.

1
2
3
4
5
6
7
8
9
10
// 如果存在可用的通知,则对Bean进行代理
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
// 创建代理对象
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}

在 createProxy 中决定了使用哪种方式(JDK动态代理、CGLIB代理)进行代理。

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

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {

if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}

ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);// 复制配置

if (!proxyFactory.isProxyTargetClass()) {//返回是否直接代理目标类以及任何接口。
if (shouldProxyTargetClass(beanClass, beanName)) {
// 如果 proxy-target-class 标签属性返回true,则表明需要基于类的代理,
// 反之则需要基于接口的代理
proxyFactory.setProxyTargetClass(true); //使用CGLIB代理(true)
}else {
evaluateProxyInterfaces(beanClass, proxyFactory); //检查是否实现了接口
// 如果实现了接口,则设置interface,否则设置proxyTargetClass为true,但前提是
// 用户没有自己设置proxyTargetClass属性
/**
* if (hasReasonableProxyInterface) {
* Must allow for introductions; can't just set interfaces to the target's interfaces only.
* for (Class<?> ifc : targetInterfaces) {
* proxyFactory.addInterface(ifc);
* }
*}else {
* proxyFactory.setProxyTargetClass(true);
*}
*/
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);

proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}

return proxyFactory.getProxy(getProxyClassLoader());
}

在这里设置了有关于proxyFactory的相关属性, 如是否继承接口的interface,和 ProxyTargetClass 。如果没有实现interface,则会自动将ProxyTargeClass为true,也就是说这两个属性是相对的。

在最后的retrun的 getProxy() 方法最终会调用 DefaultAopProxyFactory 类中的 createAopProxy 方法决定使用哪中代理方式进行代理。

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
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
/*
* 下面的三个条件简单分析一下:
*
* 条件1:config.isOptimize() - 是否需要优化
* 条件2:config.isProxyTargetClass() - 检测 proxyTargetClass 的值,
* 条件3:hasNoUserSuppliedProxyInterfaces(config) - 目标 bean 是否实现了接口
*
*/
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
// 如果设置了optimize 或者 proxyTargetClass 或者设置了进行代理的接口
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config); //如果代理的是接口,使用JDK动态代理
}
return new ObjenesisCglibAopProxy(config);//否则使用CGlib代理
}
else { //否则都使用JDK进行代理
return new JdkDynamicAopProxy(config);
}
}

前面说的如果自己设置类proxyTargetClass属性为true,就不会判断是继承了interface。如果没有单独设置,则会先判断是否继承了接口,如果没有则会自动设置proxyTargetClass属性为true。

所以得出结论:

  1. 如果设置了proxyTargetClass为true,则使用CGLIB代理。
  2. 如果没有设置proxyTargetClass,如果是接口,则使用JDK动态代理。如果没有实现接口,则还是会使用CGLIB进行代理。

(三)自动装配

Posted on 2020-05-22
Words count in article: 566 | Reading time ≈ 2
  • Spring中的自动装配
    • @Autowire、 @Qualifier、@Primary
    • @Inject、@Named

Spring中的自动装配

@Autowire、 @Qualifier、@Primary

@Autowire 注解标注在constructor, field, setter method 上,方便在Spring中进行依赖的注入(dependency injection)。和JSR-330标准javax.inject.Inject达到一样的效果,不过autowired时Spring持有的。

  • 默认使用Autowire是按照类型进行注入的,但是如果在容器中存在多个类型相同的bean时,@Autowired也会按照名称进行匹配。
  • 使用@Qualifier注解,在获取Bean时,指定要获取的bean的名称。
  • 使用@Primary注解,在声明Bean时,指定如果注入,则首选的bean。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class Person {
private String firstName;
private String lastName;

public Person(String firstName, String lastName) {
this.firstName = "w";
this.lastName = "y";
}

@Autowired
@Qualifier("abc")
private Pet pet;
// 省略getter setter
}


// 或者在声明Bean时,使用Primary进行指定。
@Bean("dotdot")
@Primary
public Pet pet2(){
return new Pet("dotdot");
}

需要注意的是:在同时指定Qualifier和Primary时,会以Qualifier为主。

@Inject、@Named

@Inject、@Named 都是Javax规范的注解,并不是Spring的注解。

使用 Inject 注解同样能够标注在constructor, field, setter method 上。基本能够代替 Autowired 注解,并且能够使用 Named 注解,指定名称进行实例的注入,可以代替 Qualifier 注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class Person {
private String firstName;
private String lastName;

public Person(String firstName, String lastName) {
this.firstName = "w";
this.lastName = "y";
}

@Inject
@Named("abc")
private Pet pet;
// 省略getter setter
}

区别点在于 Autowired 注解支持 required() 属性。而 Inject 注解并不支持。可以使用java.util.Optional或者Spring Framework 5.0 后提供的 @Nullable 注解,标志在构造方法上。

1
2
3
4
public class SimpleMovieLister {
@Autowired public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}

@Resource

使用 @Resource 同样能够达到自动装配的目的,与 @Autowire 的区别在于以下几点:

  • 处理这2个注解的BeanPostProcessor不一样
  • 匹配Bean的方式不同。
    • @Resource
      • 默认按照名称进行匹配注入。
      • 如果指定了 type,则按照类型进行匹配。当无法找到正确的类型或者找到多个时都会报错。如果都没有指定时,并且无法按照名称匹配到Bean时,会回退到按照类型寻找Bean。
    • @Autowire 默认就是按照类型进行匹配的,如果需要指定特定Bean,则需要使用 @Qualifier 注解进行指定。

(二)Bean的生命周期

Posted on 2020-05-22
Words count in article: 614 | Reading time ≈ 2
  • Bean的生命周期
    • Bean的初始化和销毁
      • 一、使用@Bean的初始化和销毁属性
      • 二、使用 InitializingBean 和 DisposableBean 接口指定
      • 三、使用JSR标准的注解 @PostConstruce 和 @PreDestroy
      • 四、BeanPostProcessor自定义初始化前后方法

Bean的生命周期

Bean的初始化和销毁

一、使用@Bean注解的初始化和销毁属性

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
// 在dog实例中指定init和destroy方法
public class Dog {

public void init(){
System.out.println("instance dog execute init method.....");
}

public void destroy(){
System.out.println("instance dog execute destroy method.....");
}
}

// 声明bean的时候指定 initMethod = "init",destroyMethod = "destroy"
@Configurable
public class LifeCycleConfig {
@Bean(initMethod = "init",destroyMethod = "destroy")
public Dog dog(){
System.out.println("begin create dog");
return new Dog();
}
}
// 执行测试
@Test
public void LifeCycleTest(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(LifeCycleConfig.class);
System.out.println("instance has created !!!");
// Dog dog = (Dog) applicationContext.getBean("dog");
((AnnotationConfigApplicationContext) applicationContext).close();
System.out.println("context has closed !!!");
}

// 测试结果:
// begin create dog
// instance dog execute init method.....
// instance has created !!!
// instance dog execute destroy method.....
// context has closed !!!

从测试结果来看当创建对象时,通过 initMethod,destroyMethod指定初始化和销毁方法时可行的。

init方法在对象创建后执行。destroy在容器销毁前执行.

二、继承 InitializingBean 和 DisposableBean 接口

方法同上,不过需要对象类实现这两个接口

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
public class Cat implements InitializingBean, DisposableBean {

// 接口中的destroy 方法,执行销毁bean
public void destroy() throws Exception {
System.out.println("instance cat execute destroy method.....");
}
// 接口中的afterPropertiesSet方法,用来创建bean后的初始化
public void afterPropertiesSet() throws Exception {
System.out.println("instance cat execute init method.....");
}
}

// 不需要在bean注解中指定相关方法
@Bean
public Cat cat(){
System.out.println("begin create cat");
return new Cat();
}

//用同样的测试程序测试得到的结果如下:
// begin create cat
// instance dog execute init method.....
// instance has created !!!
// instance dog execute destroy method.....
// context has closed !!!

三、使用JSR标准注解 @PostConstruce 和 @PreDestroy

1
2
3
4
5
6
7
8
9
10
11
12
public class Bug {

@PostConstruct
public void init(){
System.out.println("instance Bug execute init method.....");
}

@PreDestroy
public void destroy(){
System.out.println("instance Bug execute destroy method.....");
}
}

四、BeanPostProcessor自定义初始化前后方法

先自定义一个 MyBeanPostProcessor ,并将它放到容器中,在启动后,每次生成bean时,都会执行下面的 postProcessBeforeInitialization 和 postProcessAfterInitializationde 前置方法和后置方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 根据BeanName和bean可以获取bean的类信息接下来就可以进行筛选,在初始化前进行相关操作
System.out.println(beanName);
return null;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(beanName);
// 同理初始化后
return null;
}
}



深入JVM(三)JVM调优

Posted on 2020-05-20 | In JVM
Words count in article: 713 | Reading time ≈ 2

参数分类

在多数项目中不需要进行JVM调优,在出现OOM或CPU飙高,往往是程序问题,应该想办法优化程序。

但是,在一般情况下,应该在上线前将JVM调整到最优,保证虚拟机:

  • GC低停顿
  • GC低频率
  • 低内存占用
  • 高吞吐量

调优中一般会遇到两种参数,一种是任何虚拟机版本都相同的标准参数,这些参数不需要人为调整,另一种是非标准参数,也是不稳定的参数,需要根据实际情况进行设定。

标准参数

标准参数,以 - 开头,如 -version

非标准参数

非标准参数 ,以 -X 或-XX开头,这些参数非常多,并且需要根据不同的情况进行不同的调整。

虚拟机的调优就是在不同的情况下针对这些非标准参数进行调整。

常用参数

  • -Xms 初始化堆内存的最小值
  • -Xmx 初始化堆内存的最大值,一般和 Xms的值设为相同的值。
  • -Xmn 初始化堆中的新生代大小
  • -XX:ParallelGCThreads=8:新生代并行收集器的线程数。
  • -Xloggc: /opt/xxx/logs/xxx-xxx-gc-%t.log GC日志输出文件
  • -XX:+UseGCLogFileRotation
  • -XX:NumberOfGCLogFiles=5
  • -XX:GCLogFileSize=20M
  • -XX:+PrintGCTimeStamps 打印GC耗时
  • -XX:+PrintGCDetails 打印GC回收的细节
  • -XX:HeapDumpPath=./java_pid.hprof :堆内存快照的存储文件路径。文件名一般为java____heapDump.hprof。
  • -XX:+HeapDumpOnOutOfMemoryError 在OOM时,输出一个dump文件

常用工具

命令行工具

308C8A7B-5396-4C49-8E39-3BDC9F1D820E

jps

JPS(Java Virtual Machine Process Status Tool),可以显示进行中的Java线程。

使用方式:jps [options] [hostid]

jinfo

jinfo(Java Configuration Info),实时查看和调整JVM配置参数.

使用方式: jinfo -flag <name> PID 或者 jinfo -flags PID

jstat -gc

jstat(Java Virtual Machine statistics monitoring tool),能够查看JVM的使用情况

使用方式:jstat [ generalOption | outputOptions vmid [ interval [ s|ms ] [ count ] ] ]

如: jstat -gc -h3 31736 1000 10

jstack

jstack(Java stack trace)是Java的堆栈分析工具。

两个功能:

  1. 针对活着的进程做本地的或远程的线程dump;
  2. 针对core文件做线程dump。

使用方式:jstack [ option ] pid

可将堆栈输出到指定文件中:jstack -l PID >> jstack.out

jmap

jmap(Java memory map),它可以生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。

命令:jmap pid

描述:查看进程的内存映像信息,类似 Solaris pmap 命令。

命令:jmap -heap pid

描述:显示Java堆详细信息

命令:jmap -histo:live pid

描述:显示堆中对象的统计信息

命令:jmap -dump:format=b,file=heapdump.phrof pid

描述:生成堆转储快照dump文件。

arthas 线上排查工具

arthas 能够完成以上所有的工作,但是需要注意的是,使用jmap或jstack

alibaba/arthas

日志/堆栈分析工具

jvirsualvm

12…12<i class="fa fa-angle-right"></i>
NanYin

NanYin

Was mich nicht umbringt, macht mich starker.

111 posts
16 categories
21 tags
RSS
GitHub E-Mail
近期文章
  • ThreadLocal
  • Java的四种引用类型
  • Markdown语法入门
  • IDEA设置代码注释模板和JavaDoc文档生成
  • 基于Java的WebService实践
0%
© 2023 NanYin
|
本站访客数:12375
|
博客全站共140.1k字
|