为了账号安全,请及时绑定邮箱和手机立即绑定

java和main方法

标签:
Java

一开始写java,所有demo一定是写一个main方法。后面你发现这个main方法其实和普通方法也没什么区别,可以被其他类的方法调用。而且你可以在多个java文件里写main方法,只不过是需要你指定main类,java也可以根据你指定的类找到main方法执行。
基于上面的疑问,我们去虚拟机中找找答案。

虚拟机的实现

下面是直接从openjdk中获取的代码,你会发现,他的方式竟然是jni的调用。

	mainClass = LoadMainClass(env, mode, what);
    CHECK_EXCEPTION_NULL_LEAVE(mainClass);
    appClass = GetApplicationClass(env);
    NULL_CHECK_RETURN_VALUE(appClass, -1);
    PostJVMInit(env, appClass, vm);
    CHECK_EXCEPTION_LEAVE(1);
    mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                    "([Ljava/lang/String;)V");
    CHECK_EXCEPTION_NULL_LEAVE(mainID);
    mainArgs = CreateApplicationArgs(env, argv, argc);
    CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;

为什么java的main方式必须声明成如下的模式

    public static void main(String[] args){}

jvm中写的比较明了,通过jni,获取一个名字是main,返回值是void,方法参数是String[](这里的部分都是从方法签名中解读([Ljava/lang/String;)V)。
可以理解这是jvm硬编码的结果,如果把openjdk中的代码改成查找main1。再编译出来的jdk,就只会去找class文件中的main1方法了。

硬编码的风险

大家虽然都这么使用,但是这么硬编码也是带来了风险,这里的public static void main(String[] args)相当于一个约定,如果出现了约定不满足使用条件的情况,给迁移就会带来很大的风险,而且还要保证新旧的兼容性,以及特定版本要求以上的硬条件。
在应用层,这个缺点更明显,例如用asm等字节码工具做bci注入,实现的任何细节改变都会导致bci的失败。例如java中的http的注入

 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince;
                try {
                    ifModifiedSince = req.getDateHeader("If-Modified-Since");
                } catch (IllegalArgumentException var9) {
                    ifModifiedSince = -1L;
                }

                if (ifModifiedSince < lastModified / 1000L * 1000L) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

包括自己写servlet的时候都是继承这个类,去覆盖里面的doXX方法。如果有特殊情况改了方法。那么整体的都无法使用了。
我们在使用混淆工具的时候,会特别注意string常量,如果是用在反射等情况下,这个都是混淆失败的,得做特殊处理,我们常用的混淆工具,是可以处理class.forname这种的操作的,但是无法混淆classloader.loadClass。
以上的情况都是硬编码带来的麻烦。

硬编码的好处

硬编码在编程上似乎没什么好处,但是在使用中确实是一个好处。例如做加密,解密方式不可配置,硬编码成一种,这个带来的好处就是以前的版本他都可以不认可。例如做在密钥检测里,生成密钥的方式都变了,解密的方式也一起变,很快就能阻止有人破解的方式。如果不是硬编码的,在破解起来就很方便了。往往大家花时间反编译找出了校验之处,如果可以配置,直接改个配置就可以运行软件了。

小结

  • jvm为什么认可 public static void main(String[] args),主要是jvm中jni硬编码调用导致。只要修改掉这里的jni调用。就等于换了一个规范。
  • 硬编码的坏处就是不能完美衔接改变,如果是定义在协议上,改动就得做到新旧兼容,否则就出现强制升级的情况,影响范围变大。
  • 硬编码的好处就是稳固。使用在校验,编解码等上面可以防止破解和过期的接入。
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
1.6万
获赞与收藏
380

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消