Demo位置:
配置
1、在【project】的build.gradle中配置:
buildscript { repositories { google() jcenter() mavenCentral()//【aspectj】需要依赖maven仓库 } dependencies { classpath 'com.android.tools.build:gradle:3.1.2' classpath 'org.aspectj:aspectjtools:1.8.9'//【aspectj】编写gradle编译脚本 classpath 'org.aspectj:aspectjweaver:1.8.9'//【aspectj】编写gradle编译脚本 }}
1
buildscript {
2
repositories {
3
google()
4
jcenter()
5
mavenCentral()//【aspectj】需要依赖maven仓库
6
}
7
dependencies {
8
classpath 'com.android.tools.build:gradle:3.1.2'
9
classpath 'org.aspectj:aspectjtools:1.8.9'//【aspectj】编写gradle编译脚本
10
classpath 'org.aspectj:aspectjweaver:1.8.9'//【aspectj】编写gradle编译脚本
11
}
12
}
2、在【project】的build.gradle最后面添加脚本:
注意,直接粘贴到build.gradle文件的末尾即可,不要嵌套在别的指令中
//AOP需要执行的脚本,每一个application和library都需要import org.aspectj.bridge.MessageHandlerimport org.aspectj.tools.ajc.Maindef aop(variants) { variants.all { variant -> JavaCompile javaCompile = variant.javaCompile String[] args; javaCompile.doFirst { args = ["-showWeaveInfo", "-1.8", "-inpath", javaCompile.destinationDir.toString(), "-aspectpath", javaCompile.classpath.asPath, "-d", javaCompile.destinationDir.toString(), "-classpath", path, "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)] } javaCompile.doLast { new Main().run(args, new MessageHandler(false)); } }}
x
1
2
//AOP需要执行的脚本,每一个application和library都需要
3
import org.aspectj.bridge.MessageHandler
4
import org.aspectj.tools.ajc.Main
5
6
def aop(variants) {
7
variants.all { variant ->
8
JavaCompile javaCompile = variant.javaCompile
9
String[] args;
10
javaCompile.doFirst {
11
args = ["-showWeaveInfo",
12
"-1.8",
13
"-inpath", javaCompile.destinationDir.toString(),
14
"-aspectpath", javaCompile.classpath.asPath,
15
"-d", javaCompile.destinationDir.toString(),
16
"-classpath", path,
17
"-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
18
}
19
20
21
javaCompile.doLast {
22
new Main().run(args, new MessageHandler(false));
23
}
24
}
25
}
3、在【module】的build.gradle中添加依赖:
注意,每一个application和library都需要添加
implementation 'org.aspectj:aspectjrt:1.8.9'//每一个application和library都需要
1
implementation 'org.aspectj:aspectjrt:1.8.9'//每一个application和library都需要
4、在【module】的build.gradle中添加编译脚本:
注意,直接粘贴到build.gradle文件的末尾即可,不要嵌套在别的指令中
rootProject.aop(project.android.applicationVariants)//如果是【application】或rootProject.aop(project.android.libraryVariants)//如果是【library】
1
1
rootProject.aop(project.android.applicationVariants)//如果是【application】
2
或
3
rootProject.aop(project.android.libraryVariants)//如果是【library】
定义拦截点击事件的 Aspect
OnClickAspect
/** * 拦截所有View或其子类的【onClick方法】以及通过ButterKnife的注解添加的点击事件 */@Aspectpublic class OnClickAspect { @Pointcut("execution(* android.view.View.On*Listener.on*Click(..)) ")//定义匹配范围:执行指定方法时拦截 public void onClick() {//匹配View.OnClickListener中的onClick方法和View.OnLongClickListener中的OnLongClickListener方法 } @Pointcut("execution(* *.on*ItemClick(..)) ")//如果有多个匹配范围,可以定义多个,多个规则之间通过 || 或 && 控制 public void onItemClick() {//匹配任意名字以on开头以ItemClick结尾的方法 } @Pointcut("execution(@butterknife.OnClick * *(..))")//匹配通过butterknife的OnClick注解添加的点击事件 public void butterKnifeOnClick() { } @Around("onClick() || onItemClick() || butterKnifeOnClick()")//@Around 拦截方法,这个注解可以同时拦截方法的执行前后 public Object around(ProceedingJoinPoint joinPoint) throws Throwable { long beginTime = SystemClock.currentThreadTimeMillis(); printJoinPointInfo(joinPoint); if (joinPoint.getSignature() instanceof MethodSignature) { MethodSignature signature = (MethodSignature) joinPoint.getSignature();//要根据Pointcut匹配的类型强转 printMethodSignatureInfo(signature); printArgs(joinPoint); printParameterInfo(joinPoint); } Object result = joinPoint.proceed(); Log.i("bqt", "【@Around】返回值=" + ObjToStringUtils.toString(result)// null + " 方法执行耗时=" + (SystemClock.currentThreadTimeMillis() - beginTime));//2 return result; } //必须是静态方法 private static void printJoinPointInfo(ProceedingJoinPoint joinPoint) { Log.i("bqt", "【@Around】MethodSignature" + "\nKind=" + joinPoint.getKind()//method-execution + "\nArgs=" + ObjToStringUtils.toString(joinPoint.getArgs())//[android.widget.TextView{d090a1d V.ED..C.. ...P.... 0,0-1440,210}] + "\nSignature=" + ObjToStringUtils.toString(joinPoint.getSignature())//void com.bqt.aop.MainActivity.1.onClick(View) + "\nSourceLocation=" + ObjToStringUtils.toString(joinPoint.getSourceLocation())//MainActivity.java:25 + "\nStaticPart=" + ObjToStringUtils.toString(joinPoint.getStaticPart())//execution(void com.bqt.aop.MainActivity.1.onClick(View)) + "\nTarget=" + ObjToStringUtils.toString(joinPoint.getTarget())//com.bqt.aop.MainActivity$1@d5c5492 + "\nThis=" + ObjToStringUtils.toString(joinPoint.getThis()));//com.bqt.aop.MainActivity$1@d5c5492 } private static void printMethodSignatureInfo(MethodSignature signature) { //下面通过MethodSignature的方式获取方法的详细信息,也基本都可以通过Method对象获取 Log.i("bqt", "【@Around】MethodSignature" + "\n方法=" + ObjToStringUtils.toString(signature.getMethod())// public void com.bqt.aop.MainActivity$1.onClick(android.view.View) + "\n方法名=" + signature.getName()// onClick + "\n返回值类型=" + ObjToStringUtils.toString(signature.getReturnType())// void + "\n声明类型=" + ObjToStringUtils.toString(signature.getDeclaringType())// class com.bqt.aop.MainActivity$1 + "\n声明类型名=" + signature.getDeclaringTypeName()// com.bqt.aop.MainActivity$1 + "\n异常类型=" + ObjToStringUtils.toString(signature.getExceptionTypes())// [] + "\n修饰符=" + signature.getModifiers()// 1,对应为 public static final int PUBLIC = 0x00000001 + "\n参数名=" + ObjToStringUtils.toString(signature.getParameterNames())// ["v"] + "\n参数类型=" + ObjToStringUtils.toString(signature.getParameterTypes()));// [class android.view.View] } private static void printArgs(ProceedingJoinPoint joinPoint) { String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();//获取参数名列表 Object[] parameterValues = joinPoint.getArgs();//获取参数值列表 StringBuilder builder = new StringBuilder(""); for (int i = 0; i < parameterValues.length; i++) { builder.append("\n") .append(parameterNames[i]) .append("=")//拼接参数名 .append(ObjToStringUtils.toString(parameterValues[i]));//拼接参数值 } Log.i("bqt", "【@Around】参数列表" + builder.toString());//v=android.widget.TextView{d090a1d V.ED..C.. ...P.... 0,0-1440,210} } private static void printParameterInfo(ProceedingJoinPoint joinPoint) { Object[] parameterValues = joinPoint.getArgs();//获取参数值列表 for (Object obj : parameterValues) { if (obj instanceof TextView) { TextView textView = (TextView) obj; Log.i("bqt", "【@Around】TextView的信息" + " 文字=" + textView.getText() + " 所属界面=" + textView.getContext().getClass().getSimpleName() + " ID=" + textView.getId() + " 父页面名称=" + textView.getParent().getClass().getSimpleName() ); } } }}
1
90
1
/**
2
* 拦截所有View或其子类的【onClick方法】以及通过ButterKnife的注解添加的点击事件
3
*/
4
@Aspect
5
public class OnClickAspect {
6
@Pointcut("execution(* android.view.View.On*Listener.on*Click(..)) ")//定义匹配范围:执行指定方法时拦截
7
public void onClick() { //匹配View.OnClickListener中的onClick方法和View.OnLongClickListener中的OnLongClickListener方法
8
}
9
10
@Pointcut("execution(* *.on*ItemClick(..)) ")//如果有多个匹配范围,可以定义多个,多个规则之间通过 || 或 && 控制
11
public void onItemClick() { //匹配任意名字以on开头以ItemClick结尾的方法
12
}
13
14
@Pointcut("execution(@butterknife.OnClick * *(..))")//匹配通过butterknife的OnClick注解添加的点击事件
15
public void butterKnifeOnClick() {
16
}
17
18
@Around("onClick() || onItemClick() || butterKnifeOnClick()")//@Around 拦截方法,这个注解可以同时拦截方法的执行前后
19
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
20
long beginTime = SystemClock.currentThreadTimeMillis();
21
printJoinPointInfo(joinPoint);
22
23
if (joinPoint.getSignature() instanceof MethodSignature) {
24
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//要根据Pointcut匹配的类型强转
25
printMethodSignatureInfo(signature);
26
printArgs(joinPoint);
27
printParameterInfo(joinPoint);
28
}
29
30
Object result = joinPoint.proceed();
31
Log.i("bqt", "【@Around】返回值=" + ObjToStringUtils.toString(result)// null
32
+ " 方法执行耗时=" + (SystemClock.currentThreadTimeMillis() - beginTime));//2
33
return result;
34
}
35
36
//必须是静态方法
37
private static void printJoinPointInfo(ProceedingJoinPoint joinPoint) {
38
Log.i("bqt", "【@Around】MethodSignature"
39
+ "\nKind=" + joinPoint.getKind()//method-execution
40
+ "\nArgs=" + ObjToStringUtils.toString(joinPoint.getArgs())//[android.widget.TextView{d090a1d V.ED..C.. ...P.... 0,0-1440,210}]
41
+ "\nSignature=" + ObjToStringUtils.toString(joinPoint.getSignature())//void com.bqt.aop.MainActivity.1.onClick(View)
42
+ "\nSourceLocation=" + ObjToStringUtils.toString(joinPoint.getSourceLocation())//MainActivity.java:25
43
+ "\nStaticPart=" + ObjToStringUtils.toString(joinPoint.getStaticPart())//execution(void com.bqt.aop.MainActivity.1.onClick(View))
44
+ "\nTarget=" + ObjToStringUtils.toString(joinPoint.getTarget())//com.bqt.aop.MainActivity$1@d5c5492
45
+ "\nThis=" + ObjToStringUtils.toString(joinPoint.getThis()));//com.bqt.aop.MainActivity$1@d5c5492
46
}
47
48
private static void printMethodSignatureInfo(MethodSignature signature) {
49
//下面通过MethodSignature的方式获取方法的详细信息,也基本都可以通过Method对象获取
50
Log.i("bqt", "【@Around】MethodSignature"
51
+ "\n方法=" + ObjToStringUtils.toString(signature.getMethod())// public void com.bqt.aop.MainActivity$1.onClick(android.view.View)
52
+ "\n方法名=" + signature.getName()// onClick
53
+ "\n返回值类型=" + ObjToStringUtils.toString(signature.getReturnType())// void
54
+ "\n声明类型=" + ObjToStringUtils.toString(signature.getDeclaringType())// class com.bqt.aop.MainActivity$1
55
+ "\n声明类型名=" + signature.getDeclaringTypeName()// com.bqt.aop.MainActivity$1
56
+ "\n异常类型=" + ObjToStringUtils.toString(signature.getExceptionTypes())// []
57
+ "\n修饰符=" + signature.getModifiers()// 1,对应为 public static final int PUBLIC = 0x00000001
58
+ "\n参数名=" + ObjToStringUtils.toString(signature.getParameterNames())// ["v"]
59
+ "\n参数类型=" + ObjToStringUtils.toString(signature.getParameterTypes()));// [class android.view.View]
60
}
61
62
private static void printArgs(ProceedingJoinPoint joinPoint) {
63
String[] parameterNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();//获取参数名列表
64
Object[] parameterValues = joinPoint.getArgs();//获取参数值列表
65
66
StringBuilder builder = new StringBuilder("");
67
for (int i = 0; i < parameterValues.length; i++) {
68
builder.append("\n")
69
.append(parameterNames[i])
70
.append("=")//拼接参数名
71
.append(ObjToStringUtils.toString(parameterValues[i]));//拼接参数值
72
}
73
Log.i("bqt", "【@Around】参数列表" + builder.toString());//v=android.widget.TextView{d090a1d V.ED..C.. ...P.... 0,0-1440,210}
74
}
75
76
private static void printParameterInfo(ProceedingJoinPoint joinPoint) {
77
Object[] parameterValues = joinPoint.getArgs();//获取参数值列表
78
for (Object obj : parameterValues) {
79
if (obj instanceof TextView) {
80
TextView textView = (TextView) obj;
81
Log.i("bqt", "【@Around】TextView的信息"
82
+ " 文字=" + textView.getText()
83
+ " 所属界面=" + textView.getContext().getClass().getSimpleName()
84
+ " ID=" + textView.getId()
85
+ " 父页面名称=" + textView.getParent().getClass().getSimpleName()
86
);
87
}
88
}
89
}
90
}
定义拦截自定义注解的 Aspect
@Target({METHOD, CONSTRUCTOR})@Retention(RetentionPolicy.RUNTIME)public @interface CustomEvent { String key(); String id() default ""; String name() default ""; String des() default "";}
11
11
1
@Target({ METHOD, CONSTRUCTOR})
2
@Retention(RetentionPolicy.RUNTIME)
3
public @interface CustomEvent {
4
String key();
5
6
String id() default "";
7
8
String name() default "";
9
10
String des() default "";
11
}
package com.bqt.aop.aspectj;import android.util.Log;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.Signature;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.ConstructorSignature;import org.aspectj.lang.reflect.MethodSignature;import java.lang.reflect.Constructor;import java.lang.reflect.Method;/** * 拦截所有具有【CustomEvent注解】的方法、构造方法 */@Aspectpublic class CustomEventAspect { @Pointcut("within(@com.bqt.aop.aspectj.CustomEvent *)") public void withinAnnotatedClass() { } @Pointcut("execution(* *(..)) && withinAnnotatedClass()") public void methodInsideAnnotatedType() { } @Pointcut("execution(*.new(..)) && withinAnnotatedClass()") public void constructorInsideAnnotatedType() { } @Pointcut("execution(@com.bqt.aop.aspectj.CustomEvent * *(..)) || methodInsideAnnotatedType()") public void method() { } @Pointcut("execution(@com.bqt.aop.aspectj.CustomEvent *.new(..)) || constructorInsideAnnotatedType()") public void constructor() { } @Before("method() || constructor()") public void before(JoinPoint joinPoint) { Log.i("bqt", "【@Before】"); } @After("method() || constructor()") public void after(JoinPoint joinPoint) { Log.i("bqt", "【@After】"); addEvent(joinPoint); } private static void addEvent(JoinPoint joinPoint) { CustomEvent customEvent = null; Signature signature = joinPoint.getSignature(); if (signature instanceof MethodSignature) {//【public interface MethodSignature extends CodeSignature】 Method method = ((MethodSignature) signature).getMethod(); customEvent = method.getAnnotation(CustomEvent.class); } else if (signature instanceof ConstructorSignature) {//【public interface ConstructorSignature extends CodeSignature】 Constructor constructor = ((ConstructorSignature) signature).getConstructor(); customEvent = (CustomEvent) constructor.getAnnotation(CustomEvent.class); } if (customEvent != null) { Log.i("bqt", "【注解】" + customEvent.key() + " " + customEvent.id() + " " + customEvent.name() + " " + customEvent.des()); } }}
x
68
1
package com.bqt.aop.aspectj;
2
3
import android.util.Log;
4
5
import org.aspectj.lang.JoinPoint;
6
import org.aspectj.lang.Signature;
7
import org.aspectj.lang.annotation.After;
8
import org.aspectj.lang.annotation.Aspect;
9
import org.aspectj.lang.annotation.Before;
10
import org.aspectj.lang.annotation.Pointcut;
11
import org.aspectj.lang.reflect.ConstructorSignature;
12
import org.aspectj.lang.reflect.MethodSignature;
13
14
import java.lang.reflect.Constructor;
15
import java.lang.reflect.Method;
16
17
/**
18
* 拦截所有具有【CustomEvent注解】的方法、构造方法
19
*/
20
@Aspect
21
public class CustomEventAspect {
22
@Pointcut("within(@com.bqt.aop.aspectj.CustomEvent *)")
23
public void withinAnnotatedClass() {
24
}
25
26
@Pointcut("execution(* *(..)) && withinAnnotatedClass()")
27
public void methodInsideAnnotatedType() {
28
}
29
30
@Pointcut("execution(*.new(..)) && withinAnnotatedClass()")
31
public void constructorInsideAnnotatedType() {
32
}
33
34
@Pointcut("execution(@com.bqt.aop.aspectj.CustomEvent * *(..)) || methodInsideAnnotatedType()")
35
public void method() {
36
}
37
38
@Pointcut("execution(@com.bqt.aop.aspectj.CustomEvent *.new(..)) || constructorInsideAnnotatedType()")
39
public void constructor() {
40
}
41
42
@Before("method() || constructor()")
43
public void before(JoinPoint joinPoint) {
44
Log.i("bqt", "【@Before】");
45
}
46
47
@After("method() || constructor()")
48
public void after(JoinPoint joinPoint) {
49
Log.i("bqt", "【@After】");
50
addEvent(joinPoint);
51
}
52
53
private static void addEvent(JoinPoint joinPoint) {
54
CustomEvent customEvent = null;
55
Signature signature = joinPoint.getSignature();
56
if (signature instanceof MethodSignature) { //【public interface MethodSignature extends CodeSignature】
57
Method method = ((MethodSignature) signature).getMethod();
58
customEvent = method.getAnnotation(CustomEvent.class);
59
} else if (signature instanceof ConstructorSignature) { //【public interface ConstructorSignature extends CodeSignature】
60
Constructor constructor = ((ConstructorSignature) signature).getConstructor();
61
customEvent = (CustomEvent) constructor.getAnnotation(CustomEvent.class);
62
}
63
if (customEvent != null) {
64
Log.i("bqt", "【注解】" + customEvent.key() + " " + customEvent.id() + " " + customEvent.name() + " " + customEvent.des());
65
}
66
67
}
68
}
一个实用的工具类
ObjToStringUtils
public class ObjToStringUtils { private ObjToStringUtils() { throw new AssertionError("No instances."); } public static String toString(Object obj) { if (obj == null) return "null"; if (obj instanceof CharSequence) return '"' + printableToString(obj.toString()) + '"'; Class cls = obj.getClass(); if (Byte.class == cls) return byteToString((Byte) obj); if (cls.isArray()) return arrayToString(cls.getComponentType(), obj); return obj.toString(); } private static String printableToString(String string) { int length = string.length(); StringBuilder builder = new StringBuilder(length); for (int i = 0; i < length; ) { int codePoint = string.codePointAt(i); switch (Character.getType(codePoint)) { case Character.CONTROL: case Character.FORMAT: case Character.PRIVATE_USE: case Character.SURROGATE: case Character.UNASSIGNED: switch (codePoint) { case '\n': builder.append("\\n"); break; case '\r': builder.append("\\r"); break; case '\t': builder.append("\\t"); break; case '\f': builder.append("\\f"); break; case '\b': builder.append("\\b"); break; default: builder.append("\\u").append(String.format("%04x", codePoint).toUpperCase(Locale.US)); break; } break; default: builder.append(Character.toChars(codePoint)); break; } i += Character.charCount(codePoint); } return builder.toString(); } private static String arrayToString(Class cls, Object obj) { if (byte.class == cls) return byteArrayToString((byte[]) obj); if (short.class == cls) return Arrays.toString((short[]) obj); if (char.class == cls) return Arrays.toString((char[]) obj); if (int.class == cls) return Arrays.toString((int[]) obj); if (long.class == cls) return Arrays.toString((long[]) obj); if (float.class == cls) return Arrays.toString((float[]) obj); if (double.class == cls) return Arrays.toString((double[]) obj); if (boolean.class == cls) return Arrays.toString((boolean[]) obj); return arrayToString((Object[]) obj); } /** * A more human-friendly version of Arrays#toString(byte[]) that uses hex representation. */ private static String byteArrayToString(byte[] bytes) { StringBuilder builder = new StringBuilder("["); for (int i = 0; i < bytes.length; i++) { if (i > 0) builder.append(", "); builder.append(byteToString(bytes[i])); } return builder.append(']').toString(); } private static String byteToString(Byte b) { if (b == null) return "null"; return "0x" + String.format("%02x", b).toUpperCase(Locale.US); } private static String arrayToString(Object[] array) { StringBuilder buf = new StringBuilder(); arrayToString(array, buf, new HashSet
122
1
public class ObjToStringUtils {
2
3
private ObjToStringUtils() {
4
throw new AssertionError("No instances.");
5
}
6
7
public static String toString(Object obj) {
8
if (obj == null) return "null";
9
if (obj instanceof CharSequence) return '"' + printableToString(obj.toString()) + '"';
10
11
Class cls = obj.getClass();
12
if (Byte.class == cls) return byteToString((Byte) obj);
13
if (cls.isArray()) return arrayToString(cls.getComponentType(), obj);
14
15
return obj.toString();
16
}
17
18
private static String printableToString(String string) {
19
int length = string.length();
20
StringBuilder builder = new StringBuilder(length);
21
for (int i = 0; i < length; ) {
22
int codePoint = string.codePointAt(i);
23
switch (Character.getType(codePoint)) {
24
case Character.CONTROL:
25
case Character.FORMAT:
26
case Character.PRIVATE_USE:
27
case Character.SURROGATE:
28
case Character.UNASSIGNED:
29
switch (codePoint) {
30
case '\n':
31
builder.append("\\n");
32
break;
33
case '\r':
34
builder.append("\\r");
35
break;
36
case '\t':
37
builder.append("\\t");
38
break;
39
case '\f':
40
builder.append("\\f");
41
break;
42
case '\b':
43
builder.append("\\b");
44
break;
45
default:
46
builder.append("\\u").append(String.format("%04x", codePoint).toUpperCase(Locale.US));
47
break;
48
}
49
break;
50
default:
51
builder.append(Character.toChars(codePoint));
52
break;
53
}
54
i += Character.charCount(codePoint);
55
}
56
return builder.toString();
57
}
58
59
private static String arrayToString(Class cls, Object obj) {
60
if (byte.class == cls) return byteArrayToString((byte[]) obj);
61
if (short.class == cls) return Arrays.toString((short[]) obj);
62
if (char.class == cls) return Arrays.toString((char[]) obj);
63
if (int.class == cls) return Arrays.toString((int[]) obj);
64
if (long.class == cls) return Arrays.toString((long[]) obj);
65
if (float.class == cls) return Arrays.toString((float[]) obj);
66
if (double.class == cls) return Arrays.toString((double[]) obj);
67
if (boolean.class == cls) return Arrays.toString((boolean[]) obj);
68
return arrayToString((Object[]) obj);
69
}
70
71
/**
72
* A more human-friendly version of Arrays#toString(byte[]) that uses hex representation.
73
*/
74
private static String byteArrayToString(byte[] bytes) {
75
StringBuilder builder = new StringBuilder("[");
76
for (int i = 0; i < bytes.length; i++) {
77
if (i > 0) builder.append(", ");
78
builder.append(byteToString(bytes[i]));
79
}
80
return builder.append(']').toString();
81
}
82
83
private static String byteToString(Byte b) {
84
if (b == null) return "null";
85
return "0x" + String.format("%02x", b).toUpperCase(Locale.US);
86
}
87
88
private static String arrayToString(Object[] array) {
89
StringBuilder buf = new StringBuilder();
90
arrayToString(array, buf, new HashSet());
91
return buf.toString();
92
}
93
94
private static void arrayToString(Object[] array, StringBuilder builder, Setseen) {
95
if (array == null) {
96
builder.append("null");
97
return;
98
}
99
100
seen.add(array);
101
builder.append('[');
102
for (int i = 0; i < array.length; i++) {
103
if (i > 0) builder.append(", ");
104
105
Object element = array[i];
106
if (element == null) {
107
builder.append("null");
108
} else {
109
Class elementClass = element.getClass();
110
if (elementClass.isArray() && elementClass.getComponentType() == Object.class) {
111
Object[] arrayElement = (Object[]) element;
112
if (seen.contains(arrayElement)) builder.append("[...]");
113
else arrayToString(arrayElement, builder, seen);
114
} else {
115
builder.append(toString(element));
116
}
117
}
118
}
119
builder.append(']');
120
seen.remove(array);
121
}
122
}
2018-4-27