类加载器
装入到JVM的类由类装入器控制。JVM中构建了引导程序类装入器。由引导程序对类进行验证。
同时应用程序可以自定义类装载器(派生自java.lang.ClassLoader)。每个构造好的类某种意义上由类装载器所“拥有”。类装入器通常保留它们所装入类的映射,从而当再次请求某个类时,能通过名称找到该类。
类装载器为树状结构。树根是引导程序装载器。在类装载器处理实例请求时,会递归检查父类。这意味着类装载器中的类对其后代均可见,并且当多个类装载器可以装在某个类时,最上端的类装载器是实际装入器。
在J2EE框架中,每个j2ee程序都有自己独立的类装入器,
Tomcat类装入器
其中, Common 类装入器从 Tomcat 安装的某个特定目录的 JAR 文件进行装入,旨在用于在服务器和所有 Web 应用程序之间共享代码。Catalina 装入器用于装入 Tomcat 自己的类,而 Shared 装入器用于装入 Web 应用程序之间共享的类。最后,每个 Web 应用程序有自己的装入器用于其私有类。
引入反射
1.基于类的反射
Class[] types = new Class[]{};//传入构造器的参数
Constructor cons = Person.class.getConstructor(types);//按照参数寻找构造器
Object[] a = new Object[]{"a","b",1};//实际传入参数的值
Person p = cons.newInstance(a);//构造
缺省调用:
Person.class.getConstructor().newInstance();
2.通过反射增加字段
Field用来获取字段存储的位置
getField()和getDeclaredField()不同在于第二个可以访问私有变量。
//获取值
Field field = obj.getClass().getDeclaredField(name);
Object i = field.get(obj);
//设定值
field.set(obj,value);
3.反射获取方法
getMethod/getDeclaredMethod
命名方法的方式是驼峰法
Method method = obj.getClass().getMethod(methodName,types);//types可以缺省,为方法参数类型
Object result = method.invoke(obj,object[]);//object[]为方法参数,可以缺省
4.反射数组
通过反射来扩展数组
public Object growArray(Object array, int size) {
Class type = array.getClass().getComponentType();//获取数组中数据的类型
Object grown = Array.newInstance(type, size);//创建新的数组
System.arraycopy(array, 0, grown, 0,
Math.min(Array.getLength(array), size));//调整数组的大小
return grown;
}
5.反射的安全性
Constructor,Field,Method均扩展了java.lang.reflect.AccessibleObject实例。调用setAccessible()方法,可以启动或关闭对一个实例的接入检测。
file = class.getDeclaredField("a");//a是私有变量
file.get(obj);//抛出异常
//如果关闭检测
filed.setAccessible(true);
file.get(obj);//正常
特别的,在关闭检测的情况下,如果运行时添加JVM参数 -Djava.security.manager 以实现安全性管理器,它将再次失败,除非您定义了 ReflectSecurity 类的许可权限。
6.反射对性能的影响
反射获取防方法和字段均在直接获取时间的700倍以上,获取对象相对较短,在12倍。
使用Javassist进行类型转换
1.概念:javassist是使用javassist.ClassPool类来跟踪和控制所操作的类。与JVM类装载器不同的是,它不是装载,而是通过Javassist API作为数据使用。
2.常用的方法:
ClassPool:获取指定路径中的类
CtClass ct = ClassPool.getDefault().get(Object)//获取
默认路径中的类
ct.getDeclaredMethod();//方法
字段、方法和构造函数分别由 javassist.CtField、javassist.CtMethod 和 javassist.CtConstructor 的实例表示。这些类定义了修改由它们所表示的对象的所有方法的方法,包括方法或者构造函数中的实际字节码内容。
3.解决思路
·获取方法
·复制一个新的方法,把旧的方法名改为:name+”
impl”⋅重写新的方法,其中,nname+“(
$$);\n”为执行之前的函数体。
·由ctclass写入。
需要计时的方法
public class StringBuilder
{//该方法是对一个string进行反复构造来延长执行时间,需要添加计时功能,注释内是希望达到的效果
private String buildString(int length) {
// long start = System.currentTimeMillis();
String result = "";
for (int i = 0; i < length; i++) {
result += (char)(i%26 + 'a');
}
//System.out.println("Call to buildString took " + (System.currentTimeMillis()-start) + " ms.");
return result;
}
public static void main(String[] argv) {
StringBuilder inst = new StringBuilder();
for (int i = 0; i < argv.length; i++) {
String result = inst.buildString(Integer.parseInt(argv[i]));
System.out.println("Constructed string of length " +
result.length());
}
}
}
添加计时:
public class JassistTiming
{
public static void main(String[] argv) {
if (argv.length == 2) {//argv[0]表示类,argv[1]表示方法名
try {
CtClass clas = ClassPool.getDefault().get(argv[0]);
if (clas == null) {
System.err.println("Class " + argv[0] + " not found");
} else {
addTiming(clas, argv[1]);
clas.writeFile();//更新.class文件
System.out.println("Added timing to method " +
argv[0] + "." + argv[1]);
}
} catch (CannotCompileException ex) {
ex.printStackTrace();
} catch (NotFoundException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
}
} else {
System.out.println("Usage: JassistTiming class method-name");
}
}
private static void addTiming(CtClass clas, String mname)
throws NotFoundException, CannotCompileException {
CtMethod mold = clas.getDeclaredMethod(mname);
//把原本的方法重命名为func+$impl,新建一个方法,把它命名为原本的func
String nname = mname+"$impl";
mold.setName(nname);
CtMethod mnew = CtNewMethod.copy(mold, mname, clas, null);
//type是原函数的返回值的类型
String type = mold.getReturnType().getName();
StringBuffer body = new StringBuffer();//待添加的函数主体
body.append("{\nlong start = System.currentTimeMillis();\n");//开始计时
if (!"void".equals(type)) {//如果无返回值
body.append(type + " result = ");
}
body.append(nname + "($$);\n");//调用$impl方法
//结束计时
body.append("System.out.println(\"Call to method " + mname +
" took \" +\n (System.currentTimeMillis()-start) + " +
"\" ms.\");\n");
if (!"void".equals(type)) {
body.append("return result;\n");//如果返回值为空,则不处理
}
body.append("}");
mnew.setBody(body.toString());
clas.addMethod(mnew);
System.out.println("Interceptor method body:");
System.out.println(body.toString());
}
}
在经过jassistTiming修改后,原本stringBuilder中的方法实际变成了
private String buildString$impl(int length) {
String result = "";
for (int i = 0; i < length; i++) {
result += (char)(i%26 + 'a');
}
return result;
}
private String buildString(int length) {
long start = System.currentTimeMillis();
String result = buildString$impl(length);
System.out.println("Call to buildString took " +
(System.currentTimeMillis()-start) + " ms.");
return result;
}
4.使用Jassist带来的风险
由于jassist采用比较宽松的编译时代码检查,所以可能带来问题。比如将long赋值给int,又或者把string存储到int中。
但同时,javassist带来的aop这种新的设计模式