我使用标准的Oracle JDBC驱动程序(JDK6瘦驱动程序),并试图通过反射调用PreparedStatement上的方法。我可以直接调用该方法,但是当我试图通过反射调用(我认为是)相同的方法时,我得到一个IllegalAccessException,表明我试图调用的方法没有公共访问权限。检查类返回的Method对象,它显示它是一个公共方法(即Modifier=1)。
使用其他JDBC驱动程序(IBM的DB2, Microsoft的SQLServer, PostgreSQL等)进行相同的测试,均按预期工作。
由于这段代码最终是在Glassfish容器中的JEE应用程序中使用的,因此我使用DataSource而不是简单的连接。
下面是一个简短测试程序的片段(删除了敏感项),它说明了这个问题:-
System.out.println("Creating DataSource");
DataSource ds = new OracleDataSource();
((OracleDataSource) ds).setUser("HINT");
((OracleDataSource) ds).setPassword("hint");
((OracleDataSource) ds).setURL("jdbc:oracle:thin:@server-name:1521:database-name");
Connection dbCon = ds.getConnection();
dbCon.setAutoCommit(true);
System.out.println("Connection obtained : " + dbCon.getClientInfo());
PreparedStatement pstmt = (OraclePreparedStatement)dbCon.prepareStatement("SELECT * FROM "HINT"."COUNTRIES1" WHERE "COUNTRY_ID" = ?");
if (useReflection) // True=Fails, False=Works!
{
Class clazz = pstmt.getClass();
Method method = clazz.getMethod("setString", new Class[]{int.class, String.class});
System.out.println("Modifiers=" + method.getModifiers());
System.out.println(method.toGenericString());
String value = "EG";
Object[] pstmtParams = new Object[]{new Integer(1), value};
method.invoke(pstmt, pstmtParams); // <<< Fails here
}
else
{
pstmt.setString(1, "EG");
}
下面是userreflection设置为TRUE时的输出:-
Creating DataSource
Connection obtained : {}
Modifiers=1
public void oracle.jdbc.driver.OraclePreparedStatementWrapper.setString(int,java.lang.String) throws java.sql.SQLException
java.lang.IllegalAccessException: Class com.****.dataaccess.TestOracle can not access a member of class oracle.jdbc.driver.OraclePreparedStatementWrapper with modifiers "public"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.reflect.Method.invoke(Method.java:588)
at com.rocketsoftware.ascentserver.dataaccess.TestOracle.testPreparedStatementSetters(TestOracle.java:64)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
深入研究,看起来驱动程序返回的PreparedStatement对象是一个包装器类(OraclePreparedStatementWrapper),它扩展和实现了各种内部类,所以显然它在幕后做了一些聪明的事情。然而,我一直认为调用标记为公共访问的方法直接调用或通过反射调用应该没有区别。
这里有明显的错误,但我就是看不出来.....
我认为问题是你试图使用一个方法,你已经要求通过一个类,你没有访问。这显然隐式地限制了方法的可见性,即使方法本身是公共的。
下面是一个类似的例子:
// Foo.java
public interface Foo {
void method();
}
// FooFactory.java
package foo;
public class FooFactory {
public static Foo getFoo() {
return new FooImpl();
}
}
// FooImpl.java
class FooImpl implements Foo {
@Override
public void method() {
System.out.println("FooImpl.method");
}
}
// Bar.java
package bar;
import foo.*;
import java.lang.reflect.*;
public class Test {
public static void main(String[] args) throws Exception {
Foo foo = FooFactory.getFoo();
Method method = foo.getClass().getMethod("method");
method.invoke(foo);
}
}
这与你的代码失败的方式相同:
Exception in thread "main" java.lang.IllegalAccessException:
Class bar.Test can not access a member of class foo.FooImpl with modifiers "public"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:109)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:261)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:253)
at java.lang.reflect.Method.invoke(Method.java:599)
如果您通过可以看到的接口请求该方法,则可以:
Method method = Foo.class.getMethod("method");
同样的,在你的代码中,你只需要改变这一行:
Class clazz = pstmt.getClass();
:
// TODO: Change it to Class<?> to avoid the raw type :)
Class clazz = PreparedStatement.class;
或者直接内联到下一个语句中:
Method method = PreparedStatement.class.getMethod(...);