一、 类的加载概述和加载时机
- 类的加载概述:
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
- 加载:就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
- 连接:
- 验证 是否有正确的内部结构,并和其他类协调一致
- 准备 负责为类的静态成员分配内存,并设置默认初始化值
- 解析 将类的二进制数据中的符号引用替换为直接引用
- 初始化:默认初始化,构造初始化。
- 加载时机
- 创建类的实例。(new一个对象)
- 访问类的静态变量,或者为静态变量赋值。(静态成员变量)
- 调用类的静态方法
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类(父类先加载,子类再加载)
- 直接使用java.exe命令来运行某个主类
二、 类加载器的概述和分类
-
类加载器的概述
负责将.class文件加载到内存中,并为之生成对应的Class对象。虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。 -
类加载器的分类
Bootstrap ClassLoader
根类加载器Extension ClassLoader
扩展类加载器Sysetm ClassLoader
系统类加载器
- 类加载器的作用
(1) Bootstrap ClassLoader
根类加载器
- 也被称为引导类加载器,负责Java核心类的加载。
- 比如System,String等。在JDK中JRE的lib目录下rt.jar文件中
(2) Extension ClassLoader
扩展类加载器
- 负责JRE的扩展目录中jar包的加载。
- 在JDK中JRE的lib目录下ext目录
(3) System ClassLoader
系统类加载器
- 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。比如自己写的类。
三、 反射概述
- 反射概述
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
- 对于任意一个对象,都能够调用它的任意一个方法和属性;
- 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
- 要想解剖一个类,必须先要获取到该类的字节码文件对象。
- 而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
- 三种方式
- Class类中静态方法forName(),读取配置文件。如:写一个proprities文件获取数据库的连接。
- Object类的getClass()方法,判断两个对象是否是同一个字节码文件
- 静态属性class,锁对象。如,多线程中的锁对象。
Class clazz1=Class.forName("com.day27.bean.Person");
Class clazz2=Person.class;
Person person =new Person();
Class clazz3= person.getClass();
System.out.println(clazz1 ==clazz2);
System.out.println(clazz2 ==clazz3);
效果如下:
表示为同一个类
四、 Class.forName()读取配置文件举例
public static void main(String[] args) throws Exception {
//没有使用反射,只使用多态
//Juicer juicer =new Juicer();
//juicer.run(new Apple()); //向榨汁机中放入苹果
//juicer.run(new Orange());
//使用反射机制
BufferedReader br =new BufferedReader(new FileReader("config.properties"));
Class clazz =Class.forName(br.readLine());
//父类引用指向子类对象,水果的引用指向了苹果对象
Fruit f =(Fruit) clazz.newInstance();
Juicer j =new Juicer();
j.run(f);
}
}
interface Fruit{
public void squeeze();
}
class Apple implements Fruit{
public void squeeze() {
System.out.println("榨出苹果汁");
}
}
class Orange implements Fruit{
public void squeeze() {
System.out.println("榨出橘子汁");
}
}
class Juicer{
/*
* public void run(Apple a) {
* a.squeeze();
* }
* public void run(Orange o) {
* o.squeeze();
* }
*/
public void run(Fruit f) {
f.squeeze();
}
此时我们只需要修改我们的配置文件
五、 通过反射获取带参构造方法并使用
Constructor:
Class类的newInstance()
方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用Class类的getConstructor(String.class,int.class)
方法获取一个指定的构造函数然后再调用Constructor类的newInstance(“张三”,20)方法创建对象
Class clazz=Class.forName("com.day27.bean.Person");
//Person person=(Person)clazz.newInstance();
//通过无参构造创建对象
//如果我们在此删除掉person的无参构造函数,那么我们将无法使用该函数
//System.out.println(person);
Constructor
constructor=clazz.getConstructor(String.class,int.class);//获取有参构造
Person person =(Person) constructor.newInstance("张三",24);
//通过有参构造创建对象
System.out.println(person);
效果如下:
Person person=(Person)clazz.newInstance();
//通过无参构造创建对象
六、 通过反射获取成员变量并使用
Field:
Class.getField(String)
方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField("name")
方法获取,通过set(obj, "李四")
方法可以设置指定对象上该字段的值, 如果是私有的需要先调用setAccessible(true)
设置访问权限,用获取的指定的字段调用get(obj)
可以获取指定对象中该字段的值
Class clazz=Class.forName("com.day27.bean.Person");
Constructor constructor=clazz.getConstructor(String.class,int.class);
Person person =(Person) constructor.newInstance("张三",23);
//Field field =clazz.getField("name");
//获取姓名字段,此时我们并不能访问到name的属性。因为Person类已经将其私有化。所以我们采用暴力反射
//field.set(person, "李四");
//修改姓名的值
Field field =clazz.getDeclaredField("name");
//暴力反射获取字段
field.setAccessible(true); //去除私有权限,对自身开放
field.set(person, "李四");
System.out.println(person);
七、 通过反射获取方法并使用
Method:
Class.getMethod(String, Class...)
和 Class.getDeclaredMethod(String, Class...)
方法可以获取类中的指定方法,调用invoke(Object, Object...)
可以调用该方法Class.getMethod("eat") invoke(obj)
Class.getMethod("eat",int.class) invoke(obj,10)
Class clazz=Class.forName("com.day27.bean.Person");
Constructor constructor=clazz.getConstructor(String.class,int.class);
Person person =(Person) constructor.newInstance("张三",23);
//获取方法
Method method =clazz.getMethod("eat");
method.invoke(person);
//获取有参的eat方法
Method method2=clazz.getMethod("eat",int.class);
method2.invoke(person, 10);
八、 练习:通过反射越过泛型检查
需求: ArrayList的一个对象,在这个集合中添加一个字符串数据,如何实现呢?
public class Demo1_Test {
public static void main(String[] args) throws Exception {
//注意泛型只在编译期有效,在运行期会被擦除
ArrayList<Integer> list =new ArrayList<Integer>();
list.add(111);
list.add(222);
Class clazz =Class.forName("java.util.ArrayList");
Method method =clazz.getMethod("add", Object.class);
method.invoke(list, "abc");
System.out.println(list);
}
}
效果如下:
九、练习: 通过反射写一个通用的设置某个对象的某个属性为指定的值
public void setProperty(Object obj, String propertyName, Object value){}
,此方法可将obj对象中名为propertyName的属性的值设置为value。
先写一个工具类TOOL
public class Tool {
//此方法可将obj对象中名为propertyName的属性的值设置为value
public void setProperty(Object obj,String propertyName,Object value) throws Exception {
Class clazz =obj.getClass(); //获取字节码对象
Field field =clazz.getDeclaredField(propertyName); //暴力反射获取字段
field.setAccessible(true);
field.set(obj, value);
}
}
书写测试类
public class Demo2_Test {
public static void main(String[] args) throws Exception {
Student student =new Student("张三",13);
System.out.println(student);
Tool tool =new Tool();
tool.setProperty(student, "name", "李四");
System.out.println(student);
}
}
class Student{
private String name;
private int age;
public Student(){
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
十、 已知一个类,定义如下:
public class DemoClass {
public void run() {
System.out.println(“hello world!”);
}
}
(1) 写一个Properties格式的配置文件,配置类的完整名称。
(2) 写一个程序,读取这个Properties配置文件,获得类的完整名称并加载这个类,用反射的方式运行run方法。
先要自行写一个配置文件
public class DemoClass {
public void run() {
System.out.println("hello world");
}
}
通过方法测试:
BufferedReader bf =new BufferedReader(new FileReader("x.properties"));
Class clazz =Class.forName(bf.readLine());
DemoClass demoClass =(DemoClass) clazz.newInstance();//通过字节码对象创建对象
demoClass.run();
十一、 动态代理的概述和实现
-
动态代理概述
代理:本来应该自己做的事情,请了别人来做,被请的人就是代理对象。就是可以把本身的方法添加其他方法
动态代理:在程序运行过程中产生的这个对象,而程序运行过程中产生对象其实就是反射,所以,动态代理其实就是通过反射来生成一个代理
-
在Java中java.lang.reflect包下提供了一个
Proxy
类和一个InvocationHandler
接口,通过使用这个类和接口就可以生成动态代理对象。JDK提供的代理只能针对接口做代理。我们有更强大的代理cglib,Proxy类中的方法创建动态代理类对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
- 最终会调用
InvocationHandle
r的方法 InvocationHandler Object invoke(Object proxy,Method method,Object[] args)
案例:
第一步 创建接口
public interface User {
public void add();
public void delete();
}
public interface Student {
public void login();
public void submit();
}
第二步,实现接口方法
public class UserImp implements User {
@Override
public void add() {
System.out.println("添加");
}
@Override
public void delete() {
System.out.println("删除");
}
}
public class StudentImp implements Student {
@Override
public void login() {
System.out.println("登陆");
}
@Override
public void submit() {
System.out.println("提交");
}
}
第三步:书写MyInvocationHandler接口
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("权限校验");
method.invoke(target, args);
//执行被代理target对象方法
System.out.println("日志文件");
return null;
}
}
第四步做测试:
public class TEST {
public static void main(String[] args) {
UserImp ui =new UserImp();
ui.add();
ui.delete();
System.out.println("------");
MyInvocationHandler myInvocationHandler=new MyInvocationHandler(ui);
User u =(User) Proxy.newProxyInstance(ui.getClass().getClassLoader(), ui.getClass().getInterfaces(), myInvocationHandler);
u.add();
u.delete();
System.out.println("-------");
StudentImp si =new StudentImp();
si.login();
si.submit();
MyInvocationHandler myInvocationHandler2 =new MyInvocationHandler(si);
Student student=(Student) Proxy.newProxyInstance(si.getClass().getClassLoader(), si.getClass().getInterfaces(),myInvocationHandler2);
student.login();
student.submit();
}
}
效果如下: