它这是要重定向过去了,重定向拿这个请求头没有啥用吧,具体得看下RFC文档是怎么解释的
没有足够的数据
(゚∀゚ )
暂时没有任何数据
chenshun 回答了问题 · 2020-12-19
它这是要重定向过去了,重定向拿这个请求头没有啥用吧,具体得看下RFC文档是怎么解释的
它这是要重定向过去了,重定向拿这个请求头没有啥用吧,具体得看下RFC文档是怎么解释的
关注 3 回答 2
chenshun 赞了回答 · 2020-11-08
最近正好在看JVM相关的机制,这个现象挺有意思,研究了一下。首先你的代码比较乱,有很多无用信息,我整理了一下:
public class Main {
static {
System.out.println("Main static block");
}
public static void main(String[] args) {
Helper.staticMethod();
}
}
public class Helper {
static {
System.out.println("Helper static block");
}
public static void staticMethod() {
System.out.println("Helper#staticMethod");
}
public void test(XXXManager ab, XXXSubInterface xxxSubInterface) {
ab.setXXX(xxxSubInterface);
}
}
public interface XXX {}
public interface XXXSubInterface extends XXX {}
public interface XXXManager {
void setXXX(XXX xxx);
}
添加JVM -varbose参数,输出是:
[Loaded Main from file:/Users/mazhibin/project/java/loadclasstest/target/classes/]
Main static block
[Loaded Helper from file:/Users/mazhibin/project/java/loadclasstest/target/classes/]
[Loaded XXX from file:/Users/mazhibin/project/java/loadclasstest/target/classes/]
Helper static block
Helper#staticMethod
main方法执行Helper.staticMethod()
,而staticMethod
方法里面只有打印语句,所以理论上应该只要加载Helper就够了,为了什么会加载到XXX
类,好,即使接受可以加载类的情况,为什么是XXX
,而不是直接使用到的XXXManager
或者XXXSubInterface
。你提的问题大概是这个场景。
在说探索过程之前先说下最终结论:在验证Helper类时,校验到setXXX
方法,会验证XXXSubInterface
类型是否可以赋值到XXX
类型,这个时候就会去加载XXX
类,然后因为XXX
是一个接口,代码中认为接口和Object类是一样的,什么类型都可以赋值给接口类型,所以就直接校验成功,就没有去加载XXXSubInterface类了。
然后在介绍一下类加载的过程。首先要清楚一点,“类加载”和“加载”是两个概念,“加载”是“类加载”(Class Loading)的一个步骤。类加载包含加载、链接、初始化这三个步骤,其中链接又分为验证、准备、解析这三个子步骤。加载是根据特定名称查找类或接口类型的二进制表示(Binary Representation),并由此二进制表示创建类或接口的过程。链接是为了让类或接口可以被 Java 虚拟机执行,而将类或接口并入虚拟机运行时状态的过程。类或接口的初始化是指执行类或接口的初始化方法<clinit>。
类加载复杂就复杂在这些步骤执行的时机,并且其中的子步骤还不一定按顺序执行,加载、验证、准备、初始化和卸载这5个阶段的顺序是固定的,需要按这个顺序开始(允许交叉),而解析则不一定,有可能在初始化之后才进行。
那什么时候会开始加载步骤?Java虚拟机规范没有强制要求,但是对于初始化阶段,则明确规定了5种情况需要对类进行初始化,分别是:
结合上面说的,加载、验证、准备、初始化和卸载这5个阶段的顺序是固定的,需要按这个顺序开始(允许交叉),我们确定了初始化的时机,那么在初始化时或者之前,就要开始加载了。同时还有一点,也就是这个问题涉及到的场景,一个类在验证这个步骤时,会验证A类的字节码,其中可能会涉及到所以来的其他类,根据验证的具体需求,可能需要加载其他类。而这个问题具体的校验过程就是一个方法调用,涉及到类型转换赋值(传入子接口类型,需要转为父接口类型),这种情况下需要加载类型来判断是否可以进行赋值,按理是需要加载赋值左右两边的类型的,但是因为左边类型是接口,被认为都可以赋值,所以没有加载右边类型。
接下来说下是如何得到上述结论的,首先类加载的流程是Java虚拟机规范中有写的,可以看看。而具体为什么只加载了XXX类,则要调试JVM源码才能知道了。最近因为有看JVM源码,所以编译了并可以进行GDB调试,然后添加条件断点:break SystemDictionary::load_instance_class if strncmp(class_name._body, "XXX", 3) == 0
,表示在加载XXX
类的时候停下来,接着分析调用堆栈:
// 加载Main类
[Loaded Main from file:/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/mzb/]
// 执行Main的初始化方法
Main static block
// 因为要执行Helper.staticMethod()语句,触发加载Helper流程
[Loaded Helper from file:/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/mzb/]
// 接着断点停在了加载XXX接口的函数调用上
Breakpoint 1, SystemDictionary::load_instance_class (class_name=0x7ffff01a5338, class_loader=..., __the_thread__=
0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:1345
1345 instanceKlassHandle nh = instanceKlassHandle(); // null Handle
// 查看函数调用栈,分析为什么会需要加载XXX类(要从下往上看)
(gdb) bt
#0 SystemDictionary::load_instance_class (class_name=0x7ffff01a5338, class_loader=...,
__the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:1345
#1 0x00007ffff7578062 in SystemDictionary::resolve_instance_class_or_null (name=0x7ffff01a5338,
class_loader=..., protection_domain=..., __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:755
#2 0x00007ffff7576a17 in SystemDictionary::resolve_or_null (class_name=0x7ffff01a5338, class_loader=...,
protection_domain=..., __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:203
#3 0x00007ffff75765ad in SystemDictionary::resolve_or_fail (class_name=0x7ffff01a5338, class_loader=...,
protection_domain=..., throw_error=true, __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:145
// 上面就开始了加载流程了
// 下文分析了这里是在校验XXXSubInterface类型是否可以赋值到XXX
// 下文分析了为什么需要加载XXX接口,而不需要加载XXXSubInterface接口
#4 0x00007ffff75df854 in VerificationType::is_reference_assignable_from (this=0x7ffff7fe5770, from=..., context=
0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verificationType.cpp:62
#5 0x00007ffff753bc37 in VerificationType::is_assignable_from (this=0x7ffff7fe5770, from=...,
context=0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verificationType.hpp:289
#6 0x00007ffff75eba80 in StackMapFrame::pop_stack (this=0x7ffff7fe5e20, type=..., __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/classfile/stackMapFrame.hpp:181
#7 0x00007ffff75ea155 in ClassVerifier::verify_invoke_instructions (this=0x7ffff7fe60e0, bcs=0x7ffff7fe5dc0,
code_length=18, current_frame=0x7ffff7fe5e20, this_uninit=0x7ffff7fe5f1f, return_type=..., cp=...,
__the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:2064
// 下文分析了这里是因为验证Helper.test(LXXXManager;)V这个方法导致的加载XXX接口
#8 0x00007ffff75e64ea in ClassVerifier::verify_method (this=0x7ffff7fe60e0, m=...,
__the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:1237
#9 0x00007ffff75e0e75 in ClassVerifier::verify_class (this=0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:312
#10 0x00007ffff75e04b1 in Verifier::verify (klass=..., mode=Verifier::ThrowException, should_verify_class=true,
__the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:127
#11 0x00007ffff71f5d8c in instanceKlass::verify_code (this_oop=..., throw_verifyerror=true,
__the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:214
// 上面的方法是验证的过程,也就是校验字节码是否正确,是否合法
#12 0x00007ffff71f6425 in instanceKlass::link_class_impl (this_oop=..., throw_verifyerror=true,
__the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:321
#13 0x00007ffff71f5e73 in instanceKlass::link_class (this=0xfb01ab80, __the_thread__=0x7ffff0028000)
// 在类或接口被初始化之前,它必须被链接过,也就是经过验证、准备阶段,且有可能已经被解析完成了。所以上面是链接的流程
at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:230
#14 0x00007ffff71f691f in instanceKlass::initialize_impl (this_oop=..., __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:397
#15 0x00007ffff71f5cca in instanceKlass::initialize (this=0xfb01ab80, __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:199
// 从下面几个调用栈,可以看出Helper.staticMethod()语句对应的invoke_static指令触发了解析流程
// 因为JVM规范说了,在执行new,getstatic,putstatic或invokestatic这些指令时,需要确保已经进行初始化流程
#16 0x00007ffff7383903 in LinkResolver::resolve_static_call (result=..., resolved_klass=...,
method_name=0x7ffff01a4908, method_signature=0x7ffff0051f28, current_klass=..., check_access=true,
initialize_class=true, __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:629
#17 0x00007ffff738599f in LinkResolver::resolve_invokestatic (result=..., pool=..., index=65537,
__the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1077
#18 0x00007ffff738575c in LinkResolver::resolve_invoke (result=..., recv=..., pool=..., index=65537,
byte=Bytecodes::_invokestatic, __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1050
#19 0x00007ffff7239c58 in InterpreterRuntime::resolve_invoke (thread=0x7ffff0028000,
bytecode=Bytecodes::_invokestatic)
at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:686
#20 0x00007fffed039d15 in ?? ()
#21 0x00007fffed039bc3 in ?? ()
#22 0x00007ffff7fe6868 in ?? ()
#23 0x00000000fb01a170 in ?? ()
#24 0x00007ffff7fe68b8 in ?? ()
#25 0x00000000fb01a538 in ?? ()
#26 0x0000000000000000 in ?? ()
// 我们到第16号栈帧中,可以看出的确是正要执行Helper.staticMethod()方法
(gdb) f 16
#16 0x00007ffff7383903 in LinkResolver::resolve_static_call (result=..., resolved_klass=...,
method_name=0x7ffff01a4908, method_signature=0x7ffff0051f28, current_klass=..., check_access=true,
initialize_class=true, __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:629
629 resolved_klass->initialize(CHECK);
(gdb) p Klass::cast(current_klass.obj())->external_name()
$1 = 0x7fffcc002548 "Main"
(gdb) p *method_name._body@method_name._length
$5 = "staticMethod"
// 我们到第8号栈帧中,可以看出是因为验证Helper.test(LXXXManager;)V这个方法导致的加载XXX接口
(gdb) f 8
#8 0x00007ffff75e64ea in ClassVerifier::verify_method (this=0x7ffff7fe60e0, m=...,
__the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:1237
1237 &this_uninit, return_type, cp, CHECK_VERIFY(this));
(gdb) p m->name_and_sig_as_C_string()
$6 = 0x7fffcc002568 "Helper.test(LXXXManager;)V"
// 我们到第4号栈帧中,可以看出是在校验XXXSubInterface类型是否可以赋值到XXX
(gdb) f 4
#4 0x00007ffff75df854 in VerificationType::is_reference_assignable_from (this=0x7ffff7fe5770, from=...,
context=0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)
at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verificationType.cpp:62
62 Handle(THREAD, klass->protection_domain()), true, CHECK_false);
(gdb) p *from.name()._body@from.name()._length
$10 = "XXXSubInterface"
(gdb) p *name()._body@name()._length
$11 = "XXX"
上面分析出了加载是因为验证的流程,具体触发加载的验证代码如下,是验证赋值操作是否可以成功的:
// hotspot/src/share/vm/classfile/verificationType.cpp
bool VerificationType::is_reference_assignable_from(
const VerificationType& from, ClassVerifier* context, TRAPS) const {
instanceKlassHandle klass = context->current_class();
if (from.is_null()) {
// null is assignable to any reference
return true;
} else if (is_null()) {
return false;
} else if (name() == from.name()) {
return true;
} else if (is_object()) {
// 如果赋值语句左边类型是对象,判断是否是Object,如果是那都可以赋值成功,返回true
// We need check the class hierarchy to check assignability
if (name() == vmSymbols::java_lang_Object()) {
// any object or array is assignable to java.lang.Object
return true;
}
// 否则需要把左边类型加载进来 <=========================== 加载行为发生在这里
klassOop obj = SystemDictionary::resolve_or_fail(
name(), Handle(THREAD, klass->class_loader()),
Handle(THREAD, klass->protection_domain()), true, CHECK_false);
KlassHandle this_class(THREAD, obj);
// 如果左边类型是接口
if (this_class->is_interface()) {
// 这里注释说明了,认为接口和Object一样,都可以赋值成功所以返回true
// We treat interfaces as java.lang.Object, including
// java.lang.Cloneable and java.io.Serializable
return true;
} else if (from.is_object()) {
// 否则要把赋值赋予右边的类型也加载进来
klassOop from_class = SystemDictionary::resolve_or_fail(
from.name(), Handle(THREAD, klass->class_loader()),
Handle(THREAD, klass->protection_domain()), true, CHECK_false);
return instanceKlass::cast(from_class)->is_subclass_of(this_class());
}
} else if (is_array() && from.is_array()) {
VerificationType comp_this = get_component(context, CHECK_false);
VerificationType comp_from = from.get_component(context, CHECK_false);
if (!comp_this.is_bogus() && !comp_from.is_bogus()) {
return comp_this.is_assignable_from(comp_from, context, CHECK_false);
}
}
return false;
}
这样就分析完了,你尝试把XXX和XXXSubInterface改成class,可以发现两个都会被加载,符合上面这个代码的逻辑。
main方法执行Helper.staticMethod(),而staticMethod方法里面只有打印语句,所以理论上应该只要加载Helper就够了,为了什么会加载到XXX类,好,即使接受可以加载类的情况,为什么是XXX,而不是直接使用到的XXXManager或者XXXSubInterface。你提的问题大概是这个场景。
关注 2 回答 1
chenshun 回答了问题 · 2020-09-21
java.lang.NoSuchMethodError: kotlin.collections.ArraysKt.copyInto([B[BIII)[B
是不是jar包冲突了
java.lang.NoSuchMethodError: kotlin.collections.ArraysKt.copyInto([B[BIII)[B
关注 2 回答 1
chenshun 回答了问题 · 2020-08-02
怎么不尝试在a和b建立一个隧道呢,linux是比较稳定的,windows尝试下来不太行,基本上每隔个吧小时就要重启下隧道
怎么不尝试在a和b建立一个隧道呢,linux是比较稳定的,windows尝试下来不太行,基本上每隔个吧小时就要重启下隧道
关注 3 回答 2
chenshun 提出了问题 · 2020-07-18
https://github.com/plison/ope...
http://www.opendial-toolkit.net/
我是做Java的,但是这种却是没有接触过,不知道有没有老哥接触过,希望可以指点指点.
git地址 [链接] 官网 [链接] 我是做Java的,但是这种却是没有接触过,不知道有没有老哥接触过,希望可以指点指点.
关注 1 回答 0
chenshun 回答了问题 · 2020-07-08
额,不是服务启动写入到nacos,gateway读取到nacos中到路由吗
好像Route是用ID来区分到,你尝试吧ID的service-one换成service试试
额,不是服务启动写入到nacos,gateway读取到nacos中到路由吗 好像Route是用ID来区分到,你尝试吧ID的service-one换成service试试
关注 4 回答 2
chenshun 赞了文章 · 2020-06-03
Excel
导出思路参考文章 - angularjs使用指令实现table导出csv
项目中使用到了导出Excel
功能,潘老师已经在博客中将表格导出为csv
的技术难点实现,剩下的,只是根据实际的业务需求对该指令进行完善。
大体遇到了以下几个问题:
因为原指令默认导出表格所有的内容,第一次直接导出时,发现直接将查看、编辑这几个按钮的HTML
代码都导出去了,我们要的应该是人员信息和人员资质信息。
指令添加参数:有效数据列数。
如图,这里的有效数据列就是市、区县等等一直到有效期至,一共15
列,作为参数传给导出指令。
self.exportCols = scope.exportCols;
for (let i = 0; i < table.rows.length; i ++) {
// 获取每行的数据
var rowData = table.rows[i].cells;
// 如果该列为正常列,循环内容
for (let j = 0; j < self.exportCols; j ++) {
// 如果该列不存在,则赋值为空内容
var data = rowData[j] === undefined ? '' : rowData[j].innerHTML;
// 拼接内容,用逗号分隔
csvString = csvString + data + ",";
}
// 删除最后多余的逗号
csvString = csvString.substring(0, csvString.length - 1);
// 每行末尾添加\n进行换行
csvString = csvString + "\n";
}
原来内层循环的是所有数据列,现在改成传入的有效数据列数。
以这个图为例,第一行是有15
列的,但是第二行的数据只有4
列,因为其余的列是第一行设置rowspan
显示的,实际上第二行的tr
中只有人员资质4
个td
。
这就导致导出的时候第二行只有四个数据,并且是从左对齐的,所以我们需要在跨行的列添加空数据。
加一个条件判断,传入出现被跨行的列时剩余列的长度,用于区分是正常列还是被跨行的列。
if (self.isNormalItem(rowData)) {
// 如果该列为正常列,循环内容
for (let j = 0; j < self.exportCols; j++) {
// 如果该列不存在,则赋值为空内容
var data = rowData[j] === undefined ? '' : rowData[j].innerHTML;
// 拼接内容,用逗号分隔
csvString = csvString + data + ",";
}
} else {
// 根据有效数据中的跨行列拼接空数据
for (let j = 0; j < self.multipleRowCols; j ++) {
csvString = csvString + ",";
}
// 根据有效数据减去跨行列,获得非跨行列,并拼接数据
for (let index = 0; index < self.exportCols - self.multipleRowCols; index ++) {
csvString = csvString + rowData[index].innerHTML + ",";
}
}
因为这里需要为添加资格证按钮添加一个空列,导出的时候也会出现空列,效果并不好。
所以需要进行判断,当出现被跨行的列时,需要判断它的上一行的最后四项内容是否为空,如果为空,就应该把这个列的内容放到上一行。
if (self.isNormalItem(rowData)) {
// 如果该列为正常列,循环内容
...
} else if (self.isFirstItem(csvString)) {
// 如果当前跨行资质是当前人员的第一个资质,则将该资质添加到上一行
// 因为能添加人员资质的界面第一行的人员资质是空的,所以需要将该资
// 质添加到上一行
csvString = self.addDataToPreString(rowData, csvString);
} else {
// 根据有效数据中的跨行列拼接空数据
...
}
再加一个else
,如果当前被跨行的列是当前人员的第一个人员资质的话,就把这一行的人员资质数据添加到上一行人员资质的空白处。
下面是方法的具体实现:
// 判断当前表格中资质是否是第一项资质
self.isFirstItem = function(string) {
// 分隔字符串
var csvArray = string.split(',');
// 循环已有字符数组的倒数四个元素,判断是否有效
for (var i = csvArray.length - 1; i >= csvArray.length - 4; i--) {
if (self.isValid(csvArray[i])) {
return false;
}
}
return true;
};
// 判断当前字符是否有效
// 空字符和\n视为无效
self.isValid = function(char) {
if (char === '' || char === '\n') {
return false;
} else {
return true;
}
};
// 添加数据到字符串的上一行
self.addDataToPreString = function(data, string) {
// 字符串分隔为数据
var csvArray = string.split(',');
// 循环,将有效的数据添加到上一行
for (let i = 0; i < self.validNoMultipleRowCols; i++) {
csvArray[csvArray.length - self.validNoMultipleRowCols + i] = data[i].innerHTML;
}
// 数组转换为字符串
return csvArray.join(',');
};
最终实现导出的csv
效果:
Excel导出思路 参考文章 - angularjs使用指令实现table导出csv 项目中使用到了导出Excel功能,潘老师已经在博客中将表格导出为csv的技术难点实现,剩下的,只是根据实际的业务需求对该指令进行完善。 大体遇到了以下几个问题: 功能按钮不导出 因为原指令默认导出表...
赞 3 收藏 1 评论 0
查看全部 个人动态 →
(゚∀゚ )
暂时没有
(゚∀゚ )
暂时没有
注册于 2017-10-20
个人主页被 810 人浏览
推荐关注