类加载过程的核心:任何一个类被系统使用时候都会将class文件加载进内存并创建一个Class对象,同时会初始化静态成员
类加载时机:
- 创建类的实例
- 访问类的静态变量,或者为静态变量赋值
- 调用类的静态方法
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
负责人:类加载器,其分类有
BootStrap ClassLoader 根类加载器,负责Java核心类的加载,比如System,String等等,位于jre--lib--rt文件中
Extension ClassLoader 扩展加载器,负责JRE拓展包中的类加载,jre--lib--ext
System ClassLoader 系统加载器,负责在JVM启动时加载由java命令启动的class文件,以及classPath所指定的jar包和类
反射定义:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.而Class对象是在运行期间才能确定的,所以反射运用在运行期间。
对于每一种类,Class对象只有唯一的一个,但对象实例可以new多个。
获取反射对象有三种方式
Student student = new Student("Leo");
//one
Class clazz = student.getClass();
//two
Class clazz1 = Student.class;
//three
try {
Class clazz2 = Class.forName("Model_4.Student");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
开发中使用更多的是方式三,因为变化的是字符串,代码设计更灵活。
获取到Class对象后我们就操作该对象中的方法
public Constructor<?>[] getConstructors()//所有公共构造方法
public Constructor<?>[] getDeclaredConstructors()//所有构造方法
public Constructor<T> getConstructor(Class... var1)//获取特定构造方法
public Constructor<T> getDeclaredConstructor(Class... var1)
//from Constructor
public T newInstance(Object... var1)//创建该构造方法对应类型的实例,相当于new一个对象
public void setAccessible(boolean var1) 将能取消语言访问检查机制
eg:
Class clazz2 = null;
try {
clazz2 = Class.forName("Model_4.Student");
Constructor constructor = clazz2.getConstructor(String.class,int.class);
Object object = constructor.newInstance("Leo",24);
} catch (Exception e) {
e.printStackTrace();
}
//获取成员变量
public Field getField(String var1)
public Field getDeclaredField(String var1)
public Field[] getFields()
public Field[] getDeclaredFields()
eg:
try {
clazz2 = Class.forName("Model_4.Student");
Constructor constructor = clazz2.getConstructor(String.class);
Student object = (Student) constructor.newInstance("Leo");
System.out.println(object.getOfficialName());//print Leo
Field field = clazz2.getDeclaredField("officialName");
field.setAccessible(true);
field.set(object,"PDDDDD");
System.out.println(object.getOfficialName());//print PDDDDD
} catch (Exception e) {
e.printStackTrace();
}
//获取方法
public Method getMethod(String var1, Class... var2)
public Method getDeclaredMethod(String var1, Class... var2)
public Method[] getMethods()
public Method[] getDeclaredMethods()
try {
clazz2 = Class.forName("Model_4.Student");
Constructor constructor = clazz2.getConstructor(String.class);
Student object = (Student) constructor.newInstance("Leo");
Method method1 = clazz2.getMethod("setOfficialName",String.class);
method1.invoke(object,"PDDDDDD");
Method method2 = clazz2.getMethod("getOfficialName");
String string = (String) method2.invoke(object);
System.out.println(string);
} catch (Exception e) {
e.printStackTrace();
}
常规class加载模式是预先加载需要使用的Class对象(文章开头提到了加载实际),然后我们就可以通过new对象的方式使用对象,而反射是运行过程中才加载Class对象,在此时根据需求创建实例。
其他注意事项
对于方法:
getDeclaredMethods:所有本类中声明的方法,包括继承或实现过来的方法(只返回本类的实现而非父类的)
getMethods:所有声明的公共方法,包括继承或实现过来的公共方法(只返回本类的实现而非父类的),也包括所有父类的公共方法
getDeclaredMethod getMethod同理
invoke只能调用公共方法,如果需要调用default、protected、private方法,需要提前调用method.setAccessible(true);
子类覆盖父类方法时候,方法限定符发生改变,上述规则不失效,即获取方法时候只返回一个最靠近继承链新生末端的方法。
对于域:
getDeclaredFields:所有本类中声明的域
getFields:所有本类和父类和接口中的公共域,包括重名且重类型的(域不存在继承链覆盖)
getDeclaredField getField同理
默认只能操作公共域,如果需要获取或操作default、protected、private域,需要提前调用field.setAccessible(true);
调用getDeclaredField getField如果和父类或接口有重名的域,只返回一个最靠近继承链新生末端的域
反射会降低程序运行效率,主要是寻找方法和成员变量的过程比较缓慢,真正运行方法和操作成员变量的过程是不慢的。
- 在项目中使用反射
//设置配置文件,在项目中加载配置文件能够灵活实现功能,不需要时刻修改代码并重新编译项目
// 加载键值对数据
Properties prop = new Properties();
FileReader fr = new FileReader("class.txt");
prop.load(fr);
fr.close();
// 获取数据
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
// 反射
Class c = Class.forName(className);
Constructor con = c.getConstructor();
Object obj = con.newInstance();
// 调用方法
Method m = c.getMethod(methodName);
m.invoke(obj);
- 用反射跳出类型检查
//先看下面的代码
ArrayList<Integer> list = new ArrayList<>();
list.add(10);
//通过编译后,JVM实际采用的class文件通过反编译,得到的是
ArrayList list = new ArrayList();
list.add(Integer.valueOf(10));
本质上,编译器把我们的Java代码转化成了上述形式进行使用,相当于我们本来就是这么写的。
通过查看ArrayList源码,我们发现,ArrayList构建时声明的泛型类型其实是Object,我们在编码时通过泛型声明,编译器会将泛型对象转化为特定的对象,本例子中即为Integer.valueOf(),同样为一个Object。可见泛型类型确认是发生在编译过程中。
但是,ArrayList源码反应,其Class对象始终没有确认泛型对象,保持Object类型,所以我们可以做如下操作:
ArrayList<Integer> list = new ArrayList<>();
list.add(10);
try {
Class clz = list.getClass();
Method method = clz.getMethod("add",Object.class);
method.invoke(list,"pddddd");
method.invoke(list,"yjj");
method.invoke(list,new Student("leon"));
System.out.println(list);//调用的是ArrayList的toString方法,里面是迭代调用元素的toString方法
} catch (Exception e) {
e.printStackTrace();
}
-
暴力操作对象的私有数据
当项目中的文件没有提供对外操作的接口,,,
public class Tool {
public void setProperty(Object obj, String propertyName, Object value)
throws NoSuchFieldException, SecurityException,
IllegalArgumentException, IllegalAccessException {
// 根据对象获取字节码文件对象
Class c = obj.getClass();
// 获取该对象的propertyName成员变量
Field field = c.getDeclaredField(propertyName);
// 取消访问检查
field.setAccessible(true);
// 给对象的成员变量赋值为指定的值
field.set(obj, value);
}
}
动态代理
代理:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象
动态代理:在程序运行过程中产生的这个对象
而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理
在Java中java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib
Proxy类中的方法创建动态代理类对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
最终会调用InvocationHandler的方法
InvocationHandler
Object invoke(Object proxy,Method method,Object[] args)
Proxy类中创建动态代理对象的方法的三个参数;
ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
public class KaSha implements UserDao {
ArrayList<String> skills = new ArrayList<>();
int shuijin = 100;
@Override
public boolean addSkill(String skill) {
return skills.add(skill);
}
@Override
public void deleteAccessory(int number) {
shuijin = shuijin - number;
}
@Override
public void login() {
System.out.println(this.getClass().getName()+" has log in");
}
@Override
public void exit() {
System.out.println(this.getClass().getName()+" has log out");
}
}
public class HeroHandler implements InvocationHandler {
private UserDao mUserDao;
public HeroHandler(UserDao userDao) {
mUserDao = userDao;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
System.out.println("权限校验");
return method.invoke(mUserDao,objects);
}
}
public static void main(String[] args) {
UserDao userDao = new KaSha();
HeroHandler heroHandler = new HeroHandler(userDao);
UserDao userDao1 = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),heroHandler);
userDao1.login();
userDao1.addSkill("皇族旗帜");
userDao1.deleteAccessory(2);
userDao1.exit();
}
//输出结果中每个方法的实现都会转化为HeroHandler的invoke方法实现。这样对于需要批量增加的操作,就不需要在重写子类实现,比如上述的“权限检查”。