类的加载
当程序要使用某个类时, 如果该类还未被加载到内存中, 则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。
加载
就是指将 class 文件读入内存,并为之创建一个 Class对象。任何类被使用时系统都会建立一个 Class对象。
连接
验证是否有正确的内部结构,并和其他类协调一致准备负责为类的静态成员分配内存,并设置默认初始化值解析将类的二进制数据中的符号引用替换为直接引用
初始化
初始化步骤:
2 、类初始化时机
1.创建类的实例
2.访问类的静态变量,或者为静态变量赋值调用类的静态方法
3.使用反射方式来强制创建某个类或接口对应的 java.lang.Class对象
4.初始化某个类的子类
5.直接使用 java.exe 命令来运行某个主类
3 、类加载器
类加载器: 负责将 .class 文件加载到内在中,并为之生成对应的 Class对象。
虽然我们不需要关心类加载机制, 但是了解这个机制我们就能更好的理解程序的运行。
类加载器的组成
1. Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责 Java核心类的加载
比如 System,String 等。在 JDK中 JRE的 lib 目录下 rt.jar 文件中
2. Extension ClassLoader 扩展类加载器
负责 JRE的扩展目录中 jar 包的加载。
在 JDK中 JRE的 lib 目录下 ext 目录
3.Sysetm ClassLoader 系统类加载器
负责在 JVM 启动时加载来自 java 命令的 class 文件,以及 classpath 环境变量所指定的 jar 包和类路径
反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
要想解剖一个类 ,必须先要获取到该类的字节码文件对象。而解剖使用的就是 Class
类中的方法 .所以先要获取到每一个字节码文件对应的 Class 类型的对象 .
1.获取 class 文件对象的方式:
Object 类的 getClass() 方法
如: s.getClass();
数据类型的静态属性 class
如 : Student.class;
Class 类中的静态方法: public static Class forName(String className) ; // 完整类名
如: Class. forName( “cn.itcast.Student ” ) ;
一般我们到底使用谁呢 ?
A:自己玩 任选一种,
第二种比较方便
B:开发 第三种
因为第三种是一个字符串,而不是一个具体的类名。这样我们就可以把这样的字符串配置到配置文件中
2.获取构造方法
A、得到构造方法对象
public Constructor[] getConstructors() : 所有公共构造方法
public Constructor[] getDeclaredConstructors(): 所有构造方法
获取单个构造方法
public Constructor<T> getConstructor(Class<?>... parameterTypes)
参数表示:你要获取的构造方法的构造参数个数及数据类型的 class 字节码文件对象
B、创建对象
public T newInstance(Object... initargs)
使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例, 并用指定的初始化参数初始化该实例
// 通过无参构造器
Constructor con = c.getConstructor(); Object obj = con.newInstance();
// 通过带参构造
Constructor con = c.getConstructor(String. class , int . class ,String. class ); Object obj = con.newInstance( " 林青霞 " , 27, " 北京 " );
// 通过私用构造方法
Constructor con = c.getDeclaredConstructor(String.class);
// 暴力访问,值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
con.setAccessible( true );
Object obj = con.newInstance(" 风清扬 ");
3.获取成员变量
public Field getField(String name) ;
返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段
public Field[] getFields() ;
返回一个包含某些 Field 对象的数组, 这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。
// 获取字节码文件对象
Class c = Class.forName("cn.itcast_01.Person");
通过无参构造方法创建对象Constructor con = c.getConstructor(); Object obj = con.newInstance();
// 获取单个的成员变量
Field addressField = c.getField("address" );
// public void set(Object obj,Object value)
// 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
addressField.set(obj, " 北京 " ); // 给obj 对象的 addressField 字段设置值为 " 北京 "
// 获取 name并对其赋值
// NoSuchFieldException ,name属性为私有
Field nameField = c.getDeclaredField("name" );
// IllegalAccessException nameField.setAccessible(true); nameField.set(obj, " 林青霞 " );
// 获取 age 并对其赋值
Field ageField = c.getDeclaredField("age"); ageField.setAccessible( true);
ageField.set(obj, 27);
4.获取成员方法并执行
public Method getMethod(String name,Class<?>... parameterTypes) ;
第一个参数表示的方法名,第二个参数表示的是方法的参数的 class 类型 public Method[] getMethods() ;获取自己的包括父亲的公共方法 public Method getDeclaredMethod(String name,
Class<?>... parameterTypes)
获取自己的包括父亲的任意方法
public Method[] getDeclaredMethods() ;
获取自己的包括父亲的所有方法
执行
Method 类的方法: public Object invoke(Object obj,Object... args) ;
返回值是 Object 接收 , 第一个参数表示对象是谁, 第二参数表示调用该方法的实际
参数
执行私有方法: public void setAccessible(boolean flag) ;取消访问检查
案例
通过配置文件运行类中的方法
// 加载键值对数据
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> 这个集合里添加一个字符串数据
通过反射来实现
ArrayList<Integer> array = new ArrayList<Integer>();
// array.add("hello");
// array.add(10);
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" );
5.代理模式
1)动态代理
1)代理对象,不需要实现接口,目标对象必须实现接口;
2)代理对象的生成,是利用 JDKAP,I 动态的在内存中构建代理对象
(需要我们指定创建代理对象 / 目标对象实现的接口的类型; );
3) 动态代理, JDK代理,接口代理;
publicclass ProxyFactory {
// 维护一个目标对象
private Object target ;
public ProxyFactory(Object target){ this. target= target;
}
// 给目标对象,生成代理对象
public Object getProxyInstance() { return Proxy.newProxyInstance(
target .getClass().getClassLoader(),
target .getClass().getInterfaces(), newInvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System. out .println( " 开启事务 " );
// 执行目标对象方法
Object returnValue = method.invoke( target , args); System. out .println( " 提交事务 " );
return returnValue;
}
});
}
}
// 目标对象
IUserDao target = new UserDao();
// [原始的类型 class cn.itcast.b_dynamic.UserDao]
System.out.println(target.getClass());
// 给目标对象,创建代理对象
IUserDao proxy=(IUserDao) newProxyFactory(target).getProxyInstance();
// class $Proxy0 内存中动态生成的代理对象
System.out.println(proxy.getClass());
// 执行方法[代理对象]
proxy.save();
2) 静态代理
1)代理对象,要实现与目标对象一样的接口;
2)举例 :
保存用户 (模拟)
Dao , 直接保存
DaoProxy, 给保存方法添加事务处理
publicclass UserDaoProxy implements IUserDao{
// 接收保存目标对象
private IUserDao target;
public UserDaoProxy(IUserDao target) {
this. target= target;
}
@Override
publicvoid save() {
System. out .println( " 开始事务 ..." );
target .save(); // 执行目标对象的方法
System. out .println(" 提交事务 ..." );
}
}
总结静态代理:
1)可以做到在不修改目标对象的功能前提下,对目标对象功能扩展。
2)缺点:因为代理对象,需要与目标对象实现一样的接口。所以会有很多代理类,类太多。
一旦接口增加方法,目标对象与代理对象都要维护。