简体   繁体   中英

Java: why code packaged into a jar file prevent external classes from accessing?

I have a plugin system for my Java app. I invoke an external class with the URL class loader. That part works well, both when my application is run as class files, and when my application is in JAR form. The problem that I am running into is, the plugin files can run their own independent code just fine, but they create a JPanel. When I try to add that JPanel to the JPanel that's in the main application class, I get a null pointer exception referencing the main class. (com.cpcookieman.app.Main) But that doesn't happen if I run the class files for the application, only when it's packaged. How can I solve that?

Why does my code being packaged into a jar file prevent external classes from accessing classes inside the jar?

EDIT: As requested, the stack trace.

java.lang.NullPointerException
    at TestPlugin2.Main.<init>(Main.java:23)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at java.lang.Class.newInstance0(Unknown Source)
    at java.lang.Class.newInstance(Unknown Source)
    at com.cpcookieman.ph.PluginLoader$2.run(PluginLoader.java:74)
    at java.lang.Thread.run(Unknown Source)

EDIT 2: The class loading code

    String pluginsPath;
    if (Main.os.equals("Windows"))
    {
        pluginsPath = "C:\\plugins\\";
    }
    else
    {
        pluginsPath = "~/plugins/";
    }
    File file = new File(pluginsPath);
    if (!file.exists())
    {
        System.out.println("DEBUG: Plugin path could not be found!");
    }
    try
    {
        URL url = file.toURI().toURL();
        final URL[] urls = new URL[]{url};
        new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                try
                {
                    ClassLoader cl = new URLClassLoader(urls);
                    Class plugin = cl.loadClass(text + ".Main");
                    plugin.newInstance();
                    close();
                }
                **snip catch statements**
            }
        }).start();
    }
    **snip catch statements**

The plugin loads up using it's constructor, and creates a panel to be added to the frame that's inside one of the classes inside the JAR file. The JAR is the main application, if anyone's confused about that.

I really don't know the structure of your project, though I had made a small example code for you to look at, have a look.

Consider that my project is placed at C:\\Mine\\JAVA\\J2SE\\src\\testingjar>

Inside this my directory structure is as below :

                         testingjar
                             |
              ----------------------------------
             |          |           |          |
          classes      src    manifest.text  test.jar(this .jar we be creating shortly)
             |          |
             |       (Almost same as classes folder, just .java files)
      ---------------
      |             |
  actualtest       test
      |             |
   *.class       *.class

My Class that will become a part of a .jar file, is as given below :

package test;

import java.awt.*;
import javax.swing.*;

public class CustomPanel extends JPanel
{
    public CustomPanel()
    {
        setOpaque(true);
        setBackground(Color.DARK_GRAY);

        JLabel label = new JLabel(
            "I am a JLabel from Java Swing", JLabel.CENTER);
        label.setOpaque(false); 
        label.setForeground(Color.WHITE);
        add(label); 
    }

    @Override
    public Dimension getPreferredSize()
    {
        return (new Dimension(500, 300));
    }
}

I compiled this class with the following command :

C:\Mine\JAVA\J2SE\src\testingjar>javac -d classes src\test\CustomPanel.java

Now Creating Manifest File for the JAR File, the contents of which are as follows :

Main-Class: test.CustomPanel

Do remember the space between colons (:) and the package name ie test, and after CustomPanel, Press Enter and save the file.

Now for creating a JAR File named test.jar , I wrote these commands :

C:\Mine\JAVA\J2SE\src\testingjar>cd classes

C:\Mine\JAVA\J2SE\src\testingjar\classes>jar -cfm ..\test.jar ..\manifest.txt test

Now the class that will use this .jar file will be like this :

package actualtest;

import test.CustomPanel;

import java.awt.*;
import javax.swing.*;

public class ActualImplementation
{
    private void createAndDisplayGUI()
    {
        JFrame frame = new JFrame("Testing Jar Implementation");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        CustomPanel panel = new CustomPanel();
        frame.setContentPane(panel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String... args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                new ActualImplementation().createAndDisplayGUI();
            }
        });
    }
}

I compiled this by writing these commands :

C:\Mine\JAVA\J2SE\src\testingjar\classes>cd..

C:\Mine\JAVA\J2SE\src\testingjar>javac -classpath test.jar -d classes src\actualtest\ActualImplement
ation.java

Now to run I wrote these commands :

C:\Mine\JAVA\J2SE\src\testingjar>cd classes

C:\Mine\JAVA\J2SE\src\testingjar\classes>java actualtest.ActualImplementation

OUTPUT

JAR实施

Do match the steps, might be you be missing something, since it's working fine on my side.

LATEST EDIT : As asked, I did that the other way around, now JFrame is inside the .jar file and JPanel is using it.

The class that will become the part of the .jar file is as follows :

package test;

import java.awt.*;
import javax.swing.*;

public class CustomFrame extends JFrame
{
    public CustomFrame(String title)
    {
        super(title);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
}

Contents of the manifest.txt file, will change as follows :

Main-Class: test.CustomFrame

And the class which will use CustomFrame Class from the .jar file, is as follows :

package actualtest;

import test.CustomFrame;

import java.awt.*;
import javax.swing.*;


// http://stackoverflow.com/a/11150286/1057230
public class CustomPanel extends JPanel
{
    private CustomFrame frame;

    public CustomPanel()
    {
        setOpaque(true);
        setBackground(Color.DARK_GRAY);

        JLabel label = new JLabel(
            "I am a JLabel from Java Swing", JLabel.CENTER);
        label.setOpaque(false); 
        label.setForeground(Color.WHITE);
        add(label);                 
    }

    private void createAndDisplayGUI()
    {
        frame = new CustomFrame("Testing Jar Implementation");
        frame.setContentPane(this);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    @Override
    public Dimension getPreferredSize()
    {
        return (new Dimension(500, 300));
    }

    public static void main(String... args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                new CustomPanel().createAndDisplayGUI();
            }
        });
    }
}

The compilation sequence, is very much the same as before, which is as follows :

C:\Mine\JAVA\J2SE\src\testingjar>javac -d classes src\test\CustomFrame.java

C:\Mine\JAVA\J2SE\src\testingjar>cd classes

C:\Mine\JAVA\J2SE\src\testingjar\classes>jar -cfm ..\test.jar ..\manifest.txt test

C:\Mine\JAVA\J2SE\src\testingjar\classes>cd..

C:\Mine\JAVA\J2SE\src\testingjar>javac -classpath test.jar -d classes src\actualtest\CustomPanel.jav
a

C:\Mine\JAVA\J2SE\src\testingjar>cd classes

C:\Mine\JAVA\J2SE\src\testingjar\classes>java actualtest.CustomPanel

Still the same output, I am getting.

LATEST EDIT :

I just found out, that sometimes this thing works, instead of the later, when using JAR Files, you have to specify the classpath including the dot operator as well . , like

C:\Mine\JAVA\J2SE\src\testingjar>java -classpath test.jar;.; actualtest.CustomPanel

This is when I brought the actualtest package inside the testingjar Folder, then the above command worked for this situation.

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