Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
489 views
in Technique[技术] by (71.8m points)

eclipse - 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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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:MineJAVAJ2SEsrcestingjar>

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:MineJAVAJ2SEsrcestingjar>javac -d classes srcestCustomPanel.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 i.e. test, and after CustomPanel, Press Enter and save the file.

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

C:MineJAVAJ2SEsrcestingjar>cd classes

C:MineJAVAJ2SEsrcestingjarclasses>jar -cfm ..est.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:MineJAVAJ2SEsrcestingjarclasses>cd..

C:MineJAVAJ2SEsrcestingjar>javac -classpath test.jar -d classes srcactualtestActualImplement
ation.java

Now to run I wrote these commands :

C:MineJAVAJ2SEsrcestingjar>cd classes

C:MineJAVAJ2SEsrcestingjarclasses>java actualtest.ActualImplementation

OUTPUT

JAR IMPLEMENTATION

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:MineJAVAJ2SEsrcestingjar>javac -d classes srcestCustomFrame.java

C:MineJAVAJ2SEsrcestingjar>cd classes

C:MineJAVAJ2SEsrcestingjarclasses>jar -cfm ..est.jar ..manifest.txt test

C:MineJAVAJ2SEsrcestingjarclasses>cd..

C:MineJAVAJ2SEsrcestingjar>javac -classpath test.jar -d classes srcactualtestCustomPanel.jav
a

C:MineJAVAJ2SEsrcestingjar>cd classes

C:MineJAVAJ2SEsrcestingjarclasses>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:MineJAVAJ2SEsrcestingjar>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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...