我们在生产环境上遇到问题,解决方法 我目前知道的有三种:
1. 远程调试。
优点:像本地开发一样,一步一步调试,调试方便。
缺点:需要本地代码跟远程代码,一致;编译的时候,需要有行号;生产环境启动的时候,需配置了需要的参数:例如 -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y , 没有这些参数,可以添加参数,然后重启即可。
2. 热部署。
热部署,即 修改了class文件,可以立即加载。与重启差不多,只是不重启,可以保留运行中的一些环境。
优点:修改文件立即生效。
缺点:改动了服务。
3. 使用自定义的类加载器,加载特定class文件。
如果需要我们不动任何的生产环境代码,不能重启呢?或者 我们只是增加一个类,查询下某些信息即可,要以重启环境为代价吗?
第三种方法,即可解决此问题。
自定义一个类加载器,继承自 ClassLoader,根据传入的class文件字节流,加载类。
public class HotSwapClassLoader extends ClassLoader {
public HotSwapClassLoader(){
super(HotSwapClassLoader.class.getClassLoader());
}
public Class loadByte(byte[] classByte){
return defineClass(null,classByte,0,classByte.length);
}
}
JavaClassExecuter类 根据传入的字节流,替换输入输出的打印位置,方便将输出结果打印到自己需要的位置,而不在项目日志中打印。
public class JavaClassExecuter {
public static String execute(byte[] classByte){
HackSystem.clearBuffer();
ClassModifier cm = new ClassModifier(classByte);
Package pack = HackSystem.class.getPackage();
String packName=pack.getName().replaceAll("\\.","/");
byte[] modiBytes = cm.modifyUTF8Constant("java/lang/System",packName +"/HackSystem");
HotSwapClassLoader loader = new HotSwapClassLoader();
String errmsg = "no error";
try {
Class clazz = loader.loadByte(modiBytes);
Method method =clazz.getMethod("main",new Class[] {String[].class});
method.invoke(null,new String[]{null});
} catch (NoSuchMethodException e) {
e.printStackTrace();
errmsg = e.getMessage();
} catch (IllegalAccessException e) {
e.printStackTrace();
errmsg = e.getMessage();
} catch (InvocationTargetException e) {
e.printStackTrace();
errmsg = e.getMessage();
}
if("".equals(errmsg)){
return "error:" + errmsg;
} else {
return HackSystem.getBufferString();
}
}
}
HackSystem类,输入输出流方法。
public class HackSystem {
public final static InputStream in = System.in;
private static ByteArrayOutputStream buffer = new ByteArrayOutputStream();
public final static PrintStream out =new PrintStream(buffer);
public final static PrintStream err = out;
public static String getBufferString(){
return buffer.toString();
}
public static void clearBuffer(){
buffer.reset();
}
public static void setSecurityManager(final SecurityManager s){
System.setSecurityManager(s);
}
public static SecurityManager getSecurityManager(){
return System.getSecurityManager();
}
public static long currentTimeMillis(){
return System.currentTimeMillis();
}
public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length){
System.arraycopy(src,srcPos,dest,destPos,length);
}
public static int identityHashCode(Object x){
return System.identityHashCode(x);
}
}
ClassModifier类,替换输入输出流类
public class ClassModifier {
/**
* class文件中,常量池的起始偏移
*/
private static final int CONSTANT_POOL_COUNT_INDEX=8;
/**
* CONSTANT_UTF-8_INFO 常量的tag标志
*/
private static final int CONSTANT_Utf8_info=1;
/**
* 常量池中11种常量所占的长度,CONSTANT_UTF8_INFO 型常量除外,因为它不是定长的
*/
private static final int[] CONSTANT_ITEM_LENGTH = {-1,-1,-1,5,5,9,9,3,3,5,5,5,5};
private static final int u1=1;
private static final int u2=2;
private byte[] classByte;
public ClassModifier(byte[] classByte){
this.classByte = classByte;
}
/**
* 修改常量池中CONSTANT_UTF8_INFO常量的内容
*/
public byte[] modifyUTF8Constant(String oldStr,String newStr){
int cpc = getConstantPoolCount();
int offset=CONSTANT_POOL_COUNT_INDEX+u2;
for (int i = 0; i<cpc;i++){
int tag =ByteUtils.bytes2Int(classByte,offset,u1);
if(tag == CONSTANT_Utf8_info){
int len = ByteUtils.bytes2Int(classByte,offset+u1,u2);
offset +=(u1+u2);
String str = ByteUtils.bytes2String(classByte,offset,len);
if(str.equalsIgnoreCase(oldStr)){
byte[] strBytes=ByteUtils.string2Bytes(newStr);
byte[] strLen = ByteUtils.int2Bytes(newStr.length(),u2);
classByte = ByteUtils.bytesReplace(classByte,offset - u2,u2,strLen);
classByte = ByteUtils.bytesReplace(classByte,offset ,len,strBytes);
return classByte;
} else {
offset +=len;
}
}else{
offset += CONSTANT_ITEM_LENGTH[tag];
}
}
return classByte;
}
/**
* 获取常量池中常量的数量
* @return
*/
public int getConstantPoolCount(){
return ByteUtils.bytes2Int(classByte,CONSTANT_POOL_COUNT_INDEX,u2);
}
}
ByteUtils 数据处理工具
public class ByteUtils {
public static int bytes2Int(byte[] b,int start,int len){
int sum =0;
int end=start + len;
for(int i=start;i<end;i++){
int n=((int)b[i]) & 0xff;
n<<=(--len) * 8;
sum = n+sum;
}
return sum;
}
public static byte[] int2Bytes(int value,int len){
byte[] b = new byte[len];
for(int i=0;i<len;i++){
b[len-i-1] =(byte) ((value>>8*i) &0xff);
}
return b;
}
public static String bytes2String(byte[] b,int start,int len){
return new String(b,start,len);
}
public static byte[] string2Bytes(String str){
return str.getBytes();
}
public static byte[] bytesReplace(byte[] originalBytes,int offset,int len,byte[] replaceBytes){
byte[] newBytes = new byte[originalBytes.length + (replaceBytes.length - len)];
System.arraycopy(originalBytes,0,newBytes,0,offset);
System.arraycopy(replaceBytes,0,newBytes,offset,replaceBytes.length);
System.arraycopy(originalBytes,offset+len,newBytes,offset+replaceBytes.length,originalBytes.length-offset-len);
return newBytes;
}
}
Java类写好后,写一个jsp文件
<%@ page import="java.lang.*" %>
<%@ page import="java.io.*" %>
<%@ page import="com.dong.remoteExecute.*" %>
<%
InputStream is = new FileInputStream("D:\\down\\RemoteExecuterTest.class");
byte[] b = new byte[is.available()];
is.read(b);
is.close();
out.println("<textarea style='width:1000px;height : 800px' >" );
out.println("here is result:");
out.println(JavaClassExecuter.execute(b));
out.println("</textarea>");
%>
jsp中读取 一个 特定的 class文件(示例中的 RemoteExecuterTest),即我们需要执行的文件,位置不限,因为我们只需要找到文件并读文件流;jsp文件放到我们项目的页面部署位置其他class文件放到项目中class的目录下。
读取class文件字节流后,将字节流传入 JavaClassExecuter 类,进行 输入输出流的替换,类加载,反射调用,返回结果。
这样,即不影响生产环境,也不用重启,连输出结果都不会影响日志文件,毫无痕迹。且类在用完后还可以被回收。