反射机制
定义
动态调用类、对象的信息,方法和属性
反射机制的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射机制主要的API
java.lang.Class
:代表一个类java.lang.reflect.Method
:代表类的方法java.lang.reflect.Field
:代表类的成员变量java.lang.reflect.Constructor
:代表类的构造器
反射机制演示
public class ReflectionTest {
@Test
public void test1() {
// p普通方式创建对象,以及调用属性、方法
Person p1 = new Person("Tom",21);
p1.setName("张三");
p1.methods1("aa");
// 无法调用私有方法
// p1.methods2();
System.out.println(p1);
}
// 通过反射机制创建对象、调用属性、方法
@Test
public void test2() throws Exception{
Class<Person> aClass = Person.class;
// 通过反射调用构造器方法
Constructor<Person> con1 = aClass.getConstructor(String.class, int.class);
Person p1 = con1.newInstance("Mack", 21);
System.out.println(p1);
// 通过反射机制调用方法
Method methods1 = aClass.getDeclaredMethod("methods1", String.class);
methods1.invoke(p1,"哈哈哈");
// 通过反射机制调用私有方法
Method methods2 = aClass.getDeclaredMethod("methods2");
methods2.setAccessible(true);
methods2.invoke(p1);
// 通过反射机制调用普通
Field age = aClass.getDeclaredField("age");
age.set(p1,22);
System.out.println(p1);
// 通过反射机制调用私有属性
Field name = aClass.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"李四");
System.out.println(p1);
}
}
class Person {
private String name;
public int age ;
public Person() {
}
public void methods1(String s) {
System.out.println("我是一个public方法");
}
private void methods2(){
System.out.println("我是一个私有方法");
}
@Override
public String toString() {
return "Persion{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
private Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
疑问1 : 既然可以 new 对象为什么还要有 反射机制 ? 使用哪种方式呢?
平常肯定是使用 new 的方式,但是在某些应用场景下,还是需要使用反射机制。比如:我们在编译期不确定要创建哪个对象,要执行哪个方法。就需要反射机制的动态调用。
比如在 项目中,在项目启动时前端发送请求,这个时候后端就需要根据请求路径动态判断需要执行哪个 Servlet ,这就需要反射机制
以下代码截取为 javaweb 书城的 反射机制代码:
对 Class 的理解
编写完代码后,由前端编译器对源代码文件编译成 .class 字节码文件。
通过 类加载器 ClassLoader 将class文件加载到内存中去。
这个被加载到内存中的类就被称为运行时类,这个类就是Class的一个实例
Person p1 = new Person();
p1 是 Person 的实例,而Person 是 Class的实例。(前提是编译运行之后)
因此也可以说一个 Class实例对应一个运行时类
Class实例获取的四种方式
- 调用运行时类的属性: .class
- 通过运行时类的对象,调用getClass()
- 通过Class的静态方法,Class.forName(String classPath)
- 通过类加载器中的 loadClass()
@Test
public void test1() throws ClassNotFoundException {
// 第一种方式: 调用运行时类的属性
Class<Person> clazz = Person.class;
System.out.println(clazz);
//第二种方式:通过运行时类的对象,调用 getClass
Person person = new Person();
Class clazz1 = person.getClass();
System.out.println(clazz1);
//第三种方式:调用Class的静态方法 forName
Class<?> clazz2 = Class.forName("Person");
System.out.println(clazz2);
// 第四种方式:通过ClassLoader的 loadClass方法
ClassLoader classLoader = ReflectionTest2.class.getClassLoader();
Class<?> clazz3 = classLoader.loadClass("Person");
System.out.println(clazz3);
}
Class 对应内存结构说明
哪些类型可以有Class对象?
(1)class
:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface
:接口
(3)[]
:数组
(4)enum
:枚举
(5)annotation
:注解@interface
(6)primitivetype
:基本数据类型
(7)void
@Test
public void test4() {
Class s1 = Object.class;
Class s2 = Comparable.class;
Class s3 = String[].class;
Class s4 = int[][].class;
Class s5 = ElementType.class;
Class s6 = Override.class;
Class s7 = int.class;
Class s8 = void.class;
Class s9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class s10 = a.getClass();
Class s11 = b.getClass();
// 只要数组的元素类型与维度一样,就是同一个Class
System.out.println(s10 == s11);
}
加载Properties文件的俩种方式
第一种方式:使用 FileInputStream方式
@Test
public void test() throws Exception{
// 第一种方式: 文件路径默认在module下
Properties prop = new Properties();
FileInputStream is = new FileInputStream("jdbc.properties");
prop.load(is);
String name = prop.getProperty("name");
String age = prop.getProperty("age");
System.out.println(name + " "+age);
}
第二种方式:使用CLassLoader加载
@Test
public void test1() throws IOException {
// 第二种方式: 默认路径在 src 下
Properties prop = new Properties();
ClassLoader classLoader = this.getClass().getClassLoader();
InputStream asStream = classLoader.getResourceAsStream("jdbc1.properties");
prop.load(asStream);
String name = prop.getProperty("name");
String age = prop.getProperty("age");
System.out.println(name + " " + age);
}
俩种方式的区别:
第一中方式默认加载的文件路径在 此module 下
第二种方式默认加载文件路径在 src 目录下
第一种方式也可以加载src目录下,增加目录 路径就行了:
FileInputStream is = new FileInputStream("src/jdbc1.properties");
调用运行时类的结构
调用运行时类的指定属性
俩个方法,第一个方法要求类的属性权限至少是 public,第二个要求属性权限至少是private,一般都使用第二种。
// 获取运行时类的属性
@Test
public void test() throws Exception {
Class<Person> clazz = Person.class;
// 创建运行时类对象
Person person = clazz.newInstance();
// 获取指定属性
Field name = clazz.getDeclaredField("name");
// 打破封装
name.setAccessible(true);
// 设置属性
// 第一个参数为属性的对象,第二个参数为属性值
name.set(person,"张三");
// 获取属性
Object name1 =name.get(person);
System.out.println(name1);
}
调用运行时类指定的方法
使用情况和属性的一样
@Test
public void test1() throws Exception {
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
// 获取指定方法: 第一个参数为方法名,第二个参数为方法形参类型
Method methods1 = clazz.getDeclaredMethod("methods2",String.class);
methods1.setAccessible(true);
// invoke:执行方法,invoke方法的返回值是调用方法的返回值
methods1.invoke(person,"李四");
}
调用运行时类指定的构造器
和上面一样不写了
// 调用运行时类的指定构造器
@Test
public void test2() throws Exception {
Class<Person> clazz = Person.class;
Person person = clazz.newInstance();
Constructor<Person> constructor = clazz.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
Person person1 = constructor.newInstance("李四");
System.out.println(person1);
}
反射的应用:动态代理
代理模式的原理:
使用一个代理将对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
-
静态代理:特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能。
-
动态代理:是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。
-
动态代理相比于静态代理的优点:
抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。
静态代理演示:
public class StaticProxyTest {
interface ClothFactory {
void createCloths();
}
// 代理类
static class ProxyClothFactory implements ClothFactory{
private ClothFactory clothFactory;
public ProxyClothFactory(ClothFactory clothFactory) {
this.clothFactory = clothFactory;
}
@Override
public void createCloths() {
System.out.println("代理工厂的一些准备工作");
clothFactory.createCloths();
System.out.println("代理工厂的一些后序工作");
}
}
// 被代理类
static class NikeClothFactory implements ClothFactory{
@Override
public void createCloths() {
System.out.println("NIKE 生成了一批衣服");
}
}
public static void main(String[] args) {
NikeClothFactory nikeClothFactory = new NikeClothFactory();
ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nikeClothFactory);
proxyClothFactory.createCloths();
}
}
输出结果:
代理工厂的一些准备工作
NIKE 生成了一批衣服
代理工厂的一些后序工作
动态代理演示:
动态代理:通过动态生成代理类对象,去调用被代理类中的方法。
一般代理类就是一个接口,只提供方法,不提供具体的服务。在被代理类中提供服务。
- 如何通过加载到内存中的被代理类,动态生成代理类及其对象?
- 通过代理对象调用方法时,如何动态的去调用被代理类的方法?
/**
*
* Author: YZG
* Date: 2022/11/6 18:25
* Description: 动态代理:通过动态生成代理类对象调用被代理类中的方法
*/
public class DynamicProxyTest {
public static void main(String[] args) {
Man man = new Man();
// 代理类的对象
Human human = (Human) Man.ProxyFactory.getInstance(man);
human.eat("麻辣烫");
human.sleep(8);
}
// 代理类
interface Human {
void eat(String food);
void sleep(int hours);
}
// 被代理类
static class Man implements Human {
@Override
public void eat(String food) {
System.out.println("男人吃了 " + food);
}
@Override
public void sleep(int hours) {
System.out.println("男人睡了 " + hours + "小时");
}
// 动态代理类
static class ProxyFactory {
/**
* @description 调用此方法 生成一个代理类的对象
* @date 2022/11/6 19:06
* @param obj 被代理类的对象
* @return java.lang.Object
*/
public static Object getInstance(Object obj) {
return Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
/**
* @description 动态调用被代理类中的方法。需要实现 InvocationHandler 接口
* @date 2022/11/6 19:07
* @param obj 被代理类的对象
* @return java.lang.Object
*/
(Object proxy, Method method, Object[] args) -> {
// 动态调用被代理类对象中的方法
return method.invoke(obj, args);
});
}
}
}
}
关于 Proxy.newProxyInstance 的三个参数: 被代理类的类加载器,被代理类实现的接口(代理类)、需要实现指定的接口