# 二十、反射(完结)
作者:互联网
二十、反射
20.1 类的加载
20.1.1 类的加载概述
程序运行后,某个类在第一次使用时,会将该类的 class
文件读取到内存,并将此类的所有信息存储到一个Class
对象中
20.1.2 类加载的时机
- 创建类的实例
- 访问类的成员
- 使用反射方式来强制创建某个类或接口对应的
java.lang.Class
对象。 - 初始化某个类的子类,父类会先加载。
- 直接使用
java.exe
命令来运行某个主类。
总结:用到就加载,不用就不加载
20.1.3 类加载的过程介绍
当一个**Java**
** 文件要被加载到内存中使用执行的过程**
- 需要把当前的
Java
文件通过Javac
编译成字节码文件(.class
文件) - 字节码文件需要进行 加载 , 连接 , 初始化 三个动作
- 字节码数据加载到
jvm
方法区内存中, 在堆内存中创建此类的对象 ,Java
程序进行使用
注意 : 把一个字节码文件加载到内存过程 , 就需要用到类加载进行完成
20.2 类的加载过程各阶段的任务
当
Java
程序中需要使用到某个类时,虚拟机会保证这个类已经被加载、连接和初始化。而连接又包含验证、准备和解析这三个子过程,这个过程必须严格按照顺序执行
20.2.3 加载
- 通过类的全类名(包名和类名) , 查找此类的字节码文件,把类的
.class
文件中的二进制数据流读入到内存中,并存放在运行时数据区的方法区内,然后利用字节码文件创建一个Class
对象,用来封装类在方法区内的数据结构并存放在堆区内。 - 简单来说 : 就是把硬盘中的字节码文件 , 加载到
jvm
中的方法区以字节码对象的形式存在 , 并在堆内存中创建此类对象
20.2.2 连接
- 验证 : 确保被加载类的正确性。
class
文件的字节流中包含的信息符合当前虚拟机要求,不会危害虚拟机自身的安全。 - 准备 : 为类的静态变量分配内存,并将其设置为默认值。此阶段仅仅只为静态类变量(即
static
修饰的字段变量)分配内存,并且设置该变量的初始值。(比如static int num = 5
,这里只将num
初始化为0,5的值将会在初始化时赋值)。对于final static
修饰的变量,编译的时候就会分配了,也不会分配实例变量的内存。 - 解析 : 把类中的符号引用转换为直接引用。符号引用就是一组符号来描述目标,而直接引用就是直接指向目标在内存的位置,即地址,如果引用的这个类没加载进内存就会先加载,加载了就直接替换。
- 简单理解就是如果当前类中用到了其他类, 就他符号引用替换成其他类
20.2.3 初始化
- 类加载最后阶段,为静态变量赋值,静态代码块的代码也将被初始化
- 若该类具有父类,则先对父类进行初始化
- 底层的初始化方法加了锁,做了线程同步校验:如果多个线程同时对一个类继续初始化,一次只有一个线程会执行,其他线程会阻塞等待。
20.3 类加载器
20.3.1 Java虚拟机自带的类加载器 (了解)
类加载器:是负责将磁盘上的某个class文件读取到内存并生成Class的对象。
Java
中有三种类加载器,它们分别用于加载不同种类的 class
:
- 启动类加载器(
Bootstrap ClassLoader
):用于加载系统类库<JAVA_HOME>\bin目录下的class
,例如:rt.jar
- 扩展类加载器(
Extension ClassLoader
):用于加载扩展类库<JAVA_HOME>\lib\ext
目录下的class
- 应用程序类加载器(
Application ClassLoader
):用于加载我们自定义类的加载器。
public class Test{
public static void main(String[] args){
//通过Class对象,获取类加载器的方法 --> public ClassLoader getClassLoader() : 返回该类的类加载器
//如果该类由启动类加载器加载(例如核心类库中的类String 等),则将返回 null。
System.out.println(Test.class.getClassLoader());//sun.misc.Launcher$AppClassLoader
System.out.println(String.class.getClassLoader());//null
}
}
总结:
- 在程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,同时我们还可以自定义类加载器
- 需要注意的是,
java
虚拟机对class
文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class
文件加载到内存中生成class
对象 - 而且加载某个类的
class
文件时,Java
虚拟机采用的是双亲委派模式,即把加载类的请求交由父加载器处理,它是一种任务委派模式
20.3.2 类加载器--双亲委派机制介绍
上图展示了"类加载器"的层次关系,这种关系称为类加载器的 "双亲委派模型":
- "双亲委派模型"中,除了顶层的启动类加载器外,其余的类加载器都应当有自己的"父级类加载器"。
- 这种关系不是通过"继承"实现的,通常是通过"组合"实现的。通过"组合"来表示父级类加载器。
- "双亲委派模型"的工作过程:
- 某个"类加载器"收到类加载的请求,它首先不会尝试自己去加载这个类,而是把请求交给父级类加载器。
- 因此,所有的类加载的请求最终都会传送到顶层的"启动类加载器"中。
- 如果"父级类加载器"无法加载这个类,然后子级类加载器再去加载。
public class ClassLoaderDemo4 {
/*
当前是根据自己定义的类进行获取的类加载器对象
getParent方法获取父类加载器
第一条输出语句打印的是 : 系统类加载器
第二条输出语句打印的是 : 扩展类加载器
第三条输出语句打印的是 : null是根类加载器
*/
public static void main(String[] args) {
ClassLoader classLoader = ClassLoaderDemo4.class.getClassLoader();
System.out.println(classLoader);// sun.misc.Launcher$AppClassLoader@b4aac2
System.out.println(classLoader.getParent());// sun.misc.Launcher$ExtClassLoader@16d3586
System.out.println(classLoader.getParent().getParent());// null
}
20.3.3 双亲委派模型的优点
- 避免类的重复加载,
- 当父类加载器已经加载了该类时,就没有必要子
ClassLoader
再加载一次 , 字节码文件只加载一次
- 当父类加载器已经加载了该类时,就没有必要子
- 考虑到安全因素,
java
核心api
中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Object
的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API
发现这个名字的类,发现该类已被加载,并不会重新加载网络传递过来的java.lang.Object
,而直接返回已加载过的Object.class
,这样便可以防止核心**API**
库被随意篡改!!!
package java.lang;
/**
* @author: Carl Zhang
* @create: 2022-01-07 13:59
* 举例 : 如果我们自己定义一个包名字叫做java.lang , 在当前这个包下创建一个类叫做MyObject类
* 因为java.lang包属于核心包,只能由根类加载器进行加载,而根据类加载的双亲委派机制,根类加载不到这个MyObject类的(自定义的)**
* 所以只能由AppClassLoader进行加载,而这又是不允许的,因为java.lang下的类需要使用根加载器进行加载**
* 所以会报出"Prohilbited package name:java.lang"(禁止的包名)异常"
*/
public class MyObject {
public static void main(String[] args) {
System.out.println(MyObject.class.getClassLoader()); //java.lang.SecurityException: Prohibited package name: java.lang
}
}
20.4 反射的介绍
20.4.1 反射的引入
- 需求:如何根据配置文件
re.properties
里的不同的信息,创建指定的对象,调用各自的方法 - 以前的方式:读取配置文件里的信息,通过
switch
格式判断class
键对应的值,创建不同的对象,调用方法 - 问题:代码冗余,不方便
- 引入反射:实现动态创建对象,动态调用方法,这样的需求在学习框架时特别多,即通过外部文件配置,在不修改源码情况下,来控制程序,也符合设计模式的
ocp
原则(开闭原则:不修改源码,扩容功能)
20.4.2 反射的概念
- 反射是一种机制,利用该机制可以在程序运行过程中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性
- 利用反射可以无视修饰符获取类里面所有的属性和方法。先获取配置文件中的信息,动态获取信息并创建对象和调用方法
20.4.3 反射的使用前提和场景
- 使用反射操作类成员的前提:
- 要获得该类字节码文件对象,就是
Class
对象
- 要获得该类字节码文件对象,就是
- 反射在实际开发中的应用:
- 开发
IDE
(集成开发环境),比如IDEA
,Eclipse
- 各种框架的设计和学习 比如
Spring
,Hibernate
,Struct
,Mybaits
....
- 开发
20.5 Class对象的获取方式
20.5.1 三种获取方法
-
方式1:
类名.class
获得 -
方式2:
对象名.getClass()
方法获得,该方法是继承Object
类的 -
方式3:
Class
类的静态方法获得:static Class forName("类全名")
- 每一个类的
Class
对象都只有一个。
- 每一个类的
-
示例代码
package com.itheima._03反射;
public class Student{
}
public class ReflectDemo01 {
public static void main(String[] args) throws ClassNotFoundException {
// 获得Student类对应的Class对象
Class c1 = Student.class;
// 创建学生对象
Student stu = new Student();
// 通过getClass方法
Class c2 = stu.getClass();
System.out.println(c1 == c2);
// 通过Class类的静态方法获得: static Class forName("类全名")
Class c3 = Class.forName("com.itheima._03反射.Student");
System.out.println(c1 == c3); //true
System.out.println(c2 == c3); //true
}
}
20.5.2 Class类常用方法
String getSimpleName();
获得类名字符串:类名String getName();
获得类全名:包名+类名T newInstance() ;
创建Class
对象关联类的对象
示例代码
public class ReflectDemo02 {
public static void main(String[] args) throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得类名字符串:类名
System.out.println(c.getSimpleName());
// 获得类全名:包名+类名
System.out.println(c.getName());
// 创建对象
//此方式相当于通过类的空参构造创建对象,如果目标类没有空参构造,会报错 InstantiationException
Student stu = (Student) c.newInstance();
System.out.println(stu);
}
}
20.6 反射之操作构造方法
20.6.1 Constructor类概述
- 反射之操作构造方法的目的
- 获得
Constructor
对象来创建类的对象。
- 获得
Constructor
类概述- 类中的每一个构造方法都是一个
Constructor
类的对象
- 类中的每一个构造方法都是一个
20.6.2 Class类中与Constructor相关的方法
Constructor getConstructor(Class... parameterTypes)
:返回单个公共构造方法对象Constructor getDeclaredConstructor(Class... parameterTypes)
:返回单个构造方法对象,不受修饰符影响Constructor[] getConstructors()
:返回所有公共构造方法对象的数组,只能获得public
的Constructor[] getDeclaredConstructors()
:返回所有构造方法对象的数组,不受修饰符影响
20.6.3 Constructor对象常用方法
T newInstance(Object... initargs)
—— 根据指定的参数创建对象void setAccessible(true)
:设置"暴力反射"——是否取消权限检查,true
取消权限检查,false
表示不取消
示例代码
public class Student{
private String name;
private String sex;
private int age;
//公有构造方法
public Student(String name,String sex,int age){
this.name = name;
this.sex = sex;
this.age = age;
}
//私有构造方法
private Student(String name,String sex){
this.name = name;
this.sex = sex;
}
}
public class ReflectDemo03 {
/*
Constructor[] getConstructors()
获得类中的所有构造方法对象,只能获得public的
Constructor[] getDeclaredConstructors()
获得类中的所有构造方法对象,包括private修饰的
*/
@Test
public void test03() throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得类中的所有构造方法对象,只能获得public的
// Constructor[] cons = c.getConstructors();
// 获取类中所有的构造方法,包括public、protected、(默认)、private的
Constructor[] cons = c.getDeclaredConstructors();
for (Constructor con:cons) {
System.out.println(con);
}
}
/*
Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数类型获得对应的Constructor对象
*/
@Test
public void test02() throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得两个参数构造方法对象
Constructor con = c.getDeclaredConstructor(String.class,String.class);
// 取消权限检查(暴力反射)
con.setAccessible(true);
// 根据构造方法创建对象
Object obj = con.newInstance("rose","女");
System.out.println(obj);
}
/*
Constructor getConstructor(Class... parameterTypes)
根据参数类型获得对应的Constructor对象
*/
@Test
public void test01() throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得无参数构造方法对象
Constructor con = c.getConstructor();
// 根据构造方法创建对象
Object obj = con.newInstance();
System.out.println(obj);
// 获得有参数的构造方法对象
Constructor con2 = c.getConstructor(String.class, String.class,int.class);
// 创建对象
Object obj2 = con2.newInstance("jack", "男",18);
System.out.println(obj2);
}
}
20.7 反射之操作成员方法
20.7.1 Method类概述
- 反射之操作成员方法的目的
- 操作
Method
对象来调用成员方法
- 操作
Method
类概述- 每一个成员方法都是一个
Method
类的对象。
- 每一个成员方法都是一个
20.7.2 Class类中与Method相关的方法
Method getMethod(String name,Class...args);
返回单个公共的成员方法对象Method getDeclaredMethod(String name,Class...args);
返回单个的成员方法对象,不受访问修饰符限制Method[] getMethods();
返回所有公共的成员方法对象,包括继承的Method[] getDeclaredMethods();
返回所有的成员方法对象,不包括继承的
20.7.3 Method对象常用方法
Object invoke(Object obj, Object... args)
- 调用指定对象
obj
的该方法 args:
调用方法时传递的参数- 没有返回值,则返回
null
- 调用指定对象
void setAccessible(true)
设置"暴力访问"——是否取消权限检查,true
取消权限检查,false
表示不取消
示例代码
public class Student{
private void eat(String str){
System.out.println("我吃:" + str);
}
private void sleep(){
System.out.println("我睡觉...");
}
public void study(int a){
System.out.println("我学习Java,参数a = " + a);
}
}
public class ReflectDemo04 {
// 反射操作静态方法
@Test
public void test04() throws Exception {
// 获得Class对象
Class c = Student.class;
// 根据方法名获得对应的公有成员方法对象
Method method = c.getDeclaredMethod("eat",String.class);
// 通过method执行对应的方法
method.invoke(null,"蛋炒饭");
}
/*
* Method[] getMethods();
* 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
* Method[] getDeclaredMethods();
* 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
*/
@Test
public void test03() throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
// Method[] methods = c.getMethods();
// 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
Method[] methods = c.getDeclaredMethods();
for (Method m: methods) {
System.out.println(m);
}
}
/*
Method getDeclaredMethod(String name,Class...args);
* 根据方法名和参数类型获得对应的构造方法对象,
*/
@Test
public void test02() throws Exception {
// 获得Class对象
Class c = Student.class;
// 根据Class对象创建学生对象
Student stu = (Student) c.newInstance();
// 获得sleep方法对应的Method对象
Method m = c.getDeclaredMethod("sleep");
// 暴力反射
m.setAccessible(true);
// 通过m对象执行stuy方法
m.invoke(stu);
}
/*
Method getMethod(String name,Class...args);
* 根据方法名和参数类型获得对应的构造方法对象,
*/
@Test
public void test01() throws Exception {
// 获得Class对象
Class c = Student.class;
// 根据Class对象创建学生对象
Student stu = (Student) c.newInstance();
// 获得study方法对应的Method对象
Method m = c.getMethod("study");
// 通过m对象执行stuy方法
m.invoke(stu);
/// 获得study方法对应的Method对象
Method m2 = c.getMethod("study", int.class);
// 通过m2对象执行stuy方法
m2.invoke(stu,8);
}
}
20.8 反射之操作成员变量
20.8.1 Field类概述
- 反射之操作成员变量的目的
- 通过
Field
对象给对应的成员变量赋值和取值
- 通过
Field
类概述- 每一个成员变量都是一个
Field
类的对象。
- 每一个成员变量都是一个
20.8.2 Class类中与Field相关的方法
Field getField(String name);
返回单个公共的成员变量对象Field getDeclaredField(String name);
返回单个成员变量,不受修饰符限制Field[] getFields();
返回所有的公共的成员变量对象Field[] getDeclaredFields();
返回所有的成员变量对象
20.8.3 Field对象常用方法
void set(Object obj, Object value)
:给对象obj
的属性设置值Object get(Object obj)
:获取对象obj
对应的属性值void setAccessible(true);
暴力反射,设置为可以直接访问私有类型的属性。Class getType();
获取属性的类型,返回Class
对象。
示例代码
public class Student{
public String name;
private String gender;
public String toString(){
return "Student [name = " + name + " , gender = " + gender + "]";
}
}
public class ReflectDemo05 {
/*
Field[] getFields();
* 获得所有的成员变量对应的Field对象,只能获得public的
Field[] getDeclaredFields();
* 获得所有的成员变量对应的Field对象,包含private的
*/
@Test
public void test02() throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得所有的成员变量对应的Field对象
// Field[] fields = c.getFields();
// 获得所有的成员变量对应的Field对象,包括private
Field[] fields = c.getDeclaredFields();
for (Field f: fields) {
System.out.println(f);
}
}
/*
Field getField(String name);
根据成员变量名获得对应Field对象,只能获得public修饰
Field getDeclaredField(String name);
* 根据成员变量名获得对应Field对象,包含private修饰的
*/
@Test
public void test01() throws Exception {
// 获得Class对象
Class c = Student.class;
// 创建对象
Object obj = c.newInstance();
// 获得成员变量name对应的Field对象
Field f = c.getField("name");
// 给成员变量name赋值
// 给指定对象obj的name属性赋值为jack
f.set(obj,"jack");
// 获得指定对象obj成员变量name的值
System.out.println(f.get(obj)); // jack
// 获得成员变量的名字
System.out.println(f.getName()); // name
// 给成员变量gender赋值
// 获得成员变量gender对应的Field对象
Field f1 = c.getDeclaredField("gender");
// 暴力反射
f1.setAccessible(true);
// 给指定对象obj的gender属性赋值为男
f1.set(obj,"男");
System.out.println(obj);
}
}
20.9 使用反射解析注解
注:注解的基本介绍见 11.6 注解介绍
20.9.1 AnnotatedElement接口介绍
AnnotatedElement
: 是一个接口,定义了解析注解的方法
1. boolean isAnnotationPresent(Class<Annotation> annotationClass)
参数 : 注解的字节码对象
作用 : 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
2. T getAnnotation(Class<T> annotationClass)
参数 : 注解的字节码对象
返回值 : 根据注解类型获得对应注解对象 , 有了注解对象就可以调用属性(抽象方法),获取属性值
20.9.2 注解的解析原理
前提:Class,Constructor,Method,Field都实现了AnnotatedElement 接口。
解析注解的原理:获取注解作用位置的对象,来调用方法解析注解
- 解析类上的注解:借助字节码对象(Class对象)
- 解析构造方法上的注解 :借助构造器对象(Constructor对象)
- 解析方法上的注解 :借助方法对象(Method对象)
- 解析字段上的注解 :借助字段对象(Field对象)
相关方法:
isAnnotationPresent()
:判断是否存在注解getAnnotation()
:如果存在获取注解对象
20.9.3 注解的解析案例
需求如下:
1. 定义注解 `Book,要求如下:
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 限制注解使用的位置 :类和成员方法上 【Target】
- 指定注解的有效范围 :RUNTIME 【Retention】
2. 定义BookStore类,在类和成员方法上使用Book注解
3. 定义TestAnnotation测试类获取Book注解上的数据
给成员注入四大名著信息 :
西游记 --- 施耐庵
水浒传 --- 吴承恩
三国演义 --- 罗贯中
红楼梦 --- 曹雪芹 , 高鹗
Book注解 :
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//定义一个书的注解,包含属性书名,价格(默认100),作者。作者要求可以有多名作者
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
String value();
double price() default 100;
String[] author();
}
BookStore类 :
@Book(value = "水浒传", author = "施耐庵")
public class BookStore {
@Book(value = "三国演义", author = {"罗贯中"})
public void buy() {
}
}
**TestAnnotation**
** 类:**
import java.lang.reflect.Method;
import java.util.Arrays;
/**
思路:
1. 类型上的注解,使用Class对象解析
2. 方法上的注解,使用Method对象解析
*/
public class TestAnnotation {
public static void main(String[] args) throws NoSuchMethodException {
//解析类型上的注解
Class<BookStore> cls = BookStore.class;
//判断是否有Book注解,如果有进行解析
if (cls.isAnnotationPresent(Book.class)) {
//有
Book book = cls.getAnnotation(Book.class);
String name = book.value();
double price = book.price();
String[] author = book.author();
System.out.println(name);
System.out.println(price);
System.out.println(Arrays.toString(author));
}
//解析方法上面的注解
Method buyMethod = cls.getMethod("buy");
if (buyMethod.isAnnotationPresent(Book.class)) {
Book book = buyMethod.getAnnotation(Book.class);
String name = book.value();
double price = book.price();
String[] author = book.author();
System.out.println(name);
System.out.println(price);
System.out.println(Arrays.toString(author));
}
}
}
20.10 设计模式 - 代理模式
20.10.1 代理模式介绍
为什么要有 “代理” ?
- 生活中就有很多例子,比如委托业务,黄牛(票贩子)等等
- 代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,这才是“代理”存在的原因。
- 例如要租房子,房产中介可以在我们住房前代理我们找房子。中介就是代理,而自己就是被代理了。
在代码设计中,代理模式作用主要就是让 "被代理对象" 的某个方法执行之前或者执行之后加入其他增强逻辑。
前增强 : 例如获取当前时间
被代理对象调用方法
后增强 : 例如获取当前时间
计算方法执行的时间
20.10.2 代理的前提条件
- 抽象角色 :声明功能 ,相当于父接口
- 代理角色 :实现抽象功能 , 完成代理逻辑,相当于接口的实现类
- 被代理角色 :实现抽象功能,相当于接口的实现类
意味着被代理角色和代理角色有着共同的父类型(既抽象角色) , 例如我要租房子, 我只能找房产中介, 不能找票贩子
20.10.3 代理模式的两种实现方式
- 静态代理 (了解 , 用于对动态代理做铺垫)
- 动态代理 (为后面学习的框架做铺垫)
20.11 静态代理模式
20.11.1 静态代理模式的介绍
- 静态代理是由程序员创建 或 工具生成代理类的源码,再编译代理类。在程序运行前就已经存在代理类的字节码文件,代理类和被代理类的关系在运行前就确定了。
- 简单理解 : 在程序运行之前 , 代理类就存在了,这就是静态代理 ; 动态代理是程序运行时动态生成代理类
20.11.2 静态代理实现的步骤
- 存在一个抽象角色
- 定义被代理角色
- 定义代理,增强被代理角色的功能
20.11.3 静态代理案例
案例:以现实中经纪人代理明星
已知存在接口:
// 1.抽象角色
interface Star {
// 真人秀方法
double liveShow(double money);
void sleep();
}
定义被代理类:
- 定义王宝强类,实现Star方法
// 定义被代理角色(宝强)
class BaoQiang implements Star {
@Override
public double liveShow(double money) {
System.out.println("参加了真人秀节目 , 赚了" + money + "元");
return money;
}
@Override
public void sleep() {
System.out.println("宝强累了 , 睡觉了...");
}
}
定义代理类:
- 定义宋喆经纪人类
// 定义代理角色(宋喆),增强被代理角色的功能
class SongZhe implements Star {
BaoQiang baoQiang = new BaoQiang();
@Override
public double liveShow(double money) {// 1000
// 前增强
System.out.println("宋喆帮宝强接了一个真人秀的活动,获取佣金" + money * 0.8 + "元");
double v = baoQiang.liveShow(money * 0.2);
// 后增强
System.out.println("宋喆帮宝强把赚的钱存起来...");
return v;
}
@Override
public void sleep() {
System.out.println("宋喆帮宝强找了一家五星级大酒店...");
baoQiang.sleep();
System.out.println("宋喆帮宝强退房..");
}
}
定义测试类进行测试
public class StaticAgentDemo {
public static void main(String[] args) {
// 被代理角色
BaoQiang baoQiang = new BaoQiang();
double v = baoQiang.liveShow(1000);
System.out.println(v);
baoQiang.sleep();
System.out.println("===========================");
SongZhe songZhe = new SongZhe();
double v1 = songZhe.liveShow(1000);
System.out.println(v1);
songZhe.sleep();
}
}
关系图 :宋喆和宝强都有共同的父类型。他们的业务方法都是一样。
20.11.4 静态代理和装饰模式的区别
相同:
- 都要实现与目标类相同的业务接口
- 在俩个类中都要声明目标对象
不同:
- 目标不同 :
- 装饰者模式考虑的是对象某个功能的扩展,是在原有功能基础上增加
- 静态代理模式考虑的是对象某个功能的调用,对这个功能的流程把控和辅助
- 对象获取方式不同
- 装饰者模式是通过构造方法的传参来获取被装饰的对象
- 静态代理模式是内部直接创建被装饰的对象
注意:设计模式本身是为了提升代码的可扩展性,灵活应用即可,不必生搬硬套,非要分出个所以然来,装饰器模式和代理模式的区别也是如此
20.12 动态代理模式
20.12.1 动态代理模式介绍
- 在实际开发过程中往往我们自己不会去创建代理类而是通过
JDK
提供的Proxy
类在程序运行时,运用反射机制动态创建而成这就是我们所谓的动态代理。 - 与静态代理之间的区别,在于不用自己写代理类
- 虽然我们不需要自己定义代理类创建代理对象,但是我们要定义对被代理对象直接访问方法的拦截,原因就是对拦截的方法做增强。
- 动态代理技术在框架中使用居多,例如:很快要学到的数据库框架
MyBatis
框架等后期学的一些主流框架技术(Spring
,SpringMVC
)中都使用了动态代理技术。
20.12.2 动态代理相关API
- Proxy类
java.lang.reflect.Proxy
类提供了用于创建动态代理类和对象的静态方法- 它还是由这些方法创建的所有动态代理类的超类(代理类的父类是
Proxy
)。
//获取代理对象的方法
public static Object newProxyInstance (ClassLoader loader, Class<?>[] interfaces, InvocationHandler h )
/**解析:
- 返回值:该方法返回就是动态生成的代理对象
- 参数列表说明:
1. ClassLoader loader - 定义代理类的类加载器 = 被代理对象.getClass().getClassLoader();
2. Class<?>[] interfaces - 代理类要实现的接口列表,要求与被代理类的接口一样。 = 被代理对象.getClass().getInterfaces();
3. InvocationHandler h - 调用处理器:就是具体实现代理逻辑的接口
*/
**InvocationHandler**
** 接口**java.lang.reflect.InvocationHandler
是代理对象实际处理代理逻辑的接口,具体代理实现逻辑在其invoke
方法中。- 所有代理对象调用的方法,执行是都会经过
**invoke**
。因此如果要对某个方法进行代理增强,就可以在这个invoke
方法中进行定义。
interface InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args); //代理逻辑
1. 返回值:方法被代理后执行的结果。
2. 参数列表:
1. proxy - 就是代理对象
2. method -
3. args - 代理对象调用方法传入参数值的对象数组.
}
20.12.3 动态代理案例
需求:将经纪人代理明星的案例使用动态代理实现
分析:
- 把父接口定义
- 定义被代理类:宝强
- 动态生成代理类对象
- 创建执行代理逻辑的调用处理器
- 通过代理对象执行代理方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author Carl Zhang
* @description 动态代理模式对BaoQiang的show方法进行处理
* @date 2022/1/8 14:37
*/
public class DynamicProxy {
public static void main(String[] args) throws ClassNotFoundException {
//1.获取被代理的对象
BaoQiang baoQiang = new BaoQiang();
Class<?> aClass = aClass = baoQiang.getClass(); //被代理类的字节码对象
ClassLoader classLoader = aClass.getClassLoader(); //被代理类的类加载器
Class<?>[] interfaces = aClass.getInterfaces(); //被代理类实现的所以的接口的数组
//获取自定义的调用处理器对象 -- 即真正执行代理逻辑的对象
MyInvocationHandler myInvocationHandler = new MyInvocationHandler(baoQiang);
//2. 获取被代理类的代理对象
Star songZhe = (Star) Proxy.newProxyInstance(classLoader,
interfaces, myInvocationHandler);
//3. 通过代理对象执行要代理的方法 -- 此处会执行调用处理器的invoke方法
//double v = songZhe.liveShow(1000); -- 如果方法执行完返回null,会报空指针异常
songZhe.liveShow(1000);
songZhe.sleep();
}
}
/**
* 创建代理对象的调用处理器 -- 用来执行代理的逻辑
*/
@SuppressWarnings("ALL")
class MyInvocationHandler implements InvocationHandler {
BaoQiang baoQiang; //被代理的对象
public MyInvocationHandler(BaoQiang baoQiang) {
this.baoQiang = baoQiang;
}
/**
* 代理行为 - 代理对象的所有方法都会执行此处
* @param proxy 代理对象,即songZhe
* @param method 被代理的方法 , 即BaoQiang的show方法的Method对象
* @param args 被代理方法的参数
* @return 代理方法执行后的结果
* @throws Throwable 异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null; //用来保存被代理的方法执行的结果
//如果是show方法就执行代理逻辑
if (method.getName().equals("liveShow")) {
double money = (double) args[0];
//前增强
System.out.println("宋喆帮宝强接真人秀,赚了" + money * 0.2);
//查看proxy的运行时类型 -- com.heima.agent.$Proxy0 ,匿名内部类 即动态创建的代理类
//System.out.println(proxy.getClass().getName());
//被代理对象执行被代理方法 -- 即baoqiang执行liveShow方法
//Object money = method.invoke(proxy, args[0]);
invoke = method.invoke(baoQiang, money * 0.8);
//后增强
System.out.println("宋哲帮宝强存钱");
return invoke;
}
invoke = method.invoke(baoQiang, args);
return invoke;
}
}
//1.抽象角色
interface Star {
double liveShow(double money);
void sleep();
}
//2.被代理角色
class BaoQiang implements Star {
@Override
public double liveShow(double money) {
System.out.println("宝强参加了一个真人秀节目,赚了" + money + "元");
return money;
}
@Override
public void sleep() {
System.out.println("宝强累了,睡觉了!!");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
20.12.4 动态代理调用流程
20.12.5 动态代理的缺点
只能针对接口的实现类做代理对象,普通类是不能做代理对象的。后面框架学习的时候会接触到 CGLib
(Code Genneration Library
)可以实现对类的代理
标签:反射,二十,对象,代理,class,public,完结,Class,加载 来源: https://www.cnblogs.com/Carl-Zhang/p/15779020.html