精华内容
下载资源
问答
  • 一个轻量级且快速的运行时注入工具,用于日志记录和跟踪,还可以跟踪OSGi应用程序。 将日志记录注入到正在运行的应用程序中的任何位置。 设置 将具有路径的VM参数添加到跟踪jar -javaagent:dakaraphi.devtools....
  • 最近工作上遇到一个需求: 有一个老系统,由许多模块组成,这些模块之间采用http的接口互相调用 现在需要了解各接口的调用情况,如调用时间、耗时、参数、返回值等 要求对原系统的改动越少越好 思考 面对这个...

    问题

    最近工作上遇到一个需求:

    1. 有一个老系统,由许多模块组成,这些模块之间采用http的接口互相调用
    2. 现在需要了解各接口的调用情况,如调用时间、耗时、参数、返回值等
    3. 要求对原系统的改动越少越好

    思考

    面对这个需求,应该如何解决:

    1. 修改原系统的各模块,在调用接口的地方加代码
      • 优点:简单直接,想怎么加就怎么加
      • 缺点:需要对原系统的每个模块都进行改动,与需求3有很大的冲突
    2. 使用Spring AOP,通过配置的方式动态加代码
      1. 优点:实现比较简单,对原系统的改动也比较少
      2. 缺点:对没有采用Spring技术的模块就没办法了

    有没有其他办法?

    这时,想起了以前曾经了解过一些的java代理技术。这种技术能在运行时修改java代码,对原有的代码几乎不需要任何改造。

    背景知识

    • java代理能通过修改JVM中运行的Java应用的字节码,修改这些应用的行为
    • java代理本身是一个特殊的java类
      • java代理必须实现一个premain方法,作为代理的入口点
      • java代理还可以实现一个agentmain方法,用于代理后启动(晚于应用启动)的场合
      • java代理还必须在包里提供manifest,提供一些元数据(比如代理类的名字、是否可以修改类)
      • 虚拟机通过-javaagent参数指定java代理
    • 开发java代理需要对字节码进行操作,java标准库没有提供相应的API,好在社区开发了一些相关的库,如javassit和ASM

    实操

    了解过java代理的背景知识后,是时候开始实际操作了。这次我们将:

    • 编写一个java代理
    • 使用javassit库来修改HttpClient的代码,具体来说,是org.apache.http.impl.client.InternalHttpClient的doExecute方法,这可通过调查方法调用栈获得
    • 实现在调用http接口时,输出参数、耗时等信息

    确定依赖

    • 项目需要对HttpClient进行修改,因此需依赖HttpClient,而HttpClient是目标模块本身就有的依赖,所以这里将scope设置为provided
    • 项目需使用javassist进行字节码操作,因此需依赖javassist
    <dependency>
        <groupId>org.javassist</groupId>
        <artifactId>javassist</artifactId>
        <version>3.25.0-GA</version>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.6</version>
        <scope>provided</scope>
    </dependency>

    定义java代理类

    首先需要定义java代理类。

    public class Agent {
    
        public static void premain(String args, Instrumentation instrumentation) {
            modifyClass("org.apache.http.impl.client.InternalHttpClient");
        }
    
        private static void modifyClass(String className) {
            try {
                ClassPool classPool = ClassPool.getDefault();
                CtClass ctClass = classPool.get(className);
    
                // 找到目标方法
                CtMethod ctMethod = ctClass.getDeclaredMethod("doExecute");
    
                // 添加本地变量
                ctMethod.addLocalVariable("beginTime", CtClass.longType);
                ctMethod.addLocalVariable("endTime", CtClass.longType);
                ctMethod.addLocalVariable("timeCost", CtClass.longType);                       
                    
                // 在方法最前面,添加代码(初始化beginTime)
                StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.append("beginTime = System.currentTimeMillis();");
                ctMethod.insertBefore(stringBuilder.toString());
    
                // 在方法最后面,添加代码(计算耗时,调用记录方法)
                stringBuilder = new StringBuilder();
                stringBuilder.append("endTime = System.currentTimeMillis();");
                stringBuilder.append("timeCost = endTime - beginTime;");
                stringBuilder.append("cn.alfredzhang.instrument.agent.HttpClientRecorder.record($1, $2, $3, $_, beginTime, timeCost);");
                ctMethod.insertAfter(stringBuilder.toString());
                
                ctClass.toClass();
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    • premain方法:这个方法在上文的背景知识中也提到过,是代理的入口
    • 这个类里通过javassist的api,对类进行了修改

     

    一些知识点:

    • javassist修改代码的方式是用字符串(不用直接操作字节码,已经是比较方便了)
    • 本地变量必须通过addLocalVariable声明
    • insertBefore是在方法体开始的地方添加代码
    • insertAfter是在方法体最后添加代码
    • 可以通过一些特殊变量引用方法的参数和返回值($n代表第n个参数,$_代表返回值)
    • 最后的toClass()非常重要,表示将修改后的CtClass对象编译为java的Class对象,并装载,如果不调用toClass,则前面的修改不会生效

    定义辅助类HttpClientRecorder

    上面的代理类里,最后我们是让InternalHttpClient的doExecute方法调用了一个名叫HttpClientRecorder的类。这个类的任务就是接收各参数,并进行处理,代码如下:

    public class HttpClientRecorder {
        public static void record(HttpHost target, HttpRequest request, HttpContext context, CloseableHttpResponse response, long beginTime, long timeCost) {
            try {
                System.out.println("agent:target=" + target + ",request=" + request + ",context=" + context + ",beginTime=" + beginTime + ",timeCost=" + timeCost);
    
                // 对各参数进行各种解析和记录
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    定义manifest文件

    还记得背景知识里提到的,代理需要提供一个manifest文件吗?

    在resources目录下创建文件META-INF/MANIFEST.MF,内容如下:

    Premain-Class: cn.alfredzhang.instrument.agent.Agent
    Can-Redefine-Classes: true
    Can-Retransform-Classes: true

    打包设置

    我们使用maven-assembly-plugin,生成一个大jar(把依赖也打进去),否则运行时会找不到javassist的类。

    同时别忘了在configuration下配置archive,把manifest文件也打进去。

        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <version>2.6</version>
                    <configuration>
                        <descriptorRefs>
                            <descriptorRef>jar-with-dependencies</descriptorRef>
                        </descriptorRefs>
                        <archive>
                            <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
                        </archive>
                    </configuration>
                    <executions>
                        <execution>
                            <id>assemble-all</id>
                            <phase>package</phase>
                            <goals>
                                <goal>single</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>

    完整的pom文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>cn.alfredzhang.instrument</groupId>
        <artifactId>instrument-agent</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <maven.compiler.source>1.7</maven.compiler.source>
            <maven.compiler.target>1.7</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.javassist</groupId>
                <artifactId>javassist</artifactId>
                <version>3.25.0-GA</version>
            </dependency>
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.5.6</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <version>2.6</version>
                    <configuration>
                        <descriptorRefs>
                            <descriptorRef>jar-with-dependencies</descriptorRef>
                        </descriptorRefs>
                        <archive>
                            <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
                        </archive>
                    </configuration>
                    <executions>
                        <execution>
                            <id>assemble-all</id>
                            <phase>package</phase>
                            <goals>
                                <goal>single</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
    </project>

    编写应用类

    为了检查代理是否能正常运行,编写一个简单的应用类,模拟我们系统中的某个模块。

    public void test() throws IOException {
    
        CloseableHttpClient httpClient = HttpClients.createDefault();
    
        HttpGet httpGet = new HttpGet("http://localhost:10400/hello?x=dog");
    
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
    
            System.out.println("statusLine=" + response.getStatusLine());   
            // 下略
        }
    
    }

     

    测试

    两个项目分别编译打包后,运行:

    java -javaagent:instrument-agent-1.0-SNAPSHOT-jar-with-dependencies.jar -jar app.jar

    其中,-javaagent参数指定代理的路径和jar包名。

    我们得到了类似以下的输出:

    agent:target=http://localhost:10400,request=GET http://localhost:10400/hello?x=dog HTTP/1.1,context=null,beginTime=1561342757914,timeCost=160

    表示HttpClient的类确实已经被修改,并输出了以上信息,成功!

    总结

    当我们需要对已有的系统添加一些功能特性,但又不希望对原有的代码进行修改时,可以:

    • 使用java代理技术,在运行时对原有的代码类进行修改
    • 在此过程中,可以使用javassist提供的API,较方便地创建类、创建方法、在方法体中添加代码、替换方法体
    展开全文
  • 深夜更新 Jenkins 插件的时候,遇到了一个问题:插件下载速度太慢了,并且有大概率失败。 因此我研究了一下 Jenkins 的插件升级机制,研究是否可以使用镜像站点加速。 初步研究 Jenkins 的 升级信息(包含本体和插件...

    遇到问题

    起因

    深夜更新 Jenkins 插件的时候,遇到了一个问题:插件下载速度太慢了,并且有大概率失败。

    因此我研究了一下 Jenkins 的插件升级机制,研究是否可以使用镜像站点加速。

    初步研究

    Jenkins 的 升级信息(包含本体和插件)来自于 http://mirrors.jenkins-ci.org/updates/update-center.json 这个 URL。

    这个 JSON 文件描述了所有可用插件的 版本信息下载地址 ,并且 Jenkins 在 插件中心高级 选项卡里提供了修改这个地址的功能。

    我找到了 jenkins-zh 社区提供的 升级信息 镜像地址 https://updates.jenkins-zh.cn/update-center.json,他们所提供的 JSON 文件内部,将所有 下载地址 替换为等价的 清华 TUNA 镜像站 的地址。

    遭遇困境

    但是当我把镜像地址填入 Jenkins,点击 更新 时,界面突然弹出来巨长一串超出屏幕的报错,提示 签名验证失败

    为什么

    Jenkins 在这个 JSON 文件内做了签名认证。

    也就是说,Jenkins 允许你从另外的 URL 获取到 update-center.json 这个文件,但是 JSON 文件内部描述的 下载地址 等信息是 无法修改 的,否则会破坏签名认证。

    这也就意味着,插件下载的过程仍然无法使用镜像站点加速。

    怎么办

    两个方法:

    1. 重新签名 update-center.json 并替换 Jenkins 内的 CA 文件,让修改后的 update-center.json 变得合法
    2. 修改代码重新编译 Jenkins,关掉这个签名认证 (既然允许自定义 升级信息 地址,又何必非要做 签名认证 呢?)

    方法 1 的问题

    如果有一天我不使用这个镜像站了,我还得想办法恢复 CA 文件。

    并且每次 update-center.json 有变动,我都得重新签名。尽管可以制作或者寻找现有的自动化工具,但是终究需要额外部署一套工具。

    方法 2 的问题

    每次 Jenkins 版本更新都需要重新编译 Jenkins,着实麻烦。

    更好的办法

    我想到了一个更好的办法,使用 Java Agent 机制,在运行时覆盖掉 Jenkins 签名验证方法,让它跳过实际的签名验证,永远返回正确。

    这样即便是 Jenkins 版本更新,只要 升级信息 签名验证代码流程不变,这个方法就可以一直有效。

    实施流程

    什么是 Java Agent

    Java Agent 通过在 Java 命令上添加参数 -javaagent:XXXXXX.jar 来启动,它允许你在主类之外,额外加载一个 Jar 包,并提供机制,允许你在类加载时,修改类的字节码。

    通过 Java Agent 机制,可以在不修改源代码的情况下,在运行时修改 Java 程序。

    定位 Jenkins 代码

    简要查阅了 Jenkins 的源代码,定位到了 Jenkins 中,负责校验升级信息签名的方法来自 hudson.model.UpdateSite 类的 verifySignature 方法

        /**
         * Verifies the signature in the update center data file.
         */
        private FormValidation verifySignature(JSONObject o) throws IOException {
            return getJsonSignatureValidator().verifySignature(o);
        }
    

    参见 https://github.com/jenkinsci/jenkins/blob/6d6c2793e41539c214241cc49df6515ec0395ff4/core/src/main/java/hudson/model/UpdateSite.java#L267

    我们的目标是,将这个方法的代码体,修改成如下内容

    return FormValidation.ok();
    

    这样,修改后的方法,永远会返回校验正常。

    编写 Java Agent

    知道了要修改什么,就可以开始编写 Java Agent 了。

    我们选用 javassist 这个库,这是一个 IBM 推出的库,可以便捷地修改 Java 类字节码。

    创建 Maven 工程

    首先我们创建一个 Maven 工程,和常见的工程类似,但是 Pom 文件的设置有些许区别:

    <!-- 略去了不重要的内容 -->
        <dependencies>
            <!-- 引入 javassist 包 -->
            <dependency>
                <groupId>org.javassist</groupId>
                <artifactId>javassist</artifactId>
                <version>3.27.0-GA</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-jar-plugin</artifactId>
                    <configuration>
                        <!-- Java Agent 的 MANIFEST 里面不是 Main-Class-->
                        <!-- 我们使用 premain 模式,因此要写 Premain-Class -->
                        <archive>
                            <manifestEntries>
                                <Premain-Class>net.landzero.jenkins.tune.Agent</Premain-Class>
                            </manifestEntries>
                        </archive>
                    </configuration>
                </plugin>
                <!-- 使用 shade 插件,把 javassist 直接包含在成品 Jar 包内部 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-shade-plugin</artifactId>
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>shade</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <filters>
                            <!-- 过滤掉 javassist 自己的 META-INF 文件(可能多此一举了) -->
                            <filter>
                                <artifact>org.javassist:*</artifact>
                                <excludes>
                                    <exclude>META-INF/license/**</exclude>
                                    <exclude>META-INF/*</exclude>
                                    <exclude>META-INF/maven/**</exclude>
                                    <exclude>LICENSE</exclude>
                                    <exclude>NOTICE</exclude>
                                    <exclude>/*.txt</exclude>
                                    <exclude>build.properties</exclude>
                                </excludes>
                            </filter>
                        </filters>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    <!-- 略去了不重要的内容 -->
    

    编写 Agent 主类

    package net.landzero.jenkins.tune;
    
    // 此处略却一大堆 import
    
    public class Agent {
    
        // Java Agent 的启动入口不是 main,而是 premain,并且有特定的方法签名
        public static void premain(String agentArgs, Instrumentation inst) {
            // 注册一个类转换器
            inst.addTransformer(new UpdateSiteTransformer());
        }
    
        private static class UpdateSiteTransformer implements ClassFileTransformer {
    
            private static final String CLASS_NAME = "hudson.model.UpdateSite";
    
            private static final String CLASS_NAME_INTERNAL = CLASS_NAME.replace('.', '/');
    
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                if (className == null) {
                    return null;
                }
                // 如果不是 hudson.model.UpdateSite 就忽略掉,不做修改
                // 这个方法传入的 className 是使用 “/” 分割的类名,而不是标准的 "." 分割的类名
                if (!CLASS_NAME_INTERNAL.equals(className)) {
                    return null;
                }
                try {
                    // 加载 javassist 类池
                    ClassPool cp = ClassPool.getDefault();
                    // 因为 Jenkins 是 WAR 包,还需要把 loader 补充进去,不然下面 javassist 解析字节码会报告找不到 import 的类
                    cp.appendClassPath(new LoaderClassPath(loader));
                    // 把字节码载入进去,生成 CtClass
                    CtClass cc = cp.makeClass(new ByteArrayInputStream(classfileBuffer));
                    // 定位到方法 verifySignature
                    CtMethod cm = cc.getDeclaredMethod("verifySignature");
                    // 修改方法代码体
                    cm.setBody("{ return hudson.util.FormValidation.ok(); }");
                    // 返回重新编译的字节码
                    return cc.toBytecode();
                } catch (Exception e) {
                    return null;
                }
            }
    
        }
    }
    

    打包

    直接 mvn clean package 打包,得到 jenkins-tune-1.0-SNAPSHOT.jar 文件,这里面包含了我们写的 Agent 类和依赖项 javassist

    部署 Java Agent

    jenkins-tune-1.0.0-SNAPSHOT.jar 复制到服务器上,假设复制到 /usr/local/lib/jenkins-tune.jar 这个位置。

    编辑 /etc/init.d/jenkins ,寻找 JAVA_CMD=... 那一行,添加一句 -javaagent:/usr/local/lib/jenkins-tune.jar

    (如果是 yum 包安装的 Jenkins,也可以修改 /etc/default/jenkins 中的 JAVA_ARGS 字段)

    (如果使用的是 WAR 包手动部署,则需要修改 Tomcat 的启动脚本)

    systemctl restart jenkins 重新启动 Jenkins 就大功告成了。

    结果

    我重新填入了 jenkins-zh 提供的镜像地址,这次就再也没有提示签名错误了,也可以正常地从 清华 TUNA 镜像站 拉取插件更新,速度自然是飞快。

    代码地址

    https://github.com/guoyk93/jenkins-tune

    如果你不想自己编译 Jar 包,可以在 Release 页面找到预先编译好的 Jar 包

    展开全文
  • Spring Loaded是一个JVM代理,用于在JVM运行时重新加载文件更改。 它在加载转换,以使其适合以后重新加载。 与“热代码替换”不同,后者仅允许在JVM运行后进行简单更改(例如,更改方法主体),而Spring ...
  • Java运行时修改注解参数值

    千次阅读 2020-10-25 17:14:44
    注解是在java代码中增加种元数据,这些元数据可以嵌入在class文件中在编译处理,也可以保留至运行时通过Reflection进行访问。本文讨论如何在运行时修改注解值,我们示例使用级别注解。 1. 注解 Java允许使用...

    Java 在运行时修改注解参数值

    注解是在java代码中增加一种元数据,这些元数据可以嵌入在class文件中在编译时处理,也可以保留至运行时通过Reflection进行访问。本文讨论如何在运行时修改注解值,我们示例使用类级别注解。

    1. 注解

    Java允许使用现有注解创建新的注解。最简单的注释形式是@符号后接注释名:

    @Override
    

    下面创建自定义注解Greeter:

    @Retention(RetentionPolicy.RUNTIME)
    public @interface Greeter {    
        public String greet() default ""; 
    }
    

    现在我们创建类Greeting并增加类级别注解:

    @Greeter(greet="Good morning")
    public class Greetings {}
    

    现在可以使用反射访问注解,Java的Class提供方法getAnnotation获取类的注解:

    Greeter greetings = Greetings.class.getAnnotation(Greeter.class);
    System.out.println("Hello there, " + greetings.greet() + " !!");
    

    2. 修改注解

    Java Class类通过map管理注解:Annotation 类作为key,Annotation 对象作为值:

    Map<Class<? extends Annotation>, Annotation> map;
    

    我们可以在运行时更新map在,在jdk7和jdk8中访问该map有差异。

    2.1 jdk7 实现

    Class类有annotations私有属性,为了访问该属性,需设置其可访问性为true。java提供getDeclaredField 方法通过名称访问属性。

    Field annotations = Class.class.getDeclaredField(ANNOTATIONS);
    annotations.setAccessible(true);
    

    现在可以访问目标类注解map:

    Map<Class<? extends Annotation>, Annotation> map = annotations.get(targetClass);
    

    该map包含所有注解及其值的信息。如果想修改Greeter注解值,可以通过更新注解对象:

    map.put(targetAnnotation, targetValue);
    

    完整代码:

        private static final String ANNOTATIONS = "annotations";
    
        public static void alterAnnotationValueJDK7(
            Class<?> targetClass, Class<? extends Annotation> targetAnnotation, Annotation targetValue) {
            try {
                Field annotations = Class.class.getDeclaredField(ANNOTATIONS);
                annotations.setAccessible(true);
    
                Map<Class<? extends Annotation>, Annotation> map = (Map<Class<? extends Annotation>, Annotation>) annotations.get(targetClass);
                System.out.println(map);
                map.put(targetAnnotation, targetValue);
                System.out.println(map);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    2.2 jdk8实现

    java8 通过AnnotationData类存储注解信息,我们可以通过annotationData方法访问该对象。同样也需要设置该方法的可访问性为true:

    Method method = Class.class.getDeclaredMethod(ANNOTATION_METHOD, null);
    method.setAccessible(true);
    

    现在可以访问annotations字段,同样也需要设置该其访问属性:

    Field annotations = annotationData.getClass().getDeclaredField(ANNOTATIONS);
    annotations.setAccessible(true);
    

    通过annotations获取存储注解类和值的map,通过map可以修改注解值:

    Map<Class<? extends Annotation>, Annotation> map = annotations.get(annotationData); 
    map.put(targetAnnotation, targetValue);
    

    完整代码:

        private static final String ANNOTATION_METHOD = "annotationData";
        private static final String ANNOTATIONS = "annotations";
    
        public static void alterAnnotationValueJDK8(
            Class<?> targetClass, Class<? extends Annotation> targetAnnotation, Annotation targetValue) {
            try {
                Method method = Class.class.getDeclaredMethod(ANNOTATION_METHOD, null);
                method.setAccessible(true);
    
                Object annotationData = method.invoke(targetClass);
    
                Field annotations = annotationData.getClass().getDeclaredField(ANNOTATIONS);
                annotations.setAccessible(true);
    
                Map<Class<? extends Annotation>, Annotation> map = (Map<Class<? extends Annotation>, Annotation>) annotations.get(annotationData);
                map.put(targetAnnotation, targetValue);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    3. 完整测试

    首先定义类DynamicGreeter:

    public class DynamicGreeter implements Greeter {
    
        private String greet;
        public DynamicGreeter(String greet) {
            this.greet = greet;
        }
    
        @Override
        public Class<? extends Annotation> annotationType() {
            return DynamicGreeter.class;
        }
    
        @Override
        public String greet() {
            return greet;
        }
    }
    

    main函数中进行测试:

    public static void main(String ...args) {
        Greeter greetings = Greetings.class.getAnnotation(Greeter.class);
        System.err.println("Hello there, " + greetings.greet() + " !!");
    
        Greeter targetValue = new DynamicGreeter("Good evening");
        alterAnnotationValueJDK8(Greetings.class, Greeter.class, targetValue);
        //alterAnnotationValueJDK7(Greetings.class, Greeter.class, targetValue);
    
        greetings = Greetings.class.getAnnotation(Greeter.class);
        System.err.println("Hello there, " + greetings.greet() + " !!");
    }
    

    初始时注解的值为“Good morning” ,接着现再创建一个值为“Good evening”的Greeter类对象。然后通过上面定义的方法修改注解值:

    alterAnnotationValueJDK8(Greetings.class, Greeter.class, targetValue);
    

    运行结果:

    Hello there, Good morning !!
    Hello there, Good evening !!
    

    成功在运行时修改了注解的值。

    4. 总结

    Java使用两个数据字段来存储注解数据:annotation、declaredAnnotations。两者之间的区别:第一个存储来自父类的注解,之后一个仅存储当前类的注解。由于JDK 7和JDK 8中实现getAnnotation有所不同,为了简单起见在这里使用annotations字段map。

    展开全文
  • java运行环境(jre) Java开发工具包(JDK),Java虚拟机(JVM)和Java运行时环境(JRE)共同形成了强大的Java平台组件三部曲,用于开发和运行Java应用程序。 我之前已经介绍了JDK和JVM。 在本快速教程中,您将学习JRE...

    java运行环境(jre)

    Java开发工具包(JDK),Java虚拟机(JVM)和Java运行时环境(JRE)共同形成了强大的Java平台组件三部曲,用于开发和运行Java应用程序。 我之前已经介绍了JDKJVM。 在本快速教程中,您将学习JRE,它是Java的运行时环境。

    实际上, 运行时环境是一种旨在运行其他软件的软件。 作为Java的运行时环境,JRE包含Java类库,Java类加载器和Java虚拟机。 在此系统中:

    • 类加载器负责正确加载类并将它们与核心Java类库连接。
    • JVM负责确保Java应用程序具有在设备或云环境中运行和良好运行所需的资源。
    • JRE主要是那些其他组件的容器,并负责协调它们的活动。

    在以下各节中,我们将更深入地研究这些组件如何协同工作。

    安装JDK,JRE和JVM

    从安装角度来看,无论何时下载JDK ,它都将包含一个版本兼容的JRE,并且该JRE将包含一个默认的JVM。 您还可以从JDK单独下载JRE,并且可以从各种JVM中进行选择。 默认值在大多数实现中都能很好地工作,特别是在您开始使用Java时。

    什么是运行时环境?

    软件程序需要执行,并且为此需要运行环境。运行时环境将加载类文件,并确保可以访问内存和其他系统资源来运行它们。 过去,大多数软件都使用操作系统(OS)作为其运行时环境。 该程序可以在所用的任何计算机上运行,​​但都依赖于操作系统设置来进行资源访问。 在这种情况下,资源将是诸如内存和程序文件以及相关性之类的东西。 Java Runtime Environment改变了所有这些,至少对于Java程序而言。

    WORA for Java

    最初引入Java时,Java的“编写一次,随处运行”的原则被认为具有革命性,但如今,它已被大多数软件系统采用。

    Java运行时环境

    我们可以将软件视为位于系统硬件之上的一系列层次。 每一层都将提供其上一层将使用(和要求)的服务。 Java运行时环境是在计算机操作系统之上运行的软件层,提供特定于Java的附加服务。

    JRE简化了操作系统的多样性,确保Java程序几乎可以在任何操作系统上运行而无需修改。 它还提供增值服务。 自动内存管理是JRE最重要的服务之一,可确保程序员不必手动控制内存的分配和重新分配。

    简而言之,JRE是一种用于Java程序的元OS。 这是一个抽象的经典示例,将底层操作系统抽象到运行Java应用程序的一致平台中。

    JRE如何与JVM配合使用

    Java虚拟机是运行中的软件系统,负责执行实时Java程序。 JRE是磁盘上的系统,它使用Java代码,将其与必要的库组合在一起,然后启动JVM来执行它。

    JRE包含Java程序需要运行的库和软件。 例如, Java类加载器是Java运行时环境的一部分。 这个重要的软件将编译后的Java代码加载到内存中,并将代码连接到适当的Java类库。

    在我刚刚描述的分层视图中,JVM是由JRE创建的。 从包的角度来看,JRE包含JVM,如图1所示。

    jw whatisjre图1 马修·泰森(Matthew Tyson)

    图1.分层的架构视图显示JRE包含JVM,类加载器和Java类库

    安装和使用JRE

    JRE有一个概念方面,在实际操作中,它只是安装在计算机上的软件,其目的是运行Java程序。 作为开发人员,您将主要使用JDK和JVM,因为它们是用于开发和运行Java程序的平台组件。 作为Java应用程序用户,您将更多地参与JRE,它使您可以运行那些程序。

    在大多数情况下,您的计算机将安装Java,并且JRE将包含在其中。 如果确实需要手动安装或升级,则可以从Oracle 下载当前的JRE版本

    JRE版本

    Java运行时环境针对Java的每个新版本进行了更新,并且其版本号与Java平台版本控制系统保持一致,因此,例如JRE 1.8运行Java8。尽管您可以选择各种JDK软件包(例如Enterprise Edition)或标准版),而JRE并非如此。 大多数计算机都运行针对Java SE开发的JRE,该JRE能够运行任何Java应用程序,而不管其开发方式如何。 大多数移动设备都随附有用于Java ME的JRE,该JRE已预先安装在移动设备上,无法下载。

    安装JRE后,您可以在命令行上输入java -version与之交互,这将告诉您所安装的版本。 在POSIX系统上,您始终可以检查which java的安装位置。

    devops中的JRE

    JRE在开发阶段并不是很引人注目,在J阶段,它仅在您选择的OS或IDE中运行程序。 JRE在开发和系统管理中扮演着更为重要的角色,因为它用于监视和配置。

    基本上,JRE提供了用于配置和控制Java应用程序特征的“旋钮”。 内存使用是一个很好的例子,它是系统管理的基础。 尽管内存使用始终很重要,但它在云配置中至关重要,而devops是基于云的技术。 如果您在devops环境中工作,或者对分支到devops感兴趣,那么最好了解Java内存的工作方式以及如何在JRE中对其进行监视。

    Devops还是sysadmin?

    Devops是一个新术语,但它描述了几十年来一直存在的事实,即开发与运营之间的互操作性。 从这个意义上讲, devops只是过去称为操作或系统管理的较新术语。 像sysadmin一样,devops的重要方面是管理执行软件所必需的系统。 管理JRE是管理运行Java应用程序的系统的一部分。

    Java内存和JRE

    Java内存由三个组件组成:堆,堆栈和元空间(以前称为permgen)。

    • 元空间是Java保持程序不变信息(例如类定义)的地方。
    • 堆空间是Java保留变量内容的地方。
    • 堆栈空间是Java存储函数执行和变量引用的地方。

    Java 8中的内存管理

    在Java 8之前,元空间被称为permgen。 除了名称更酷之外,元空间是开发人员与Java内存空间交互方式的重大变化。 以前,您将使用命令java -XX:MaxPermSize监视permgen空间的大小。 从Java 8开始,Java会自动增加元空间的大小,以适应程序的元需求。 Java 8还引入了一个新标志MaxMetaspaceSize ,该标志可用于限制元空间的大小。

    其他内存选项(堆和栈)在Java 8中保持不变。

    配置堆空间

    堆空间是Java内存系统中最动态的部分。 您可以使用-Xms-Xmx标志来告诉Java启动堆的大小以及允许堆大小的大小。 了解如何针对特定程序需求调整这些标志是Java内存管理的重要方面。 理想的做法是使堆足够大,以实现最有效的垃圾收集。 也就是说,您想要允许足够的内存来让程序运行,但是您不希望它的大小超出必要。

    配置堆栈空间

    堆栈空间是函数调用和变量引用排队的地方。 堆栈空间是Java编程中第二臭名昭著的错误的根源:堆栈溢出异常(第一个是空指针异常)。 堆栈溢出异常表示您已用完堆栈空间,因为已预留了太多空间。 通常,当一个或多个方法以循环方式相互调用时,您会得到堆栈溢出,从而将越来越多的函数调用投入到堆栈中。

    您可以使用-Xss开关来配置堆栈起始大小。 然后,堆栈会根据程序的需要动态增长。

    Java应用程序监控

    尽管应用程序监视是JVM的功能,但是JRE提供了配置选项,这是监视的必要基准。 从经典工具(例如Unix command top )到复杂的远程监视解决方案(例如Oracle的基础架构监视),可以使用多种工具来监视Java应用程序。

    在这些选项之间是可视分析器,例如VisualVM ,可用于检查正在运行的JVM。 这些工具使您能够跟踪热点和内存泄漏,以及查看系统中的整体内存消耗。

    结论

    Java运行时环境是一种磁盘程序,它加载Java应用程序以使JVM执行。 下载Java开发工具包时,默认情况下会包含JRE,并且每个JRE都包含核心Java类库,Java类加载器和Java虚拟机。 了解JVM,JDK和JRE的交互方式非常有帮助,特别是在云和devops环境中工作时。 在这些环境中,与传统的Java应用程序开发相比,JRE在监视和配置中扮演着更重要的角色。

    这个故事“什么是JRE?Java运行时环境简介”最初由JavaWorld发布。

    翻译自: https://www.infoworld.com/article/3304858/what-is-the-jre-introduction-to-the-java-runtime-environment.html

    java运行环境(jre)

    展开全文
  • java运行时内存参数设置

    千次阅读 2018-06-01 16:01:35
    服务器引导程序,用于调度服务器启动接口 * (springApplicationContext = "classpath*:mobanker-*-application.xml",springServletContext = "classpath*:mobanker-*-servlet.xml") */ @NettyBootstrap ( ...
  • Java运行时改变注解值

    千次阅读 2017-07-26 00:17:13
    可能有人会问为什么要在运行是改变注解值?...从上面的字段可以看出,士兵日志表就是一个id字段+veteran对象,定义日志这个bean如下:Veteran{ @ColumnAnnotation(fieldName="id", sequence="ve
  • 很多时候,我们在写代码时候,会出现这个问题: 为什么Eclipse中,打开多个java文件运行一个java文件,运行的却是另一个java文件结果?
  • Java基础知识面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:11:27
    文章目录Java概述何为编程什么是Javajdk1.5之后的三大版本JVM、JRE和JDK的关系什么是跨平台性?原理是什么Java语言有哪些特点什么是字节码?采用字节码的最大好处是什么什么是Java程序的主类?应用程序和小程序的...
  • Java 编译运行时、构建理解

    千次阅读 2018-07-09 18:46:24
    在开发和设计的时候,我们需要考虑编译运行时以及构建这三概念。理解这几概念可以更好地帮助你去了解一些基本的原理。下面是初学者晋级中级水平需要知道的一些问题。 Q.下面的代码片段中,行A和行B所标识...
  • 一个应用试图通过Java的new操作符构造一个抽象或者接口抛出该异常. java.lang.InternalError 内部错误。用于指示Java虚拟机发生了内部错误。 java.lang.LinkageError 链接错误。该错误及其所有子类指示...
  • Java运行时反射修改注解值

    千次阅读 2018-11-12 16:11:30
    Java实现运行时修改注解值 由于java的注解不能传参,而项目中经常需要将运行中动态值传到注解中,因为需要在运行修改注解的值,达到我们想要的目的。再次记录我项目中实现java通过反射实现运行时修改注解值。 ...
  • Eclipse 想运行一个java文件,结果却默认运行了上一次正常运行java文件 有两种情况: #第一种情况: main函数没有加static关键字,加上就能正常运行了 例: 第二种情况:main函数不在与文件名一致的里 例:文件...
  • IntelliJ IDEA 2020.3提供了许多实用的功能,例如调试的交互式提示,Git暂存支持,对Java 15记录和密封的扩展支持等等。它简化了端点,框架和事件探查器的日常工作。通过基于机器学习技术的更好的代码完成,更...
  • java基础:编译运行时的区别

    千次阅读 2018-12-12 16:51:19
    在java开发设计过程中,了解java运行时和编译的区别是非常有必要的。如下从几问题来描述两者的区别 Q1: 如下代码片段中,A行和B行的区别是什么 A行是在编译计算值,B行是在运行计算值,当该编译后,...
  • Java编译和运行时类查找机制

    千次阅读 2015-01-26 18:24:29
    运行JAVA程序,经常碰到...首先,我们从JAVA程序的编译说起,JAVA运行时查找机制同编译,只不过是运行所有的源文件已经被编译成了文件。 对JAVA源代码进行编译时分分成三步骤: 第:形成三表,分别
  • 配置java运行时环境出现could not find java.dll

    万次阅读 多人点赞 2017-11-22 20:53:21
    因为新出了java9,便早早在本机上装了最新版,但是在部署本机项目到服务器出现了java运行时不兼容的情况,遂需要在服务器重装java9,想来其兼容性确实不好。重装过程中出现了该问题,我是按照一般的步骤来配置java...
  • Java字节码介绍及动态修改类

    千次阅读 2018-09-06 18:36:30
    前言 对于Java字节码,它是在Java类的编译过程产生的,即由.java源文件到.class二进制字节码文件的过程。...修改字节码可以通过ASM这个开源框架实现,ASM是一个Java字节码引擎的库,具体可以查看...
  • Java运行时动态生成class的方法

    万次阅读 2016-12-21 18:30:11
    廖雪峰 / 编程 / 4-27 20:45 / 阅读: 2459 Java是一门静态语言,通常,我们需要的class在编译的时候就已经生成了,为什么有时候我们还想在运行时动态生成class呢?
  • JAVA修改运行内存

    千次阅读 2017-07-03 19:38:34
    -server -Xms4g -Xmx4g -Xmn2g -Xss1024k -XX:PermSize=128m -XX:MaxPermSize=512m -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=60 -XX:+UseCMSInitiatingOccupancyOnly -XX:AutoBoxCacheMax=20000
  • java多线程:线程运行时的内存模型

    千次阅读 2018-03-29 16:20:30
    Java虚拟机是一个完整的计算机的一个模型,因此这个模型自然也包含一个内存模型——又称为Java内存模型。 如果你想设计表现良好的并发程序,理解Java内存模型是非常重要的。Java内存模型规定了如何和何时可以看到由...
  • java运行环境

    千次阅读 2019-02-27 16:58:04
    1996年1月,第一个JDK-JDK1.0诞生; 1996年4月,10个最主要的操作系统供应商申明将在其产品中嵌入Java技术; 1996年9月,约8.3万个网页应用了Java技术来制作; 1997年2月18日,JDK1.1发布; 1997年4月2日,JavaOne...
  • 编译异常&运行时异常 A: 编译时期异常:是Exception的子类,非RuntimeExcpetion的子类,在编译时期必须处理 B:RuntimeException和他的所有子类异常,都属于运行时期异常。NullPointerException,...
  • Java Web创建并运行一个简单的网页

    千次阅读 2020-06-25 16:09:55
    @创建并运行一个简单的网页TOC 在使用eclipse创建运行一个web项目前,需安装和配置好Tomcat和jre。 1.新建一个项目 2.选择过滤器文本Dynamic Web Project 3.输入项目名 4.输入项目名后点击下一步 (最好把创建xml...
  • 1.1jdk的安装 1.2第一个java程序——hello world 1.3java运行原理
  • Java程序运行机制及cmd编译运行探究(二) cmd编译运行Java程序并打成jar包中,我编写了一个生成随机数的工具,编译过后,把字节码文件打成了2个jar包,放在d:\jar目录下。randomutil是正常打包,只包含了与...
  • 分享一个大牛的人工智能教程。零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!... 任何序列化该的尝试都会因NotSerializableException而...Java序列化是一个重要概念,但它很少用作持久性解决方案,...
  • Java遍历一个类的所有属性和值

    万次阅读 多人点赞 2017-10-18 16:20:27
    private void bianLi(Object obj){ Field[] fields = obj.getClass().getDeclaredFields(); for(int i = 0 , len = fields.length;... // 对于每属性,获取属性名 String varName = fields[i].getNam
  • java项目如何运行

    千次阅读 2021-06-30 10:06:14
    、项目运行步骤,一般为以下3步骤(必读): (1)、创建数据库,导入数据库脚本 (2)、开发工具导入源码,更改源码内的数据库链接,确保是正确的用户名和密码 (3)、开发工具运行(tomcat或者springboot方式,根据...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,308,742
精华内容 523,496
关键字:

java运行时修改一个类

java 订阅