【笔记】Vue学习

[笔记系列文章说明]: 该类型的文章是笔者学习过程中整理的学习笔记.

Vue 是一种JS,HTML,CSS渲染框架,通过其独特的绑定渲染方式,使页面编程更加方便,容易(JS语法糖).

生态圈

  1. Vue Router
  2. Vuex
  3. Vue Loader
  4. Vue Test Utils
  5. Vue Dev-Tools
  6. Vue CLI
  7. Vetur
阅读更多

【笔记】ECMAScript6学习

[笔记系列文章说明]: 该类型的文章是笔者学习过程中整理的学习笔记.

ECMAScript6 是JS的版本升级,主流浏览器和手机固件都已支持解析. 对过去来说的JS其实也是ECMAcript, 不过是老版本,名字也没有变. 6版本对JS的编写规则进行了封装, 形成了一套新的编写方式. 对于不支持解析的容器上,
可以通过EC6转EC5工具进行转换后运行(如Babel)

背景故事

ECMAScript 和 JavaScript 的关系 ECMAScript是发布国际标准的组织,JavaScript是Netscape创建的,后提交给ECMAScript ECMAScript= 标准 JavaScript= 实现(
3.0)
ECMAScript的版本 从3.0到 3.1=5 到6 中间版本没有通过…

语法

新的关键字

一, let(只作用于代码块内的变量声明关键字)

1
2
3
{
let name = "脆饼";
}

1, 适用于局部使用的变量: for循环, 临时变量 2, let非顺序执行将报错,var将显示undefined(var的变量提升未执行先使用将导致undefined)
3, let在区块中后者覆盖前者,前者将报错失效(单位内唯一性)
4, var中作用域为全局和方法中, ECMA6增加区块作用域,使用let实现

二, const(常量声明且初始化)

1
const DEFAULT_VALUE = 3;

1, 声明后必须赋值 2, 再赋值将报错 3, 支持区块作用域 4, 地址级别的常量,类似JAVA中的对象常量

阅读更多

【笔记】markdown学习

[笔记系列文章说明]: 该类型的文章是笔者学习过程中整理的学习笔记.

Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档.(文档格式语言)

语法规则

标题

一, 使用(=,-)标识一级,二级标题(数量大于等于1)

1
2
3
4
一级标题
=========
二级标题
---------

一, 使用(#1-6个)标识

1
2
3
4
5
6
# 一
## 二
### 三
#### 四
##### 五
###### 六
阅读更多

【笔记】对接接口定义标准化规范

[笔记系列文章说明]: 该类型的文章是笔者学习过程中整理的学习笔记.

与不绝对信任的机构或企业交换数据的时候, 通常需要考虑严格的数据安全问题, 我在这里定义了一种, 完整版就不放了, 列一些关键的点

文档定义

一, 接口访问定义

请求方式: http请求post方式
请求类型: application/json;charset=utf-8
返回类型: application/json;charset=utf-8
域名:

二, 参数定义

  1. 请求头信息(Header)
参数 类型 说明
plantformCode String 商家授权码 如:
timeStamp Long 时间戳 如:1626255054672 (相当于2021-07-14 17:30:54)
sign String 签名 8d2472fac5a4a1879bde18075f7a879e
  1. 请求体内容(Body),具体内容依据具体接口
参数 类型 说明
messageId String 消息唯一标识
data String JSON字符串
timeStamp Long 时间戳
dataSource String 数据来源,供应商唯一标识 如BOLIAN
阅读更多

【笔记】GIT代码管理&提交规范

[笔记系列文章说明]: 该类型的文章是笔者学习过程中整理的学习笔记.

本篇整理一下项目中用到的管理代码的规范

一, 项目分支结构

分支环境:
开发环境:dev
测试环境:test
预生产环境:prev
生产环境:master

根据需要会分出test_2.0、test_bug等次级分支,测试到上线都会在这个分支进行。最终会同步到test、master分支。

二, 需求&BUG分支创建与命名

分支代码从prev拉取

分支命名规则: 项目名++日期++类型++开发名称/bug号/bug名称(++修复人标识)

需求开发分支命名:

1
2
3
4
5
stc_20210629_dev_version2



stc_20210629_dev_version2_lxz
阅读更多

【笔记】项目管理工具(持续更新)

[笔记系列文章说明]: 该类型的文章是笔者学习过程中整理的学习笔记.

开发工具

1
2
3
IDE: idea
idea插件: Lombok, SonarLint, Alibaba Java Coding Guidelines

项目管理工具

1
2
3
4
5
6
7
8
代码管理: git, gitlab
需求管理平台: 禅道
项目发布工具: jenkins
日志收集展示: logstash收集 + elasticsearch检索 + kibana展示 简称: ELK
接口API文档管理: YAPI
数据库平台查询: yearning
配置管理中心: necos, disconf, stcconfig
系统监控平台: CAT

框架

1
2
3
4
5
6
远程调用体系: 
1,SpringCloud(注册Eureka, 负载均衡ribbon, 服务注册Feign, Hystix服务治理熔断降级隔离监控, zuul网关过滤)
2,Dubbo(zookeeper/necos注册, mock降级, retry熔断, 配置最大并发数)
熔断降级框架: Sentinel, Hystrix, nginx(对你没有看错,可以控制http请求)
分布式事务框架: tx-lcn(停更), seata 原理: 托管事务管理,利用记录日志控制一致提交回滚
seata: https://www.cnblogs.com/leeego-123/p/12677124.html

【笔记】logback+MDC全解析

[笔记系列文章说明]: 该类型的文章是笔者学习过程中整理的学习笔记.

logback的配置详解

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
<?xml version="1.0" encoding="UTF-8"?>
<!-- 表达式一览 -->
<!-- -X号: X信息输出时左对齐;-->
<!-- %p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,-->
<!-- %d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921-->
<!-- %r: 输出自应用启动到输出该log信息耗费的毫秒数-->
<!-- %c: 输出日志信息所属的类目,通常就是所在类的全名-->
<!-- %t: 输出产生该日志事件的线程名-->
<!-- %l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main (TestLog4.java:10)-->
<!-- %x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像Java servlets这样的多客户多线程的应用中。-->
<!-- %%: 输出一个”%”字符-->
<!-- %F: 输出日志消息产生时所在的文件名称-->
<!-- %L: 输出代码中的行号-->
<!-- %m: 输出代码中指定的消息,产生的日志具体信息-->
<!-- %n: 输出一个回车换行符,Windows平台为”\r\n”,Unix平台为”\n”输出日志信息换行-->

<!-- scan属性未true时,如果配置文档发生改变将会进行重新加载 -->
<!-- scanPeriod属性设置监测配置文件修改的时间间隔,默认单位为毫秒,在scan为true时才生效 -->
<!-- debug属性如果为true时,会打印出logback内部的日志信息 -->
<configuration scan="true" scanPeriod="5 seconds" debug="true">
<!--定义变量-->
<property name="app.name" value="app" />
<property name="log.level" value="debug" />
<property name="log.maxHistory" value="30" />
<property name="log.filePath" value="${catalina.base}/logs/webapps" />
<property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSSS} [%thread] %-5level %logger{50}-%msg%n" />
<property name="log.pattern2" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />

<!--每个appender代表一个输出-->
<!-- org.apache.log4j.ConsoleAppender(控制台)-->
<!-- org.apache.log4j.FileAppender(文件)-->
<!-- org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)-->
<!-- org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)-->
<!-- org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)-->
<!--控制台输出-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern2}</pattern>
</encoder>
</appender>

<appender name="FILE1" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<file>log/output.log</file>
<!-- 滚动策略-->
<!-- TimeBasedRollingPolicy - 基于时间-->
<!-- SizeAndTimeBasedRollingPolicy - 基于文件大小和时间-->
<!-- FixedWindowRollingPolicy - 基于自定义滚动触发 配合triggeringPolicy配置触发-->
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>rolllog/output.log.%i</fileNamePattern>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>1KB</MaxFileSize>
</triggeringPolicy>
</appender>

<appender name="FILE2" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d [%t] %-5level %logger{36} [%file : %line] - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--获取java启动时的配置值,可根据环境不同利用启动区分,java -Dscheduler.manager.server.home=/path/to-->
<file>log/${app.name}.log</file>
<!--日志滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>rolllog/${app.name}.log.%i</fileNamePattern>
</rollingPolicy>
<!-- <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">-->
<!--日志文件输出的文件名-->
<!-- <FileNamePattern>${LOG_HOME}/${LOG_NAME}.info.log.%d{yyyy-MM-dd-HH}.%i.log</FileNamePattern>-->
<!--日志文件保留天数-->
<!-- <MaxHistory>7</MaxHistory>-->
<!--日志文件的最大大小-->
<!-- <MaxFileSize>100MB</MaxFileSize>-->
<!-- </rollingPolicy>-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>1KB</MaxFileSize>
</triggeringPolicy>
</appender>
<!--为appender增加异步-->
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<!-- 添加附加的appender,最多只能添加一个 -->
<appender name="file.async" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>256</queueSize>
<includeCallerData>true</includeCallerData>
<appender-ref ref="FILE2" />
</appender>

  <!-- 使用异步来记录其他信息-->
<appender name="file.async.other" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>256</queueSize>
<includeCallerData>true</includeCallerData>
<appender-ref ref="FILE1" />
</appender>



<!--定义日志输出匹配的级别,包,默认root-->
<logger name="ch.qos" level="%{log.level}" additivity="false">
<appender-ref ref="file.async" />
<appender-ref ref="file.async.other" />
</logger>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE1" />
<appender-ref ref="FILE2" />
</root>
</configuration>

<!-- https://www.cnblogs.com/wenbronk/p/6529161.html-->
<!-- http://www.51gjie.com/javaweb/1108.html-->

MDC,NDC

什么是MDC?
Mapped Diagnostic Context
线程独立自定义Map存储上下文信息,子线程拷贝父线程Map信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.保存信息到上下文
MDC.put(key, value);

2.从上下文获取设置的信息
MDC.get(key);

3.清楚上下文中指定的key的信息
MDC.remove(key);

4.清除所有
clear()

5.输出模板,注意是大写[%X{key}]
log4j.appender.consoleAppender.layout.ConversionPattern = %-4r [%t] %5p %c %x - %m - %X{key}%n

什么是NDC?
Nested Diagnostic Context
线程独立自定义栈存储上下文信息,子线程拷贝父线程栈信息

1
2
3
4
5
6
7
8
9
10
11
1.开始调用
NDC.push(message);

2.删除栈顶消息
NDC.pop();

3.清除全部的消息,必须在线程退出前显示的调用,否则会导致内存溢出。
NDC.remove();

4.输出模板,注意是小写的[%x]
log4j.appender.stdout.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ssS}] [%x] : %m%n

logback中的MDC

单机MDC使用
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

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

public class SimpleMDC {
static public void main(String[] args) {
Logger logger = LoggerFactory.getLogger(SimpleMDC.class);
programmaticConfiguration();
logger.info("今天的天气真不错");
logger.info("是的, 雨好大啊");
MDC.put("one", "one");
MDC.put("two", "two");
logger.info("今天的天气真不错");
logger.debug("是的, 雨好大啊");
MDC.put("one", "three");
MDC.put("two", "four");
logger.info("今天的天气真不错");
logger.info("是的, 雨好大啊");
// 实际使用,通常在finally中使用MDC.remove清除掉旧值
MDC.remove("one");
MDC.remove("two");
}

/**
* 定义日志输出格式
*/
static void programmaticConfiguration() {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.reset();
PatternLayoutEncoder layout = new PatternLayoutEncoder();
layout.setContext(loggerContext);
layout.setPattern("%X{one} %X{two} - %m%n");
layout.start();
ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<ILoggingEvent>();
appender.setContext(loggerContext);
appender.setEncoder(layout);
appender.start();
ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("root");
root.addAppender(appender);
}
}

跨服务MDC使用(web + dubbo)

  1. 使用Spring拦截器HandlerInterceptorAdapter初始化MDC trace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 日志跟踪
*/
public class TraceLogInterceptor extends HandlerInterceptorAdapter {

/* 追踪ID */
private static final String TRACE_KEY = "traceId";

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
String a = UUID.randomUUID().toString().replace("-", "");
MDC.put(TRACE_KEY, a);
HttpLogLocalCache.setMsg( a);
return true;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
MDC.clear();
}
}

  1. 使用Dubbo Filter拦截, RpcContext传递
    调用者设置值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TraceConsumerFilter implements Filter {
private static Logger logger = LoggerFactory.getLogger(TraceConsumerFilter.class);
/* 追踪ID */
private static final String TRACE_KEY = "traceId";
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
LoggerFactory.getLogger(invoker.getInterface().getName()).info(" [dubbo consumer] 服务调用-请求|method:{}|参数:{}| client[ip:{}-protocol:{}]", invocation.getMethodName(), invocation.getArguments(), RpcContext.getContext().getRemoteAddressString(), invoker.getUrl().getProtocol());
try {
logger.debug("MDC-traceId:" + MDC.get(TRACE_KEY));
RpcContext.getContext().setAttachments(MDC.getCopyOfContextMap());
} catch (Exception ex) {
logger.error("dubbo consumer MDC-traceId:reset fail");
}
Result result = invoker.invoke(invocation);
LoggerFactory.getLogger(invoker.getInterface().getName()).info(" [dubbo consumer] 服务调用-返回|method:{}|结果:{}| client[ip:{}-protocol:{}]", invocation.getMethodName(), result, RpcContext.getContext().getRemoteAddressString(), invoker.getUrl().getProtocol());
return result;
}

}

消费者获取值

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
public class TraceProviderFilter implements Filter {

@Override
public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException {
RpcContext context = RpcContext.getContext();
MDC.setContextMap(context.getAttachments());
LoggerFactory.getLogger(invoker.getInterface().getName()).info(" [dubbo provider] 服务调用-接收|method:{}|参数:{}| client[ip:{}-protocol:{}]", inv.getMethodName(), inv.getArguments(), RpcContext.getContext().getRemoteAddressString(), invoker.getUrl().getProtocol());
long start = System.currentTimeMillis();
Result result = invoker.invoke(inv);
long time = System.currentTimeMillis() - start;
Object rval = result.getValue();
String logtxt = null;
if (rval != null) {
Class<?> clazz = rval.getClass();
if (List.class.isAssignableFrom(clazz)) {
int size = ((List) rval).size();
if (size > 5) {
logtxt = size + "(SIZE)";
} else {
logtxt = JSON.toJSONString(rval, new LogArgumentFilter());
}
} else if (Set.class.isAssignableFrom(clazz)) {
int size = ((Set) rval).size();
if (size > 5) {
logtxt = size + "(SIZE)";
} else {
logtxt = JSON.toJSONString(rval, new LogArgumentFilter());
}
} else {
logtxt = JSON.toJSONString(rval, new LogArgumentFilter());
}
}
LoggerFactory.getLogger(invoker.getInterface().getName()).info(" [dubbo provider] 服务调用-返回|method:{}|结果:{}| client[ip:{}-protocol:{}]", inv.getMethodName(), logtxt, RpcContext.getContext().getRemoteAddressString(), invoker.getUrl().getProtocol());
LoggerFactory.getLogger(invoker.getInterface().getName()).info(" [dubbo provider 服务性能] 统计|method:{}|耗时:{} ms|host:{}", inv.getMethodName(), time, RpcContext.getContext().getRemoteAddressString());
try {
MDC.clear();
} catch (Exception ex) {
LoggerFactory.getLogger(TraceProviderFilter.class).error("dubbo provider MDC-traceId:reset fail");
}
return result;
}
}

调用者配置 spring-dubbo-reference.xml

1
<dubbo:consumer filter="traceConsumerFilter"  version="1.0" timeout="3000" check="${dubbo.reference.check}"/>

提供者者配置 spring-dubbo-provider.xml

1
<dubbo:provider protocol="dubbo" filter="traceProviderFilter" version="1.0" timeout="3500" delay="-1" />

增加dubbo filter配置

1
2
3
4
路径: src/main/resources/META-INF/dubbo/com.alibaba.dubbo.rpc.Filter
内容:
traceConsumerFilter=com.qctrip.monitor.common.base.dubbo.spi.TraceConsumerFilter
traceProviderFilter=com.qctrip.monitor.common.base.dubbo.spi.TraceProviderFilter

【笔记】Sonarqube-使用

[笔记系列文章说明]: 该类型的文章是笔者学习过程中整理的学习笔记.

帮助理解

技术债务:没有按照规范所编写设计的代码(走捷径),或者错误代码.
这些产生的问题,需要在以后的工作中需要被修复所花的时间(即称为债务).

本地Sonarqube环境

测试地址:192.168.10.221:9000 用户名:admin 密码:admin 数据库:192.168.10.222 scholar2test/scholar2test

#####根据债务的不同特性分为8种:
下图对应:
可重用性
可移植性
可维护性
安全性
实用性
效率
可变性(低耦合?)
可靠性
可测性

产生以上技术债务的原因有大概七种(常用原因,默认规则):

1:Bug和潜在的Bug
2,违反编码标准
3,重复
4,单元测试信息
5,复杂的代码
6,结构设计
7,注释

重新强调使用sonar为了什么

产生债务原因已经找到,通过人力去分析代码,进行优化改进比较麻烦。通过工具,按照我们使用者的意愿去分析代码并且呈现出来,这些就是sonar所要做的事情。
前提:想要对项目进行严格的分析,修改,把控,需要项目具有良好的编码规范。没有这方面需求,使用sonar没有意义。

以下介绍七种产生债务原因在图形化界面上的查看与分析方式
以下会穿插部分界面的解释,以默认规则,默认插件为例。

1:Bug和潜在的Bug

根据字面意思可以理解
这种原因通常问题严重性标记为:Blocker(阻断)
通过下图观察:

阅读更多

【笔记】Sonarqube-安装

[笔记系列文章说明]: 该类型的文章是笔者学习过程中整理的学习笔记.

软件作用

根据规则分析源代码,进行数据统计,呈现在可视化界面,方便使用者对代码分析。

使用软件:

  1. 安装软件。
  2. 使用者根据具体需要定义代码分析规则。
  3. 利用Sonar软件对源代码分析上传到Sonar服务器。
  4. 访问服务器图形界面,对代码进行分析。
  5. 使用者根据分析结果,对项目进行优化调整。

帮助理解

Sonar-runner客户端:配置sonar服务端配置之后,可与sonar服务端结合分析,存储项目
maven或者Jenkins集成sonar,底层利用Sonar-runner客户端工具。

Sonar+Sonar-runner进行项目的源代码分析

准备

  1. 下载sonarqube安装文件,下载地址:http://www.sonarqube.org/downloads/
  2. 数据库一个(支持各种数据库,自带H2,速度慢,测试使用)
  3. sonar-runner工具包
安装步骤
一,sonar

1, 配置

1
2
3
4
5
配置sonarqube配置文件\conf\sonar.properties
sonar.jdbc.username=scholar2test
sonar.jdbc.password=scholar2test
sonar.jdbc.url=jdbc:oracle:thin:@192.168.10.222:1521:ora11g
sonar.web.port=9000

2, 版权问题使用oracle时自己导jdbc包
extensions/jdbc-driver/oracle目录下放入对应的oracle驱动jar包
路径oracle客户端目录 client_1\jdbc\lib下

3, 导入汉化包extensions/plugins
下载地址:http://docs.codehaus.org/display/SONAR/Chinese+Pack

4, 找到对应系统的执行目录,这里运行/bin/linux-x86-64/sonar.sh

阅读更多

【笔记】Java版本更新历史(ing)

[笔记系列文章说明]: 该类型的文章是笔者学习过程中整理的学习笔记.

历史版本特性

JDK Version 1.0

开发代号为Oak(橡树),于1996-01-23发行.

JDK Version 1.1

于1997-02-19发行.

引入的新特性包括:

引入JDBC(Java DataBase Connectivity);

支持内部类;

引入Java Bean;

引入RMI(Remote Method Invocation)

引入反射(仅用于内省)

J2SE Version 1.2

阅读更多