类文件结构
Class文件是一组以8位字节为基础单位的二级制字节流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间没有分隔符。
无符号数和表
- 无符号数属于几本的数据结构,以u1u2u3u4为代表的1248个字节的无符号数
- 无符号数用来描述数字,索引引用,数量值,或者按照UTF-8编码构成字符串值
表是由无符号数或其他表作为数据项构成的复合数据类型,所有的表都习惯的以_info结尾,整个Class文件本质上是一个表
HOTSPOT虚拟机是虚拟机的一种版本
u4 魔数(magic)
第五第六是次版本号,第七第八是主版本号
主版本后面就是常连池入口,常量池是class文件结构中的与其他项目关联最多的数据类型,也是占用class文件空间最大的数据项目之一
由于常量池中的敞亮的数量是不固定的,所以在常量池的入口需要放置意一项u2类型的数据,代表常量池容量计数。这个容量计数是从1开始的。
常量池中主要存放两大类常量:字面量literal和字符引用Symbolic References。
字面量比较接近于java语言层面的常亮概念,比如文本字符串、被声明为final的常量值等。
符号引用包括三类变量:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
常量池中的每一项常亮都是一个表,一共11种结构各不相同的表,这些表都有一个共同的特点,开始的第一位是一个u1类型的标志位,取值1-12,没有2,代表当前这个常亮属于哪种类型,下表是11种常亮类型
类加载
三种的加载器,最顶层的是bootstrap加载器,然后是extension加载器,然后是application加载器,然后才是自定义的加载器。
双亲委派原则,如果是从自定义加载器加载的话那就是送给父类看父类能够加载吗,直到送到顶层父类bootstrap加载器,所以说这样保持了我们可以书写一个系统类可不剥夺系统类。
这样一来我们自己来完成一个类加载的过程
首先先声明我们使用使用时eclipse,不同的编译器好像是对于类文件有着不一样的编码方式,首先我们先创建一个类F1
package com.ll.test1;
public class F1 {
public int add(int a,int b){
System.out.println("add....");
return a+b;
}
}
这是一个很简单的类,里面有一个很简单的增加的方法。
然后我们找到这个文件的class文件,然后我们在eclipse中将这个文件删除。
我是把它放在了D盘下面。
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
public class SimpleClassloader extends ClassLoader{
private byte[] getBytes(String filename) throws IOException {
File file = new File(filename);
long len = file.length();
byte[] raw = new byte[(int) len];
FileInputStream fin = new FileInputStream(file);
// 一次读取class文件的全部二进制数据
int r = fin.read(raw);
if (r != len)
throw new IOException("无法读取全部文件:" + r + " != " + len);
fin.close();
return raw;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] raw = getBytes(name);
String classFullName1=new String(raw,16,(int)raw[15]);
System.out.println(classFullName1);
String ClassFullName=classFullName1.replace('/','.');
return defineClass(ClassFullName, raw, 0, raw.length);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws Exception {
SimpleClassloader scl = new SimpleClassloader();
Class clazz = scl.loadClass("D:\\F1.class");
Method method = clazz.getDeclaredMethod("add", int.class,int.class);
Object newIn = clazz.newInstance();
Object invoke = method.invoke(newIn, 5,6);
System.out.println(invoke);
}
}
这是一个类,去继承了ClassLoader
,然后我们在启动的方法中,先去new出这个对象,然后使用对象.loadClass("D:\\F1.class");
,其实这个loadClass并不会被真调用,他会去调用你写的findClass,在虚拟机中的类加载形式中有所提及,破坏双亲委派的一种形式就是直接去重写loadClass,变成了重写findClass。
然后我们在findClass中先去使用一个参数,然后自己写一个读文件成byte数组的方法,具体实现我就在这里不说了,学习读文件很简单的,然后拿到了byte的数组。
不过我们在这里首先先去看一下类文件是什么样子的。
在这里呢,首先前四位是魔数,第五位第六位是次版本号,第七位第八位是主版本号,
第九第十是一个u2类型的,是指的常量的个数。然后在向后一个是一个tag位07的常量,他是一个类或接口的符号引用,后面跟的是u2。
经过查表得出,后面的那个01是一个tag位,然后他后面的u2是字符串的长度,在后面的就是字符串的个数,后面跟的就是全限类名,但是不同的编译器是类文件的格式是不一样的,在IDEA里面我就吃够了苦,在这里他的全限类名就是第十五位以后的第十五位的长度,在这里的是0f,也就是15,我使用的工具是EditPlus,最右侧是他自己给你的翻译。
最右侧的翻译命了我们的推测。
其中因为你读出的全限类名是有\分隔开的,所以我是使用了一个replace方法将\变成了.方便我们在后面使用反射生成一个对象,完成后面的方法的调用。
strictp
Integer.TYPE=int
forname如何实现
invoake如何去实现
new 的是孩子
反射(reflect)
java的反省机制
什么是反射?
我的理解就是动态的去获得一个类的各种的信息,使用一个路径去获得一个类的信息,然后我们可以去完成很多的东西。
先去完成一个Person的类当成例子
import java.io.Serializable;
import javax.swing.JFrame;
public class Person <E> extends JFrame implements Serializable,Cloneable {
private Class entity;
public Class getEntity() {
if(entity==null) {
entity=GenericsUtlis.getGenericClass(this.getClass());
// System.out.println(entity+"if");
}
// System.out.println(entity+"ret");
return entity;
}
private int id;
protected String name;
public int age;
double sal;
public Person() {
System.out.println("默认构造");
}
public Person(int age) {
System.out.println("int 默认构造");
}
public void f1() {
System.out.println("f1");
}
public String t1() {
return "t1";
}
public void f2(Integer a) {
System.out.println(a);
System.out.println("f2 Integer");
}
public void f2(int a) {
System.out.println(a);
System.out.println("f2");
}
public void f2(String a,int b) {
System.out.println("String + int");
}
}
下面我们完成一个反射的例子去获得一个类的所有的信息
Class clazz = Class.forName("com.ll.reflect.Person");//这是类类型,我们需要去得到的他的类对象
Object obj = clazz.newInstance();//拿到了这个类型的对象
Constructor c = clazz.getDeclaredConstructor(int.class);
Object obj1 = c.newInstance(1);
System.out.println(clazz.getSimpleName());//Person
System.out.println(clazz.getName());//com.ll.reflect.Person
Package pack = clazz.getPackage();
System.out.println(pack);//package com.ll.reflect
Class superclass = clazz.getSuperclass();
System.out.println(superclass.getSimpleName());//JFrame
Class[] interfaces = clazz.getInterfaces();
for (Class class1 : interfaces) {
System.out.println(class1.getSimpleName());
}
上面的的代码最开始使用了他的默认构造器生成了他的对象,然后是利用有参构造器生成了他的有参对象,然后是获得这个类的名字,还有权限类名,包名,父类名,接口集合名,
Field field = clazz.getField("public1");//访问权限
System.out.println(field.getName());
/**
* 其他的全部都是访问不到,只能够访问得到public下面的
*/
// Field[] fields = clazz.getFields();
Field[] fields = clazz.getDeclaredFields();//全部进行设置
for (Field field2 : fields) {
System.out.println(field2.getName());
}
上面的代码是生成了类中的对象的值,去或者一个类中会有多少个对象,在使用get方法中使用带有DeclaredFields的,这样子可以去忽略它上面的修饰符。
Field declaredField = clazz.getDeclaredField("age");
System.out.println(declaredField);//public int com.ll.reflect.Person.age
System.out.println(declaredField.getType());//int
System.out.println(declaredField.getModifiers());
我们在这里去获得了这个属性的名字,类型,还有修饰符,但是他的修饰符用的是数字来表示的我把能够想到的数字总结了一下
/**
* defalut 0
* public 1
* private 2
* protect 4
* static 8
* final 16
* synchronized 32
* volatile 64
* transient 128
* native 256
* abstract 1024
* strictfp 2048
*
* transient是在序列话的时候,不能被序列化出去的变量,
* 序列化:将一个类转换成分子状在管道中游走
*/
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method.getName());
}
Method ms = clazz.getDeclaredMethod("f1");
ms.invoke(obj, null);
System.out.println(ms);//public void com.ll.reflect.Person.f1()
System.out.println(ms.getModifiers());
Class[] argtype= {String.class,int.class};
Method ms1 = clazz.getDeclaredMethod("f2", argtype);//Integer.TYPE底层是int
Object[] value= { "s",2};
ms1.invoke(obj,value);
上面的代码就是展示了寻找一个类中的方法,并且将我们找到的方法使用,使用invoke的方法,使用方法的对象.invoke(这个类的对象,参数(没有可以去写null)),这样就可以调用这个类了
import java.io.Serializable;
public class Sun extends Person<Integer> implements Serializable,Cloneable {
}
去完成一个子类去将这个父类的泛型打印出来
Sun s=new Sun();
System.out.println(s.getEntity());
String s1=new String();
Class<? extends String> class1 = s1.getClass();
System.out.println(class1);
Person<String> p=new Person<>();
System.out.println(p.getEntity());
我们能够实现将将父类的泛型打印
就是因为有一个GenericsUtlis.java
这个类
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class GenericsUtlis {
public static Class getGenericClass(Class clazz) {
return getGenericClass(clazz,0);
}
public static Class getGenericClass(Class clazz,int index) {
Type gentype = clazz.getGenericSuperclass();
if(gentype instanceof ParameterizedType) {
Type[] params = ((ParameterizedType) gentype).getActualTypeArguments();
if((params!=null)&&(params.length)>=(index-1)){
return (Class)params[index];
}
}
return null;
}
}
这是一个spring的一部分的源码,我在这里就不展开讲了,就说一下这个的作用就是去讲一个类传入,然后将他的父类的泛型进行返回。
代理
静态代理
一个原始类一个代理类
底层是动态码生成技术。
我们在这里举一个例子
public class Worker {
public void work() {
System.out.println("正在工作");
}
}
完成一个worker的类,这个类的作用是用来完成事情也可以叫他原始类
public class WorkProxy {
private Worker worker;
public WorkProxy(Worker worker) {
this.worker=worker;
}
public void work() {
System.out.println("开始了");
worker.work();
System.out.println("结束了");
}
}
这是一个代理类,我们在这里对于我们的这个类进行代理
我们再去完成一个类去调用他们
public static void main(String[] args) {
Worker ww=new Worker();
WorkProxy aa=new WorkProxy(ww);
aa.work();
}
这就是静态代理需要去完成一个代理类去将我们的原始类进行代理,但是这个样子有着明显的缺点,需要完成一个原始类就要去完成一个被代理的类,所以我们有了动态代理。
动态代理
jdk
必须要切继承同一个接口
public interface ForumService {
void removeTopic(int topicId);
void removeForum(int forumId);
}
先去完成一个接口,然后去完成一个实现类
public class ForumServiceImpl implements ForumService {
public void removeTopic(int topicId) {
System.out.println("模拟删除Topic记录:"+topicId);
}
public void removeForum(int forumId) {
System.out.println("模拟删除Forum记录:"+forumId);
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Cross implements InvocationHandler{
private ForumService fs;
public Cross(ForumService fs){
this.fs = fs;
}
/**
* 完成一个代理类,实现了一个代理类的创建
* @return
*/
public ForumService bind(){
return (ForumService) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{ForumService.class},this);
}
public Object invoke(Object arg0, Method method, Object[] obj2) throws Throwable {
// TODO Auto-generated method stub
if("removeForum".equals(method.getName())){
System.out.println("代理删除");
}else{
return method.invoke(fs,obj2);//原始类在干活
}
return null;
}
}
在这里我稍微的重点的去解释里面的两个方法,首先的这个类中实现了两个角色,第一个是这个东西实现了动态代理类的生成
return (ForumService) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{ForumService.class},this);
这个返回的就是这个动态代理的类,然后是利用了Proxy.newProxyInstance这个方法里面的是三个参数分别是类加载器,接口,回调函数,类加载器使用的本类的加载器就可以,接口使用的是集合的形式因为是多个接口的形式,然后回调函数选择本身就好了
然后就是invoke
这个方法,这个方法每一个方法都回去加载这个方法,里面的三个参数第一个是传入要被代理的类,第二个是方法,第三个是传入的参数。
在这个方法里可以对于传入的方法进行判断,如果是什么样子的方法名可以选择执行使用方法名.invoke(原始类,参数)。
我们可以利用这个去代理掉自己不想被实现的代码,比如说我们在执行一些东西的时候不喜欢被人删除掉一些东西,所以我们就可以删除这个方法代理掉,
cglib
生成的代理类直接继承(不能是final)
public class ForumServiceImpl {
public void removeTopic(int topicId) {
System.out.println("模拟删除Topic记录:"+topicId);
}
public void removeForum(int forumId) {
System.out.println("模拟删除Forum记录:"+forumId);
}
}
先完成一个类使用的方法,这是一个被代理类
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {
System.out.println("检查权限");
Object result = proxy.invokeSuper(obj, args);
System.out.println("善后处理");
return null;
}
}
getProxy
这个方法是生成一个代理类,然后后面的intercept
的方法具体是为了添加你需要添加的方法,
public class TestForumService {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
ForumServiceImpl forumService = (ForumServiceImpl)proxy.getProxy(ForumServiceImpl.class);
forumService.removeForum(10);
forumService.removeTopic(1023);
}
}
JavaAgent
Java 从 1.5 开始提供了 java.lang.instrument(doc)包,该包为检测(instrument) Java 程序提供 API,比如用于监控、收集性能信息、诊断问题。通过 java.lang.instrument 实现工具被称为 Java Agent。Java Agent 可以修改类文件的字节码,通常是,在字节码方法插入额外的字节码来完成检测。
javassist
我们常用到的动态特性主要是反射,在运行时查找对象属性、方法,修改作用域,通过方法名称调用方法等。在线的应用不会频繁使用反射,因为反射的性能开销较大。其实还有一种和反射一样强大的特性,但是开销却很低,它就是Javassit。
Javassit其实就是一个第三方包,提供了运行时操作Java字节码的方法。大家都知道,Java代码编译完会生成.class文件,就是一堆字节码。JVM(准确说是JIT)会解释执行这些字节码(转换为机器码并执行),由于字节码的解释执行是在运行时进行的,那我们能否手工编写字节码,再由JVM执行呢?答案是肯定的,而Javassist就提供了一些方便的方法,让我们通过这些方法生成字节码。