反射
关于java.lang.Class类的理解
类加载过程:
程序经过javac.exe命令编译以后,会生成一个或多个字节码文件(.class结尾)。
加载:接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就成为运行时类,此运行时类,就作为Class的一个实例,我们可以通过这个实例获取相关信息(不需要new,直接赋值)。类缓存:一旦被加载,他就会缓存一段时间。
链接:设置类变量的默认初始化值
初始化:将属性进行赋值
反射:程序可以访问、检测和修改它本身状态或行为的一种能力。
java.lang.reflect包
反射的动态性(作用):反射机制允许程序在执行时借助API取得任何类的内部信息,从而直接操作任意对象的内部属性和方法。换句话说,当我们只有在程序运行时才知道该创 建哪个对象时(就是传参),才会创建哪个对象。在框架中大多使用反射,模板写好,只需要我们动态传入少量的参数,通过反射来调用参数对应的类创建对象,执行操作。
好处:
- 可以在程序运行中,操作这些对象
- 可以解耦,提高程序的可扩展性
类加载器
作用:用于将类加载到内存当中
- 引导类加载器:主要负责加载Java核心类库,无法加载自定义类,无法获得
- 扩展类加载器:主要加载jre/ext目录下的jar包,可以通过
系统类加载器.getParent()
获得 - 系统类加载器:负责加载自定义类,可以通过
类名.class.getClassLoader()
获得
使用类加载器读取配置文件
Properties pros = new Properties();
//方式1:使用输入流的方式
//此时文件默认在当前module下
FileInputStream fis = new FileInputStream("jdbc.properties");
pros.load(fis);
//方式2:使用类加载器读取
//此时文件默认在当前module的src目录下
ClassLoader classLoader = test.class.getClassLoader();
InputSteam is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
==所有的类都属于Class类==
获取Class对象的方式
(常用)Class.forname(“全类名”):调用Class的静态方法,将字节码文件加载进内存,返回Class对象
- 多用于配置文件,将类名定义在配置文件中,读取文件加载类
Class clazz = Class.forName("com.jm.java.Person");
类名.class:通过类名的属性class获取
- 多用于参数的传递
Class clazz = Person.class;
对象.getClass():通过运行时类的对象,调用getClass(),方法在object类中定义着
- 多用于对象的获取字节码方式
Person p1 = new Person(); Class clazz = p1.getClass();
使用类的加载器:ClassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader(); Class clazz4 = classLoader.loadClass("com.jm.java.Person");
结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪种方式获取的Class对象都是同一个。
Class对象功能:
获取功能:
Class对象.
获取成员变量们
- Filed[] getFields() :获取运行时类及其父类所有==public==修饰的成员变量
- Filed[] getField(String org)
- Filed[] getDeclaredFields():不考虑修饰符,获取所有(不包含父类)的成员变量
- Filed[] getDeclaredField()
获取构造方法
- Constructor
getConstructor(xxx.class,xxx.class):获取有参构造器,一般很少用得到,用的都是 clazz.newInstance()
使用空参构造器创建对象 - Constuctor getDeclaredConstructor(xxx.class)
- Constructor
获取运行时类的父类
- Class getSuperclass()
- Class
getGenericSuperclass():获取带泛型的父类
获得成员方法
Method[] getMethods():获取运行时类及其父类所有==public==修饰的方法
Method getMethod(String s,xxx.class……)
Method[] getDeclaredMethods(String name,String.class参数列表):不考虑修饰符,获取所有(不包含父类)的方法
获取类名
- String getName()
操作运行时类指定的属性:
- 成员变量.get(Object o):获取哪个对象的值
- 成员变量.set(Object o,String s):给哪个对象设置值
- 成员变量.setAccessible(true):暴力反射,忽略私有限制,同样可以获取成员变量
操作运行时类指定的构造方法:
- T newInstance(org) :==创建对象==
- 要求:
- 运行时类必须提供空参构造器
- 空参构造器的访问权限需要是public
- 引申:在javaBean中要求提供一个Public的空参构造器,原因:
- 便于通过反射,创建运行时类的对象
- 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
- 引申:在javaBean中要求提供一个Public的空参构造器,原因:
- 简化:直接用类对象来创建对象:personClass.newInstance()
操作运行时类指定的方法:
invoke(Object o,org) : 执行方法。
- 参数1:方法的调用者 参数2:给方法形参赋值的实参
- 返回值就是方法的返回值
- 静态方法的调用者是:运行时类.class
//1.获取class对象 Class clazz = person.class; //2.获取运行时类对象 Person p = (Person) clazz.newInstance(); //3.获取方法对象 Method method = clazz.getMethod("study",String.class); //4.0 暴力反射,忽略修饰符 method.setAccessible(true); //4.执行方法 method.invoke(p,"好好学习");
getName() : 获取方法名称
getAnnotations():获取方法注解(前提是注解的生命周期必须是RUNTIME,因为反射是runtime)
动态代理
代理模式:
使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何调用原始对象都需要通过代理,代理对象再决定方法逻辑和是否要转到原始对象方法
//静态代理
interface User{
void save();
}
class userImpl implements User{
@Override
public void save() {
System.out.println("保存操作");
}
}
class userProxy implements User{
private User u;
public userProxy(User u){
this.u = u ;
}
@Override
public void save() {
System.out.println("开启事务");
u.save();
System.out.println("结束事务");
}
}
class Test{
public static void main(String[] args) {
userImpl u = new userImpl();
userProxy userProxy = new userProxy(u);
userProxy.save();
}
}
代理模式优点:在不修改原码的基础上,对方法进行增强
静态代理的缺点:
每个代理实现类实现的接口都写死了,如果每个业务都需要增加功能,都需要创建响应的代理类,
所以动态代理油然而生,写一个通用的代理实现类,当运行时根据调用的对象来动态生成代理类,
其动态代理类的特点在于:不知道要创建哪个被代理对象的代理类,不知道被代理对象的方法,因此使用到反射来获取其代理对象和代理方法。
动态代理
普通静态代理,是指代理类和原始对象的类都已经在编译时期确定下来,不利于程序的扩展,而且一个代理类只能为一个接口服务。而动态代理客户通过代理类调用其他对象的方法,并且在程序运行时根据需要动态创建代理对象。
//第一步:创建接口,JDK动态代理基于接口实现,所以接口必不可少(准备工作)
//接口
interface Man{
void save();
}
//被代理类
class ManImpl implements Man{
@Override
public void save() {
System.out.println("说话!");
}
}
//第二步:实现InvocationHandler接口,重写invoke方法(准备工作)
//动态代理实现类
class MyHandler implements InvocationHandler{
//创建handler对象时,初始化obj
private Object obj ;
public MyHandler(Object obj){
this.obj = obj;
}
//执行方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置操作");
method.invoke(obj,args);
System.out.println("后置操作");
return null;
}
//第三步:调用Proxy的静态方法newProxyInstance方法生成代理实例(生成实例时需要提供类加载器,我们可以使用接口类的加载器即可)
public Object getProxyInstance(){
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
}
}
public static void main(String[] args) {
Man man = new ManImpl();//创建被代理对象
MyHandler handler = new MyHandler(man);//创建handler
//Man manImpl = (Man) handler.getProxyInstance();//创建代理类
Man manImpl = (Man) Proxy.newProxyInstance(ManImpl.class.getClassLoader(),ManImpl.class.getInterfaces(),handler);
//第四步:使用新生成的代理实例调用某个方法实现功能。
manImpl.save();//执行方法
}
案例
需求:写一个”框架”,可以帮我们创建任意类的对象,并且执行其中任意方法
思路:创建配置文件,写入全类名和方法名,将配置文件加载进内存,获取配置文件的值,创建对象,执行方法
public class ReflectTest {
public static void main(String[] args) throws Exception {
//获取properties对象
Properties properties = new Properties();
//加载配置文件
InputStream is = ReflectTest.class.getClassLoader().getResourceAsStream("pro.properties");
properties.load(is);
//获取配置文件的值
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//获取class对象
Class cls = Class.forName(className);
//创建对象,执行方法
//这里cls.newInstance()被废弃了
Object obj = cls.getDeclaredConstructor().newInstance();
Method method = cls.getMethod(methodName);
method.invoke(obj);
}
}
问题
什么时候使用反射的方式
反射是视为动态语言的关键,反射机制允许程序在运行期借助api获取任意类的内部信息,并可以直接操作任意对象的内部属性和方法 反射的特征:动态性。当不确定实例化哪个对象和方法的时候,才用得到反射
反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?
不矛盾,两个的目的性不一样 面向对象的封装性的目的就是为了让人知道,哪些属性推荐你调用的,哪些属性并不需要调用也能实现相同的效果。 反射机制的目的是表明自己可以调用这些属性,虽然不推荐调用,但是如果真的想调用也不是不可以。
写出获取Class实例的三种常见方式
Class clazz = Person.class; Class clazz = Class.forName("com.jm.java.Person"); Person person = new Person(); Class clazz = person.getClass();
关于Class类的理解
1.Class实例对应加载到内存中的一个运行时类 2.Class实例调取其中的方法,就是调取对应运行时类的结构
创建Class对应运行时类的对象的通用方法,以及这样操作,需要对应运行时类构造器方面满足的要求
Object obj = clazz.newInstance(); 1.必须要有空参构造器 2.空参构造器的权限修饰符的权限要够,通常为public
调用方法
Class clazz = Person.class; Person person = (Person)clazz.newInstance(); Method method = clazz.getDeclaredMethod(); method.setAccessable(true); method.invoke(person);
读取配置文件
Properties props = new Properties(); ClassLoader classloader = test.class.getClassLoader(); InputStream is = classloader.getResourceAsStream(); props.load(is);
反射机制能提供的功能
在运行时判断任意一个对象所属的类 在运行时构造任意一个类的对象 在运行时判断任意一个类所具有的成员变量和方法 在运行时获取泛型信息 在运行时调用任意一个对象的成员变量与方法 在运行时处理注解 生成动态代理
相关API
java.lang.Class java.lang.reflect.Method java.lang.reflect.Field
创建类的对象的方式
1. new+构造器
2. 工具类中是否有静态方法存在
3. 通过反射
反射机制就是程序.java文件在编译之后生成class文件,通过类加载器将class文件加载进内存,加载进内存的就称之为运行时类,而运行时类就对应着一个class实例,我们可以通过操作这个实例来获取运行时类对应的属性及方法。
反射的动态性:因为是操控class实例,已经经过了编译过程进入运行过程,换句话说我们只有在运行程序时才知道到底创建了哪个对象,从而进行相应对象的逻辑操作