使用同一类的不同版本进行类加载:java.lang.LinkageError:尝试重复名称的类定义 [英] Classloading Using Different Versions Of The Same Class : java.lang.LinkageError : attempted duplicate class definition for name

查看:127
本文介绍了使用同一类的不同版本进行类加载:java.lang.LinkageError:尝试重复名称的类定义的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个工作代码,可以动态加载具有不同类名的不同类实现.将类文件加载到内存数据库( Apache Derby Db )中,然后类加载器从检索 .class 文件. BLOB 列.

I have a working code that loads dynamically different Class Implementations with different Class Names. The class files are loaded into the in-memory database (Apache Derby Db), and classloader retrieve the .class file from the BLOB columns.

我要做的是,将 .class 文件作为带有版本列和IS_ENABLED标志的二进制BLOB插入,然后classloader将在运行时加载不同版本的类.将有与已编译类版本相同数量的数据库条目,并且只有一个将IS_ENABLED标志设置为 TRUE 的类.

What I want to do is, inserting the .class files as binary BLOB with version column and IS_ENABLED flags, then classloader will load the class for different versions on run-time. There will be db entries same amount of the compiled class versions and there will only one class with IS_ENABLED flag set to TRUE.

因为我尝试使用自定义类加载器加载相同的类名称,所以出现以下异常;

Because that I try to load the same class name with custom classloader, I get the following Exception;

Exception in thread "main" java.lang.LinkageError: loader (instance of  com/levent/classloader/DerbyServerClassLoader): attempted  duplicate class definition for name: "com/levent/greeter/Greeter"
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at com.levent.classloader.DerbyServerClassLoader.findClass(DerbyServerClassLoader.java:38)
    at com.levent.example.ClientClassLoaderDBVersionDemo.main(ClientClassLoaderDBVersionDemo.java:43)

有两个不同的 .class 文件( Greeter.class.v1 Greeter.class.v2 )(在代码)( Greeter.java )

There are two different .class files (Greeter.class.v1, Greeter.class.v2) (inserted at the begining of the code) for the same Interface (Greeter.java)

在测试代码的开头,从lib/classes/文件夹中检索类文件,并将其作为Blob二进制数据插入到内存db中,然后,数据库依次检索.class文件并进行加载.加载具有相同名称的类时,会发生异常.

In the beginning of test code, class files are retrieved from the lib/classes/ folder and inserted as blob binary data to the in-memory db, after then, .class files are sequentially retrieved by the database and loaded. While loading the class with same name, Exception occurs.

我该如何解决这个问题?有什么方法可以卸载一个类,或者是否可以以相同的名称重新加载一个类?

How can I solve this problem? Is there any way to unload a class, or else, anyway to reload a class with same name?

package com.levent.classloader;

import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class DerbyServerClassLoader extends ClassLoader {

    private ClassLoader parent;
    private String connectionString;

    public DerbyServerClassLoader(String connectionString) {
        this(ClassLoader.getSystemClassLoader(), connectionString);
    }

    public DerbyServerClassLoader(ClassLoader parent, String connectionString) {
        super(parent);
        this.parent = parent;
        this.connectionString = connectionString;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        Class cls = null;

        try {
            cls = parent.loadClass(name);           // Delegate to the parent Class Loader
        } catch (ClassNotFoundException clnfE) {    // If parent fails, try to locate and load the class
            byte[] bytes = new byte[0];
            try {
                bytes = loadClassFromDatabase(name);
            } catch (SQLException sqlE) {
                throw new ClassNotFoundException("Unable to load class", sqlE);
            }
            return defineClass(name, bytes, 0, bytes.length);
        }

        return cls;
    }

    private byte[] loadClassFromDatabase(String name) throws SQLException {
        PreparedStatement pstmt = null;
        Connection connection = null;

        try {
            connection = DriverManager.getConnection(connectionString);

            String sql = "SELECT CLASS FROM CLASSES WHERE CLASS_NAME = ? AND IS_ENABLED = ?";
            pstmt = connection.prepareStatement(sql);
            pstmt.setString(1, name);
            pstmt.setBoolean(2, true);
            ResultSet rs = pstmt.executeQuery();

            if (rs.next()) {
                Blob blob = rs.getBlob(1);
                byte[] data = blob.getBytes(1, (int) blob.length());
                return data;
            }
        } catch (SQLException e) {
            System.out.println("Unexpected exception: " + e.toString());
        } catch (Exception e) {
            System.out.println("Unexpected exception: " + e.toString());
        } finally {
            if (pstmt != null) {
                pstmt.close();
            }

            if(connection != null) {
                connection.close();
            }
        }

        return null;
    }

}

Greet.java-Greeter接口

package com.levent.greeter;

public interface Greet {

    public String getGreetMessage();

}

DbSingleton.java-连接DerbyDb的实用程序类

package com.levent.derbyutility;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DbSingleton {

    private static DbSingleton instance = null;

    private Connection conn = null;

    private DbSingleton() {
        try{
            DriverManager.registerDriver(new org.apache.derby.jdbc.EmbeddedDriver());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static DbSingleton getInstance() {
        if(instance == null) {
            synchronized(DbSingleton.class) {
                if(instance == null) {
                    instance = new DbSingleton();
                }
            }
        }

        return instance;
    }

    public Connection getConnection() throws SQLException {
        if(conn == null || conn.isClosed()) {
            synchronized (DbSingleton.class) {
                if(conn == null || conn.isClosed()) {
                    try{
                        //String dbUrl = "jdbc:derby://localhost:1527/myDB;create=true;user=me;password=mine";
                        String dbUrl = "jdbc:derby://localhost:1527/memory:myDB;create=true";

                        conn = DriverManager.getConnection(dbUrl);
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        return conn;
    }

}

ClientClassLoaderDBVersionDemo.java-测试代码

package com.levent.example;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;

import com.levent.classloader.DerbyServerClassLoader;
import com.levent.derbyutility.DbSingleton;
import com.levent.greeter.Greet;

public class ClientClassLoaderDBVersionDemo {

    // apache derby in-memory db
    private static final String connectionString = "jdbc:derby://localhost:1527/memory:myDB;create=true";
    private static final String classFileName1 = "Greeter.class.v1";
    private static final String classFileName2 = "Greeter.class.v2";
    private static final String className = "com.levent.greeter.Greeter";

    public static void main(String[] args) {
        prepareClass();

        try {
            Greet greet = null;

            DerbyServerClassLoader cl = new DerbyServerClassLoader(connectionString);

            updateVersion(className, "v1");
            Class clazz1 = cl.findClass(className);
            greet = (Greet) clazz1.newInstance();
            System.out.println("Version 1 Greet.getGreetMessage() : " + greet.getGreetMessage());

            updateVersion(className, "v2");
            Class clazz2 = cl.findClass(className);
            greet = (Greet) clazz2.newInstance();
            System.out.println("Version 2 Greet.getGreetMessage() : " + greet.getGreetMessage());           
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private static void prepareClass() {
        DbSingleton instance = DbSingleton.getInstance();

        Connection conn = null;

        try {
            conn = instance.getConnection();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }

        Statement sta;

        if (conn != null) {
            try {
                sta = conn.createStatement();
                int count = sta
                        .executeUpdate("CREATE TABLE CLASSES (CLASS_NAME VARCHAR(50), CLASS BLOB, IS_ENABLED BOOLEAN, VERSION VARCHAR(10) )");
                System.out.println("CLASSES Table created");
                sta.close();

                sta = conn.createStatement();

                PreparedStatement psta = conn.prepareStatement("INSERT INTO CLASSES (CLASS_NAME, CLASS, IS_ENABLED, VERSION) values (?, ?, ?, ?)");
                byte[] bytes = null;
                InputStream blobObject = null;

                psta.setString(1, className);
                bytes = readJarFileAsByteArray(classFileName1);
                blobObject = new ByteArrayInputStream(bytes); 
                psta.setBlob(2, blobObject, bytes.length);
                psta.setBoolean(3, false);
                psta.setString(4, "v1");
                count = psta.executeUpdate();

                psta.setString(1, className);
                bytes = readJarFileAsByteArray(classFileName2);
                blobObject = new ByteArrayInputStream(bytes); 
                psta.setBlob(2, blobObject, bytes.length);
                psta.setBoolean(3, false);
                psta.setString(4, "v2");
                count += psta.executeUpdate();

                System.out.println(count + " record(s) created.");
                sta.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    private static byte[] readJarFileAsByteArray(String classFileName) {
        Path currentRelativePath = Paths.get("");
        String s = currentRelativePath.toAbsolutePath().toString();

        File file = new File(s + "/lib/classes/" + classFileName);
        byte[] fileData = new byte[(int) file.length()];
        DataInputStream dis;
        try {
            dis = new DataInputStream(new FileInputStream(file));
            dis.readFully(fileData);
            dis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return fileData;
    }

    private static void updateVersion(String className, String version) {
        DbSingleton instance = DbSingleton.getInstance();

        Connection conn = null;

        try {
            conn = instance.getConnection();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }

        Statement sta;

        if (conn != null) {
            try {
                int count = 0;
                sta = conn.createStatement();
                PreparedStatement psta = conn.prepareStatement("UPDATE CLASSES SET IS_ENABLED = ? WHERE CLASS_NAME = ?");
                psta.setBoolean(1, false);
                psta.setString(2, className);
                count = psta.executeUpdate();
                System.out.println(count + " record(s) updated.");

                psta = conn.prepareStatement("UPDATE CLASSES SET IS_ENABLED = ? WHERE CLASS_NAME = ? AND VERSION = ?");

                psta.setBoolean(1, true);
                psta.setString(2, className);
                psta.setString(3, version);

                count = psta.executeUpdate();

                System.out.println(count + " record(s) updated.");
                sta.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

}

输出

CLASSES Table created
2 record(s) created.
2 record(s) updated.
1 record(s) updated.
Version 1 Greet.getGreetMessage() : Hail to the King Baby!
2 record(s) updated.
1 record(s) updated.
Exception in thread "main" java.lang.LinkageError: loader (instance of  com/levent/classloader/DerbyServerClassLoader): attempted  duplicate class definition for name: "com/levent/greeter/Greeter"
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at java.lang.ClassLoader.defineClass(Unknown Source)
    at com.levent.classloader.DerbyServerClassLoader.findClass(DerbyServerClassLoader.java:38)
    at com.levent.example.ClientClassLoaderDBVersionDemo.main(ClientClassLoaderDBVersionDemo.java:43)

推荐答案

我认为问题可能出在您使用的父级ClassLoader上.您没有重载loadClass方法,因此在父类中(具体在ClassLoader.getSystemClassLoader()中)进行委派.

I think the problem would become from the parent ClassLoader you are using. You are not overloading loadClass method, so you are delegating in parent class, concretely in ClassLoader.getSystemClassLoader().

javadoc在 https上说://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#getSystemClassLoader()

此方法在运行时的启动顺序中首先被调用, 这时它将创建系统类加载器,并将其设置为 调用线程的上下文类加载器.

This method is first invoked early in the runtime's startup sequence, at which point it creates the system class loader and sets it as the context class loader of the invoking Thread.

您想加载更改后的类,但是将操作委托给Thread ClassLoader,这有点令人困惑.

You want to load the changed classes but you are delegating to the Thread ClassLoader the operation, is a bit confusing.

您可以使用自己的Class ClassLoader

You can do something like this, using your own Class ClassLoader

package a;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

public class ReloadTest {

    public static void main(String[] args) throws ClassNotFoundException, IOException {

        final Class<?> clazz = ReloadTest.class;

        System.out.println("Class: " +  clazz.hashCode());

        final URL[] urls = new URL[1];

        urls[0] =  clazz.getProtectionDomain().getCodeSource().getLocation();
        final ClassLoader delegateParent = clazz.getClassLoader().getParent();

        try (final URLClassLoader cl = new URLClassLoader(urls, delegateParent)) {

            final Class<?> reloadedClazz = cl.loadClass(clazz.getName());
            System.out.println("Class reloaded: " + reloadedClazz.hashCode());
            System.out.println("Are the same: " + (clazz != reloadedClazz) );
        }
    }
}

希望有帮助!

PD:此链接与相同的问题有关,可能也有帮助.使用重载Java运行时的类

P.D: This link is related to the same problems, may it help too Reload used classes at runtime Java

这篇关于使用同一类的不同版本进行类加载:java.lang.LinkageError:尝试重复名称的类定义的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆