Dev Singh
Dev Singh

Reputation: 71

Dynamically loading method from external class

I am trying to load methods Customer.cypher and Customer.cypherCBC method from my class Configuration. Customer class is rendering from different environments so few environmets are having cypherCBC() and cypher() method and few are having only cypher() method.

Now i want to check if cypherCBC if not there in Customer class then load cypher() method. My function is so far;

   try {
        Class<?> customerClass = Class.forName("com.myapp.impl.service.Customer");

        Object  obj = customerClass.newInstance();

        //here getting "NoSuchMethodException" exception
        Method methodCBC = customerClass.getDeclaredMethod("cypherCBC", String.class); //line - 7


         if(methodCBC.getName().equals("cypherCBC")){
            methodCBC.invoke(obj, new String(dbshPass));
            System.out.println("CYPHER_CBC: "
               + methodCBC.invoke(obj, new String(dbshPass)));
        }else{

            Method method = customerClass.getDeclaredMethod("cypher", String.class);
            method.invoke(obj, new String(dbshPass));
            System.out.println("CYPHER: " + method.invoke(obj, new String(dbshPass)));
        }

    }catch (Exception e){
        e.printStackTrace();
    }

Getting an error at line 7.

NoSuchMethodException: com.myapp.impl.service.Customer.cypherCBC(java.lang.String)

that means for particular environment class Customer doesn't having cypherCBC() method, but ideally it should come in else part and execute cypher() method.

Class<?> client = null;
    Object  obj = null;
        try{
            client = Class.forName("com.myapp.impl.service.Client");
            obj = client.newInstance();

        }catch (InstantiationException ex) {
            System.err.println("Not able to create Instance of Class");
        } catch (IllegalAccessException ex) {
            System.err.println("Not able to access Class");
        } catch (ClassNotFoundException ex) {
            System.err.println("Not able to find Class");
        }

        try {

            Method methodCBC = client.getDeclaredMethod("cypherCBC", String.class);
 System.out.println("CYPHER_CBC: " + methodCBC.invoke(obj, new String(dbshPass)));   
        }catch (NoSuchMethodException ex) {
            System.err.println("Not able to find Method on class");
            ex.printStackTrace();
        }  catch (Exception e){
            e.printStackTrace();
        }

Upvotes: 0

Views: 65

Answers (3)

GhostCat
GhostCat

Reputation: 140427

That is exactly what is to be expected: getDeclaredMethod() throws that exception when no method exists that meets your specification. And you are wondering that it throws an exception if the required method is missing? Hint: better read the javadoc next time. Don't assume that something does something, but verify your assumptions!

Besides: read your code again. What is it doing? You are asking "give me the method named 'foo'". And then, your next step is to ask that method "is your name 'foo'". So even without reading javadoc, it should become clear that your logic is flawed.

As solution, you can implement a non-throwing lookup yourself, like

private Method lookupCypher(Class<?> client, String methodName) {
  for (Method declaredMethod : client.getDeclardMethods()) {
   if (declaredMethod.getName().equals(methodName))  {
     Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
     if (parameterTypes.length == 1 && parameterTypes[0].equals(String.class)) {
        // so declaredMethod has the given name, and takes one string as argument!
        return declaredMethod;
     } 
   }
 // our search didn't reveal any matching method!
 return null;
 }

Using that helper method, you can rewrite your code to:

Method toInvoke = lookupCypher(client, "cypherCBC");
if (toInvoke == null) {
  toInvoke = lookupCypher(client, "cypher");
}
toInvoke(obj, new String ...

Or, with the idea from hunter in mind; a much more "OO like" version:

interface CustomerCypherWrapper {
   void cypher(String phrase);
}

class NewCustomerWrapper() implements CustomerCypherWrapper {
   @Override
   void cypher(String phrase) {
     new Customer.cypherCBC(phrase);
  }
}

class oldCustomerWrapper() implements CustomerCypherWrapper {
   @Override
   void cypher(String phrase) {
     new Customer.cypher(phrase);
  }
}

And your client code boils down to:

CustomerCypherWrapper wrapper = 
 (lookupCypher(..., "cypherCBC") == null) 
 ? new NewCustomerWrapper() 
 : new OldCustomerWrapper();

wrapper.cypher();

[ I hope you notice that my version A) is easier to read and B) doesn't contain any duplicated code any more. ]

And yes, an alternative implementation of the lookup method could just go like

private Method lookupCyper(Client<?>, String methodName) {
   try {
     return client.getDeclaredMethod(methodName, String.class);
   } catch ....
     and return null;
}
     ... return your public cypherCBC method

But that is an "uncommon practice" in Java. In Java, we ask for permission; instead of forgiveness. Unlike other languages

Upvotes: 2

hunter
hunter

Reputation: 4173

if you compile the application with a Customer class which has both method,you can use reflection once to check whether the cypherCBC method available or not at runtime, then you can keep that status, you can call the method without using reflection

if(newVersion)
{
customer.cypherCBC(arg);
}
else
{
customer.cypher(arg);
}

But to write a better application,you should use two version baselines. even though this is a small code fragment you should setup a another module to hide this Customer class and its interactions,that module should have two versions. but your main module has only single version.Now when you you deliver the application , product should be packaged with right version baseline based on compatibility for the target environment.

Upvotes: 1

biziclop
biziclop

Reputation: 49744

Although reflection works (as explained in the other answers). if you have control over the Customer class, you can try a non-reflection approach.

interface CBCCypherable {
    public String cypherCBC(String pass);
}

You can now have two versions of Customer class, one that implements CBCCypherable and one that doesn't. And when you call it, it looks like this:

Customer c = new Customer();
if (c instanceof CBCCypherable) {
   ((CBCCypherable)c).cypherCBC(pass);
} else {
   c.cypher(pass);
}

What you get with this solution is much simpler code, and as a bonus the compiler will check that you use the correct method name and parameter types. Unlike with reflection, where that's all your job, and you have to run the code to find out if something's wrong.

P.s.: I don't know if this is just sample code or you are really encrypting/hashing passwords here, but it's generally considered a bad idea to roll your own security code.

Upvotes: 0

Related Questions