精华内容
下载资源
问答
  • CalcEngine4J:Java的计算解析器,评估器和转换器。 解析公式并求值。 允许您使用域模型dsl对相互依赖的公式及其源数据进行建模。 最终目标是能够生成代码以评估此模型中的公式。 这可能是sql存储过程java代码或...
  • Java类加载

    2020-07-18 18:33:58
    Java类加载Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。 知道类加载的过程吗? 类加载过程:加载->连接->初始化。 连接过程...

    Java类加载器(Java Classloader)是Java运行时环境(Java Runtime Environment)的一部分,负责动态加载Java类到Java虚拟机的内存空间中。

    知道类加载的过程吗?
    类加载过程:加载->连接->初始化。
    连接过程又可分三步:验证->准备->解析。

    那么加载时这一步又做了什么?
    类加载过程的第一步,主要完成下面3件事情:
    1、通过全类名获取此类的二进制字节流
    2、将字节流所代表的的静态存储结构转换为方法区的运行时数据结构
    3、在内存中生成一个代表该类的Class对象,作为方法区这些数据的访问入口。

    有哪些类加载器?
    JVM中内置了三个重要的ClassLoader,除了BootstrapClassLoader 是用C++语言写的,由其他类加载器均 Java实现且全部继承自java.lang.ClassLoader :

    1、BootstrapClassLoader(引导启动类加载器):
    嵌在JVM内核中的加载器,是最顶层的加载器,主要负载加载JAVA_HOME/lib下的类库,引导启动类加载器无法被应用程序直接使用。

    2、ExtensionClassLoader(扩展类加载器):
    主要加载JAVA_HOME/lib/ext目录中的类库。它的父加载器是BootstrapClassLoader

    3、App ClassLoader(应用类加载器):
    App ClassLoader是应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文件。它的父加载器为Ext ClassLoader(JKD1.8之后为PlatformClassLoader)

    双亲委派模型介绍

    每一个类都有对应它的类加载器。系统中的ClassLoader 在协同工作的时候会默认使用双亲委派模型

    即在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载过的类会直接返回,否则才会尝试加载。

    加载的时候,首先会把该请求委派给该父类加载器的loadClass()处理,因此所有的请求最终都应该会传送到顶层的启动类加载器BootstrapClassLoader中。当父类加载器无法处理时,才由自己来处理。当父类加载器为null,会使用启动类加载器BootstrapClassLoader

    在这里插入图片描述
    每个类加载都有一个父类加载器,我们可以通过程序来验证。

    public class ClassLoaderTest {
        public static void main(String[] args) {
            System.out.println("1、ClassLoaderTest类的加载器" +
                    ClassLoaderTest.class.getClassLoader());
            System.out.println("2、ClassLoaderTest类的父类的加载器" +
                    ClassLoaderTest.class.getClassLoader().getParent());
            System.out.println("3、ClassLoaderTest类的父类的父类的加载器" +
                    ClassLoaderTest.class.getClassLoader().getParent().getParent());
        }
    }
    

    在这里插入图片描述

    AppClassLoader的父类加载器是PlatformClassLoader,而PlatformClassLoader为null,null并不代表PlatformClassLoader没有父类加载器,而是BootstrapClassLoader。

    双亲委派模型实现源码分析
    双亲委派模型代码集中在java.lang.ClassLoader的loadClass()中,相关代码如下:

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                //首先检查请求的类是否加载过
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {  //父加载器不为空,调用父加载器loadClass()方法处理
                            c = parent.loadClass(name, false);
                        } else {   //父加载器为空,使用BootstrapClassLoader加载
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found   抛出异常说明父类加载器无法完成加载请求
                        // from the non-null parent class loader
                    }
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order   自己尝试加载
                        // to find the class.
                        long t1 = System.nanoTime();
                        c = findClass(name);
    
                        // this is the defining class loader; record the stats
                        PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    

    双亲委派模型带来的好处是:可以避免类的重复加载,保证了Java程序的稳定运行。

    展开全文
  • flexmark-java是解析Java实现,使用块,在Markdown解析体系结构后内联。 它的优势是速度,灵活性,基于Markdown源元素的AST,以及源位置的详细信息,直至构成元素的词素的单个字符和可扩展性。 该API允许对...
  • JAVA类加载

    千次阅读 2018-09-21 18:00:44
    每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件, ”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,...

    装载验证流程

    每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,
    ”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class”文件,并创建对应的class对象,
    将class文件加载到虚拟机的内存,这个过程称为类加载,这里我们需要了解一下类加载的过程,如下:

    image

    加载

    类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象

    链接

    验证

    目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,
    元数据验证,字节码验证,符号引用验证。

    • 文件格式的验证

    是否以0xCAFEBABE开头,版本号是否合理

    • 元数据验证

    是否有父类,继承了final类?非抽象类实现了所有的抽象方法

    • 字节码验证 (很复杂)

    运行检查,栈数据类型和操作码数据参数吻合,跳转指令指定到合理的位置

    • 符号引用验证

    常量池中描述类是否存在,访问的方法或字段是否存在且有足够的权限

    准备

    为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,
    至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,
    类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。

    分配内存,并为类设置初始值 (方法区中)

    • public static int v=1;
    • 在准备阶段中,v会被设置为0
    • 在初始化的中才会被设置为1
    • 对于static final类型,在准备阶段就会被赋上正确的值
    • public static final int v=1;

    解析

    主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,
    而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,
    接口方法解析(这里涉及到字节码变量的引用,如需更详细了解,可参考《深入Java虚拟机》)。

    初始化

    类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,
    成员变量也将被初始化)。

    • 执行类构造器
      • static变量 赋值语句
      • static{}语句
    • 子类的调用前保证父类的被调用
    • 是线程安全的

    这便是类加载的5个过程,而类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,
    然后转换为一个与目标类对应的java.lang.Class对象实例,在虚拟机提供了3种类加载器,
    引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)

    加载器

    启动(Bootstrap)类加载器

    启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,
    它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,
    注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,
    即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。

    扩展(Extension)类加载器

    扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,
    是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,
    开发者可以直接使用标准扩展类加载器。

    //ExtClassLoader类中获取路径的代码
    private static File[] getExtDirs() {
         //加载<JAVA_HOME>/lib/ext目录中的类库
         String s = System.getProperty("java.ext.dirs");
         File[] dirs;
         if (s != null) {
             StringTokenizer st =
                 new StringTokenizer(s, File.pathSeparator);
             int count = st.countTokens();
             dirs = new File[count];
             for (int i = 0; i < count; i++) {
                 dirs[i] = new File(st.nextToken());
             }
         } else {
             dirs = new File[0];
         }
         return dirs;
     }
    

    系统(System)类加载器

    也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。
    它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,
    开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。
    在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,
    需要注意的是,Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,
    而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类处理,它一种任务委派模式,下面我们进一步了解它。

    双亲委派模式

    双亲委派模式工作原理

    双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,
    请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,
    类加载器间的关系如下:

    image

    image

    双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,
    而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,
    如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,
    即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊?
    那么采用这种模式有啥用呢?

    双亲委派模式优势

    采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,
    当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,
    假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,
    发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
    可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,
    经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,
    最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常

    java.lang.SecurityException: Prohibited package name: java.lang
    

    编写自己的类加载器

    通过前面的分析可知,实现自定义类加载器需要继承ClassLoader或者URLClassLoader,继承ClassLoader则需要自己重写findClass()方法并编写加载逻辑,
    继承URLClassLoader则可以省去编写findClass()方法以及class文件加载转换成字节码流的代码。那么编写自定义类加载器的意义何在呢?

    • 当class文件不在ClassPath路径下,默认系统类加载器无法找到该class文件,
      在这种情况下我们需要实现一个自定义的ClassLoader来加载特定路径下的class文件生成class对象。

    • 当一个class文件是通过网络传输并且可能会进行相应的加密操作时,需要先对class文件进行相应的解密后再加载到JVM内存中,
      这种情况下也需要编写自定义的ClassLoader并实现相应的逻辑。

    • 当需要实现热部署功能时(一个class文件通过不同的类加载器产生不同class对象从而实现热部署功能),
      需要实现自定义ClassLoader的逻辑。

    自定义File类加载器

    这里我们继承ClassLoader实现自定义的特定路径下的文件类加载器并加载编译后DemoObj.class,源码代码如下

    public class DemoObj {
        @Override
        public String toString() {
            return "I am DemoObj";
        }
    }
    
    /**
     * Created by zejian on 2017/6/21.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class FileClassLoader extends ClassLoader {
        private String rootDir;
    
        public FileClassLoader(String rootDir) {
            this.rootDir = rootDir;
        }
    
        /**
         * 编写findClass方法的逻辑
         * @param name
         * @return
         * @throws ClassNotFoundException
         */
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            // 获取类的class文件字节数组
            byte[] classData = getClassData(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                //直接生成class对象
                return defineClass(name, classData, 0, classData.length);
            }
        }
    
        /**
         * 编写获取class文件并转换为字节码流的逻辑
         * @param className
         * @return
         */
        private byte[] getClassData(String className) {
            // 读取类文件的字节
            String path = classNameToPath(className);
            try {
                InputStream ins = new FileInputStream(path);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];
                int bytesNumRead = 0;
                // 读取类文件的字节码
                while ((bytesNumRead = ins.read(buffer)) != -1) {
                    baos.write(buffer, 0, bytesNumRead);
                }
                return baos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 类文件的完全路径
         * @param className
         * @return
         */
        private String classNameToPath(String className) {
            return rootDir + File.separatorChar
                    + className.replace('.', File.separatorChar) + ".class";
        }
    
        public static void main(String[] args) throws ClassNotFoundException {
            String rootDir="/Users/zejian/Downloads/Java8_Action/src/main/java/";
            //创建自定义文件类加载器
            FileClassLoader loader = new FileClassLoader(rootDir);
    
            try {
                //加载指定的class文件
                Class<?> object1=loader.loadClass("com.zejian.classloader.DemoObj");
                System.out.println(object1.newInstance().toString());
    
                //输出结果:I am DemoObj
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    

    显然我们通过getClassData()方法找到class文件并转换为字节流,并重写findClass()方法,
    利用defineClass()方法创建了类的class对象。在main方法中调用了loadClass()方法加载指定路径下的class文件,由于启动类加载器、
    拓展类加载器以及系统类加载器都无法在其路径下找到该类,因此最终将有自定义类加载器加载,即调用findClass()方法进行加载。
    如果继承URLClassLoader实现,那代码就更简洁了,如下:

    /**
     * Created by zejian on 2017/6/21.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class FileUrlClassLoader extends URLClassLoader {
    
        public FileUrlClassLoader(URL[] urls, ClassLoader parent) {
            super(urls, parent);
        }
    
        public FileUrlClassLoader(URL[] urls) {
            super(urls);
        }
    
        public FileUrlClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
            super(urls, parent, factory);
        }
    
    
        public static void main(String[] args) throws ClassNotFoundException, MalformedURLException {
            String rootDir="/Users/zejian/Downloads/Java8_Action/src/main/java/";
            //创建自定义文件类加载器
            File file = new File(rootDir);
            //File to URI
            URI uri=file.toURI();
            URL[] urls={uri.toURL()};
    
            FileUrlClassLoader loader = new FileUrlClassLoader(urls);
    
            try {
                //加载指定的class文件
                Class<?> object1=loader.loadClass("com.zejian.classloader.DemoObj");
                System.out.println(object1.newInstance().toString());
    
                //输出结果:I am DemoObj
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    

    非常简洁除了需要重写构造器外无需编写findClass()方法及其class文件的字节流转换逻辑。

    自定义网络类加载器

    自定义网络类加载器,主要用于读取通过网络传递的class文件(在这里我们省略class文件的解密过程),并将其转换成字节流生成对应的class对象,如下

    /**
     * Created by zejian on 2017/6/21.
     * Blog : http://blog.csdn.net/javazejian [原文地址,请尊重原创]
     */
    public class NetClassLoader extends ClassLoader {
    
        private String url;//class文件的URL
    
        public NetClassLoader(String url) {
            this.url = url;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] classData = getClassDataFromNet(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                return defineClass(name, classData, 0, classData.length);
            }
        }
    
        /**
         * 从网络获取class文件
         * @param className
         * @return
         */
        private byte[] getClassDataFromNet(String className) {
            String path = classNameToPath(className);
            try {
                URL url = new URL(path);
                InputStream ins = url.openStream();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int bufferSize = 4096;
                byte[] buffer = new byte[bufferSize];
                int bytesNumRead = 0;
                // 读取类文件的字节
                while ((bytesNumRead = ins.read(buffer)) != -1) {
                    baos.write(buffer, 0, bytesNumRead);
                }
                //这里省略解密的过程.......
                return baos.toByteArray();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private String classNameToPath(String className) {
            // 得到类文件的URL
            return url + "/" + className.replace('.', '/') + ".class";
        }
    
    }
    
    

    比较简单,主要是在获取字节码流时的区别,从网络直接获取到字节流再转车字节数组然后利用defineClass方法创建class对象,
    如果继承URLClassLoader类则和前面文件路径的实现是类似的,无需担心路径是filePath还是Url,
    因为URLClassLoader内的URLClassPath对象会根据传递过来的URL数组中的路径判断是文件还是jar包,
    然后根据不同的路径创建FileLoader或者JarLoader或默认类Loader去读取对于的路径或者url下的class文件。

    热部署类加载器

    所谓的热部署就是利用同一个class文件不同的类加载器在内存创建出两个不同的class对象(关于这点的原因前面已分析过,即利用不同的类加载实例),
    由于JVM在加载类之前会检测请求的类是否已加载过(即在loadClass()方法中调用findLoadedClass()方法),如果被加载过,则直接从缓存获取,
    不会重新加载。注意同一个类加载器的实例和同一个class文件只能被加载器一次,多次加载将报错,
    因此我们实现的热部署必须让同一个class文件可以根据不同的类加载器重复加载,以实现所谓的热部署。
    实际上前面的实现的FileClassLoader和FileUrlClassLoader已具备这个功能,但前提是直接调用findClass()方法,
    而不是调用loadClass()方法,因为ClassLoader中loadClass()方法体中调用findLoadedClass()方法进行了检测是否已被加载
    ,因此我们直接调用findClass()方法就可以绕过这个问题,当然也可以重新loadClass方法,但强烈不建议这么干。利用FileClassLoader类测试代码如下:

    public static void main(String[] args) throws ClassNotFoundException {
            String rootDir="/Users/zejian/Downloads/Java8_Action/src/main/java/";
            //创建自定义文件类加载器
            FileClassLoader loader = new FileClassLoader(rootDir);
            FileClassLoader loader2 = new FileClassLoader(rootDir);
    
            try {
                //加载指定的class文件,调用loadClass()
                Class<?> object1=loader.loadClass("com.zejian.classloader.DemoObj");
                Class<?> object2=loader2.loadClass("com.zejian.classloader.DemoObj");
    
                System.out.println("loadClass->obj1:"+object1.hashCode());
                System.out.println("loadClass->obj2:"+object2.hashCode());
    
                //加载指定的class文件,直接调用findClass(),绕过检测机制,创建不同class对象。
                Class<?> object3=loader.findClass("com.zejian.classloader.DemoObj");
                Class<?> object4=loader2.findClass("com.zejian.classloader.DemoObj");
    
                System.out.println("loadClass->obj3:"+object3.hashCode());
                System.out.println("loadClass->obj4:"+object4.hashCode());
    
                /**
                 * 输出结果:
                 * loadClass->obj1:644117698
                   loadClass->obj2:644117698
                   findClass->obj3:723074861
                   findClass->obj4:895328852
                 */
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    双亲委派模型的破坏者-线程上下文类加载器

    在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现,
    如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载,
    而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下,
    由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由引导类加载器来加载的,
    而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。
    在这种情况下,我们就需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器就是很好的选择。
    线程上下文类加载器(contextClassLoader)是从 JDK 1.2 开始引入的,我们可以通过java.lang.Thread类中的getContextClassLoader()和
    setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器。如果没有手动设置上下文类加载器,
    线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),
    在线程中运行的代码可以通过此类加载器来加载类和资源,如下图所示,以jdbc.jar加载为例

    image

    从图可知rt.jar核心包是有Bootstrap类加载器加载的,其内包含SPI核心接口类,由于SPI中的类经常需要调用外部实现类的方法,
    而jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径)无法通过Bootstrap类加载器加载,
    因此只能委派线程上下文类加载器把jdbc.jar中的实现类加载到内存以便SPI相关类使用。
    显然这种线程上下文类加载器的加载方式破坏了“双亲委派模型”,它在执行过程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器,
    当然这也使得Java类加载器变得更加灵活。为了进一步证实这种场景,不妨看看DriverManager类的源码,DriverManager是Java核心rt.jar包中的类,
    该类用来管理不同数据库的实现驱动即Driver,它们都实现了Java核心包中的java.sql.Driver接口,如mysql驱动包中的com.mysql.jdbc.Driver,
    这里主要看看如何加载外部实现类,在DriverManager初始化时会执行如下代码

    //DriverManager是Java核心包rt.jar的类
    public class DriverManager {
        //省略不必要的代码
        static {
            loadInitialDrivers();//执行该方法
            println("JDBC DriverManager initialized");
        }
    
    //loadInitialDrivers方法
     private static void loadInitialDrivers() {
         sun.misc.Providers()
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    //加载外部的Driver的实现类
                    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                  //省略不必要的代码......
                }
            });
        }
    

    在DriverManager类初始化时执行了loadInitialDrivers()方法,在该方法中通过ServiceLoader.load(Driver.class);去加载外部实现的驱动类,
    ServiceLoader类会去读取mysql的jdbc.jar下META-INF文件的内容

    而com.mysql.jdbc.Driver继承类如下:

    public class Driver extends com.mysql.cj.jdbc.Driver {
        public Driver() throws SQLException {
            super();
        }
    
        static {
            System.err.println("Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. "
                    + "The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.");
        }
    }
    

    从注释可以看出平常我们使用com.mysql.jdbc.Driver已被丢弃了,取而代之的是com.mysql.cj.jdbc.Driver,
    也就是说官方不再建议我们使用如下代码注册mysql驱动

    //不建议使用该方式注册驱动类
    Class.forName("com.mysql.jdbc.Driver");
    String url = "jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8";
    // 通过java库获取数据库连接
    Connection conn = java.sql.DriverManager.getConnection(url, "root", "root@555");
    

    而是直接去掉注册步骤,如下即可

    String url = "jdbc:mysql://localhost:3306/cm-storylocker?characterEncoding=UTF-8";
    // 通过java库获取数据库连接
    Connection conn = java.sql.DriverManager.getConnection(url, "root", "root@555");
    

    这样ServiceLoader会帮助我们处理一切,并最终通过load()方法加载,看看load()方法实现

    public static <S> ServiceLoader<S> load(Class<S> service) {
         //通过线程上下文类加载器加载
          ClassLoader cl = Thread.currentThread().getContextClassLoader();
          return ServiceLoader.load(service, cl);
      }
    

    很明显了确实通过线程上下文类加载器加载的,实际上核心包的SPI类对外部实现类的加载都是基于线程上下文类加载器执行的,
    通过这种方式实现了Java核心代码内部去调用外部实现类。我们知道线程上下文类加载器默认情况下就是AppClassLoader,
    那为什么不直接通过getSystemClassLoader()获取类加载器来加载classpath路径下的类的呢?其实是可行的,
    但这种直接使用getSystemClassLoader()方法获取AppClassLoader加载类有一个缺点,那就是代码部署到不同服务时会出现问题,
    如把代码部署到Java Web应用服务或者EJB之类的服务将会出问题,因为这些服务使用的线程上下文类加载器并非AppClassLoader,
    而是Java Web应用服自家的类加载器,类加载器不同。,所以我们应用该少用getSystemClassLoader()。
    总之不同的服务使用的可能默认ClassLoader是不同的,但使用线程上下文类加载器总能获取到与当前程序执行相同的ClassLoader,
    从而避免不必要的问题。ok~.关于线程上下文类加载器暂且聊到这,前面阐述的DriverManager类,大家可以自行看看源码,
    相信会有更多的体会,另外关于ServiceLoader本篇并没有过多的阐述,毕竟我们主题是类加载器,
    但ServiceLoader是个很不错的解耦机制,大家可以自行查阅其相关用法。

    Thread. setContextClassLoader()

    • 上下文加载器
    • 是一个角色
    • 用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
    • 基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例

    双亲模式的破坏

    • 双亲模式是默认的模式,但不是必须这么做
    • Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent
    • OSGi的ClassLoader形成网状结构,根据需要自由加载Class

    参考

    原文

    展开全文
  • Java 类加载

    2018-03-18 22:57:47
    每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机...

    每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载。

    简单的说,类加载器(class loader)就是用来加载 Java 类到 Java 虚拟机中去的。

    1. 类加载器类型

    1.1 系统提供的类加载器

    系统提供的类加载器主要有以下几个:

    • 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader

    bootstrap class loader不是Java类,因此它不需要被别人加载,它嵌套在Java虚拟机内核里面,也就是JVM启动的时候Bootstrap就已经启动,它是用C++写的二进制代码(不是字节码)

    • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
    • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

    注意:类加载器的体系并不是“继承”体系,而是委派体系,大多数类加载器首先会到自己的parent中查找类或者资源,如果找不到才会到自己本地查找。类加载器的委托行为动机是为了避免相同的类被加载多次。

    除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。

    1.2 双亲委派机制

    双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。

    双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

    优点
    1. Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载
    2. 其次是安全因素,防止核心API库被随意篡改

    1.3 委托机制的意义 — 防止内存中出现多份同样的字节码

    比如两个类A和类B都要加载System类:

    • 如果不用委托而是自己加载自己的,那么类A就会加载一份System字节码,然后类B又会加载一份System字节码,这样内存中就出现了两份System字节码。
    • 如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。

    2. 类加载过程

    类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段,其中验证、准备、解析三个部分统称链接。

    这些阶段通常都是互相交叉的混合式进行的,通常会在一个阶段执行的过程中调用或激活另外一个阶段。

    这里简要说明下Java中的绑定:绑定指的是把一个方法的调用与方法所在的类(方法主体)关联起来,对java来说,绑定分为静态绑定和动态绑定:

    • 静态绑定:即前期绑定。在程序执行前方法已经被绑定,此时由编译器或其它连接程序实现。针对java,简单的可以理解为程序编译期的绑定。java当中的方法只有final,static,private和构造方法是前期绑定的。
    • 动态绑定:即晚期绑定,也叫运行时绑定。在运行时根据具体对象的类型进行绑定。在java中,几乎所有的方法都是后期绑定的。

    2.1 加载

    加载阶段是“类加载机制”中的一个阶段,这个阶段通常也被称作“装载”,主要完成:

    1. 通过一个类的全限定名来获取其定义的二进制字节流。
    2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    3. 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

    相对于类加载过程的其他阶段,加载阶段(准确地说,是加载阶段中获取类的二进制字节流的动作)是开发期可控性最强的阶段,因为加载阶段可以使用系统提供的类加载器(ClassLoader)来完成,也可以由用户自定义的类加载器完成,开发人员可以通过定义自己的类加载器去控制字节流的获取方式。

    加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式有虚拟机实现自行定义,虚拟机并未规定此区域的具体数据结构。然后在java堆中实例化一个java.lang.Class类的对象,这个对象作为程序访问方法区中的这些类型数据的外部接口。

    2.2 验证

    验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证。

    • 文件格式的验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,该验证的主要目的是保证输入的字节流能正确地解析并存储于方法区之内。经过该阶段的验证后,字节流才会进入内存的方法区中进行存储,后面的三个验证都是基于方法区的存储结构进行的。
    • 元数据验证:对类的元数据信息进行语义校验(其实就是对类中的各数据类型进行语法校验),保证不存在不符合Java语法规范的元数据信息。
    • 字节码验证:该阶段验证的主要工作是进行数据流和控制流分析,对类的方法体进行校验分析,以保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。
    • 符号引用验证:这是最后一个阶段的验证,它发生在虚拟机将符号引用转化为直接引用的时候(解析阶段中发生该转化,后面会有讲解),主要是对类自身以外的信息(常量池中的各种符号引用)进行匹配性的校验。

    2.3 准备

    准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。

    对于该阶段有以下几点需要注意:

    1. 这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
    2. 这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。
    3. 如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。

    这里还需要注意如下几点:

    • 对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。
    • 对于同时被static和final修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被final修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。
    • 对于引用数据类型reference来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即null。
    • 如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的零值。

    2.4 解析

    解析阶段是虚拟机将常量池中的符号引用转化为直接引用的过程。

    1. 类或接口的解析:判断所要转化成的直接引用是对数组类型,还是普通的对象类型的引用,从而进行不同的解析。
    2. 字段解析:对字段进行解析时,会先在本类中查找是否包含有简单名称和字段描述符都与目标相匹配的字段,如果有,则查找结束;如果没有,则会按照继承关系从上往下递归搜索该类所实现的各个接口和它们的父接口,还没有,则按照继承关系从上往下递归搜索其父类,直至查找结束。
    3. 类方法解析:对类方法的解析与对字段解析的搜索步骤差不多,只是多了判断该方法所处的是类还是接口的步骤,而且对类方法的匹配搜索,是先搜索父类,再搜索接口。
    4. 接口方法解析:与类方法解析步骤类似,知识接口不会有父类,因此,只递归向上搜索父接口就行了。

    2.5 初始化

    初始化是类加载过程的最后一步,到了此阶段,才真正开始执行类中定义的Java程序代码。在准备阶段,类变量已经被赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序指定的主观计划去初始化类变量和其他资源,或者可以从另一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程。

    在以下四种情况下初始化过程会被触发执行:

    1. 遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需先触发其初始化。生成这4条指令的最常见的java代码场景是:使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用类的静态方法的时候;
    2. 使用java.lang.reflect包的方法对类进行反射调用的时候;
    3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先出发其父类的初始化;
    4. jvm启动时,用户指定一个执行的主类(包含main方法的那个类),虚拟机会先初始化这个类。

    2.6 总结

    整个类加载过程中,除了在加载阶段用户应用程序可以自定义类加载器参与之外,其余所有的动作完全由虚拟机主导和控制。到了初始化才开始执行类中定义的Java程序代码(亦及字节码),但这里的执行代码只是个开端,它仅限于()方法。类加载过程中主要是将Class文件(准确地讲,应该是类的二进制字节流)加载到虚拟机内存中,真正执行字节码的操作,在加载完成后才真正开始。


    参考

    1. JVM(三):类加载机制(类加载过程和类加载器)
    2. 深入理解java虚拟机—双亲委派模型
    3. 关于Java类加载双亲委派机制的思考(附一道面试题)
    4. 深入探讨 Java 类加载器
    展开全文
  • 类的加载过程图类的加载过程加载1、通过类加载,加载.class文件到内存中。2、将读取到.class数据存储到运行时内存区的方法区。3、然后将其转换为一个与目标类型对应的java.lang.Class对象实例。这...

    Java 类加载机制

    类从被加载到JVM中开始,到卸载为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。

    其中类加载过程包括加载、验证、准备、解析和初始化五个阶段。

    14334bc2a5b23e0935bd6516b64e6dc8.png

    类的加载过程图

    类的加载过程

    加载

    1、通过类加载器,加载.class文件到内存中。

    2、将读取到.class数据存储到运行时内存区的方法区。

    3、然后将其转换为一个与目标类型对应的java.lang.Class对象实例。这个Class对象在日后就会作为方法区中该类的各种数据的访问入口。

    链接

    验证

    确保被加载的类(.class文件的字节流),是否按照java虚拟的规范。不会造成安全问题

    1、文件格式验证:

    第一阶段要验证字节流是否符合 Class文件格式的规范, 井且能被当前版本的虚拟机处理。这一阶段可能包括下面这些验证点:

    • 是否以魔数 0xCAFEBABE开头
    • 主、次版本号是否在当前虚拟机处理范围之内 。
    • 常量池的常量中是否有不被支持的常量类型(检查常量tag 标志)。
    • 指向常量的各种索引值中是否有指向不存在的常量或不符合装型的常量 。
    • CONSTANT_Utf8_info型的常量中是否有不符合 UTF8编码的数据
    • Class 文件中各个部分及文件本身是否有被删除的或附加的其他信息

    实际上第一阶段的验证点还远不止这些, 这是其中的一部分。只有通过了这个阶段的验证之后, 字节流才会进入内存的方法区中进行存储, 所以后面的三个验证阶段全部是基于方法区的存储结构进行的,不会再直接操作字节流。

    2、元数据验证

    第二阶段是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求,这个阶段可能包括的验证点如下:

    • 这个类是否有父类(除了java.lang.0bject之外,所有的类都应当有父类)
    • 这个类的父类是否继承了不允许被继承的类(被finaI修饰的类)
    • 如果这个类不是抽象类, 是否实現了其父类或接口之中要求实现的所有方法
    • 类中的字段、 方法是否与父类产生了矛盾(例如覆盖了父类的final字段, 或者出現不符合规则的方法重载, 例如方法参数都一致, 但返回值类型却不同等)

    第二阶段的验证点同样远不止这些,这一阶段的主要目的是对类的元数据信息进行语义检验, 保证不存在不符合 Java语言规范的元数据信息。

    3、字节码验证

    第三阶段是整个验证过程中最复杂的一个阶段, 主要目的是通过数据流和控制流的分析,确定语义是合法的。符号逻辑的。在第二阶段对元数据信息中的数据类型做完校验后,这阶段将对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的行为,例如:

    • 保证任意时刻操作数栈的数据装型与指令代码序列都能配合工作, 例如不会出现类似这样的情况:在操作栈中放置了一个 int类型的数据, 使用时却按long类型来加载入本地变量表中。
    • 保证跳转指令不会跳转到方法体以外的字节码指令上
    • 保证方法体中的类型转换是有效的, 例如可以把一个子类对象赋值给父类数据装型,这是安全的,但是把父类对象意赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系、 完全不相干的一个数据类型, 则是危险和不合法的。

    即使一个方法体通过了字节码验证, 也不能说明其一定就是安全的。

    4、符号引用验证

    最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候 , 这个转化动作将在连接的第三个阶段——解析阶段中发生。符号引用验证可以看做是对类自身以外(常量池中的各种符号引用) 的信息进行匹配性的校验, 通常需要校验以下内容:

    • 符号引用中通过字将串描述的全限定名是否能找到对应的类
    • 在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段 。
    • 符号引用中的类、字段和方法的访问性(privateprotectedpublic)是否可被当前类访问

    符号引用验证的目的是确保解析动作能正常执行, 如果无法通过符号引用验证, 将会抛出一个java.lang.IncompatibleClassChangError异常的子类, 如 java.lang.IllegalAccessErrorjava.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。

    准备

    主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值,此时的赋值是Java虚拟机根据不同变量类型的默认初始值:

    如8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值

    1、final static temp = 100,此时temp就是赋值 100。

    2、String temp = “123456”,此时temp值就是null。

    3、int temp = 100,此时temp值就是0。

    解析

    将类的二进制数据中的符号引用替换成直接引用(符号引用是用一组符号描述所引用的目标;直接引用是指向目标的指针)

    可以认为是一些静态绑定的会被解析,动态绑定则只会在运行时进行解析;静态绑定包括一些final方法(不可以重写),static方法(只会属于当前类),构造器(不会被重写)

    在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。

    初始化

    初始化,则是为标记为常量值的字段赋值的过程。

    换句话说,只对static修饰的变量或语句块进行初始化。

    如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。

    如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。

    涉及问题

    一个类的构造器,代码块,静态代码块,成员变量的 的执行顺序。

    //父类

    bcfb466b1068662014b3aa865f4d1a81.png

    //子类

    91a295ca3515b4b39a252a7c78d9f38f.png

    执行结果:

    我是父静态成员变量p2

    我是父静态代码块1

    我是父静态代码块2

    我是子静态成员变量c2

    我是子静态代码块1

    我是子静态代码块2

    我是父成员变量p1

    我是父代码块1

    我是父代码块2

    我是父构造器

    我是子成员变量c1

    我是子代码块1

    我是子代码块2

    我是子构造器

    展开全文
  • Java类加载过程

    2020-12-26 14:50:07
    将字节流所代表的静态存储结构转换为⽅法区的运⾏时数据结构 在内存中⽣成⼀个代表该类的 Class 对象,作为⽅法区这些数据的访问⼊⼝ ⼀个⾮数组类的加载阶段(加载阶段获取类的⼆进制字节流的动作)是可控性最强的...
  • Java的类加载

    2020-04-17 23:10:36
    每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机...
  • java类的加载过程

    2019-12-07 16:28:12
    Java 类加载机制 类从被加载到JVM中开始,到卸载为止,整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。 其中类加载过程包括加载、验证、准备、解析和初始化五个阶段。 类的加载过程....
  • java类加载以及spi

    千次阅读 2018-10-01 14:38:54
    每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机...
  • 先给大家简单介绍下mybatisMyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装。MyBatis可以使用简单的XML或注解用于配置...
  • 类的加载过程.png类的加载过程加载1、通过类加载,加载.class文件到内存中。2、将读取到.classs数据存储到运行时内存区的方法区。3、然后将其转换为一个与目标类型对应的java.lang.Class对象...
  • java对象序列化的概念序列化是将对象状态信息转换为可存储或传输的过程,序列化时,对象会将当前状态写入到临时或持久性的存储区。一 java对象序列化的概念1.序列化的概念 序列化的概念就是把一个Object直接转换成为...
  • 2) 加载:类加载负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例 3) 链接 : 原始的类信息平滑的转入...
  • 每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机...
  • 创建存储过程的XML注释文件

    千次阅读 2002-05-14 19:39:00
    TSQLDoc是Windows的脚本解释,可从Transact-SQL的存储过程中萃取相关元数据和内嵌注释。本文介绍用TSQLDoc为数据库的存储过程生成相应的XML注释文件,然后用XSLT将XML注释文件转换成HTML或其他格
  • Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程。 在加载阶段,java虚拟机需要完成以下3件事: a.通过一个类...
  • 每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机...
  • 每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机...
  • 每个编写的”.java”拓展名类文件都存储着需要执行的程序逻辑,这些”.java”文件经过Java编译器编译成拓展名为”.class”的文件,”.class”文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机...
  • gson是一种组件库,可以把java对象数据转换成json数据格式 【Json串的解析和处理】 实际开发过程中我们经常会遇到Json串的解析问题。下面做一下举例分析 [{"tableName":"students","tableData":[{"id":1,...
  • 第一章 mybatis简介 1.1 mybatis的历史 1. mybatis在2010年前叫ibatis是apache内部的一个项目,名字来源于internet+...2. 支持定制化sql,存储过程以及高级映射 3. 可以避免几乎所有的JDBC代码手动设置参数以及获取结果
  • 第一章 mybatis简介 1.1 mybatis的历史 1. mybatis在2010年前叫ibatis是apache内部的一个项目,名字来源于internet+...2. 支持定制化sql,存储过程以及高级映射 3. 可以避免几乎所有的JDBC代码手动设置参数以及获取结果
  • Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。反序列化:即将字节序列转换回对象的过程转换后的...
  • 数据库BLOB读写Java字段类型定义为byte[],数据库表的字段类型为BLOB,常用的类型转换器是BlobTypeHandler批量更新配置&lt;!-- 配置默认的执行类型是批量模式 --&gt; &lt;setting name="...
  • 类加载机制JVM把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是JVM的类加载机制。 加载在加载阶段虚拟机需要完成以下三件事: 1. 通过...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 453
精华内容 181
热门标签
关键字:

存储过程java转换器

java 订阅