反映实用程序类中的方法,并用Java中的varargs调用它们

问题描述 投票:2回答:1

我在Java中构建了一个_VERY_初级实用程序类来处理数据库操作(连接检索,插入等),如下所示:

// define the package name
package com.foo.bar.helpers;

// import all needed resources
import com.foo.bar.helpers.database.MySQL;
import com.foo.bar.helpers.database.SQLite;
import java.lang.reflect.Method;
import java.sql.Array;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;

/**
 * database
 * @author John Doe <...>
 */
class Database {
    // private constructor to prevent instantiation
    private Database() throws InstantiationException {
        // throw the appropriate exception
        throw new InstantiationException();
    }

    // database classes
    public static final String SQLITE_CLASS = SQLite.class.getCanonicalName();
    public static final String MYSQL_CLASS = MySQL.class.getCanonicalName();

    /**
     * returns a connection to the database using a set of parameters
     * @param parameters the connection parameters
     * @return a connection to the database
     * @author John Doe <...>
     */
    public static Connection getConnection(Object... parameters) {
        Connection output = null;

        try {
            if (parameters.length > 0) {
                // create an instance of the target class
                Class<?> target_class = Class.forName(parameters[0].getClass().getCanonicalName());

                // remove the first parameter (database class)
                Object[] class_parameters = Arrays.copyOfRange(parameters, 1, parameters.length);

                // retrieve the class type for each parameter
                Class<?>[] class_types = new Class[class_parameters.length];

                for (int i = 0; i < class_parameters.length; i++) {
                    class_types[i] = class_parameters[i].getClass();
                }

                // reflect the target class method
                Method class_method = target_class.getDeclaredMethod("getConnection", class_types);

                // output the database connection
                output = (Connection) class_method.invoke(null, class_parameters);
            } else {
                throw new Throwable("unable to establish a connection with the database (no parameters were provided)");
            }
        } catch (Throwable e) {
            // print the stack trace
            e.printStackTrace();
        }

        return output;
    }
}

除了数据库助手之外,我有两个这样的数据库连接器(MySQL和SQLite)(显示了MySQL连接器:]

// define the package name
package com.foo.bar.helpers.database;

// import all needed resources
import com.foo.bar.helpers.Configuration;
import com.foo.bar.helpers.Log;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.HashMap;

/**
 * MySQL
 * @author John Doe <....>
 */
public class MySQL {
    // private constructor to prevent instantiation
    private MySQL() throws InstantiationException {
        // throw the appropriate exception
        throw new InstantiationException();
    }

    // connection key
    public static final String CONNECTION_KEY = "mysql";

    // default connection profile
    public static final String DEFAULT_CONNECTION_PROFILE = "default";

    /**
     * returns a connection to the database
     * @return a connection to the database
     * @author John Doe <....>
     */
    public static Connection getConnection() {
        Connection output = null;

        try {
            // compose the database connection profile key
            String profile_key = String.format("%s_%s", CONNECTION_KEY, DEFAULT_CONNECTION_PROFILE);

            // retrieve the database connection profile keyset
            HashMap<String, String> keyset = Configuration.getConfiguration(profile_key);

            // output the database connection
            output = DriverManager.getConnection(String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC", keyset.get("host"), keyset.get("port"), keyset.get("schema")), keyset.get("username"), keyset.get("password"));
        } catch (Throwable e) {
            Log.error(MySQL.class, e);
        }

        return output;
    }

    /**
     * returns a connection to the database
     * @param profile the database configuration profile
     * @return a connection to the database
     * @author John Doe <....>
     */
    public static Connection getConnection(String profile) {
        Connection output = null;

        try {
            // compose the database connection profile key
            String profile_key = String.format("%s_%s", CONNECTION_KEY, profile);

            // retrieve the database connection profile keyset
            HashMap<String, String> keyset = Configuration.getConfiguration(profile_key);

            // output the database connection
            output = DriverManager.getConnection(String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC", keyset.get("host"), keyset.get("port"), keyset.get("schema")), keyset.get("username"), keyset.get("password"));
        } catch (Throwable e) {
            Log.error(MySQL.class, e);
        }

        return output;
    }

    /**
     * returns a connection to the database
     * @param host the database host
     * @param port the database port
     * @param schema the database schema
     * @param username the database username
     * @param password the database user password
     * @return a connection to the database
     * @author John Doe <....>
     */
    public static Connection getConnection(String host, int port, String schema, String username, String password) {
        Connection output = null;

        try {
            // output the database connection
            output = DriverManager.getConnection(String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC", host, port, schema), username, password);
        } catch (Throwable e) {
            Log.error(MySQL.class, e);
        }

        return output;
    }
}

DISCLAIMER:请不要过多注意使用变量的命名约定使用snake_case(更多的Javascript / PHP / Python / R-ish),使用Throwable代替Exception,我正在使用它们的方法,属性以及其域(公共,私有,受保护等)的所有内容正确建立实用程序类而不是full-fledged类的事实,并且应该正确地建立许多其他内容而事实并非如此。这(实际上)是我第二周的Java学习,我非常乐意提出改进建议,并且我承认这里有很多工作要做,所以仁慈:P

也就是说,如果我尝试这样做:

// define the package name
package com.foo.xxxxxxxx;

// import all needed resources
import com.foo.bar.helpers.Database;
import java.sql.Connection;

/**
 * application
 * @author John Doe <...>
 */
class Application {
    public static void main(String[] args) {
        Connection connection = Database.getConnection(Database.MYSQL_CLASS, "localhost", 3306, "foo_db", "john_doe_123", "this_is_not_my_real_password!");
    }
}

我明白了:

java.lang.NoSuchMethodException: java.lang.String.getConnection(java.lang.String, java.lang.Integer, java.lang.String, java.lang.String, java.lang.String)
    at java.base/java.lang.Class.getDeclaredMethod(Class.java:2475)
    at com.foo.bar.helpers.Database.getConnection(Database.java:146)
    at com.foo.xxxxxxxx.Application.main(Application.java:61)

如果我确实正确阅读了文档,则需要获取要反映的类的实例,获取要与getDeclaredMethod一起使用的特定方法(因为我的任何实用工具类中的每个方法都是静态的)方法名称为String,并带有可变数量的参数(或者数组,如果我正确使用的话),以及用于调用该方法的每个参数的类类型。

完成,我需要调用传递null作为第一个参数的方法(因为它是一个静态方法,并且静态方法不需要我要为其调用特定方法的类的实例)和一个变量参数本身的参数数量(与以前相同)。

[我从e.printStackTrace()得到的错误告诉我方法获取失败,或者是因为我没有正确指定类类型(我非常怀疑使用Class<?>[]而不是Class[],但是IntelliJ抱怨Raw use of parameterized class 'Class'),或者我没有真正从该类的实例中获得我打算从中获得实例的类,而是获得了某种通用类对象(因此我无法真正看到所需的方法) 。

或者也许是因为我声明了一个私有构造函数以避免实例化(但是我认为,在阅读了一些文章之后,实用程序类(如果您确实需要使用它们)应该有一个避免实例化...因此私有构造函数声明),但无论哪种方式,此刻我都有些困惑:(

想法是能够连接到任何给定的数据库(因为现在只是MySQL和SQLite,但将来可能是Amazon Redshift,BigQuery,PostgreSQL,Oracle等),但我可能会得到关于以错误方式进行通用访问的想法。

你能给我一个提示吗?

java reflection static-methods variadic-functions utility-method
1个回答
0
投票

您给出的异常提示您试图在类getConnection()中找到方法java.lang.String。我怀疑您没有把它放在那里,所以找不到任何东西。

Database#getConnection类中,我注意到以下语句

Class<?> target_class = Class.forName(parameters[0].getClass().getCanonicalName());

这基本上意味着您将第一个参数作为类Object处理。 (首先获取类类型实例,然后从那里获取名称)。因此,这里有一些注意事项。每个对象(即每个非原始值)都有一个类类型,甚至返回的类类型本身也是如此。如果不谨慎的话,这会造成混乱。

所以我可以想到有3种情况,您可以在几秒钟内处理这个特殊情况:

  1. 传递Class实例,例如

    Database.getConnection(MySQL.class,...);

    //在#getConnection类中class target_class = parameters [0] //类型已经是一个类,因此只需赋值

  2. 传递所需的类类型的实例,如

    Database.getConnection(new MySQL(),...); //绝对不建议使用,仅在需要实例本身时才真正可用(例如,非静态访问)

    //在#getConnection类中class target_class = parameters [0] .getClass()//获取类类型的实例

  3. 传递所需类类型的字符串表示形式(规范名称)

    Database.getConnection(new MySQL(),...); //绝对不建议使用,仅在需要实例本身时才真正可用(例如,非静态访问)

    //在#getConnection类中class target_class = Class.forName(parameters [0])// #forName需要一个String参数,因此我们可以直接传递它。

在最新的示例中,您可以使用ClassLoader等。它提供了不错的功能,例如缓存和类卸载。但是它相当复杂,因此您的第一个副手可能就不算什么了。

最后,作为一般建议。 Java是强类型的,具有诸如方法重载之类的功能。因此,为了您自己的理智,请尝试尽可能地滥用它。上面的3可以很容易地被重载,从而使参数验证变得不那么费力。它使API用户可以很好地使用它“万无一失”,因为在编译过程中会注意到类型缺失匹配。

© www.soinside.com 2019 - 2024. All rights reserved.