反射-JVM运行和类加载器-RSA加密算法-尚学堂

37 阅读12分钟

文章目录


提示:以下是本篇文章正文内容,下面案例可供参考

一、反射加载类

1.1.1 什么是构造器

1.构造器,也称构造方法、构造函数。作用是构造出来一个类的实例,确保对象得到初始化。
2.构造器的格式: 权限修饰符 类名(无参/有参){}。
3.根据有无参数,可分为无参构造 和有参构造。
构造器最大的用处就是在创建对象时执行初始化,当创建一个对象时,系统会为这个对象的实例进行默认的初始化。如果想改变这种默认的初始化,就可以通过自定义构造器来实现。
构造器可以用来在初始化对象时初始化数据成员,一个类可以有多个构造器。一个类的构造器的名称必须与该类的名称一致。要退出构造,可以使用返回语句“return;”newInstance()必须要

1.1.2 newInstance()和new 对象的区别

代码仅供参考.仅写出关键代码!

 /**
   * 在newInstance()之前必须先加载类才能实例化
   * Class<?> clazz = Class.forName(path);
   * class.getDeclaredConstructor().newInstance()
   * class.getDeclaredConstructor()来实例化的方式能根据传入的参数,调用任意构造构造函数。 
   */
   
Class<?> clazz = Class.forName(path);
class.getDeclaredConstructor().newInstance()

/**
* 而new对象不需要类加载
*/

相关差别请看此处,这家精妙独到.

newInstance实际上是把new这个方式分解为两步,即,首先调用class的加载方法加载某个类,然后实例化。
这样分步的好处是显而易见的。new者,包括用A.class,在编译期已经确定,不可能在运行期变更,一旦要变更必须改变这部分代码,而newInstance者,Class.forName(String)的参数可以在运行期配置,而无须改动代码,
我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了我们降耦的手段。

———————————————— 版权声明:本文为CSDN博主「panda1234lee」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:blog.csdn.net/panda1234le…

1.2.1 获取类对象

package com.vector.controller;

import com.vector.pojo.User;

public class Demo1 {
    public static void main(String[] args) {
        String path = "com.vector.pojo.User";
        try {
            //Class.forName()获取类对象
            Class<?> aClass = Class.forName(path);
            //对象.getClass()获取类对象
            Class<? extends User> aClass1 = new User().getClass();
            //类.class获取类对象
            Class<User> pathClass = User.class;
            System.out.println(aClass);
            System.out.println(aClass1);
            System.out.println(pathClass);
        } catch (Exception e) {
            System.out.println("error! path is not found!");
        }
    }
}

在这里插入图片描述

1.1.2 获取类结构信息

以下是关键代码:

String path = "com.vector.pojo.User";
        try {
        /**
             * 在newInstance()之前必须先加载类才能实例化
             * Class<?> clazz = Class.forName(path);
             * class.getDeclaredConstructor().newInstance()
             */
            Class<User> clazz = Class.forName(path);
            //获得类名
            System.out.println(clazz.getName());//获得包名+类名
            System.out.println(clazz.getSimpleName());//仅获得类名
             //获得属性信息
            User u2 = clazz.getDeclaredConstructor().newInstance();//调用无参构造
            Field[] fields = clazz.getFields();//仅能获得public属性
            Field[] fields1 = clazz.getDeclaredFields();//获得所有属性
            Field fields2 = clazz.getDeclaredField("name");//获得指定属性
            fields2.set(u2,"pig");
            //获得方法信息
            User u3 = clazz.getDeclaredConstructor().newInstance();//调用无参构造
            Method[] declaredMethods = clazz.getDeclaredMethods();//获得所有的方法
            Method declaredMethod = clazz.getDeclaredMethod("getName",null);//获得指定方法,第一个参数为方法名,第二个为参数类型对应的class对象.
            declaredMethod.invoke(u3,"pig");//u3.getName("pig");
            //获得构造器信息
            Constructor<User> declaredConstructor =  clazz.getDeclaredConstructor();//获得指定的构造器,根据参数类型,个数,顺序决定
            User user = declaredConstructor.newInstance();//调用无参构造
        } catch (Exception e) {
            System.out.println("error! path is not found!");
        }

二. javaCompiler动态编译

2.1.1 反射调用main方法问题

场景:可以由页面向服务器发送一个java字符串,然后由服务器生成java文件,动态调用解析

package com.vector.controller;


import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.*;
import java.nio.charset.StandardCharsets;

public class Demo1 {
    public static void main(String[] args) {
        BufferedWriter bw = null;
        try {
            //通过io操作,将字符串存储成一个临时文件(Hi.java),然后调用动态编译方法!
            String str = "public class Hi{public static void main(String[] args){ System.out.println(\"学习使我快乐\");}}";
            //创建文件
            File file = new File("d:/Hi.java");
            file.createNewFile();
            //写入文件
            bw = new BufferedWriter(new FileWriter("d:/Hi.java"));
            bw.write(str);
            bw.flush();
            //动态编译
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            /**
             * in - “标准”输入; 使用System.in如果为null
             * out - “标准”输出; 如果为空,请使用System.out
             * err - “标准”错误; 如果为空,请使用System.err
             * arguments - 传递给工具的参数
             */
            int result = compiler.run(null,null,null,"d:/Hi.java");
            System.out.println(result==0?"编译成功!":"编译失败!");
            //动态运行
            Runtime runtime = Runtime.getRuntime();
            //运行时编码 -Dfile.encoding=UTF-8不加的话运行时以gbk编码
            //通过runtime调用执行类
            Process exec = runtime.exec(" java -Dfile.encoding=UTF-8 -cp d:/ Hi");
            InputStream in = exec.getInputStream();
            BufferedReader br  = new BufferedReader(new InputStreamReader(in));
            String str1 = null;
            while((str1=br.readLine()) != null){
                System.out.println(str1);
            }
        } catch (Exception e) {
            e.printStackTrace();
            if(bw != null){
                try {
                    bw.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

三.脚本引擎执行javaScript代码

用途举例:java可以调用js简化字符串公式运算.eval

package com.vector.controller;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;


public class Demo1 {
    public static void main(String[] args) throws Exception {
        //获得脚本运行对象
        /**
         * 注意!根据JEP 372,Nashorn 已从 JDK 15 中删除,
         * 但您可以从https://search.maven.org/artifact/org.openjdk.nashorn/nashorn-core/15.0/jar获取最新的 nashorn
         */
        ScriptEngineManager sem = new ScriptEngineManager();
        ScriptEngine engine = sem.getEngineByName("javascript");
        //定义变量,存储到引擎脚本
        engine.put("msg","good good study and day day up!");
        String str = "let user = {name:'gaoqi',age:18,schools:['清华大学','北京尚学堂']};";
        str += "println(user.name)";

        //执行脚本
        engine.eval(str);
        engine.eval("msg = 'sxt is good school';");
        System.out.println(engine.get("msg"));

        //定义js函数
        engine.eval("function add(a,b){var sum = a + b;return sum;}");
        //执行js函数
        Invocable jsInvoke = (Invocable) engine;
        Object result1 = jsInvoke.invokeFunction("add",new Object[] {13,20});
        System.out.println(result1);
    }
}

四.JVM核心机制_类加载全过程

目的:-有助于了解JVM运行过程
-更深入了解java动态性,(了解热部署,动态加载),提高程序灵活性 JVM把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成JVM可以直接使用的Java类型的过程。
同样一个类,jvm只会加载一个对象,不会创建多个对象.

-加载将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
–链接将java类的二进制代码合并到JVM的运行状态之中的过程。(1)验证:-确保加载的类信息符合JVM规范,没有安全方面的问题 (2)准备:-正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配(3)解析:-虚拟机常量池内的符号引用替换为直接引用的过程。
-初始化(1)执行类构造器方法的过程,类构造器方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。(2)当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。(3)虚拟机会保证一个类的方法在多线程环境中被正确加锁和同步。(4)当访问一个java的静态域时,只有真正声明这个域的类才会被初始化。

在这里插入图片描述

4.1.1 类的引用

1、主动引用( 一定会初始化)

new一个类的对象。 调用类的静态成员(除了final常量)和静态方法。
使用java.lang.reflect包的方法对类进行反射调用。
当虚拟机启动,java Hello,则一定会初始化Hello类。说白了就是先启动main方法所在的类。
当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类

2、被动引用

当访问一个静态域时,只有真正声明这个域的类才会被初始化。例如:通过子类引用父类的静态变量,不会导致子类初始化。
通过数组定义类引用,不会触发此类的初始化。
引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)。

—尚学堂

4.2.1 类加载器的作用

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。

类缓存
标准的Java SE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)—段时间。不过,JVM垃圾收集器可以回收这些Class对象。

在这里插入图片描述

4.3.1代理与双亲委托机制

代理模式:交给其他加载器来加载指定的类
双亲委托机制:

就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次追溯,直到最高的爷爷辈的,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

双亲委托机制是为了保证Java核心库的类型安全。

这种机制就保证不会出现用户自己能定义java.lang.Object类的情况。类加载器除了用于加载类,也是安全的最基本的屏障。
双亲委托机制是代理模式的一种

并不是所有的类加载器都采用双亲委托机制。

tomcat服务器类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果
找不到再代理给父类加载器。这与一般类加载器的顺序是相反的.

在这里插入图片描述

4.4.1 自定义类加载器

(1)首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回;否则转入步骤2。
(2)委派类加载请求给父类加载器,如果父类加载器能够完成,则返回父类加载器加载的Class实例;否则转入步骤3。
(3)调用本类加载器的findClass(…)方法,试图获取对应的字节码,如果获取的到,则调用defineClass(…)导入类型到方法区;如果获取不到对应的字节码或者其他原因失败,返回异常给loadClass(…), loadClass(…)转抛异常,终止加载过程(注意:这里的异常种类不止一种)。
注意:被两个类加载器加载的同一个类,JVM认为是不相同的类。
自定义类加载器代码如下

package com.vector;

import java.io.*;

//自定义类加载器
public class FileSystemClassLoader extends ClassLoader {
    //定义根目录,以后加载类,都在定义的目录路径加载
    private String rootDir;

    //定义构造器

    public FileSystemClassLoader() {
    }

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);
        //先查询有没有加载过这个类,如果已经加载,直接返回.如果没有,则加载
        if (c != null) {
            return c;
        } else {
            ClassLoader parent = this.getParent();
            try {
                //双亲委派机制
                c = parent.loadClass(name);
            } catch (ClassNotFoundException e) {
//                e.printStackTrace();
            }
            if (c != null) {
                return c;
            } else {
                //接收字节数组
                byte[] classData = getClassData(name);
                if (classData == null) {
                    throw new ClassNotFoundException();
                } else {
                    c = defineClass(name, classData, 0, classData.length);
                }
            }
        }
        return c;
    }


    //加载类变成字节数组
    private byte[] getClassData(String classname) {
        String path = rootDir + "/" + classname.replace('.', '/') + ".class";
        InputStream inputStream = null;
        ByteArrayOutputStream baos = null;
        try {
            inputStream = new FileInputStream(path);
            byte[] buffer = new byte[1024];
            int temp = 0;
            while ((temp = inputStream.read(buffer)) != -1) {
                baos.write(buffer, 0, temp);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (inputStream != null)
                    inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
            try {
                if (baos != null)
                    baos.close();
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
    }
}

4.5.1 线程类加载器

线程类加载器是为了抛弃双亲委派加载链模式。由你灵活使用.
每个线程都有一个关联的上下文类加载器。如果你使用new Thread()方式生成新的线程,新线程将继承其父线程的上下文类加载器。如果程序对线程上下文类加载器没有任何改动的话,程序中所有的线程将都使用系统类加载器作为上下文类加载器。

Thread.currentThread.getContextClassLoader()
//重新设定类加载器
Thread.currentThread.setContextClassLoader()

4.6.1 Tomcat服务器的类加载机制

在这里插入图片描述

五.RSA加密算法

可以用加密算法加密自定义加载器

RSA算法 RSA 加密算法是目前最有影响力的 公钥加密算法,并且被普遍认为是目前 最优秀的公钥方案 之一。RSA 是第一个能同时用于 加密和 数字签名 的算法,它能够 抵抗 到目前为止已知的 所有密码攻击,已被 ISO 推荐为公钥数据加密标准。
RSA 加密算法 基于一个十分简单的数论事实:将两个大 素数 相乘十分容易,但想要对其乘积进行 因式分解 却极其困难,因此可以将 乘积 公开作为 加密密钥。

import net.pocrd.annotation.NotThreadSafe;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

@NotThreadSafe
public class RsaHelper {
    private static final Logger logger = LoggerFactory.getLogger(RsaHelper.class);
    private RSAPublicKey publicKey;
    private RSAPrivateCrtKey privateKey;

    static {
        Security.addProvider(new BouncyCastleProvider()); //使用bouncycastle作为加密算法实现
    }

    public RsaHelper(String publicKey, String privateKey) {
        this(Base64Util.decode(publicKey), Base64Util.decode(privateKey));
    }

    public RsaHelper(byte[] publicKey, byte[] privateKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            if (publicKey != null && publicKey.length > 0) {
                this.publicKey = (RSAPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));
            }
            if (privateKey != null && privateKey.length > 0) {
                this.privateKey = (RSAPrivateCrtKey)keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public RsaHelper(String publicKey) {
        this(Base64Util.decode(publicKey));
    }

    public RsaHelper(byte[] publicKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            if (publicKey != null && publicKey.length > 0) {
                this.publicKey = (RSAPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public byte[] encrypt(byte[] content) {
        if (publicKey == null) {
            throw new RuntimeException("public key is null.");
        }

        if (content == null) {
            return null;
        }

        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            int size = publicKey.getModulus().bitLength() / 8 - 11;
            ByteArrayOutputStream baos = new ByteArrayOutputStream((content.length + size - 1) / size * (size + 11));
            int left = 0;
            for (int i = 0; i < content.length; ) {
                left = content.length - i;
                if (left > size) {
                    cipher.update(content, i, size);
                    i += size;
                } else {
                    cipher.update(content, i, left);
                    i += left;
                }
                baos.write(cipher.doFinal());
            }
            return baos.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public byte[] decrypt(byte[] secret) {
        if (privateKey == null) {
            throw new RuntimeException("private key is null.");
        }

        if (secret == null) {
            return null;
        }

        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            int size = privateKey.getModulus().bitLength() / 8;
            ByteArrayOutputStream baos = new ByteArrayOutputStream((secret.length + size - 12) / (size - 11) * size);
            int left = 0;
            for (int i = 0; i < secret.length; ) {
                left = secret.length - i;
                if (left > size) {
                    cipher.update(secret, i, size);
                    i += size;
                } else {
                    cipher.update(secret, i, left);
                    i += left;
                }
                baos.write(cipher.doFinal());
            }
            return baos.toByteArray();
        } catch (Exception e) {
            logger.error("rsa decrypt failed.", e);
        }
        return null;
    }

    public byte[] sign(byte[] content) {
        if (privateKey == null) {
            throw new RuntimeException("private key is null.");
        }
        if (content == null) {
            return null;
        }
        try {
            Signature signature = Signature.getInstance("SHA1WithRSA");
            signature.initSign(privateKey);
            signature.update(content);
            return signature.sign();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public boolean verify(byte[] sign, byte[] content) {
        if (publicKey == null) {
            throw new RuntimeException("public key is null.");
        }
        if (sign == null || content == null) {
            return false;
        }
        try {
            Signature signature = Signature.getInstance("SHA1WithRSA");
            signature.initVerify(publicKey);
            signature.update(content);
            return signature.verify(sign);
        } catch (Exception e) {
            logger.error("rsa verify failed.", e);
        }
        return false;
    }
}