When sealing a .jar file (the whole .jar, not specific packages), which packages are actually sealed? Is it only the packages that contain .class files, or does it also include parent packages and sub-packages?
To give an example, say I have a .jar containing the single .class file com.company.city.london.class, is it only the com.company.city package that is sealed?
Would the JVM allow me to create and instantiate the class com.company.city.building.house.class outside of the .jar?
Would the JVM allow me to create and instantiate the class com.company.world.class outside of the .jar?
OK, after writing a test application, I have the answers. And they weren't quite what I had expected after reading the documentation.
I have the following two classes packaged into a .jar that has been sealed:
TestClass.java:
package com.company.testjar;
public class TestClass {
}
TestClass2.java:
package com.company.testjar2;
public class TestClass2 {
}
The .jar manifest looks like this:
Manifest-Version: 1.0
Ant-Version: Apache Ant 1.9.3
Created-By: 1.8.0_40-b26 (Oracle Corporation)
Implementation-Vendor: Company
Implementation-Title: Test Jar
Implementation-Version: 6.4.0.0
Sealed: true
According to the documentation, sealing the entire .jar seals ALL packages within the .jar. However, this statement is ambiguous as I found out.
I then wrote some JUnit test cases to see what other classes I could define without causing issues with the sealed .jar.
For my unit tests, I also added the following three test classes. Note that these are NOT defined in the .jar, but do use the same package structure - this is important for the tests.
Bogus.java:
package com.company.testjar;
public class Bogus {
}
SubBogus.java
package com.company.testjar.subpackage;
public class SubBogus {
}
ParentBogus.java:
package com.company;
public class ParentBogus {
}
The JUnit tests:
package com.company.test;
import static org.junit.Assert.*;
import org.junit.Test;
import com.company.ParentBogus;
import com.company.testjar.Bogus;
import com.company.testjar.TestClass;
import com.company.testjar.subpackage.SubBogus;
import com.company.testjar2.TestClass2;
/**
* A set of tests for testing the effects of .jar sealing.
*
* These tests rely on a built .jar named TestJar.jar which is built from the command line.
*
* Only one of these tests can be run at a time because one a package has been loaded, it cannot
* be unloaded again. Because of this, each test must be run separately.
*/
public class TestPackages {
#Test
public void SealedFail1() {
// Instantiate instance of TestClass from sealed .jar.
TestClass t = new TestClass();
// Following should blow up because package com.company.testjar has already
// been loaded by instantiating TestClass above.
try {
new Bogus();
// Should not get here. Throw if we do.
assertFalse(true);
} catch (SecurityException ex) {
// Expected.
}
}
#Test
public void SealedFail2() {
Bogus b = new Bogus();
// Following should blow up because package com.company.testjar has already
// been loaded by instantiating Bogus above.
try {
new TestClass();
// Should not get here. Throw if we do.
assertFalse(true);
} catch (SecurityException ex) {
// Expected.
}
}
/**
* Test to see if instantiating object from package in a sealed .jar will effectively
* load and seal all packages in that .jar.
*/
#Test
public void SealedFail3() {
// Instantiate object from com.company.testjar2 package. This package will now be
// loaded and sealed.
TestClass2 t2 = new TestClass2();
// Instantiate object from com.company.testjar package NOT from sealed .jar.
// This should work because this package has not been sealed yet!
Bogus b = new Bogus();
// This should now throw because the com.company.testjar package has already
// been loaded by instantiating Bogus above, and the TestClass is from that
// same package from the sealed .jar.
try {
new TestClass();
// Should not get here. Throw if we do.
assertFalse(true);
} catch (SecurityException ex) {
// Expected.
}
}
/**
* Test to see if instantiating an object from a sealed .jar prevents us from then
* instantiating an instance of an object from a sub-package NOT defined in the
* same .jar.
*/
#Test
public void SubPackage() {
// Instantiate instance of TestClass from sealed .jar. Loads and seals the
// com.company.testjar package.
TestClass t = new TestClass();
// Now attempt to instantiate an instance of an object from a sub-package of
// com.company.testjar which is not defined in the same .jar.
new SubBogus();
}
/**
* Test to see if instantiating an object from a sealed .jar prevents us from then
* instantiating an instance of an object from a parent package NOT defined in the
* same .jar.
*/
#Test
public void ParentPackage() {
// Instantiate instance of TestClass from sealed .jar. Loads and seals the
// com.company.testjar package.
TestClass t = new TestClass();
// Now attempt to instantiate an instance of an object from the parent-package of
// com.company.testjar which is not defined in the same .jar.
new ParentBogus();
}
}
The individual tests should be run independently because once a package has been loaded, I'm not unloading it again and would affect the result of the tests. If you run all the tests in once go, there will be failures because packages are loaded by the first test and stay loaded.
All of the tests pass when run individually.
From these tests, we can determine the following:
Sealing an entire .jar does not seal the empty parent packages. So the following empty packages are not sealed : 'com' and 'com.company'. Only packages that contain classes are sealed. See test ParentPackage().
If you load a package from a .jar by instantiating a class from it, and then attempt load a class from the same package external to the .jar, this will fail. See test SealedFail1().
If you load a package externally from the .jar by instantiating a class that shares the same package name as a .class within the .jar, attempting to then instantiate a class from the sealed .jar from that same package will fail. See test SealedFail2().
Instantiating an object from a sealed .jar only loads (and seals) the package that that specific class is located in. No other packages from the same .jar are loaded at the same time. See test SealedFail3().
You can successfully instantiate objects defined in a sub-package of an already sealed and loaded package from a .jar without issue. See test SubPackage().
Related
Edited to restart question from scratch due to complaints. I am a newbie to this format and to intellij so please excuse...
I am building a project in intellij for class. This project imports jnetcap and uses it to process a captured pcap file. My issue is I have two class files I am trying to integrate. NetTraffic which is the user interface class, and ProcessPacket that actually reads in the packet and does the work.
I have tried to make a project and import ProcessPacket into NetPacket but have been unsuccessful so far. I am sure I am missing something simple in this process but I just can not find anything showing the proper way to do this.
I have gotten it working by making a package under the src directory and adding both files to that package. This doesn't require an import from the NetPacket class and seems to work but my worry is that I need to be able to run this from a linux command line. I have been working all semester so far with everything in one source file so it hasn't been an issue until now. I don't remember using packages in the past under eclipse to do this.
Can someone offer a step by step process on how to properly add these source files to my project so that I am able to import ProcessPacket into NetTraffic or will leaving like this in a package work fine?
The files in question reside in package named nettraffic in src directory.
NetTraffic.java
package nettraffic;
public class NetTraffic {
public static ProcessPacket pp;
public static void main (String args[]) {
pp = new ProcessPacket();
pp.PrintOut();
}
}
ProcessPacket.java
package nettraffic;
import org.jnetpcap.*;
public class ProcessPacket {
public ProcessPacket() {
}
public void PrintOut() {
System.out.println("Test");
}
}
Note there is no real functionality in these at this time. Just trying to get the class import syntax correct before continuing. Again while this seems to work as a package I want to have it done without using a package and importing ProcessPacket.java into NetTraffic.java.
public class NetTraffic {
ProcessPacket pp = new ProcessPacket();
pp.PrintOut();
}
You're calling the PrintOut() method outside of any constructor or method or similar block (static or non-static initializer blocks...), and this isn't legal. Put it in a constructor or method.
public class NetTraffic {
public NetTraffic() {
ProcessPacket pp = new ProcessPacket();
pp.PrintOut();
}
}
I can't get the Service Provider Interface to load an implementation from another JAR in the same directory. It only works when I use -Djava.ext.dirs=. on the command line. Should it not work without?
I have the following interface:
package playground;
public interface TestIface {
public String test();
}
which is implemented here:
package playground;
public class TestImpl implements TestIface {
public String test() {
return "TEST";
}
}
Here I try to load the implementation:
package playground;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Lalala {
public static void main(String[] args) {
ServiceLoader<TestIface> loader = ServiceLoader.load(TestIface.class);
Iterator<TestIface> it = loader.iterator();
while (it.hasNext()) {
TestIface a = it.next();
System.out.println(a.test());
}
System.out.println("DONE");
}
}
The interface and the last class are packaged in main.jar, the implementation in impl.jar.
main.jar has the Main class set and impl.jar has the META-INF/services/playground.TestIface file which contains "playground.TestImpl". Both JARs are in the same directory.
Running
java -jar main.jar
only prints "DONE", the implementation apparently is not found.
If I instead run
java -Djava.ext.dirs=. -jar main.jar
it also prints "TEST" as it should.
What am I doing wrong? Why is the implementation from the other JAR not loaded unless I change the java.ext.dirs setting?
The java.ext.dirs setting automatically adds all jar-files found in the specified directory to the main classloader, which is why the ServiceLoader can find and load TestIface.class (from the Apidocs: "Creates a new service loader for the given service type, using the current thread's context class loader.").
But you should not use java.ext.dirs for this (see here for one of the dangers). And when you use java -jar you cannot use java -cp to set a classpath (you can only use one of them). This leaves you the option to use the URLClassLoader to load additional jars and then call ServiceLoader.load(class from another jar).
Tip: to load other jar-files, use the location of the main.jar file as explained in the answers of this question. Other variables (like startup directory) depend on how and from where Java was started and can easily result in not finding the other jar-files.
I'm using the org.reflections library to detect some classes that are annotated in my project. When I run the project from eclipse, the annotated classes are found.
However, when the project is packaged into a JAR file and run, no annotated classes are found.
Here is some example code:
#TestAnnotation
public class App {
public static void main (String[] args) {
Reflections reflections = new Reflections("com.package");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(TestAnnotation.class);
for (Class c : annotated) {
System.out.println ("found annotated class " + c.getName());
}
}
}
When I run this code in a JAR file there is no output. What am I doing wrong?
Thanks
Pay attention to
Reflections reflections = new Reflections("com.package");
There you try to explore annotated classes located in package package which is nested in com package. But package is Java keyword. So it can not be used as name of package. After I moved App and TestAnnotation just into com package everything works fine.
But it is possible that your case is really bad. May be some tool or some person created package that has package name (for example, used Cyrillic а instead of Latin a, both are looking same in editor, but they are totally different for Java). When letters are mixed everything looks fine but doesn't work. So try to create new valid root package and move your classes into it.
I have two classes listed as follows
package foo;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
public class CustomClassLoader extends ClassLoader {
public CustomClassLoader(ClassLoader parent){
super(parent);
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
System.out.println( " >>> loading " + name );
if (name.startsWith("foo")) {
return getClass(name);
}
return super.loadClass(name);
}
public Class getClass(String name){
try {
byte[] data= getClassByteData(name);
return this.defineClass(name, data, 0, data.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public byte[] getClassByteData(String name) throws IOException {
String file = name.replace(".", File.separator)+".class";
InputStream in = this.getClass().getResourceAsStream("/"+file);
int length = in.available();
byte[] datas = new byte[length];
in.read(datas, 0, length);
return datas;
}
}
package foo;
public class Test {
public static void main(String[] args) {
System.out.println(" Who is my loader? >>" + Test.class.getClassLoader());
}
}
Run: java -Djava.system.class.loader=foo.CustomClassLoader foo.Test
Output:
>>> loading java.lang.System
>>> loading java.nio.charset.Charset
>>> loading java.lang.String
>>> loading foo.Test
>>> loading java.lang.Object
>>> loading java.lang.StringBuilder
>>> loading java.lang.Class
>>> loading java.io.PrintStream
Who is my loader? >>foo.CustomClassLoader#de6ced
My questions are below:
Why those java.lang.System, java.nio.charset.Charset etc as above would be loaded by CustomClassLoader? In my ideas I think when I run java -Djava.system.class.loader foo.Test, JVM first searches out class foo.Test, loads it, executes the main method, and then when it detects System.out.println(), it will continue to load Class java.lang.System and java.io.PrintWriter because those classes are used by it, right?
When I run a class which uses some classes located in java.lang package, those classes will also be loaded again, in my case delegated CustomClassLoader>>ExtClassLoader>>BoostrapClassLoader to load?
When /lib/rt.jar and /lib/ext/**.jar are loaded, before we run a class like java foo.Test all those classes have bean loaded already?
Thanks all your help in advance!
To answer your questions, let's take them in turn.
Java's ClassLoading mechanism is based on a delegation model where an attempt to load a class is normally delegated to parent class loaders first. If the parent class loader is able to successfully load the class, then the loaded class is passed back down the delegate chain and return by whichever class loader was originally invoked. Since you set your custom class loader as the system class loader, the JVM instantiated it as the default loader used when your application starts. It is then used to load all classes that it needs. Most of those classes (i.e. anything that doesn't start with foo.*) are delegated to the parent class loaders, which explains why you see messages about your class loader attempting to load them.
Because the loading of non foo.* classes is delegated to the parent class loader, loading a system class, such as System.out, again will be handled by the parent class loader, which handles caching all loaded classes and will return the previously loaded instance.
Loading of the rt.jar and lib/ext/*.jar files is handled by the parent class loader (or its parent), not you. Therefore, there is no need for your class loader to concern itself with those JAR files.
Generally, a custom class loader is built to insure that classes can be loaded from a non-standard location, such as JARs stored in a database.
I assume that actual command line you running is java -Djava.system.class.loader=foo.CustomClassLoader foo.Test.
In general all classes can be loaded multiple times by different class loaders. In fact they are even considered different classes. So then foo.Test needs a java.lang.System, its class loader is invoked to find or load it.
In your case, CustomClassLoader delegates non foo class loading to super, which will not load same class second time, but return previously loaded one.
Talking about loading jars in misleading. Classes from those jars are loaded individually, on-demand. To load any program JVM needs to create a thread, so Thread class and its dependencies are loaded even before your class.
you can run sun's java with -verbose:class to see how classes are loaded.
When preparing for the SCJP exam, we were going through the following code:
package certificaton;
public class OtherClass
{
public void testIt()
{
System.out.println("otherclass");
}
}
And this:
package somethingElse;
import certification.OtherClass;
public class AccessClass
{
public static void main( String args[])
{
OtherClass o= new OtherClass();
o.testIt();
}
}
I placed both the above files in the following directory: C:\scjp\temp8 ; and the strange thing is that, the .java files are compiling and results in two .class files being created in the same directory. The thing I want to ask, is that, the difference between packages and directory. Isn't it true that the class files could be created in a directory other than the one stated in the package declaration? And the package declaration is something 'virtual', and disregards the windows directory structure. In addition, isn't it also true that, by executing the following command:
javac -d . OtherClass.java
The directories are created conforming to the package declaration, which isn't always mandatory?
The directories are created conforming
to the package declaration, which
isn't always mandatory?
No, the package and directory structures must match. It's mandatory, not optional.