简体   繁体   中英

How can I pass only Exceptions from my Java Library to the implementing application?

According to the Gradle Documentation you can include a dependency as "API" meaning it will be passed on to the application implementing it's class path. Is there a way to pass only an individual class/exception instead of the entire dependency?

Example

In Library A (written by me) I am using Open SAML Core. The Class CoolClass in the library throws Exception B (from Open SAML Core). The build.gradle file includes:

dependencies {
    implementation group: 'org.opensaml', name: 'opensaml-core', version: "24.0.2"

In Java Application C, I am implementing Library A. If I use CoolClass in my application, then gradle build gives me the following error:

/ApplicationC/src/main/java/com/sorry/CoolClass.java:10: error: cannot access ExceptionB
                                           .someMethod();
                                                       ^
  class file for org.opensaml.core.xml.io.ExceptionB not found

Is there a way to pass just that exception rather than including the entire library in the class path of my application?

Not really, but your plan ('include just this one thing') is the wrong solution.

The general principle you've run into is 'transitive dependencies'.

Project A is dependent on project B. However, B in turn depends on C... so does A depend on C now?

transitive dependencies come in two 'flavours': Exported and non-exported.

Project B is a whole bunch of code, but crucially, some of that code is 'visible'. Specifically, the public signatures .

If project B's API (which A can see) includes, say, public List<String> getNames() , that means that the List dependency is now something A also needs to have, otherwise it can't interact with this method at all. Fortunately, List is java.util.List which everybody always haves so that wouldn't be a problem, but imagine it's a type from something not in the java.* space. To make this work, you export . And, it means that anytime you use any type from one of your dependencies in one of your public signatures, you have now forced that dependency to be exported .

That's what you've done here, by declaring throws SomethingSpecificFromOpenSAML , you must now export openSAML.

Solution: Just.. don't throw that

If that's the one and only thing you use in a public signature, the answer seems very obvious. Stop throwing that, your code is just wrong. You should not declare in your throws statement anything that's an implementation detail, and if you have no intention of 'exporting' OpenSAML here, then clearly it's an implementation detail.

Instead, catch it and throw it as something else. Or, keep it as is, but declare to throw Exception:

// Example 1
public void saveGame() throws OpenSamlException {
  doSamlStuffHere();
}

// Example 2
public void saveGame() throws Exception {
  doSamlStuffHere();
}

// Example 3
public class SaveException extends Exception {
  public SaveException(String msg, Throwable cause) {
    super(msg, cause);
  }
}

public void saveGame() throws SaveException {
  try {
    doSamlStuffHere();
  } catch (OpenSamlException e) {
    throw new SaveException("Saving game " + getSaveName() + " failed", e);
  }
}

Example 1 is what you have now and it exports the idea of OpenSaml conceptually by explicitly naming it in the 'visible' part of the method (which is just its signature).

Example 2 fixes that by naming java.lang.Exception . It also takes away the ability to meaningfully interact with the problem as thrown ( catch (Exception e) catches rather a lot), so it's a bit ugly. But it solves the problem.

Example 3 is 'correct': It fully hides SAML as an implementation detail (nothing publicly visible shows anything about SAML at all, just like Example 2), but it does let you interact specifically with the problem, by rethrowing it as an exception that is still specific. I'm using 'save a game' here just as example.

Example 2 and 3 mean you no longer need to export your transitive dependency.

Transitive dependencies do need to be on the classpath

Exported transitive dependencies need to be available as you compile (and write) the code. And, of course, that dep needs to be on the classpath when you run the code.

Non-exported transitive dependencies don't need to exist at all when you compile/write. But they still need to be on the classpath when you run the code, obviously. After all, you need SAML to exist in order to run your saveGame() method. You can't wish that away.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM