JAVA反射

什么是Java反射

反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行的时候才动态加载类,并且对于任意一个类,都能够知道这个类的所有属性和方法,调用方法/访问属性,不需要提前在编译期知道运行的对象是谁,他允许运行中的Java程序获取类的信息,并且可以操作类或对象内部属性。程序中对象的类型一般都是在编译期就确定下来的,而当我们的程序在运行时,可能需要动态的加载一些类,这些类因为之前用不到,所以没有加载到jvm,这时,使用Java反射机制可以在运行期动态的创建对象并调用其属性,它是在运行时根据需要才加载。

反射的原理

反射的优缺点

优点:使用反射,我们就可以在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

缺点:

  1. 反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
  2. 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

反射的用途

  1. 反编译:.class–>.java
  2. 通过反射机制访问java对象的属性,方法,构造方法等
  3. 当我们在使用IDEA时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。
  4. 反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。

反射机制常用的类

1
Java.lang.reflect.Constructor;
2
Java.lang.reflect.Field;
3
Java.lang.reflect.Method;
4
Java.lang.reflect.Modifier;

反射的基本使用

  • Object.getclass() //需要创建对象

  • Object.class //需要导入对应的类包

  • Class.forName //最常用

代码实例:

1、创建一个需要被反射的测试类

1
import static java.lang.System.out;
2
import java.lang.String;
3
4
public class User {
5
    private String username;
6
    public String interest;
7
8
    public User() {
9
        ;
10
    }
11
12
    private User(String name) {
13
        System.out.println("init.");
14
    }
15
16
    public String getUsername(){
17
        return username;
18
    }
19
20
    public void setUsername(String username){
21
        this.username = username;
22
    }
23
24
    public String getInterest(){
25
        return interest;
26
    }
27
28
    public void setInterest(String interest){
29
        this.interest = interest;
30
    }
31
32
    public static void main(String args[]) {
33
        User userinfo = new User();
34
        userinfo.setUsername("xiaoming");
35
        out.println(userinfo.getUsername());
36
    }
37
}

2、反射调用User类

1
import java.lang.reflect.*;
2
import java.lang.reflect.InvocationTargetException;
3
import java.lang.System;
4
import java.lang.String;
5
6
public class Reflect {
7
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
8
        Class clazz = Class.forName("User"); //包路径,这里测试写在同目录
9
10
        System.out.println("===获取公共的构造函数===");
11
        Constructor[] constructors = clazz.getConstructors();
12
        for (Constructor constructor: constructors)
13
            System.out.println(constructor);
14
15
        System.out.println("===获取所有的构造函数===");
16
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
17
        for (Constructor constructor: declaredConstructors)
18
            System.out.println(constructor);
19
20
        System.out.println("===获取公共的属性===");
21
        Field[] fields = clazz.getFields();
22
        for (Field field: fields)
23
            System.out.println(field);
24
25
        System.out.println("===获取所有的属性===");
26
        Field[] declaredFields = clazz.getDeclaredFields();
27
        for (Field field: declaredFields)
28
            System.out.println("ALL: " + field);
29
        
30
        System.out.println("===获取特定的函数===");
31
        Method method = clazz.getDeclaredMethod("setUsername", String.class);
32
        System.out.println(method);
33
34
        //赋值操作
35
        Object obj = clazz.getConstructor().newInstance();
36
        Object invoke = method.invoke(obj, new String[]{"我是科比"});
37
38
        System.out.println("===测试===");
39
        String name= (String) clazz.getDeclaredMethod("getUsername").invoke(obj);
40
        System.out.println(name);
41
    }
42
}

这个只做了部分的测试,get_系列方法的是获取公用,getDeclared_系列方法获取所有,get_(参数)系列获取特定的。如果需要查看所有的方法,可以查看官方文档,或者IDEA跟进Class.java文件查看。这边需要注意的是 method.invoke(obj, args) ,如果method是静态方法,obj是可以省略的。

JAVA动态代理

代理模式简介

代理模式是一种常用的设计模式。代理模式为其对象提供了一种代理以控制对这个对象的访问。代理模式可以将主要业务与次要业务进行松耦合的组装。根据代理类的创建时机和创建方式的不同,可以将其分为静态代理和动态代理两种形式:

  • 在程序运行前就已经存在的编译好的代理类是为静态代理,
  • 在程序运行期间根据需要动态创建代理类及其实例来完成具体的功能是为动态代理。

动态代理简介

对代理模式而言,一般来说,具体主题类与其代理类是一一对应的,这也是静态代理的特点。但是,也存在这样的情况:有N个主题类,但是代理类中的“预处理、后处理”都是相同的,仅仅是调用主题不同。那么,若采用静态代理,必然需要手动创建N个代理类,这显然让人相当不爽。动态代理则可以简单地为各个主题类分别生成代理类,共享“预处理,后处理”功能,这样可以大大减小程序规模,这也是动态代理的一大亮点。

在动态代理中,代理类是在运行时期生成的。因此,相比静态代理,动态代理可以很方便地对委托类的相关方法进行统一增强处理,如添加方法调用次数、添加日志功能等等。

JDK动态代理机制的相关类与接口

java.lang.reflect.Proxy:该类用于动态生成代理类,只需传入被监控对象隶属的类文件在内存中真实地址、被监控对象隶属的类文件实现接口以及InvocationHandler通知对象便可为目标接口生成代理类及代理对象。

1
// 方法 1: 该方法用于获取指定代理对象所关联的InvocationHandler static InvocationHandler
2
getInvocationHandler(Object proxy)
3
4
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象 static Class
5
getProxyClass(ClassLoader loader, Class[] interfaces)
6
7
// 方法 3:该方法用于判断指定类是否是一个动态代理类 static boolean isProxyClass(Class cl)
8
9
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 static Object
10
newProxyInstance(ClassLoader loader, Class[] interfaces,
11
InvocationHandler h)

java.lang.reflect.InvocationHandler:该接口包含一个invoke方法,通过该方法实现对委托类的代理的访问,是代理类完整逻辑的集中体现,包括要切入的增强逻辑和进行反射执行的真实业务逻辑。

1
Object invoke(Object proxy, Method method, Object[] args)

该方法是代理类完整逻辑的集中体现。在被监控行为将要执行时,会被JVM拦截。被监控行为和行为实现方法会被作为参数输送invoke,通常通过反射完成对具体角色业务逻辑的调用,并对其进行增强。

  • 第一个参数既是代理类实例。
  • 第二个参数是被调用的方法对象。
  • 第三个方法是调用参数。

java.lang.ClassLoader:类加载器类,负责将类的字节码装载到Java虚拟机中并为其定义类对象,然后该类才能被使用。Proxy静态方法生成动态代理类同样需要通过类加载器来进行加载才能使用,它与普通类的唯一区别就是其字节码是由JVM在运行时动态生成的而非预存在于任何一个.class 文件中。JDK动态代理使用步骤

JDK动态代理的一般步骤

1、创建被代理的接口和类;

2、实现InvocationHandler接口,对目标接口中声明的所有方法进行统一处理;

3、调用Proxy的静态方法,创建代理类并生成相应的代理对象;

4、使用代理。

生活案例

饭前便后要洗手
一、分析出主要业务和次要业务
【主要业务】:吃饭,上厕所
【次要业务】:洗手

二、JDK代理模式实现

  1. 接口角色: 定义所有需要被监听行为

  2. 接口实现类:中国人、印度人

  3. 通知类:

    • 次要业务进行具体实现
    • 通知JVM,当前被拦截的主要业务方法与次要业务方法应该如何绑定执行
  4. 监控对象(代理对象)

    • 被监控实例对象 需要被监控的行为
    • 具体通知类实例对象

代码实现

1、定义接口类

1
public interface BaseService {
2
    void eat();
3
    void wc();
4
}

2、编写接口的实现类,即具有某些行为的实体

1
public class Person implements BaseService {
2
3
    @Override
4
    public void eat() { //主要业务,代理模式要求开发人员只关心主要业务
5
        System.out.println("吃饭.");
6
    }
7
8
    @Override
9
    public void wc() {
10
        System.out.println("上厕所.");
11
    }
12
}

3、创建通知类

1
import java.lang.reflect.InvocationHandler;
2
import java.lang.reflect.Method;
3
4
public class Invocation implements InvocationHandler {
5
6
    private BaseService obj;//具体被监控对象
7
8
    public Invocation(BaseService param){
9
        this.obj = param;
10
    }
11
12
    /*
13
     *
14
     *  invoke方法:在被监控行为将要执行时,会被JVM拦截
15
     *             被监控行为和行为实现方会被作为参数输送invoke
16
     *             ****
17
     *             通知JVM,这个被拦截方法是如何与当前次要业务方法绑定实现
18
     *  invoke方法三个参数
19
     *
20
     *           int v= 小明.eat();//JVM拦截
21
     *            eat方法封装为Mehtod类型对象
22
     *            eat方法运行时接受所有的实参封装到Object[]
23
     *            将负责监控小明的代理对象作为invoke方法第一个参数
24
     *
25
     */
26
    @Override
27
    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
28
        //1.局部变量,接受主要业务方法执行完毕后返回值
29
        Object value;
30
        //2.确认当前被拦截行为
31
        String methodName= method.getName();
32
        //3.根据被拦截行为不同,决定主要业务和次要业务如何绑定执行
33
        if("eat".equals(methodName)){//饭前要洗手
34
            wash();                            //洗手
35
            value=method.invoke(this.obj, params);   //吃饭
36
        }else{//便后要洗手
37
            value=method.invoke(this.obj, params);
38
            wash();
39
        }
40
        return value; //返回被拦截方法,需要调用地方
41
    }
42
43
    //次要业务
44
    public void wash(){
45
        System.out.println("-----洗手----");
46
    }
47
}

4、创建监控对象,通过Proxy类的静态方法newProxyInstance创建代理对象

1
import java.lang.reflect.InvocationHandler;
2
import java.lang.reflect.Proxy;
3
4
class ProxyFactory {
5
    /*
6
     *
7
     *  JDK动态代理模式下,代理对象的数据类型
8
     *  应该由监控行为来描述
9
     *  参数: Class文件,监控类
10
     */
11
    public static BaseService Builder(Class classFile) throws Exception {
12
13
        //1.创建被监控实例对象
14
        BaseService obj = (BaseService) classFile.newInstance();
15
        //2.创建一个通知对象 用接口来描述
16
        InvocationHandler adviser = new Invocation(obj);
17
        //3.向JVM申请负责监控obj对象指定行为的监控对象(代理对象)
18
        /*
19
         *  loader:被监控对象隶属的类文件在内存中真实地址
20
         *  interfaces:被监控对象隶属的类文件实现接口
21
         *  adviser:监控对象发现小明要执行被监控行为,应该由哪一个通知对象进行辅助
22
         */
23
        BaseService $proxy = (BaseService) Proxy.newProxyInstance(
24
                obj.getClass().getClassLoader(),
25
                obj.getClass().getInterfaces(),
26
                adviser);
27
        return $proxy;
28
    }
29
}

5、测试

1
import java.lang.*;
2
3
public class Test {
4
    public static void main(String[] args) throws Exception {
5
        BaseService mike= ProxyFactory.Builder(Person.class);
6
        mike.eat();
7
        System.out.println("================");
8
        mike.wc();
9
    }
10
}

JAVA动态字节码

动态字节码技术

Java 代码都是要被编译成字节码后才能放到 JVM 里执行的,而字节码一旦被加载到虚拟机中,就可以被解释执行。字节码文件(.class)就是普通的二进制文件,它是通过 Java 编译器生成的。而只要是文件就可以被改变,如果我们用特定的规则解析了原有的字节码文件,对它进行修改或者干脆重新定义,这不就可以改变代码行为了么。动态字节码技术优势在于 Java 字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修改。

通过动态编程的方式,我可以直接对已经存在的java字节码进行操作,也可以在内存中动态生成JAVA代码,动态编译执行,在安全中常用于生成payload,这种方式生成的payload优点:能够注入pure-java的shell来绕过java原生的安全防护。

Java 生态里有很多可以动态处理字节码的技术,比较流行的有两个,一个是 ASM,一个是 Javassist 。

  • ASM:直接操作字节码指令,执行效率高,但涉及到JVM的操作和指令,要求使用者掌握Java类字节码文件格式及指令,对使用者的要求比较高。

  • Javassist:提供了更高级的API,执行效率相对较差,但无需掌握字节码指令的知识,简单、快速,对使用者要求较低。

Javassist简介

Javassist 是一个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简单,而且快速。直接使用 java 编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

Javassist 中最为重要的是 ClassPool,CtClass ,CtMethod 以及 CtField 这几个类。

1
ClassPool:一个基于 Hashtable 实现的 CtClass 对象容器,其中键是类名称,值是表示该类的 CtClass 对象。
2
CtClass:CtClass 表示类,一个 CtClass (编译时类)对象可以处理一个 class 文件,这些 CtClass 对象可以从 ClassPool 获得。
3
CtMethods:表示类中的方法。
4
CtFields :表示类中的字段。

下面以简单的一个实例来说明下基本的使用方法。这个实例是在执行完类中的方法时候加入一些操作。

1、定义个测试类User

1
package com.demo.manba;
2
3
import java.lang.String;
4
5
public class User {
6
    private String name;
7
8
    public User() {
9
    }
10
11
    public String getUser() {
12
        return this.name;
13
    }
14
15
    public void setUser(String name) {
16
        this.name = name;
17
    }
18
19
}

2、 执行

1
package com.demo.manba;
2
3
import javassist.*;
4
import java.lang.reflect.InvocationTargetException;
5
6
public class DynGenerateClass {
7
    public static void main(String[] args) throws NotFoundException, CannotCompileException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
8
            // 获取默认池
9
            ClassPool pool = ClassPool.getDefault();
10
            CtClass cc = pool.get("com.demo.manba.User");
11
            // 获取所有方法
12
            CtMethod[] cms = cc.getDeclaredMethods();
13
            for(CtMethod cm:cms){
14
                System.out.println(cm.getName());
15
            }
16
            // 在执行setUser方法后执行
17
            cms[1].insertAfter("System.out.println(\"I hava execute setUser Success.\");");
18
            Class clazz = cc.toClass();
19
            // 实例化
20
            User u=(User) clazz.getConstructor(new Class[]{}).newInstance(new Object[]{});
21
            u.setUser("mike");
22
            System.out.println(u.getUser());
23
            // cc.writeFile();
24
        }
25
26
}

输出

参考:

https://blog.csdn.net/a745233700/article/details/82893076

https://blog.csdn.net/vae1314chuanchen/article/details/87974728

https://blog.csdn.net/justloveyou_/article/details/79407248

https://blog.csdn.net/vae1314chuanchen/article/details/78266299