一、类加载器
1.类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,
则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
(1)加载
就是指将class文件读入内存,并为之创建一个Class对象。
任何类被使用时系统都会建立一个Class对象。
(2)连接
验证 是否有正确的内部结构,并和其他类协调一致
准备 负责为类的静态成员分配内存,并设置默认初始化值
解析 将类的二进制数据中的符号引用替换为直接引用
(3)初始化 就是我们以前讲过的初始化步骤(静态初始化、构造初始化等等)
2.类初始化时机
创建类的实例
访问类的静态变量,或者为静态变量赋值
调用类的静态方法
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类
3.类加载器分类
类加载器
负责将.class文件加载到内在中,并为之生成对应的Class对象。
虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
类加载器的组成
Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载,比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载。在JDK中JRE的lib目录下ext目录
Sysetm ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径
二、反射
(一)、反射的概述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
(通俗的讲就是通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法。)
1.Class类
Class类就是字节码文件对象,每个class类编译后都会生成一个字节码文件,字节码文件有许多共同的特性,向上抽取,形成一个Class类。
2.一个Class类中包含
成员变量 Field
构造方法 Constructor
成员方法 Method
Class类将成员变量,构造方法,成员方法都封装成相应的类来使用。
Class类中就包含属性有field(字段)、method(方法)、constructor(构造函数)。
通过Class的方法可以获取相应的属性和方法,并且返回相应的对象。
3.获取Class类的三种方式
方式一:
通过Object类的getClass()方法
例如:
Student s = new Student();
Class c1 = s.getClass();
方式二:
数据类型的静态属性class
例如:
Class c2 = Student.class;
方式三:
Class类中的静态方法
public static Class forName(String className)
例如:
Class c3 = Class.forName("带包名的类名");
思考? 三种方式到底使用哪一种比较好
A:自己用 随便一种,第二种比较方便
B:开发中 使用第三种
因为第三种接收的是一个字符串,而不是一个具体的类名,这样我们就可以将这样的字符串写入到配置文件,
动态的获取类名。
(二)、反射的使用
1.Class类中的方法
获取构造方法
public Constructor[] getConstructors():获取所有公共构造方法
public Constructor[] getDeclaredConstructors():获取所有构造方法,包括私有。
public Constructor<T> getConstructor(Class<?>... parameterTypes):
获取单个构造方法,参数表示的是:你要获取的构造方法的构造参数个数及数据类型的class字节码文件对象
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):
获得指定参数的单个构造方法,包括私有
获取成员变量
public Field getField(String name):获取指定名称的成员变量
public Field[] getFields():获取所有共有的成员变量
public Field getDeclaredField(String name):获取指定名称的所有成员变量,包括私有
public Field[] getDeclaredFields():获取所有成员变量,包括私有
获取成员方法
public Method[] getMethods():获取自己和父类的所有共有方法,
public Method getMethod(String name, Class<?>... parameterTypes):获取单个公有方法
第一个参数是指,方法名称,第二个参数指,方法所需要的参数的class类型。
public Method getDeclaredMethod(String name,Class<?>... parameterTypes):获取单个方法,包括私有
public Method[] getDeclaredMethods():获取自己所有的方法,包括私有
2.通过反射获取构造方法。
1.获取字节码文件对象。
2.通过字节码文件对象获取Constructor对象。
3.通过Constructor对象调用public T newInstance(Object... initargs)方法
使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
2.通过反射获取构造方法。
1.获取字节码文件对象。
2.通过字节码文件对象获取Constructor对象。
3.通过Constructor对象调用public T newInstance(Object... initargs)方法
使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。
例如:
//Person类
public class Person {
private String name;
int age;
public String address;
public Person() {
}
private Person(String name) {
this.name = name;
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public void show() {
System.out.println("show");
}
public void method(String s) {
System.out.println("method " + s);
}
public String getString(String s, int i) {
return s + "---" + i;
}
private void function() {
System.out.println("function");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", address=" + address
+ "]";
}
}
public class ReflectionDemo
{
//
public static void main (String[] args) throw Exception{
//获取字节码文件对象
Class c = Class .forName("cn.package.Person");
//获取单个构造方法,使用无参构造,没有加泛型
Constructor con = c.getConstructor();// 返回的是构造方法对象
//创建对象
Object obj = con.newInstance();
System.out.println(obj);
}
}
//使用带参构造,如Person p = new Person("张三",20,"深圳");
public class ReflectionDemo2{
public static void main(String [] args){
//获取字节码文件对象
Class c = Class .forName("cn.package.Person");
//获取带参构造
Constructor con = c.getConstructorString.class, int.class,
String.class);
//通过带参构造创建对象
Object obj = con.newInstance("张三",20,"深圳");
System.out.println(obj);
}
}
//获取私有构造方法并使用
public class ReflectionDemo3{
public static void main(String [ ] args){
//获取字节码文件对象
Class c = Class .forName("cn.package.Person");
//获取私有带参构造
Constructor con = c.getDeclaredConstructor(String.class);
//如果没有设置取消Java语言访问检查,会出现IllegalAccessException:非法的访问异常。
con.setAccessible(true);// 值为true则指示反射的对象在使用时应该取消Java语言访问检查。
Object obj = con.newInstance("张三");
System.out.println(obj);
}
}
3.通过反射获取成员变量并使用。
1.获取字节码文件对象。
2.通过字节码文件对象获取Constructor对象,并通过无参构造方法创建对象
3.获取单个的成员变量 获取名称并对其赋值
通过 public void set(Object obj,Object value)方法赋值
set()是将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
例如:
public class ReflectDemo4 {
public static void main(String[] args) throws Exception {
// 获取字节码文件对象
Class c = Class.forName("cn.package.Person");
// 通过无参构造方法创建对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();
System.out.println(obj);
// 获取单个的成员变量
// 获取address并对其赋值
Field addressField = c.getField("address");
addressField.set(obj, "深圳"); // 给obj对象的addressField字段设置值为"深圳"
System.out.println(obj);
// 获取name并对其赋值,name是私有,必须使用getDeclaredField方法,
//并需要通过setAccessible()方法取消Java语言访问检查。
Field nameField = c.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(obj, "张三");
System.out.println(obj);
// 获取age并对其赋值
Field ageField = c.getDeclaredField("age");
ageField.setAccessible(true);
ageField.set(obj, 20);
System.out.println(obj);
}
}
4.通过反射获取成员方法并使用
1.获取字节码文件对象。
2.通过字节码文件对象获取Constructor对象,并通过无参构造方法创建对象
3.获取单个的成员方法,
4.通过public Object invoke(Object obj,Object... args)方法调用方法。(调用obj对象的args方法)
返回值是Object接收,第一个参数表示对象是谁,第二参数表示调用该方法的实际
例如:
public class ReflectDemo5 {
public static void main(String[] args) throws Exception {
// 获取字节码文件对象
Class c = Class.forName("cn.package.Person");
// 通过无参构造方法创建对象
Constructor con = c.getConstructor();
Object obj = con.newInstance();
// public void show(),
Method m1 = c.getMethod("show");
// 返回值是Object接收,第一个参数表示对象是谁,第二参数表示调用该方法的实际参数
m1.invoke(obj); // 调用obj对象的m1方法
System.out.println("---------------------");
// public void method(String s)
Method m2 = c.getMethod("method", String.class);
m2.invoke(obj, "hello");
System.out.println("----------");
// public String getString(String s, int i)
Method m3 = c.getMethod("getString", String.class, int.class);
Object objString = m3.invoke(obj, "hello", 100);
System.out.println(objString);
System.out.println("----------");
// private void function()
Method m4 = c.getDeclaredMethod("function");
m4.setAccessible(true);
m4.invoke(obj);
}
}
(三)、反射应用举例
1.通过配置文件运行类中的方法
需要有配置文件配合使用。
可以用class.txt代替。
并且你知道有两个键。
className
methodName
思路:
1.通过Properties类可以将文件的数据通过键值对的形式加载到内存中。
2.通过Properties特有的方法通过键获取值。
3.通过反射,将获取到的值动态的传递给字节码文件对象。
4.通过字节码文件对象获取Constructor对象,创建对象,
5.调用方法
public class Test {
public static void main(String[] args) throws Exception {
// 加载键值对数据
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);
}
}
2.通过反射越过泛型检查
用ArrayList<Integer>,在这个集合中添加一个字符串数据,如何实现呢?
根据以前学过的知识根本无法完成,但是通过反射可以越过泛型的检查,
泛型是用来给编译器看的,运行的时候就没有了,而反射就是在运行状态中可以获取到类
因为通过查看源码,实际上运行的时候是接收的是Object类型,可以是任意类型。
public class ArrayListDemo {
public static void main(String[] args) throws NoSuchMethodException,
SecurityException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
// 创建集合对象
ArrayList<Integer> array = new ArrayList<Integer>();
Class c = array.getClass(); // 集合ArrayList的class文件对象
Method m = c.getMethod("add", Object.class);
m.invoke(array, "hello"); // 调用array的add方法,传入的值是hello
m.invoke(array, "world");
m.invoke(array, "java");
System.out.println(array);
}
}
3.通过反射给任意的一个对象的任意的属性赋值为指定的值
写一个方法为 public void setProperty(Object obj, String propertyName, Object value){},
此方法可将obj对象中名为propertyName的属性的值设置为value。
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);
}
}
三、 动态代理
1.概述
代理:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象。
举例:春季回家买票让人代买
动态代理:在程序运行过程中产生的这个对象
而程序运行过程中产生对象其实就是我们刚才反射讲解的内容,所以,动态代理其实就是通过反射来生成一个代理
2.
在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对象上
每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的invoke 方法来进行调用。
InvocationHandler接口中invoke方法的三个参数:
proxy:代表动态代理对象
method:代表正在执行的方法
args:代表调用目标方法时传入的实参
Proxy.newProxyInstance
创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,
也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,
以$开头,proxy为中,最后一个数字表示对象的标号。
System.out.println(u.getClass().getName());
例如:
学生类中有两个方法,分别是登陆和注册。
但是不是所有的学生都有权限的,需要权限校验,并且需要留下日志记录。
如果说另开一个类的话,需要的类比较多,比较麻烦。
可以通过反射的方式创建一个动态代理类对象,来完成这件事情。
//创建一个学生接口。
public interface StudentDao {
public abstract void login();
public abstract void regist();
}
//创建学生实现类。
public class StudentDaoImpl implements StudentDao {
@Override
public void login() {
System.out.println("登录功能");
}
@Override
public void regist() {
System.out.println("注册功能");
}
}
//因为动态代理类需要接收一个InvocationHandler接口,所以要实现一个Invocationhandler接口
public class MyInvocationHandler implements InvocationHandler {
private Object target; // 目标对象
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("权限校验");
Object result = method.invoke(target, args);
System.out.println("日志记录");
return result; // 返回的是代理对象
}
}
public class Test {
public static void main(String[] args) {
StudentDaoImpl s= new StudentDaoImpl();
s.login();
s.regist();
System.out.println("-----------");
// 我们要创建一个动态代理对象
// Proxy类中有一个方法可以创建动态代理对象
// 我准备对sd对象做一个代理对象
StudentDao sd = new StudentDaoImpl();
MyInvocationHandler handler = new MyInvocationHandler(sd);
StudentDao proxy = (StudentDao) Proxy.newProxyInstance(sd.getClass()
.getClassLoader(), sd.getClass().getInterfaces(), handler);
proxy.login();
proxy.regist();
}
}