【转】Get a load of that name! (很不错的文章!)

本文详细对比了Java中Class.forName()和ClassLoader.loadClass()两种动态加载类的方法的区别,包括它们使用不同的类加载器以及初始化行为的不同。通过实例演示了如何利用这些差异进行灵活的类加载控制,并解释了类初始化错误的处理方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

Get a load of that name!

Subtle differences in various ways you can dynamically load a class

By Vladimir Roubtsov

rule_ltblue.gif
Printer-friendly versionPrinter-friendly version | Send this article to a friendMail this to a friend

 
 

 

March 14, 2003

Q What is the difference between Class.forName() and ClassLoader.loadClass()?

A Both methods try to dynamically locate and load a java.lang.Class object corresponding to a given class name. However, their behavior differs regarding which java.lang.ClassLoader they use for class loading and whether or not the resulting Class object is initialized.

The most common form of Class.forName(), the one that takes a single String parameter, always uses the caller's classloader. This is the classloader that loads the code executing the forName() method. By comparison, ClassLoader.loadClass() is an instance method and requires you to select a particular classloader, which may or may not be the loader that loads that calling code. If picking a specific loader to load the class is important to your design, you should use ClassLoader.loadClass() or the three-parameter version of forName() added in Java 2 Platform, Standard Edition (J2SE): Class.forName(String, boolean, ClassLoader).

Additionally, Class.forName()'s common form initializes the loaded class. The visible effect of this is the execution of the class's static initializers as well as byte code corresponding to initialization expressions of all static fields (this process occurs recursively for all the class's superclasses). This differs from ClassLoader.loadClass() behavior, which delays initialization until the class is used for the first time.

You can take advantage of the above behavioral differences. For example, if you are about to load a class you know has a very costly static initializer, you may choose to go ahead and load it to ensure it is found in the classpath but delay its initialization until the first time you need to make use of a field or method from this particular class.

The three-parameter method Class.forName(String, boolean, ClassLoader) is the most general of them all. You can delay initialization by setting the second parameter to false and pick a given classloader using the third parameter. I recommend always using this method for maximum flexibility.

Class initialization errors are tricky
Just because you successfully load a class does not mean there won't be any more problems. Recollect that static initialization code can throw an exception, and it will get wrapped in an instance of java.lang.ExceptionInInitializerError, at which point, the class becomes unusable. Thus, if it is important to process all such errors at a known point in code, you should use a Class.forName() version that performs initialization.

Furthermore, if you handle ExceptionInInitializerError and take measures so that the initialization can be retried, it will likely not work. This code demonstrates what happens:

public class Main
{
    public static void main (String [] args) throws Exception
    {
        for (int repeat = 0; repeat < 3; ++ repeat)
        {
            try
            {
                // "Real" name for X is outer class name+$+nested class name:
                Class.forName ("Main$X");
            }
            catch (Throwable t)
            {
                System.out.println ("load attempt #" + repeat + ":");
                t.printStackTrace (System.out);
            }
        }
    }

    private static class X
    {
        static
        {
            if (++ s_count == 1)
                throw new RuntimeException ("failing static initializer...");
        }
        
    } // End of nested class

    private static int s_count;

} // End of class

This code attempts to load the nested class X three times. Even though X's static initializer fails only on the first attempt, all of them fail:

>java Main
load attempt #0:
java.lang.ExceptionInInitializerError
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:140)
        at Main.main(Main.java:17)
Caused by: java.lang.RuntimeException: failing static initializer...
        at Main$X.<clinit>(Main.java:40)
        ... 3 more
load attempt #1:
java.lang.NoClassDefFoundError
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:140)
        at Main.main(Main.java:17)
load attempt #2:
java.lang.NoClassDefFoundError
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:140)
        at Main.main(Main.java:17)

It is slightly surprising that the errors on subsequent load attempts are instances of java.lang.NoClassDefFoundError. What happens here is that the JVM has already noted the fact that X has been loaded (before the initialization is attempted), and the class cannot be unloaded until the current classloader is garbage collected. So, on subsequent calls to Class.forName(), the JVM does not attempt initialization again but, rather misleadingly, throws an instance of NoClassDefFoundError.

The proper way to reload such a class is to discard the original classloader instance and create a new one. Of course, this can be done only if you had anticipated that and used the proper three-parameter form of forName().

The hidden Class.forName()
I am sure you have used Java's X.class syntax to obtain a Class object for a class whose name is known at compile time. Less well known is how this is implemented at the byte-code level. The details are different across compilers, but all of them generate code that uses the one-parameter form of Class.forName() behind the scenes. For example, javac from J2SE 1.4.1 translates Class cls = X.class; into the following equivalent form:

    ...
        // This is how "Class cls = X.class" is transformed:
        if (class$Main$X == null)
        {
            class$Main$X = class$ ("Main$X");
        }
        Class cls = class$Main$X;

    ...

    static Class class$ (String s)
    {
        try
        {
            return Class.forName (s);
        }
        catch (ClassNotFoundException e)
        {
            throw new NoClassDefFoundError (e.getMessage());
        }
    }

    static Class class$Main$X; // A synthetic field created by the compiler

Of course, everything mentioned above about Class.forName()'s short form always initializing the class in question applies to X.class syntactic form as well. The details are different when such syntax is used to get Class objects for primitive and array types, and I leave that as an exercise for curious readers.

Fun with Sun's javac
In the previous example, you saw that the result of loading the class was cached in a special package-private static field artificially created by the compiler, and a synthetic helper method executed Class.forName(). The reason this is convoluted may be because the syntax used was unavailable in early Java versions, so the feature was added on top of the Java 1.0 byte-code instruction set.

Armed with this insight, you can have a bit of fun at the compiler's expense. Compile this tongue-in-cheek code snippet using javac from J2SE 1.3.1:

public class Main
{
    public static void main (String [] args) throws Exception
    {
        System.out.println ("String class: " + String.class);
        class$java$lang$String = int.class;
        System.out.println ("String class: " + String.class);
    }
    
    static Class class$java$lang$String;

} // End of class

If you run it, you will get the following, which is ridiculous at best:

>java Main
String class: class java.lang.String
String class: int

At least the compiler in J2SE 1.4.1 will reject the code above. But you can still fool it by setting the field reflectively:

    public static void main (String [] args) throws Exception
    {
        System.out.println ("String class: " + String.class);
        Main.class.getDeclaredField ("class$java$lang$String").set (null, int.class);
        System.out.println ("String class: " + String.class);
    }

So, next time you code the familiar Class.forName() incantation, you should know what it entails and what alternatives exist. jw-dingbat.gif

Printer-friendly versionPrinter-friendly version | Send this article to a friendMail this to a friend

About the author
Vladimir Roubtsov has programmed in a variety of languages for more than 13 years, including Java since 1995. Currently, he develops enterprise software as a senior developer for Trilogy in Austin, Texas.

Resources
Jeff Friesen's "Class and Object Initialization" Java 101 lesson ( JavaWorld, November 2001) studies initialization in some detail:
http://www.javaworld.com/javaworld/jw-11-2001/jw-1102-java101.html Want more? See the Java Q&A index page for the full Q&A catalog:
http://www.javaworld.com/columns/jw-qna-index.shtml For more than 100 insightful Java tips, visit JavaWorld's Java Tips index page:
http://www.javaworld.com/columns/jw-tips-index.shtml Browse the Core Java section of JavaWorld's Topical Index:
http://www.javaworld.com/channel_content/jw-core-index.shtml Get more of your questions answered in our Java Beginner discussion:
http://forums.devworld.com/webx?50@@.ee6b804 Sign up for JavaWorld's free weekly email newsletters:
http://www.javaworld.com/subscribe You'll find a wealth of IT-related articles from our sister publications at IDG.net

转载于:https://www.cnblogs.com/deepnighttwo/archive/2010/07/25/1964320.html

<think>好的,我现在需要解决用户在PowerShell中运行nginx时出现的“命令未找到”问题。首先,我应该回忆一下类似的问题,比如用户之前提到的环境变量问题。例如,引用[1]中提到的,当nginx没有添加到环境变量时,会导致找不到二进制文件。同样,引用[3]中的Angular CLI的ng命令未被识别,也是因为环境变量未配置正确。 用户的问题是在PowerShell中执行nginx命令时出现错误,提示nginx不被识别为cmdlet或可执行文件。这通常意味着系统在环境变量PATH中找不到nginx的可执行文件。因此,我需要引导用户检查nginx的安装路径,并确保该路径被添加到系统的环境变量中。 首先,用户需要确认nginx是否已正确安装。如果安装正确,应该存在nginx.exe文件。常见的安装路径可能是C:\nginx或用户自定义的路径。用户需要找到该路径,例如C:\nginx\nginx.exe。 接下来,用户需要将nginx的安装目录添加到系统的PATH环境变量中。这可以通过PowerShell临时设置,或者永久性地修改系统环境变量。临时方法是在PowerShell会话中使用$env:Path += ";C:\nginx",但这只在当前会话有效。永久性设置需要通过系统属性中的环境变量设置,将C:\nginx添加到PATH中。 此外,用户可能需要以管理员权限运行PowerShell,尤其是在安装或配置系统级服务时。例如,启动nginx服务可能需要管理员权限,否则会失败。 还有可能出现的情况是,用户可能将nginx安装到了其他目录,或者安装过程中出现了错误,导致nginx.exe不存在。这时候需要用户验证安装是否成功,并检查路径是否正确。 另外,引用[4]中的例子显示,配置文件的语法错误也可能导致nginx启动失败,但当前用户的问题更基本,是命令未找到,所以应优先解决环境变量问题。 总结步骤:检查安装路径、添加路径到环境变量、验证安装、管理员权限运行。同时,提醒用户注意路径中的空格或特殊字符,可能需要使用引号包裹路径。如果用户已经完成这些步骤,可能需要进一步排查其他可能性,比如防病毒软件拦截或文件权限问题。</think>在PowerShell中出现`nginx: not recognized as a cmdlet or executable`错误,通常是由于环境变量未正确配置或nginx未正确安装。以下是分步解决方案: ### 1. 验证nginx安装路径 首先确认nginx的可执行文件路径,默认安装路径可能为: - `C:\nginx\nginx.exe` - 自定义路径如`D:\Programs\nginx\nginx.exe` 在PowerShell中手动定位: ```powershell Get-ChildItem -Path C:\ -Filter nginx.exe -Recurse -ErrorAction SilentlyContinue ``` ### 2. 添加环境变量(临时生效) 在**当前会话**中添加路径: ```powershell $env:Path += ";C:\nginx" # 替换为你的实际安装路径 ``` ### 3. 永久配置环境变量 通过系统设置永久生效: 1. 右键点击「此电脑」→「属性」→「高级系统设置」 2. 点击「环境变量」→ 在「系统变量」中选中`Path`→「编辑」 3. 新建条目并添加nginx的安装目录(如`C:\nginx`) ### 4. 验证配置结果 重启PowerShell后执行: ```powershell Get-Command nginx # 应显示nginx.exe路径 ``` ### 5. 以管理员权限运行 若需操作服务(如`nginx -s reload`): ```powershell Start-Process powershell -Verb RunAs -ArgumentList "nginx -s reload" ``` ### 常见问题排查 | 现象 | 解决方案 | |-------|----------| | 路径包含空格 | 使用引号包裹路径:`$env:Path += ";'C:\Program Files\nginx'"` | | 防病毒软件拦截 | 临时关闭安全软件或添加例外规则 | | 文件权限不足 | 右键nginx.exe→属性→安全→赋予Users组完全控制权限 | ### 示例配置验证 ```powershell # 检查环境变量是否生效 echo $env:Path # 尝试启动nginx nginx -v # 应返回版本号如"nginx/1.23.4" ``` 若问题仍未解决,可能需要重新安装nginx并确保安装时勾选「添加至系统PATH」选项[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值