Java注解编译期处理AbstractProcessor详解

news2024/11/15 6:52:09

文章目录

    • 概述
    • 注解处理器
      • 注解处理流程
      • AbstractProcessor
        • getSupportedOptions()
        • getSupportedAnnotationTypes
        • getSupportedSourceVersion
        • init初始化
        • process 处理方法
    • 如何注册注解处理器
    • 如何调试编译期代码
    • Maven相关配置(指定生效的Processor)
    • 注意事项
    • 自定义注解处理器范例
      • 范例一: 自动生成Build构造器
      • 范例二:
    • 如何给编译期设置入参

概述

我们接触的注解主要分为以下两类

  1. 运行时注解:通过反射在运行时动态处理注解的逻辑

  2. 编译时注解:通过注解处理器在编译期动态处理相关逻辑

平时我们接触的框架大部分都是运行时注解,比如:@Autowire @Resoure @Bean 等等。

那么我们平时有接触过哪些编译期注解呢,@Lombok @AutoService 等等

像这些编译时注解的作用都是自动生成代码,一是为了提高编码的效率,二是避免在运行期大量使用反射,通过在编译期利用反射生成辅助类和方法以供运行时使用。

那这些编译器注解都是如何运行的呢? 又是怎么自动生成代码的呢?

我们今天来详细介绍一下,不过再介绍之前,可以先简单了解一下Java注解的基本概念
Java注解

注解处理器

注解处理流程

注解编译期处理流程最关键的一个类就是Processor ,它是注解处理器的接口类,我们所有需要对编译期处理注解的逻辑都需要实现这个Processor接口,当然,AbstractProcessor 抽象类帮我们写好了大部分都流程,所以我们只需要实现这个抽象类就可以很方便的定义一个注解处理器

注解处理流程由多轮完成。每一轮都从编译器在源文件中搜索注解并选择适合这些注解的 注解处理器(AbstractProcessor) 开始。每个注解处理器依次在相应的源上被调用。

如果在此过程中生成了任何文件,则将以生成的文件作为输入开始另一轮。这个过程一直持续到处理阶段没有新文件生成为止。

注解处理器的处理步骤:

  • 在java编译器中构建;
  • 编译器开始执行未执行过的注解处理器;
  • 循环处理注解元素(Element),找到被该注解所修饰的类,方法,或者属性;
  • 生成对应的类,并写入文件;
  • 判断是否所有的注解处理器都已执行完毕,如果没有,继续下一个注解处理器的执行(回到步骤1)。

图TODO

AbstractProcessor

这是注解处理器的核心抽象类,我们主要来看看里面的方法

getSupportedOptions()

默认的实现是 从注解SupportedOptions获取值,该值是一个字符数组,例如


@SupportedOptions({"name","age"})
public class SzzTestProcessor extends AbstractProcessor {
}

不过貌似该接口并没有什么用处。

有资料表示 该可选参数可以从processingEnv获取到参数。

String resultPath = processingEnv.getOptions().get(参数);

实际上这个获取的参数是编译期通过入参 -Akey=name 设置的,跟getSupportedOptions没有什么关系。

getSupportedAnnotationTypes

获取当前的注解处理类能够处理哪些注解类型,默认实现是从SupportedAnnotationTypes注解里面获取;
注解值是个字符串数组 String [] ;
匹配上的注解,会通过当前的注解处理类的 process方法传入。

例如下面使用 * 通配符支持所有的注解

@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class PrintingProcessor extends AbstractProcessor {

}

又或者可以直接重写这个接口

  @Override
  public ImmutableSet<String> getSupportedAnnotationTypes() {
    return ImmutableSet.of(AutoService.class.getName());
  }

最终他们生效的地方就是用来做过滤,因为处理的时候会获取到所有的注解,然后根据这个配置来获取自己能够处理的注解。

getSupportedSourceVersion

获取该注解处理器最大能够支持多大的版本,默认是从注解 SupportedSourceVersion中读取,或者自己重写方法,如果都没有的话 默认值是 RELEASE_6

@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class PrintingProcessor extends AbstractProcessor {

}

或者重写(推荐 , 获取最新的版本)

  @Override
  public SourceVersion getSupportedSourceVersion() {
    //设置为能够支持最新版本
    return SourceVersion.latestSupported();
  }

init初始化

init是初始化方法,这个方法传入了ProcessingEnvironment 对象。一般我们不需要去重写它,直接使用抽象类就行了。
当然你也可以根据自己的需求来重新

    @Override
    public synchronized void init(ProcessingEnvironment pe) {
        super.init(pe);
        System.out.println("SzzTestProcessor.init.....");
        // 可以获取到编译器参数(下面两个是一样的)
        System.out.println(processingEnv.getOptions());
        System.out.println(pe.getOptions());

    }

可以获取到很多信息,例如获取编译器自定义参数, 自定义参数的设置请看下面的 如何给编译期设置入参 部分

一些参数说明

方法描述
Elements getElementUtils()返回实现Elements接口的对象,用于操作元素的工具类。
Filer getFiler()返回实现Filer接口的对象,用于创建文件、类和辅助文件。
Messager getMessager()返回实现Messager接口的对象,用于报告错误信息、警告提醒。
Map<String,String> getOptions()返回指定的参数选项。
Types getTypeUtils()返回实现Types接口的对象,用于操作类型的工具类。

process 处理方法

process方法提供了两个参数,第一个是我们请求处理注解类型的集合(也就是我们通过重写getSupportedAnnotationTypes方法所指定的注解类型),第二个是有关当前和上一次循环的信息的环境。

返回值表示这些注解是否由此 Processor 声明
如果返回 true,则这些注解不会被后续 Processor 处理;
如果返回 false,则这些注解可以被后续的 Processor 处理。


    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        System.out.println("SzzTestProcessor.process.....;");

        return false;
    }
    

我们可以通过RoundEnvironment接口获取注解元素,注意annotations只是注解类型,并不知道哪些实例被注解标记了,RoundEnvironment是可以知道哪些被注解标记了的。

方法描述
Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a)返回被指定注解类型注解的元素集合。
Set<? extends Element> getElementsAnnotatedWith(TypeElement a)返回被指定注解类型注解的元素集合。
processingOver()如果循环处理完成返回true,否则返回false。

关于这部分的使用介绍,请看下面的自定义注解处理器范例

如何注册注解处理器

上面介绍了注解处理器的一些核心方法,那么我们如何注册注解处理器呢

并不是说我们实现了AbstractProcessor类就会生效,由于注解处理器(AbstractProcessor) 是在编译期执行的,而且它是作为一个Jar包的形式来生效,所以我们需要将注解处理器作为一个单独的Module来打包。
然后在需要使用到注解处理器的Module引用。

这个注解处理器 所在Module打包的时候需要注意:

因为AbstractProcessor本质上是通过ServiceLoader来加载的(SPI), 所以想要被成功注册上。则有两种方式

一、配置SPI

  1. resource/META-INF.services文件夹下创建一个名为javax.annotation.processing.Processor的文件;里面的内容就是你的注解处理器的全限定类名
    在这里插入图片描述

  2. 设置编译期间禁止处理 Process,之所以这样做是因为,如果你不禁止Process,ServiceLoader就会去加载你刚刚设置的注解处理器,但是因为是在编译期,Class文件被没有被成功加载,所以会抛出下面的异常

    
    服务配置文件不正确, 或构造处理程序对象javax.annotation.processing.Processor: Provider org.example.SzzTestProcessor not found时抛出异常错误
    
    
    

    如果是用Maven编译的话,请加上如下配置 <compilerArgument>-proc:none</compilerArgument>

              <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.5.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                    <executions>
                        <execution>
                            <id>default-compile</id>
                            <configuration>
                                <compilerArgument>-proc:none</compilerArgument>
                            </configuration>
                        </execution>
                        <execution>
                            <id>compile-project</id>
                            <phase>compile</phase>
                            <goals>
                                <goal>compile</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
    
  3. 注解处理器打包成功,就可以提供给别的Module使用了

二、使用@AutoService 自动配置SPI的配置文件

@AutoService 是Google开源的一个小插件,它可以自动的帮我们生成META-INF/services 的文件,也就不需要你去手动的创建配置文件了。

当然,上面的 <compilerArgument>-proc:none</compilerArgument>参数也不需要了。

所以也就不会有编译期上述的问题xxx not found 问题了。因为编译的时候META-INF/services 还没有配置你的注解处理器,也就不会抛出加载异常了。

例如下面,使用@AutoService(Processor.class),他会自动帮我们生成对应的配置文件。

@AutoService(Processor.class)
public class SzzBuildProcessor extends AbstractProcessor {

}

在这里插入图片描述

另外,实际上 @AutoService 自动生成配置文件也是通过AbstractProcessor来实现的。

具体的使用方式请看 :@AutoService 详解

如何调试编译期代码

在我们自己写了注解处理器之后,可能想要调试,那么编译期的调试跟运行期的调试不一样。

请看:如何在IDEA中调试编译期源码

Maven相关配置(指定生效的Processor)

如果你使用的是Maven来编译,那么有一些参数可以设置

比如指定注解处理器生效 、代码生成的源路径。默认是 target/generated-sources/annotations

除非特殊情况,一般不需要设置这些参数。

<build>
    <plugins>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
                <!-- 主动设置生成的源码的文件夹路径,默认的就是下面的地址。一般不需要主动设置除非你有自己的需求  -->
                <generatedSourcesDirectory>${project.build.directory} /generated-sources/</generatedSourcesDirectory>
                 <!-- 指定生效的注解处理器,这里设置之后,只会有下面配置的注解处理器生效; 一般情况也不用主动配置,可以将下面的全部删除 -->
                <annotationProcessors>
                    <annotationProcessor>
                            org.example.SzzTestProcessor
                    </annotationProcessor>
                </annotationProcessors>
            </configuration>
        </plugin>

    </plugins>
</build>

注意事项

注解注解处理器是单独的module:注解处理器只需要在编译的时候使用,注解的Module只需要引入注解处理器的Jar包就行了。因此我们需要将注解处理器分离为单独的module。

并且打包的时候请先打包注解处理器的Module.

自定义Processor类最终是通过打包成jar,在编译过程中调用的。

自定义注解处理器范例

范例一: 自动生成Build构造器

1. 需求描述

假设我们的注释用户模块中有一些简单的 POJO 类,其中包含几个字段:


public class Company {

    private String name;

    private String email ;
    
}

public class Personal {

    private String name;

    private String age;
}

我们想创建对应的构建器帮助类来更流畅地实例化POJO类

        Company company = new CompanyBuilder()
                .setName("ali").build();
        Personal personal = new PersonalBuilder()
                .setName("szz").build();

2. 需求分析

如果没有POJO都要手动的去创建对应的Build构建器,未免太繁杂了,我们可以通过注解的形式,去自动的帮我们的POJO类生成对应的Build构建器,但是当然不是每个都生成,按需生成;

  1. 定义一个 @BuildProperty 注解,在需要生成对应的setXX方法的方法上标记注解

  2. 自定义 注解处理器扫描@BuildProperty注解,按照需求自动生成Build构建器。例如CompanyBuilder

    	public class CompanyBuilder {
    
        private Company object = new Company();
    
        public Company build() {
            return object;
        }
    
        public CompanyBuilder setName(java.lang.String value) {
            object.setName(value);
            return this;
        }
    
    }
    

3. 编码

创建一个注解处理器Module:szz-test-processor-handler

@BuildProperty

@Target(ElementType.METHOD) // 注解用在方法上
@Retention(RetentionPolicy.SOURCE) // 尽在Source处理期间可用,运行期不可用
public @interface BuildProperty {
}

注解处理器

@SupportedAnnotationTypes("org.example.BuildProperty") // 只处理这个注解;
public class SzzBuildProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        System.out.println("SzzBuildProcessor.process ;");

        for (TypeElement annotation : annotations) {
            // 获取所有被该注解 标记过的实例
            Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotation);

            // 按照需求 检查注解使用的是否正确 以set开头,并且参数只有一个
            Map<Boolean, List<Element>> annotatedMethods = annotatedElements.stream().collect(
                    Collectors.partitioningBy(element ->
                            ((ExecutableType) element.asType()).getParameterTypes().size() == 1
                                    && element.getSimpleName().toString().startsWith("set")));

            List<Element> setters = annotatedMethods.get(true);
            List<Element> otherMethods = annotatedMethods.get(false);

            // 打印注解使用错误的case
            otherMethods.forEach(element ->
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                            "@BuilderProperty 注解必须放到方法上并且是set开头的单参数方法", element));

            if (setters.isEmpty()) {
                continue;
            }


            Map<String ,List<Element>> groupMap = new HashMap();

            // 按照全限定类名分组。一个类创建一个Build
            setters.forEach(setter ->{
                // 全限定类名
                String className = ((TypeElement) setter
                        .getEnclosingElement()).getQualifiedName().toString();
                List<Element> elements = groupMap.get(className);
                if(elements != null){
                    elements.add(setter);
                }else {
                    List<Element> newElements = new ArrayList<>();
                    newElements.add(setter);
                    groupMap.put(className,newElements);
                }
            });

            
            groupMap.forEach((groupSetterKey,groupSettervalue)->{
                //获取 类名SimpleName 和 set方法的入参
                Map<String, String> setterMap = groupSettervalue.stream().collect(Collectors.toMap(
                        setter -> setter.getSimpleName().toString(),
                        setter -> ((ExecutableType) setter.asType())
                                .getParameterTypes().get(0).toString()
                ));
                try {
                    // 组装XXXBuild类。并创建对应的类文件
                    writeBuilderFile(groupSetterKey,setterMap);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

            });
        }

        // 返回false 表示 当前处理器处理了之后 其他的处理器也可以接着处理,返回true表示,我处理完了之后其他处理器不再处理
        return true;
    }

    private void writeBuilderFile(
            String className, Map<String, String> setterMap)
            throws IOException {

        String packageName = null;
        int lastDot = className.lastIndexOf('.');
        if (lastDot > 0) {
            packageName = className.substring(0, lastDot);
        }

        String simpleClassName = className.substring(lastDot + 1);
        String builderClassName = className + "Builder";
        String builderSimpleClassName = builderClassName
                .substring(lastDot + 1);

        JavaFileObject builderFile = processingEnv.getFiler()
                .createSourceFile(builderClassName);

        try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {

            if (packageName != null) {
                out.print("package ");
                out.print(packageName);
                out.println(";");
                out.println();
            }

            out.print("public class ");
            out.print(builderSimpleClassName);
            out.println(" {");
            out.println();

            out.print("    private ");
            out.print(simpleClassName);
            out.print(" object = new ");
            out.print(simpleClassName);
            out.println("();");
            out.println();

            out.print("    public ");
            out.print(simpleClassName);
            out.println(" build() {");
            out.println("        return object;");
            out.println("    }");
            out.println();

            setterMap.entrySet().forEach(setter -> {
                String methodName = setter.getKey();
                String argumentType = setter.getValue();

                out.print("    public ");
                out.print(builderSimpleClassName);
                out.print(" ");
                out.print(methodName);

                out.print("(");

                out.print(argumentType);
                out.println(" value) {");
                out.print("        object.");
                out.print(methodName);
                out.println("(value);");
                out.println("        return this;");
                out.println("    }");
                out.println();
            });

            out.println("}");
        }
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        System.out.println("----------");

        System.out.println(processingEnv.getOptions());

    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }


}

4. 注册注解处理器

在这里插入图片描述

5. 配置编译参数

因为这里选择的是手动配置了 META-INF.services; 所以我们需要配置一下编译期间忽略Processor;
主要参数就是

<compilerArgument>-proc:none</compilerArgument>

如下所示

<build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.5.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                    <executions>
                        <execution>
                            <id>default-compile</id>
                            <configuration>
                                <compilerArgument>-proc:none</compilerArgument>
                            </configuration>
                        </execution>
                        <execution>
                            <id>compile-project</id>
                            <phase>compile</phase>
                            <goals>
                                <goal>compile</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>

             </plugins>

        </pluginManagement>

    </build>


6. 执行编译打包

mvn install一下, 其他Module就可以引用了。

7. Demo Module 依赖注解处理器

创建一个新的Module: szz-test-demo ; 让它依赖上面的 szz-test-processor-handler

并在Company的一些方法上使用注解。
在这里插入图片描述

8. Demo Module 进行编译,会自动生成BuildCompany类

Demo Module 编译之后,就会在target文件夹生成BuildXXX类。 并且只有我们用注解BuildProperty标记了的方法才会生成对应的方法。
而且如果注解BuildProperty使用的方式不对,我们也会打印出来了异常。
在这里插入图片描述

范例二:

待补充。

如何给编译期设置入参

在init初始化的接口中,我们可以获取到编译器的一些自定义参数;

    String verify = processingEnv.getOptions().get("自定义key");

注意这个获取到的编译器参数只能获取的是以-A开头的参数,因为是过滤之后的

在这里插入图片描述

那么这个自定义参数从哪里设置的呢?

如果你是IDEA 编译

-Akey=value 或者 -Akey

在这里插入图片描述

如果是用Maven编译

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/431872.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

多线程小知识

多线程 多线程环境使用哈希表HashMapHashtableConcurrentHashMap 死锁死锁是什么两者之间的死锁多人之间的死锁 死锁产生的四个必要条件:互斥使用不可抢占请求和保持循环等待 不同的锁ReentrantLockSynchronized和ReentrantLock之间的区别:具体选择Synchronized加锁工作过程偏向…

using namespace std 是什么意思——C++命名空间

目录 namespace命名空间局部域和全局域namespace展开命名空间指定命名空间 命名空间的嵌套不同文件中的同名命名空间 using namespace std 是什么意思 我们先看一段C代码&#xff1a; #include <stdio.h> #include <stdlib.h>int rand 0;int main() {printf(&quo…

6.2 统计量与抽样分布

学习目标&#xff1a; 我的理解: 统计量是从一个样本中计算得到的数值&#xff0c;用于描述样本的某种特征或性质。统计量可以用来推断总体的特征或性质&#xff0c;因为样本是总体的一部分。 通常&#xff0c;统计量是通过对样本中的数据进行计算得到的&#xff0c;例如平均…

3年轻人20万开店日营业额79.2元,年轻人开店还能赚到钱吗?

最近&#xff0c;河南郑州的蔡先生和2个朋友合伙开一家汉堡店&#xff0c;生意很惨淡&#xff0c;日营业额79.2元。 蔡先生表示&#xff0c;他们开店失败的最大原因就是没有做好前期准备工作&#xff0c;产品没有吸引人的特色&#xff0c;导致正式营业后吸引不到客户。 蔡先生用…

Python 实现海康机器人工业相机 MV-CU060-10GM 的实时显示视频流及拍照功能

一、背景介绍 1、最近项目中需要给客户对接海康机器人工业相机 MV-CU060-10GM&#xff1b; 2、客户要求通过部署的管理平台&#xff0c;可以在页面上实现如下功能&#xff1a; 1&#xff09;相机视频流开始预览&#xff1b; 2&#xff09;相机视频流停止预览&#xff1b; 3&am…

三月份跳槽了,历经阿里测开岗4轮面试,不出意外,被刷了...

大多数情况下&#xff0c;测试员的个人技能成长速度&#xff0c;远远大于公司规模或业务的成长速度。所以&#xff0c;跳槽成为了这个行业里最常见的一个词汇。 前几天&#xff0c;我看到有朋友留言说&#xff0c;他在面试阿里的测试开发工程师的时候&#xff0c;灵魂拷问三小…

NIO非阻塞式网络通信实例

一、概述 1、NIO有三大核心部分&#xff1a;Channel(通道)&#xff0c;Buufer(缓存区)&#xff0c;Selector(选择器) Buffer缓存区 缓冲区本质上是一块可以写入数据&#xff0c;然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象&#xff0c;并提供了 一组方法&am…

❤Linux文件、目录与磁盘格式总结❤

文章目录 Linux文件、目录与磁盘格式总结Linux文件权限chgrpchmodchown文件种类Linux文件扩展名 Linux文件与目录管理常用的目录解释目录树绝对路径与相对路径cd(切换目录)pwd(显示当前目录)mkdir(建立一个新目录)rmdir(删除空目录)执行文件路径变量$PATHls(文件与目录的查看)c…

青藤首提“业安融合”理念,正式发布先进云安全方案CNAPP

4月18日&#xff0c;以“云时代&#xff0c;安全变了”为主题的2023年云安全高峰论坛在北京举行。会上&#xff0c;青藤首次提出“业安融合”理念&#xff0c;正式发布先进云安全方案CNAPP。 中国全面进入云和数字化时代 当前&#xff0c;全球已进入数字经济时代&#xff0c;…

物联感知产品如何助力企业实现智能化转型?

如果您的企业想要实现设备全生命周期管理、设备信息管理、设备监控和数据处理等功能&#xff0c;想要进行智能化数字转型&#xff0c;不妨了解一下物联感知。 物联感知产品是利用物联网技术&#xff0c;实现对物理世界的感知、联接和智能化。物联感知产品一般由四个部分组成&a…

python中的pyc文件了解一下?

基本说明 pyc 文件是 Python 编译过的字节码文件。当你运行一个 Python 程序时&#xff0c;Python 解释器首先将源代码&#xff08;通常是 .py 文件&#xff09;编译成字节码。这个字节码是一种低级的、与平台无关的代码&#xff0c;它可以被 Python 虚拟机&#xff08;Python…

TCP的连接管理机制(三次握手与四次挥手)

目录为啥要三次握手与四次挥手三次握手syn 与 ack三次握手具体流程四次挥手注意为啥要三次握手与四次挥手 相比于UDP, TCP是有连接的, 这个连接就体现在这了. 三次握手就是TCP建立连接, 四次挥手就是TCP断开连接. 三次握手 握手是指通信双方进行网络交互. 三次握手就相当于…

记录上传文件异常 /tmp/tomcat... (No space left on device)

一&#xff0c;问题描述 用postman调用上传接口&#xff0c;基本每两次调用会有一次报错&#xff0c;如下 {"timestamp": "2023-04-11T03:00:15.4690000","status": 500,"error": "Internal Server Error","exceptio…

【活动】想对大学的自己说……

写在前面&#xff1a; 时间过得真的很快&#xff0c;眨眼间我们已经走过了这么多年的大学时光。回想起来&#xff0c;我们曾经有过无数的欢笑和泪水&#xff0c;有过无数的成功和挫败&#xff0c;但是这些经历都让我们变得更加坚强和成熟。如果现在有机会回到大学时光&#xff…

【LInux】进程间通信 -- 匿名管道

前言 我们在学习进程管理&#xff0c;进程替换时&#xff0c;都强调了进程的独立性&#xff0c;那进程间通信是什么&#xff1f;这好像和进程的独立性相矛盾吧&#xff1f; 那么今天&#xff0c;我们就来学习进程间通信&#xff0c;和第一种通信方式 – 管道 文章目录 前言一. …

高效管理 Linux 进程:如何后台执行程序、查看进程、终止任务

目录 前言一、nohup命令详解1-1、nohup命令介绍1-2、语法格式1-2-1、基础语法介绍1-2-2、执行脚本文件1-2-3、执行python文件1-2-4、拓展延申&#xff1a;在服务器上运行后台进程1-2-5、nohup和&的区别 二、进程查看2-1、jobs命令&#xff08;基本不用&#xff09;2-2、ps命…

Android进阶宝典—在Compose中跳转Fragment

使用场景 我们原有的项目中基本采用的是单Activity架构&#xff0c;页面之间的跳转都是通过Navigation进行的&#xff0c;举个简单的例子。 在这种单Activity架构模式下&#xff0c;有一天我们想把MainActivity或者BFragment使用Compose重构&#xff0c;这个时候我们就需要去…

PasteSpider软件优势介绍

PasteSpider采用.netcore编写&#xff0c;运行于linux服务器的docker/podman里面&#xff0c;涉及的技术或者工具有podman/docker,registry,nginx,top,ssh,git,svn等。 PasteSpider可以更好的为你执行服务的升级和维护工作。支持集群模式安装&#xff0c;也支持单例模式运行。…

如何为 Apple 官方 SwiftUI 示例中的图表元素加上首显动画?

0. 概览 在 Apple 官方教程示例 Animating Views and Transitions 中,苹果为我们展示了如何为 SwiftUI 中的各种视图添加动画和过渡效果。 在示例的最后,我们在完成 3 种不同数据类型(Elevation, Heart Rate, Pace)切换的同时,顺面收获了美美的图表元素动画效果: 不过,…

科研方向与个人思考

文章目录 关于科研选题与方法的若干思考如何选题选题tips确定研究问题之后如何读论文——与一篇优秀论文作者的博弈阅读论文的技巧代码实现过程中成果发表四步走&#xff1a;科技论文的书写顺序学会利用工具科研的正确姿势 研究生时间线个人思考入门一个领域的步骤&#xff08;…