反射注解 动态代理

前言

反射、注解和动态代理是Java编程中的三个重要概念,它们之间有着密切的关系,特别是在处理AOP(面向切面编程)和某些框架(如Spring)时。

  1. 反射

    • 反射是Java提供的一种机制,允许程序在运行时获取类的信息以及动态调用类的方法。通过反射,程序可以创建对象、调用方法和访问属性,而无需在编译时知道类的信息。
  2. 注解

    • 注解是Java的一种元数据形式,可以附加在类、方法、字段等上。它们提供了关于程序的额外信息,可以用于配置、文档生成、代码分析等。
    • 注解本身并不直接影响程序的逻辑,但可以通过反射读取和处理它们,以实现特定功能。
  3. 动态代理

    • 动态代理是Java的一种机制,允许在运行时生成代理对象。通过动态代理,可以在不修改目标对象的情况下,围绕目标对象添加一些处理逻辑,比如日志记录、权限校验等。
    • Java主要通过java.lang.reflect.Proxy类来实现动态代理。需要实现一个接口,并在运行时提供一个InvocationHandler来定义代理行为。

关系

  • 反射与注解:反射机制可以用来读取注解的信息。开发者可以遍历类的所有方法和字段,查找特定的注解并获取它们的值,以便在运行时根据这些注解的配置来决定行为。

  • 注解与动态代理:在许多框架中,注解用于标记需要被代理的方法或类。动态代理可以根据这些注解信息,通过反射动态生成代理对象,并在调用目标方法之前或之后插入额外的逻辑。

  • 反射与动态代理:动态代理通常依赖于反射来调用目标对象的方法。代理对象会将方法调用转发到实际的目标对象,这个过程使用了反射来执行方法调用。

示例

在Spring中,使用@Transactional注解来标识一个方法需要进行事务管理。Spring通过反射读取这个注解,并在运行时生成动态代理,以在调用这个方法前开启事务,调用后提交或回滚事务。

总结来说,反射、注解和动态代理是紧密结合的,通过反射来操作注解,通过注解来控制动态代理的行为,从而实现灵活的编程模式。

(一)反射 Reflection

加载类(该类的字节码文件加载到内存中),并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造器)

万物皆对象:
Class类代表字节码(字节码文件也是一个对象)
获取类即获取类的Class对象

学习内容:获取类的信息/操作它们

  1. 反射第一步:加载类,获取类的字节码: Class 对象
    获取 Class 对象的三种方式:
  • Class c1 = 类名.class
  • 调用 Class 提供方法:public static Class forName(String package);
  • Object提供的方法:public Class getClass(); Class c3=对象.getClass();
  1. 获取类的构造器: Constructor 对象
    Class提供了从类中获取构造器的方法:
  • Constructor<?>[] getConstructors() 获取全部构造器(只能获取public修饰的)

  • Constructor<?>[] getDeclaredConstructors() 获取全部构造器(只要存在就能拿到)

  • Constructor getConstructor(Class<?>…parameterTypes) 获取某个构造器(只能获取public修饰的)

  • ConstructorgetDeclaredConstructor(Class<?>… parameterTypes) 获取某个构造器(只要存在就能拿到)

​ 获取类构造器的作用:依然是初始化对象返回

​ Constructor 提供的方法:

  • T newInstance(object…initargs) 调用此构造器对象表示的构造器,并传入参数,完成对象的初始化并返回

  • public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射 私有也可访问到)

  1. 获取类的成员变量: Field 对象

    • 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,表示禁止检查访问控制(暴力反射) 让JVM不要去管 private 修饰的
  2. 获取类的成员方法: Method 对象
    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,0bject… args) 触发某个对象的该方法执行
​ public void setÃccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)

作用、应用场景

反射的作用:
(1) 基本作用: 可以得到一个类的全部成分,然后操作。
(2) 可以破坏封装性。(访问私有成分)
(3) 最重要用途:适合做Java的框架,基本上,主流的框架都会基于反射设计出一些通用的功能。

使用反射做一个简易版的框架
需求:
对于任意一个对象,该框架都可以把对象的字段名和对应的值,保存到文件中去。

(二)注解 Annotation

就是 Java 代码里的特殊标记,比如: @Override 、 @Test 等
作用是:让其他程序根据注解信息来决定怎么执行该程序

注意:注解可以用在类上、构造器上、方法上、成员变量上、参数上、等位置处。

注解本身没有任何意义,单独的注解就是一种注释,他需要结合其他如反射、插桩等技术才有意义。

自定义注解

1
2
3
public @interface 注解名{
public 属性类型 属性名() default 默认值;
}

使用:

1
@注解名()

特殊属性名:value
如果注解中只有一个value属性,使用注解时,value名称可以不写!!

注解的原理

将注解编译成Class,再将这个Class反编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//注解
public @interface MyTest1 {
String aaa();
boolean bbb();
String[]ccc();
}


//使用
//相当于创建一个实现类的对象
@MyTest1(aaa = "李四",bbb=true,ccc={"Go","Python"})
public void test(){

}

注解本质是一个接口,Java中所有注解都是继承了 Annotation 接口的。
@注解(…) 其实就是一个实现类对象,实现了该注解以及 Annotation 接口。

1
2
3
4
5
public interface MyTestl extends Annotation{
public abstract String aaa();
public abstract boolean bbb();
public abstract String[] ccc();
}

元注解

元注解:修饰注解的注解

@Target
作用:声明被修饰的注解只能在哪些位置使用

@Target(ElementType.TYPE)

  1. TYPE:类,接口
  2. FIELD:成员变量
  3. METHOD:成员方法
  4. ANNOTATION_TYPE:作用在注解上
  5. PARAMETER:方法参数
  6. CONSTRUCTOR:构造器
  7. LOCAL_VARIABLE:局部变量

@Retention
作用:声明注解的保留周期

@Retention(RetentionPolicy.RUNTIME)

  1. SOURCE 只作用在源码阶段,Class 字节码文件中不存在。
  2. CLASS(默认值) 保留到Class字节码文件阶段,运行阶段不存在(会被保留到class 但是会被jvm抛弃) 编译时
  3. RUNTIME(开发常用) 一直保留到运行阶段(保留到虚拟机当中) 运行时存活。

注解的解析

什么是注解的解析?
就是判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来。

Class、Method、Field,Constructor、都实现了AnnotatedElement接口,它们都拥有解析注解的能力。
AnnotatedElement接口提供了解析注解的方法:
public Annotation[] getDeclaredAnnotations() 获取当前对象上面的注解
public T getDeclaredAnnotation(Class annotationClass) 获取指定的注解对象
public boolean isAnnotationPresent(Class annotationClass) 判断当前对象上是否存在某个注解

注解应用场景

配合反射等技术做框架的

(三.0)静态代理

在不修改原有代码的基础上增加功能——代理

  • 静态代理需要手动编写代理类代理类中直接调用被代理对象的方法,并在方法前后添加额外的逻辑。
    • 缺点:原始的类BigStar(需要被代理的对象)如果有多个方法,那么我需要实现更多的方法
    • 编译的时候,写代码的时候就确定了它需要代理的是哪个对象
  • 与动态代理相比,静态代理的代码量较大但性能更好,适合代理逻辑固定类数量较少的场景。
  • 动态代理通过反射机制动态生成代理类,适合代理逻辑需要动态变化代理多个类的场景。
    • 运行期间才确定的代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//接口
public interface Star{
String sing(String name);
void dance();
}
//接口实现类
public class BigStar implements Star{
private String name;

//构造函数
public BigStar(String name){
this.name = name;
}

public String sing(String name){
System.out.println(this.name + "正在唱:" + name);
return "谢谢!谢谢!"
}

public void dance(){
System.out.println(this.name + "正在优美地跳舞~~");
}

}

//静态代理类
public class StarProxy implements Star{
private BigStar bigStar;//被代理的对象

//构造函数
private StarProxy(BigStar bigStar){
this.bigStar = bigStar;
}

@Override
publid String sing(String name){
//代理逻辑:在调用被代理对象的方法前做一些事情
System.out.println("准备话筒,收钱20万");
//调用被代理对象的方法
String result = bigStar.sing(name);
System.out.println("唱歌完毕,收工!");
return result;
}

@Override
public void dance() {
// 代理逻辑:在调用被代理对象的方法前做一些事情
System.out.println("准备场地,收钱1000万");
// 调用被代理对象的方法
bigStar.dance();
// 代理逻辑:在调用被代理对象的方法后做一些事情
System.out.println("跳舞完毕,收工!");
}
}


//测试类
public class StaticProxyDemo {
public static void main(String[] args) {
// 创建被代理对象
BigStar bigStar = new BigStar("杨超越");

// 创建代理对象
Star starProxy = new StarProxy(bigStar);

// 调用代理对象的 sing 方法
String result = starProxy.sing("好日子");
System.out.println(result);

// 调用代理对象的 dance 方法
starProxy.dance();
}
}

//结果:
/**
准备话筒,收钱20万
杨超越正在唱:好日子
唱歌完毕,收工!
谢谢!谢谢!

准备场地,收钱1000万
杨超越正在优美地跳舞~~
跳舞完毕,收工!
*/

(三)JDK的动态代理

动态代理是一种在运行时生成代理对象的技术,主要用于在不修改原始类代码的情况下增强其功能。

JDK是根据接口进行动态代理的

动态代理通过反射机制动态生成代理类,适合代理逻辑需要动态变化代理多个类的场景。

  • 运行期间才确定的代理

需要实现InvocationHandler接口

写代码的时候不知道要代理哪个对象

动态代理的特点:

  1. 代理对象,不需要实现接口

  2. 代理对象的生成,是利用JDK的API动态的在内存中构建代理对象

    (需要我们指定创建代理对象/目标对象实现的接口的类型)

  3. 动态代理也叫做:JDK代理,接口代理

(1)为什么需要代理?

对象如果嫌身上干的事太多的话,可以通过代理转移部分职责
对象有什么方法想被代理,代理就一定要有对应的方法。

代理如何知道要派有唱歌、跳舞方法的代理呢?
找接口

  1. 明星 BigStar (实现了 Star 接口)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class BigStar implements Star{
private String name;

public BigStar(String name){
this.name = name;
}

public String sing(String name){
System.out.println(this.name + "正在唱:" + name);
return "谢谢!谢谢!"
}

public void dance(){
System.out.println(this.name + "正在优美地跳舞~~");
}

}
  1. 接口 Star
    明星通过一个接口,告诉中介公司,帮他找什么样的代理
    给中介公司用来生成代理的
  • 抽象方法(声明明星有哪些方法需要被代理)
1
2
3
4
public interface Star{
String sing(String name);
void dance();
}
  1. 中介公司 ProxyUtil
  2. 代理

(2)如何为Java对象创建一个代理对象?

java.lang.reflect.Proxy 类: 提供了为对象产生代理对象的方法:

  • Proxy.newProxyInstance()
    参数:

    • **参数1——ClassLoader ** loader 指定一个类加载器(该类加载器去生成代理类)

      1
      ProxyUtil.class.getClassLoader()  
    • 参数2——Class<?>[] interfaces 这是一个接口数组,指定生成的代理长什么样子(有哪些方法) 代理类所实现的接口数组。
      动态代理只能为接口创建代理,因此这里需要传入要代理的接口列表。
      可以监听传入接口中的方法的变化

    • 参数3 ※※※——InvocationHandler ** h 用来指定生成的代理对象要干什么事情
      InvocationHandler 是一个接口,使用匿名对象完成实例化
      重写了 **invoke (Object proxy,Method method,Object[] args)**方法

      • proxy 当前代理对象本身(即动态生成的代理类实例)
      • method 需要代理的对象的方法 当前被调用的方法(通过反射获取)
      • args 被调用方法的参数列表
      • 返回值:Object 被代理方法的执行结果

      代理做什么事情,由 invoke 方法决定,代理对象要做的事情,会在这里写代码

      (当代理类的方法被调用时,执行的处理器。这个处理器实现了 InvocationHandler 接口,负责定义方法的具体实现。

sing 和 dance 方法在调用invoke方法时,会把三个参数传进来
(外部创建的代理对象、外部调用的方法、外部调用的方法的参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class ProxyUtil{

//这是一个静态方法
//该 createProxy 传入的参数,表示为谁创建代理
public static Star createProxy(BigStar bigStar){

//创建代理对象
Star starProxy = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),// 参数1

new Class[]{Star.class}, // 参数2

new InvocationHandler(){ // 参数3※※※
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
//写代理对象要做的事情

//根据方法名判断是在代理哪个方法
if (method.getName().equals("sing")) {
System.out.println("准备话筒,收钱20万")
}else if (method.getName().equals("dance")) {
System.out.println("准备场地,收钱1000万")
}

//让指定明星对象去执行对应方法
//从外界传入需要代理的对象是谁 bigStar
//返回被代理方法的执行结果
return method.invoke(bigStar,args);
}
});
return starProxy;
}
}

在主程序给明星对象派代理

1
2
3
4
5
6
7
8
9
10
11
12
//传入需要代理的对象
BigStar s = new BigStar("杨超越");
Star starProxy = ProxyUtil.createProxy("s");

//调用sing方法
//sing 方法会调用 invoke 方法 (代理做什么事情由 invoke 方法决定)
//invoke 方法中的 method 就代表 sing 方法
String rs = starProxy.sing("好日子");
System.out.println(rs);

//调用dance方法
starProxy.dance();

(四)面试题目

Java注解处理主要有三种方式

  1. 编译时注解处理器:使用Java提供的注解处理器工具(Annotation Processor)来在编译时处理注解,生成Java源代码或者其他资源文件。通过自定义注解处理器,可以在编译期间对注解进行验证、代码生成等操作。

  2. 源码级别的注解技术:APT 技术 在编译期能够获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类。

  3. 字节码增强:通过字节码操作库(如ASM、Javassist)来在编译后的字节码中处理注解,可以在类加载时对字节码进行修改,实现在运行时对注解进行处理的功能。这种方式通常应用于AOP(面向切面编程)等场景。

  4. 反射:通过Java反射机制来处理注解,可以在运行时通过反射 API 获取并解析注解信息,然后根据注解信息执行相应的操作。

APT 技术

annotation processor tools 注解处理器
APT 是由 javac 提供的
apt 可以看成 javac 的小插件

1.7 什么是反射机制?(享学)

反射机制的应用场景有 哪些? Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够 调用它的任意一个方法和属性;这种动态获取的信息以及动 态调用对象的方法的功能称为 Java 语言的反射机制。

应用场景:

  1. 逆向代码,例如反编译
  2. 与注解相结合的框架,如 Retrofit
  3. 单纯的反射机制应用框架,例如 EventBus(事件总线)
  4. 动态生成类框架 例如Gson

1.12 说说你对Java注解的理解?

Java 注解(Annotation)是 Java 5 引入的一种元数据机制,用于为代码提供额外的信息。注解本身不会直接影响代码的逻辑,但可以通过反射编译时处理工具来读取和处理这些信息。以下是对 Java 注解的详细理解:


1. 注解的定义

  • 注解是一种特殊的接口

    • 注解使用 @interface 关键字定义。
    • 示例:
      1
      2
      3
      4
      public @interface MyAnnotation {
      String value() default "default";
      int count() default 0;
      }
  • 注解可以包含元素

    • 注解的元素类似于接口的方法,可以有默认值。
    • 示例:
      1
      2
      @MyAnnotation(value = "example", count = 10)
      public class MyClass {}

2. 注解的作用

(1)提供元数据

  • 注解可以为类、方法、字段等提供额外的信息。这些信息可以用于描述代码的某些特性或行为,而不直接影响代码的执行逻辑。
  • 示例:
    1
    2
    3
    4
    @Override
    public String toString() {
    return "MyClass";
    }

(2)编译时检查

  • 注解可以用于编译时检查,例如 @Override 用于检查方法是否重写了父类方法。
  • 示例:
    1
    2
    3
    4
    @Override
    public boolean equals(Object obj) {
    return super.equals(obj);
    }

(3)运行时处理

  • 通过反射可以在运行时读取注解信息,并根据注解执行相应的逻辑
  • 示例:
    1
    2
    3
    4
    if (MyClass.class.isAnnotationPresent(MyAnnotation.class)) {
    MyAnnotation annotation = MyClass.class.getAnnotation(MyAnnotation.class);
    System.out.println(annotation.value());
    }

(4)生成代码

  • 注解可以用于生成代码,例如 Lombok 库通过注解自动生成 gettersetter 等方法。
  • 示例:
    1
    2
    3
    4
    5
    @Getter
    @Setter
    public class MyClass {
    private String name;
    }

3. 注解的分类

(1) 内置注解

1.1标准注解
  • @Override:表示方法重写父类方法。
  • @Deprecated:表示方法或类已过时。
  • @SuppressWarnings:抑制编译器警告。
  • 示例:
    1
    2
    3
    4
    @Override
    public String toString() {
    return "MyClass";
    }
1.2元注解
  • 用于定义注解的注解。
  • 常见的元注解:
    • @Target:指定注解可以应用的目标(如类、方法、字段等)。
    • @Retention:指定注解的保留策略(保留周期)(如源码级别、类级别、运行时级别)。
    • @Documented:表示注解应包含在 Javadoc 中。
    • @Inherited:表示注解可以被子类继承。
  • 示例:
    1
    2
    3
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {}

(2) 自定义注解

  • 开发者可以根据需求定义自己的注解。
  • 示例:
    1
    2
    3
    4
    5
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
    String value() default "default";
    }

4. 注解的使用场景

  • 框架配置

    • 例如 Spring 框架中的 @Controller@Service@Autowired 等注解用于配置和管理 Bean。
    • 示例:
      1
      2
      @Service
      public class MyService {}
  • 单元测试

    • 例如 JUnit 中的 @Test@Before@After 等注解用于定义测试方法。
    • 示例:
      1
      2
      3
      4
      @Test
      public void testMethod() {
      // 测试逻辑
      }
  • 代码生成

    • 例如 Lombok 库中的 @Getter@Setter@Data 等注解用于自动生成代码。
    • 示例:
      1
      2
      3
      4
      @Data
      public class MyClass {
      private String name;
      }
  • 数据校验

    • 例如 Hibernate Validator 中的 @NotNull@Size 等注解用于数据校验。
    • 示例:
      1
      2
      3
      4
      public class User {
      @NotNull
      private String name;
      }

5. 注解的局限性

  • 运行时性能开销
    • 使用反射读取注解信息可能会影响性能。
  • 复杂性
    • 过度使用注解可能导致代码难以理解和维护。

6. 总结

Java 注解是一种强大的元数据机制,可以为代码提供额外的信息,并支持编译时检查、运行时处理和代码生成等功能。通过合理使用注解,可以提高代码的可读性、可维护性和开发效率。理解注解的定义、分类和使用场景,有助于更好地应用注解解决实际问题。

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2023-2025 Annie
  • Visitors: | Views:

嘿嘿 请我吃小蛋糕吧~

支付宝
微信