什么是字节码,什么是字节码对象?
- 让我们重新审视类实例化对象的过程
- 编译
- 首先源代码文件Student.java编译为Student.class字节码文件
- 加载字节码
- 当做类似于
new Student()创建对象操作时,JVM先将文件Student.class读入内存。 - 将里面的字节码指令、方法定义、成员变量等核心数据存入到JVM方法区中,这部分数据是"死的",它们是类的模板数据。
- 当做类似于
- 创建字节码对象(Class对象)
- JVM会根据加载出的字节码,再JVM堆内存中创建一个字节码对象
- 这个Class对象内部会持有指向方法区Student类元数据的引用
- 可以通过
Class.forName("Student类的全路径")获取到这个堆中的Class对象。
- 实例化普通对象:
- JVM根据堆中的Student.class 字节码对象,找到再JVM方法区中对应的字节码模板
- 然后根据这个模板,在堆内存中开辟空间,创建出一个真正的Student实例对象
- 编译
- 由此可以看出一个类的字节码对象,就是指向其字节码的对象,因此如果我们可以获得字节码对象,就能手动获得类的信息,甚至在没有源码的情况下根据字节码反编译出类源码信息。
- 所以字节码对象(Class对象)是 JVM 在堆内存中创建的、用于封装和代表方法区中类字节码数据的一个特殊"代理对象",它是连接 Java 代码与底层类结构的唯一入口。
反射
什么是反射
- 反射就是:用字节码对象加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器等)。

- 在IDE中的代码提示功能就是利用了反射的原理

- 反射有哪些知识点要学的
- 学习获取类的信息、操作它们
- 反射第一步:加载类,获取类的字节码对象:Class对象
- 获取类的构造器:Constructor对象
- 获取类的成员变量:Field对象
- 获取类的成员方法:Method对象
- 全部认识完后,再看反射的应用场景
- 学习获取类的信息、操作它们
反射:获取字节码对象三种方式
-
第一步先获得字节码对象

-
三种方式的语法
- 通过类名
Class c1 = 类名.class - 调用Class提供的方法 + 类的全路径:
public static Class forName(String package);,package为类的全路径(全类名) - 基类Object提供的方法:
public Class getClass();Class c3 = 对象.getClass();
- 通过类名
-
代码示例
javapackage com.ita._02反射第一步获取字节码对象; public class Demo021 { public static void main(String[] args) throws Exception { //目标:反射第一步:或者class对象黑马程 // Class c1 = 类名.class Class clazz1 = Demo021.class; // 调用Class提供方法: public static Class forName(String package); Class clazz2 = Class.forName("com.ita._02反射第一步获取字节码对象.Demo021"); // Object提供的方法: public Class getClassO;Class c3 = 对象.getClass(); Demo021 demo021 = new Demo021(); Class clazz3 = demo021.getClass(); System.out.println(clazz1); System.out.println(clazz2); System.out.println(clazz3); } }
反射:获取构造器成员
-
语法
-
Class提供了从类中获取构造器的方法。
方法 说明 Constructor<?>\[\] getConstructors() 获取全部构造器(只能获取public修饰的) Constructor<?>\[\] getDeclaredConstructors() 获取全部构造器(只要存在就能拿到) Constructor getConstructor(Class<?>... parameterTypes) 获取某个构造器(只能获取public修饰的) Constructor getDeclaredConstructor(Class<?>... parameterTypes) 获取某个构造器(只要存在就能拿到) -
获取类构造器的作用:依然是初始化对象返回。
Constructor提供的方法 说明 T newInstance(Object... initargs) 调用此构造器对象表示的构造器,并传入参数,完成对象的初始化并返回 public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)
-
-
暴力反射:暴力反射,指的是反射操作可以直接无视类似于
private等修饰符对访问的控制,获得字节码对象的各种信息 -
代码示例
-
先创建一个Dog类作为素材
javapackage com.ita._03反射获取字节码对象里面所有成员; public class Dog { private String name; private int age; public void eat(){ System.out.println("狗吃东西..."); } private String eat(String name){ System.out.println("狗吃" + name); return "谢谢!谢谢!"; } public Dog() { } private Dog(String name){ this.name = name; } public Dog(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + '}'; } } -
Demo031,解析字节码对象构造器
javaimport java.lang.reflect.Constructor; public class Demo031 { public static void main(String[] args) throws Exception { //目标:使用反射方式动态获取指定类的构造器方法 //作用:可以通过反射灵活执行构造器创建对象 System.out.println("=======================解析Dog类========================"); demo(Dog.class); System.out.println("=======================解析Object类========================"); demo(Object.class); } //反射实现的通用方法:可以接受任意类型,创建无参构造器对象和一个String类型有参构造器对象 public static void demo(Class clazz) throws Exception { //1.获取这个类所有的构造器方法 Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); //2.打印所有构造器方法 System.out.println("类中的构造方法有:"); for (Constructor declaredConstructor : declaredConstructors) { System.out.println(declaredConstructor); } //3.获取指定的无参构造器对象 Constructor declaredConstructor = clazz.getDeclaredConstructor(); declaredConstructor.setAccessible(true);//如果当前构造器是私有的就需要执行这句代码,否则不允许访问私有。 //4.执行构造器动态创建对象 Object obj = declaredConstructor.newInstance(); // 5.打印对象 System.out.println("创建并获取到对象:"); System.out.println(obj); //5.解析一个够参构造器 try { Constructor declaredConstructor1 = clazz.getDeclaredConstructor(String.class); declaredConstructor1.setAccessible(true);//设置私有允许访问 //执行构造器 Object obj2 = declaredConstructor1.newInstance("黑马"); System.out.println(obj2); } catch (Exception e) { System.out.println(clazz+"没有一个String类型参数的构造器"); } } } -
运行结果

-
反射:获取构造器成员
-
语法
-
Class提供了从类中获取成员变量的方法。
方法 说明 public Field\[\] getFields() 获取类的全部成员变量(只能获取public修饰的) public Field\[\] getDeclaredFields() 获取类的全部成员变量(只要存在就能拿到) public Field getField(String name) 获取类的某个成员变量(只能获取public修饰的) public Field getDeclaredField(String name) 获取类的某个成员变量(只要存在就能拿到) -
获取到成员变量的作用:依然是赋值、取值。
方法 说明 void set(Object obj, Object value): 赋值 Object get(Object obj) 取值 public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)
-
-
代码示例
javaimport java.lang.reflect.Field; public class Demo032 { public static void main(String[] args) throws Exception { //目标:使用反射解析操作成员变量 // 获取字节码对象 Class clazz = Dog.class; //1.解析字节码对象中所有成员变量(包括私有) Field[] declaredFields = clazz.getDeclaredFields(); //打印字节码中所有的成员变量 System.out.println("类中的成员变量有:"); for (Field declaredField : declaredFields) { System.out.println(declaredField); } //3.指定无参构造器创建对象 Object obj = clazz.getConstructor().newInstance(); System.out.println("通过解析字节码创建对象:"+obj); //4.获取name私有成员变量 Field nameField = clazz.getDeclaredField("name"); nameField.setAccessible(true);// 开启暴力反射 //5.将name的值设置为"旺财" nameField.set(obj,"旺财");// 对象名.实例成员变量 = 值 System.out.println("修改对象的name成员后打印:"+obj); } }- 运行结果

- 运行结果
反射:获取成员方法
-
语法
-
Class提供了从类中获取成员方法的API
方法 说明 Method\[\] getMethods() 获取类的全部成员方法(只能获取public修饰的) Method\[\] getDeclaredMethods() 获取类的全部成员方法(只要存在就能拿到) Method getMethod(String name, Class<?>... parameterTypes) 获取类的某个成员方法(只能获取public修饰的) Method getDeclaredMethod(String name, Class<?>... parameterTypes) 获取类的某个成员方法(只要存在就能拿到) -
成员方法的作用:依然是执行
Method提供的方法 说明 public Object invoke(Object obj, Object... args) 触发某个对象的该方法执行。 public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)
-
-
代码示例:
javaimport java.lang.reflect.Method; public class Demo033 { public static void main(String[] args) throws Exception { //目标:使用反射解析操作成员方法 //1.获取所有成员方法(包括私有) Class clazz = Dog.class; System.out.println("获得类:" + clazz); System.out.println("获取类中所有成员方法:"); Method[] declaredMethods = clazz.getDeclaredMethods(); //2.打印 for (Method declaredMethod : declaredMethods) { System.out.println(declaredMethod); } //3.指定无参构造器创建对象 Object obj = clazz.getConstructor().newInstance(); System.out.println("通过解析出的构造器创建对象" + obj); //4.获取private String eat(String name) 方法 Method declaredMethod = clazz.getDeclaredMethod("eat",String.class);// 方法名加参数类型 declaredMethod.setAccessible(true);// 开启暴力反射 //5.执行方法private String eat(String name) System.out.println("传入通过解析构造器创建的对象+成员方法的参数,调用解析出的方法"); declaredMethod.invoke(obj,"酱骨架");//相当于 对象名.实例方法(参数) } }- 运行结果

- 运行结果
反射案例:任意对象数据写入磁盘文件
-
需求:对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去

-
思路:利用反射获得对象的字段名和对应的值,利用IO缓冲流将信息写入文件中
-
代码:
- 创建两个实体类,
Student、Teacher作为例子
javapackage src.reflectionExpection; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Student { private String name; private int age; private char sex; private double height; private String hobby; }javapackage src.reflectionExpection; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class Teacher { private String name; private double salary; }- 核心解析对象类
javapackage src.reflectionExpection; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.Writer; import java.lang.reflect.Field; public class ObjectDataToFileUtil { public static void writeData(Object obj) throws Exception{ try( //1.创建输出流对象 //低级流字符输出流 Writer out = new FileWriter("./src/reflectionExpection/objectData.txt",true); //高级流字符缓冲输出流 BufferedWriter bout = new BufferedWriter(out) ){ //2.根据obj得到字节码对象 Class clazz = obj.getClass(); //3.输出类名字写到文件中 String simpleName = clazz.getSimpleName(); bout.write(String.format("============%s============", simpleName)); //format为格式化字符串 bout.newLine();//在文件中输出换行 //4.获取字节码对象中所有成员变量得到Field[] Field[] declaredFields = clazz.getDeclaredFields(); //5.Field[] 数组遍历 for (Field declaredField : declaredFields) { //5.1 解析获取每个成员变量名字和值 declaredField.setAccessible(true); String name = declaredField.getName();//成员变量的名字 Object value = declaredField.get(obj); //5.2 使用输出流输出数据 bout.write( String.format("%s=%s%n", name, value)); } //提示成功 System.out.printf("%s对象的数据写入磁盘文件成功!%n",simpleName); } } }- 主类运行测试
javapackage src.reflectionExpection; public class Main { public static void main(String[] args) { try { //准备学生对象数据 Student student = new Student("播仔", 20, '男', 1.88D, "敲代码"); //准备老师对象数据 Teacher teacher = new Teacher("宇哥", 40000); //写入磁盘 ObjectDataToFileUtil.writeData(student); ObjectDataToFileUtil.writeData(teacher); } catch (Exception e) { e.printStackTrace();//给程序员看 System.out.println("服务器忙,请稍后重试...");//给用户看的 } } }- 运行结果

- 创建两个实体类,
注解
什么是注解
-
就是Java代码里的特殊标记,比如:@Override、@Test等,作用是:让其他程序根据注解信息来决定怎么执行该程序。
-
注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上、等位置处。

自定义注解
- 就是自己定义注解,
-
注解内可以有参数属性但注解的参数和类中的参数(成员变量)有不同的语法
javapublic @interface 注解名称 { public 属性类型 属性名() default 默认值 ; } -
特殊属性名: value (如果注解中只有一个value属性,使用注解时,value名称可以不写!! )
-
- 代码示例
-
创建自定义注解
MyBook和MyBook2javapackage src.annotation; public @interface MyBook { //属性语法:public 属性类型属性名() [default 默认值]; public String bookName(); public String author() default "黑马程序员"; //有默认值,在使用注解的时候可以不用给这个成员赋值 public String price(); public String value(); }javapackage src.annotation; public @interface MyBook2 { public String value(); } -
使用两个注解(可以在类、成员变量、成员方法上使用)
javapackage src.annotation; @MyBook(value = "test",bookName = "java从入门到起飞",price = "100") @MyBook2("test")//因为这个注解只有一个属性变量value,赋值的时候value名字可以不写 public class Demo041 { @MyBook(value = "test",bookName = "java从入门到起飞",price = "100") private String name; @MyBook(value = "test",bookName = "java从入门到起飞",price = "100") public Demo041(){} @MyBook(value = "test",bookName = "java从入门到起飞",price = "100") public void show( @MyBook(value = "test",bookName = "java从入门到起飞",price = "100") String demo){ @MyBook(value = "test",bookName = "java从入门到起飞",price = "100") int age =20; } @MyBook(value = "test",bookName = "java从入门到起飞",price = "100") public static void show2(){ int age =20; } }
-
注解概述-注解的原理
- 注解本质是一个接口 ,Java中所有注解都是继承了Annotation接口的。如下图所示,MyTest1实际上是一个继承了Annotation接口的接口

@注解(...)操作:其实就是一个实现类对象,实现了该注解以及Annotation接口。如下图所示,@MyTest实际上是实现了继承Annotation接口的MyTest1接口
- 注解的作用
- 对Java中类、方法、成员变量做标记,然后进行特殊处理。
- 例如:JUnit框架中,标记了注解
@Test的方法就可以被当成测试方法执行,而没有标记的就不能当成测试方法执行
元注解
- 元注解是描述注解的注解,每个注解都有两个元注解分别是
@Retention和@Target - @Target:
- 作用:声明被修饰的注解只能在哪些位置使用
- 语法:
@Target(ElementType.TYPE)- TYPE,类,接口
- FIELD, 成员变量
- PARAMETER, 方法参数
- CONSTRUCTOR, 构造器
- LOCAL_VARIABLE, 局部变量
- 如果需要指定在多个位置类型使用可以写多参数如
@Target({ElementType.TYPE, ElementType.METHOD})即在类与接口中使用,又在方法中使用。
- @Retention:
- 作用:声明注解的保留周期。
- 语法:
@Retention(RetentionPolicy.RUNTIME)- SOURCE,只作用在源码阶段,字节码文件中不存在。
- CLASS(默认值),保留到字节码文件阶段,运行阶段不存在.
- RUNTIME(开发常用),一直保留到运行阶段。
- 代码示例:
-
创建注解用于测试
javapackage src.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME)//推荐使用 public @interface MyBook3 { public String value() default "demo"; } -
测试注解使用位置是否被限制,可以发现无法在成员变量上使用注解了,测试注解保留的时间可以看到注解在编译后的字节码中出现,说明至少保留到了字节码文件阶段(因为运行阶段目前还无法查看)
javapackage src.annotation; @MyBook3 public class Demo042 { // @MyBook3 报错,不支持 private String name; // @MyBook3 报错,不支持 public Demo042(){} @MyBook3 public void show( // @MyBook3 报错,不支持 String demo){ int age =20; } @MyBook3 public static void show2(){ int age =20; } public static void main(String[] args) { System.out.println("hello"); } }- 从运行结果看确实是在生成字节码阶段被保留了

- 从运行结果看确实是在生成字节码阶段被保留了
-
下面更改Retention注解的参数,仅保留注解到源码阶段
javapackage src.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.SOURCE) public @interface MyBook3 { public String value() default "demo"; }-
再次运行返现编译后的字节码中没有注解了

-
-
- 元注解是什么?各自的作用是什么?
- 注解注解的注解
- @Target约束自定义注解可以标记的范围。@Retention用来约束自定义注解的存活范围。
注解:注解的解析
- 就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来。
- 如何解析注解?
- 指导思想:要解析谁上面的注解,就应该先拿到谁。
- 比如要解析类上面的注解,则应该先获取该类的Class对象,再通过Class对象解析其上面的注解。(通过反射)
- 比如要解析成员方法上的注解,则应该获取到该成员方法的Method对象,再通过Method对象解析其上面的注解。
- Class 、 Method 、 Field , Constructor、都实现了AnnotatedElement接口,它们都拥有解析注解的能力。(只要拿到各个对象就能通过这些封装好的方法进行解析) | AnnotatedElement接口提供了解析注解的方法 | 说明 | | --------------------------------------------------------------------- | --------------- | | public Annotation\[\] getDeclaredAnnotations() | 获取当前对象上面的注解。 | | public T getDeclaredAnnotation(Class annotationClass) | 获取指定的注解对象 | | public boolean isAnnotationPresent(Class annotationClass) | 判断当前对象上是否存在某个注解 |
代码示例
java
package src.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)//推荐使用
public @interface MyBook3 {
public String value() default "demo";
}
- 利用反射获得字节码对象,并解析类中的内容,最后解析出各个对象的注解
java
package src.annotation;
import java.lang.reflect.Method;
@MyBook3("helloworld")
public class Demo043 {
@MyBook3
public void show(
String demo){
int age =20;
}
public static void main(String[] args) throws Exception {
//目标:解析当前类和方法的注解,确保注解运行时存在
//1. 解析类上的注解
//1.1 获取字节码对象
Class clazz = Demo043.class;
//1.2 获取指定类上的注解 @MyBook3
// public T getDeclaredAnnotation(Class<T> annotationClass)
MyBook3 myBook3 = (MyBook3) clazz.getDeclaredAnnotation(MyBook3.class);
//1.3 获取注解value属性的值
String value = myBook3.value();
System.out.println("类上注解Mybook3的value值:"+value);//helloworld
//2.解析方法上的注解
//2.1 获取字节码上show方法Method对象
Method method = clazz.getMethod("show", String.class);
//2.2 获取方法上面指定的Mybook3注解
myBook3 = method.getDeclaredAnnotation(MyBook3.class);
//2.3 打印value值
System.out.println("value值:" + myBook3.value());//demo
}
}
注解案例:简易版本Junit开发
- 需求:定义若干方法,只要加了MyTest注解,就会触发该方法执行
- 分析:
- 定义一个自定义注解MyTest,只能注解方法,存活范围到运行阶段(一直都在)。
- 定义若干方法,部分方法上面加@MyTest注解修饰,部分方法不加。
- 模拟一个junit程序,可以触发@MyTest主机方法执行
- 代码:
-
MyTest注解
javapackage src.annotation.MyJunit; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyTest { } -
Demo044通过注解解析实现和Junit类似的功能
javapackage src.annotation.MyJunit; import java.lang.reflect.Method; public class Demo044 { @MyTest public void test1(){ System.out.println("test1"); } @MyTest public void test2(){ System.out.println("test2"); } public void test3(){ System.out.println("test3"); } @MyTest public void test4(){ System.out.println("test4"); } public static void main(String[] args) throws Exception { //模拟开发简易的Junit框架,解析当前类有@MyTest方法直接执行 //1.获取当前字节码文件对象 Class clazz = Demo044.class; //2.获取字节码里面所有成员方法 Method[] Method[] methods = clazz.getMethods(); //3.创建对象,便于执行方法使用 Object obj = clazz.getConstructor().newInstance(); //4.遍历Method[] for (Method method : methods) { //4.1 判断每个Method上是否有@MyTest注解 if(method.isAnnotationPresent(MyTest.class)) { //4.2 有,执行方法 method.invoke(obj); } } } }
-
代理设计模式
程序为什么需要代理?代理长什么样?
- 为什么需要代理:对象如果嫌身上干的事太多的话,可以通过代理来转移部分职责。这样职责划分会更为清晰
-
比如说歌手的主要能力是唱歌,无心处理开演唱会,准备演出器材,收门票钱等工作,那么歌手会找一个经纪人来处理这些琐碎且繁杂的事务,那么演唱会相关的事务会先经过经纪人,歌手再通过经纪人安排去演唱会进行唱歌
-
在这里歌手职责通过经纪人进行拆分并将琐碎的职责转移给经纪人,经纪人将琐碎的职责处理完成后,安排歌手去唱歌,在这一套流程中经纪人就是代理,歌手就是代理目标,他们干的是同一件事,但是职责不同。

-
除了代理还有中介公司,中介公司需要找到相对应的代理来安排演唱会,中介要如何找到合适的代理呢?通过接口(即为通过接口的抽象方法进行约束),而代理和代理目标则作为接口的实现类被接口管理就可以了。
-
静态代理设计模式
- 设计模式:解决某种问题的最优解,前辈总结解决某一类问题的通用解决方案
- 静态代理设计模式
- 解决:职责更加清晰 (多余职责交给代理),通过代理实现方法增强, ,并且不修改目标对象职责的任何代码。(在一些企业中为了代码的稳定性,在为旧的功能做拓展、增强的时候,不允许修改原有的代码,此时就可以选在静态代理模式)
- 特点:需要手动创建代理类
- 体系结构

- 代码
-
先创建接口
javapackage src.proxy; public interface Star { //唱歌 void sing(); //跳舞 void dance(); } -
再创建代理目标
javapackage src.proxy; public class ZRNStar implements Star{ @Override public void sing() { System.out.println("章若楠在唱歌。。。"); } @Override public void dance() { System.out.println("章若楠在跳舞。。。"); } } -
再创建代理
javapackage src.proxy; public class DLStar implements Star { private Star star; public DLStar(Star star){ this.star = star; } @Override public void sing() { System.out.println("准备话筒,收钱。。。"); //调用目标去唱歌 star.sing(); } @Override public void dance() { System.out.println("准备话筒,收钱。。。"); //调用目标去跳舞 star.dance(); } } -
最后再主类进行测试
javapackage src.proxy; public class Main { public static void main(String[] args) { ZRNStar zrnStar = new ZRNStar(); //目标对象 //创建1级代理对象 Star proxy1 = new DLStar(zrnStar); // 代理对象 proxy1.sing(); proxy1.dance(); } } -
运行结果

-
- 除了一级代理,我们还可以设计二级代理 ,在原有的基础上进行功能增强,比如歌手除了经纪人,还需要法律顾问,这个时候就可以在经纪人代理上再套一个代理,要先进行法律评估再进行演唱会准备工作
-
代码演示,创建法律代理(二级代理)
javapackage src.proxy; public class FWStar implements Star { private Star star; public FWStar(Star star){ this.star = star; } @Override public void sing() { System.out.println("谈合同,符合法律要求。。。"); //调用目标去唱歌 star.sing(); } @Override public void dance() { System.out.println("谈合同,符合法律要求。。。"); //调用目标去跳舞 star.dance(); } } -
再在主类中测试
javapackage src.proxy; public class Main { public static void main(String[] args) { ZRNStar zrnStar = new ZRNStar(); //目标对象 //创建1级代理对象 Star proxy1 = new DLStar(zrnStar); // 代理对象 FWStar proxy2 = new FWStar(proxy1); proxy2.sing(); proxy2.dance(); } } -
运行结果

-
动态代理设计模式
-
动态代理设计模式
- 解决:让目标对象方法增强的问题,也可以让目标方法职责更加清晰,并且不修改目标对象的职责任何代码。
- 特点:动态创建代理类,无需手动创建,扩展性更强
-
如何为Java对象动态创建一个代理对象?
-
java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法: -
语法
javapublic static Object newProxyInstance( ClassLoader loader , Class<?>[] interfaces , InvocationHandler h)- 参数一:用于指定用哪个类加载器,去加载生成的代理类
- 参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法(要求接收一个接口数组,因为在java中接口是多继承的)
- 参数三:用来指定生成的代理对象要干什么事情(需要自己实现,可以作为内部类实现)
-
-
代码
-
创建接口
javapackage src.dynamicProxy; public interface Star { //唱歌 void sing(); //跳舞 void dance(); } -
实现接口创建目标类
javapackage src.dynamicProxy; public class ZRNStar implements Star { @Override public void sing() { System.out.println("章若楠在唱歌。。。"); } @Override public void dance() { System.out.println("章若楠在跳舞。。。"); } } -
设计代理并调用测试
javapackage src.dynamicProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class dynamicProxyTest { public static void main(String[] args) { //1.创建目标对象 Star star = new ZRNStar(); //2.创建代理对象(功能增强的对象)匿名内部类方式 Star proxy = (Star) Proxy.newProxyInstance( star.getClass().getClassLoader(), //写法1:写死接口 new Class[]{Star.class}, //写法2:获取目标实现的接口数组 star.getClass().getInterfaces(),//获取目标字节码中所有实现的接口类型数组 new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //写增强的代码和调用目标的方法 //参数1:proxy 就是生成的代理对象,一般不用 //参数2:method 反射的方法对象(实现接口的方法) //参数3:args 方法的参数数据(实参) //增强的代码 System.out.println("准备实施,收钱"); //调用目标对象的方法 Object result = method.invoke(star, args);//返回值可能有也可能没有 return result;//将返回值返回给调用方 } } ); // 创建代理对象(功能增强的对象)Lambda表达式方式 Star proxy2 = (Star) Proxy.newProxyInstance( proxy.getClass().getClassLoader(), proxy.getClass().getInterfaces(),//获取目标字节码中所有实现的接口类型数组 (proxy1, method, args1) -> { //因为lambda表达式没有单独的作用域所以参数会冲突 // ,proxy和一级代理冲突改为proxy1 // ,args和main方法的args冲突改为args1 //增强的代码 System.out.println("签合同"); //调用目标对象的方法 //返回值可能有也可能没有 return method.invoke(proxy, args1);//将返回值返回给调用方 } ); //3.执行代理的方法(增强目标功能方法) proxy2.sing(); proxy2.dance(); } }
-
-
代码执行顺序

使用代理优化用户管理类
- 场景:某系统有一个用户管理类,包含用户登录,删除用户,查询用户等功能,系统要求统计每个功能的执行耗时情况,以便后期观察程序性能。
- 需求:现在,某个初级程序员已经开发好了该模块,请观察该模块的代码,找出目前存在的问题,并对其进行改造。
- 代码
-
新建用户服务接口
javapublic interface UserService { void login(); void add(); void delete(); } -
实现接口创建用户类
javapublic class UserServiceImpl implements UserService{ private Random random = new Random(); @Override public void login() { System.out.println("执行登录操作。。。"); try { Thread.sleep(random.nextInt(1000)); } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override public void add() { System.out.println("执行添加用户操作。。。"); try { Thread.sleep(random.nextInt(1000)); } catch (InterruptedException e) { throw new RuntimeException(e); } } @Override public void delete() { System.out.println("执行用户删除操作。。。"); try { Thread.sleep(random.nextInt(1000)); } catch (InterruptedException e) { throw new RuntimeException(e); } } } -
设计一个测试耗时代理
javapackage src.ExecuteTimeProxyObjectUtil; import java.lang.reflect.Proxy; public class ExecuteTimeProxyObjectUtil{ public static Object getProxyObject(Object target){ return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy1, method, args1) -> { //目标:给每个目标方法增强统计耗时执行时间 //1.定义开始毫秒数 Long start = System.currentTimeMillis(); //2.执行目标方法 Object result = method.invoke(target, args1); //3.定义结束毫秒数 Long end = System.currentTimeMillis(); //4.计算打印耗时时间=结束毫秒数-开始毫米数 System.out.printf("方法名:%s,耗时时间:%d%n", method.getName(),end-start); return result; } ); } } -
在主函数中运行代理
javapackage src.ExecuteTimeProxyObjectUtil; public class Main { public static void main(String[] args) { //1.创建目标对象 UserService userService = new UserServiceImpl(); //2.创建代理对象 UserService proxy = (UserService) ExecuteTimeProxyObjectUtil.getProxyObject(userService); //3.执行代理方法(看增强后的效果) proxy.add(); proxy.login(); proxy.delete(); } } -
运行结果

-
动态代理是怎么实现的?(底层原理简介)
-
底层用的就是 Java 的反射(Reflection)机制。甚至可以说,JDK 动态代理就是反射技术最巅峰的代表作之一。
-
直接去查看
Proxy类和InvocationHandler接口,它们俩就堂堂正正地躺在java.lang.reflect这个包下面。
-
JVM会通过反射自动的拿到代理目标,如下方代码会先通过字节码对象(这里是Star类的)获得的信息,而在内部类的method参数就是通过字节码对象反射过来的
- 当外面调用代理对象的
sing()方法时,JVM 会把sing这个方法包装成一个java.lang.reflect.Method对象。 - 传入
invoke方法后,你写的那行method.invoke(target, args),就是最典型的利用反射去动态调用目标对象的方法。
javaStar proxy = (Star) Proxy.newProxyInstance( star.getClass().getClassLoader(), //写法1:写死接口 new Class[]{Star.class}, //写法2:获取目标实现的接口数组 star.getClass().getInterfaces(), //获取目标字节码中所有实现的接口类型数组 new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ... } } ); - 当外面调用代理对象的
反射、注解、动态代理在框架中的应用
-
后续学习的Spring框架就是利用反射、注解、动态代理这几项技术来做的

-
第一步:反射(Reflection)------ Spring 的眼睛
- Spring 启动时,它怎么知道你的项目里有哪些类?怎么在不使用
new的情况下把对象创建出来?- 全靠反射 !Spring 会动态扫描你的类,利用
Class.forName()拿到字节码,然后用反射动态调用构造方法,把对象塞进它的 IOC 容器里。
- 全靠反射 !Spring 会动态扫描你的类,利用
- Spring 启动时,它怎么知道你的项目里有哪些类?怎么在不使用
-
第二步:注解(Annotation)------ Spring 的暗号
- Spring 怎么知道哪些类需要它管理?哪些方法需要加事务?
- 全靠注解 !你在类上写个
@Component,Spring 的反射机制一读:"哦,这个我得管";你在方法上写个@Transactional,Spring 一读:"哟,这个方法需要带事务保护"。注解就是你和 Spring 之间不言自明的暗号。
- 全靠注解 !你在类上写个
- Spring 怎么知道哪些类需要它管理?哪些方法需要加事务?
-
第三步:动态代理(Dynamic Proxy)------ Spring 的肌肉
- Spring 读到了你的
@Transactional注解,它要在不修改你原本代码的前提下,帮你加上"开启事务、提交事务、回滚事务"的功能。它怎么做?- 就是利用我们刚才讨论的
Proxy.newProxyInstance()!Spring 在内存中为你动态生成一个代理对象 。以后别人调用你的方法时,其实是在调用这个代理对象,代理对象先帮你把事务开了,再去调用你原本的方法。这就是 Spring 最核心的王牌功能------AOP(面向切面编程) 。
- 就是利用我们刚才讨论的
- Spring 读到了你的