【Java基础】15.脚本、编译、注解

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加

文章目录

  • 系列文章目录
    • 15.脚本、编译、注解
      • 15.1 Java的脚本机制
        • 15.1.1 获取脚本引擎
        • 15.1.2 脚本计算与绑定
        • 15.1.3 重定向输入和输出
        • 15.1.4 调用脚本的函数和方法
        • 15.1.5 编译脚本
      • 15.2 编译器`API`
        • 15.2.1 调用编译器
        • 15.2.2 发起编译任务
        • 15.2.3 捕获诊断信息
        • 15.2.4 从内存中读取源文件
        • 15.2.5 将字节码写出到内存中
      • 15.3 注解简介
      • 15.4 注解语法
        • 15.4.1 注解接口
        • 15.4.2 注解
        • 15.4.3 注解各类声明
        • 15.4.4 注解类型用法
      • 15.5 标准注解
        • 15.5.1 用于编译的注解
        • 15.5.2 用于管理资源的注解
        • 15.5.3 元注解


15.脚本、编译、注解

15.1 Java的脚本机制

脚本语言是一种在通过在运行时解释程序文本,从而避免使用通常的编辑/编译/链接/运行循环的语言。

优点:

  • 便于快速变更、鼓励不断试验
  • 可以修改运行着的程序的行为
  • 支持程序用户的定制化

另外,大部分脚本语言都缺乏可以使编写复杂应用受益的特性。

所以将脚本语言与传统语言结合,脚本API使得可以在Java上实现这个目标。可以在Java平台上使用JavaScript,Groovvy,Ruby等。

15.1.1 获取脚本引擎

脚本引擎是一个可以执行某种特定语言编写的脚本的类库。虚拟机启动时,就会发现可用的脚本引擎,为了枚举他们,需要构造一个ScriptEngineManager并调用getEngineFactories方法。可以向每个引擎工厂询问他们的引擎名,MIME类型和文件扩展名。

同时可以使用引擎名去请求他。

15.1.2 脚本计算与绑定

一旦有了引擎,就可以调用脚本。

Object result=engine.eval(scriptString)

如果脚本存储在文件中,就需要先打开一个Reader,然后在进行调用。

Object result=engine.eval(reader)

同一个引擎上保存的变量、函数或类,大多数引擎都会保留这些定义。如:

engine.put("n = 1");
Object result=engine.eval("n+1") // =2

可以为新增的变量进行绑定:engine.eval("k",1) 变量k的值为1。
或者绑定对象:engine.eval("obj",new Date())。反过来还可以通过变量名获取对象。

除了引擎作用域外,还可以使用全局作用域,添加到ScriptEngineManager的对象对所有引擎都是可视的。

还可以将所有绑定收集到一个类型为Bindings的对象中,然后再传递给eval

Bindings scope = engine.createBindings();
scope.put("b",new Date());
engine.eval(scriptString,scopr);
15.1.3 重定向输入和输出

可以通过调用脚本上下文的setReadersetWriter来重定向输入和输出。

这两个 方法只会影响脚本的标准输入源和输出源

15.1.4 调用脚本的函数和方法

对于诸多脚本引擎而言,可以调用脚本语言的函数,而不必对具体代码进行计算。因此可以是用提供了这种功能的接口Invocable

如果想从Java中调用脚本代码,又不想因为这种脚本语言的语法而受到困扰,那么Invocable接口便很实用。

要调用一个函数,就用函数名使用invokeFunction方法:

engine.eval("function greet(how,whom)...");//定义的脚本函数
result=((Invocable)engine).invokeFuntion("grret","hello","world");//调用函数

如果脚本语言是面向对象的,则可以用invokeMethod方法:

engine.eval("function Greeter(how)...");//定义类
engine.eval("Greeter.prototype.welcom="+"function(whom)...");//定义对象的方法
Object obj=engine.eval("new Greeter('You')");
result=((Invocable)engine).invokeMethod(obj,"welcome","world");

如果在Java中定义了接口,同时在脚本中有同名的函数,就使用getInterFace方法来调用它:

engine.eval("function welcom(whom)...");//定义的脚本函数
//Java接口
interface Greeter{
    String welcome(String whom);
}
//直接使用Java调用
Greeter g=((Invocable)engine).getInterFace(Greeter.class);
result=g.welcome("world");
15.1.5 编译脚本

出于对执行效率的考虑,可以将脚本代码编译为某种中间格式。这些引擎实现了Compilable接口。

var reader = new FIleReader("myscript.js");
CompiledScript script=null;
if(engine implements Compliable)
    script=((Compilable)engine).compile(reader);

一旦脚本被编译,就可以直接执行它。如果引擎不支持编译,则会执行原始的脚本。

15.2 编译器API

15.2.1 调用编译器

调用编译器很简单,直接使用JavaCompiler创建对象即可。

JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();
OutputStream out=...;
OutputStream err=...;
int result = compiler.run(null,out,err,"-sourcepath","src","Test.java");

返回值为0则编译成功。

编译器会向提供的流发送输出信息和错误信息。如果将参数置为null,编译器就会使用System.out和System.err

15.2.2 发起编译任务

可以通过使用CompilationTask对编译过程进行更多的操作。应用场景:在字符串中提供源码、在内存中捕获类文件、处理错误和警告信息等。

15.2.3 捕获诊断信息

为了监听错误信息,需要安装一个DiagnosticListener。这个监视器在编译期报告警告或错误信息时就会收到一个Diagnostic对象。DiagnosticCollector类实现了这个接口,它将收集所有的诊断信息,使得你可以在编译完成之后遍历这些信息。

Diagnostic对象包含有关问题位置的信息(包括文件名、行号和列号)以及人类可阅读的描述。

还可以在标准的文件管理器上安装一个 DiagnosticListener对象,这样就可以捕获到有关文件缺失的消息:

StandardJavaFileManager fileManager= compiler.getStandardFileManager(diagnostics, null, null);

15.2.4 从内存中读取源文件

如果动态地生成了源代码,那么就可以从内存中获取它来进行编译,而无须在磁盘上保存文件。可以使用下面的类来持有代码:

public class StringSource extends SimpleJavafile0biect{
	private String code;
	StringSource(String name, String code){
		super(URl.create("string:///"+ name.replace('.','/')+ ".java"),Kind.SOURCE);this.code = code;
    }
	public CharSequence getCharContent(boolean ignoreEncodingErrors){
		return code;
    }
}

然后,生成类的代码,并提交给编译器一个StringSource对象的列表:

List<StringSource> sources = List.of(new StringSource(classNamel,classlCodeString)...);
task = compiler.getTask(null,fileManager, diagnostics, null, null, sources);
15.2.5 将字节码写出到内存中

如果动态地编译类,那么就无须将类文件写出到硬盘上。可以将它们存储在内存中,并立即加载它们。

首先,要有一个类来持有这些字节:

public class ByteArrayClass extends SimpleJavaFile0bject{
    private ByteArrayOutputStream out;
    ByteArrayClass(String name){
    	super(URI.create("bytes:///"+ name.replace(',','/')+".class"),Kind.CLASS);
    }
    public byte[] getcode(){
    	return out.toByteArray();
    }
    public OutputStream openOutputStream()throws IOException{
        out = new ByteArrayOutputStream();
        return out;
    }
}

接下来,需要将文件管理器配置为使用这些类作为输出:

List<ByteArrayClass>classes = new ArrayList<>();
StandardJavaFileManager stdFileManager=compiler.getStandardFileManager(null, null, null);
JavaFileManager fileManager=new ForwardingJavaFileManager<JavaFileManager>(stdFileManager){
    public JavaFile0bject getJavaFileFor0utput(Location location,String className,Kind kind, File0bject sibling)throws IOException{
    if(kind == Kind.CLASS){
	    ByteArrayClass outfile = new ByteArrayClass(className);
        classes.add(outfile);
        return outfile;
    }
    else
    	return super.getJavaFileForOutput(location, className, kind, sibling);
    }
}

为了加载这些类需要使用类加载器:

public class ByteArrayClassLoader extends ClassLoader{
    private Iterable<ByteArrayClass> classes;
    public ByteArrayClassLoader(Iterable<ByteArrayClass> classes){
        this.classes = classes;
    }
    public Class<?>findClass(String name)throws ClassNotFoundException{
	    for(ByteArrayClass cl :classes){
	        if (cl.getName().equals("/" + name.replace('.','/')+ ".class")){
                byte[]bytes = cl.getCode();
                return defineClass(name,bytes,0,bytes.length);
            }
        }
	    throw new ClassNotFoundException(name);
    }
}

编译完成后,用上面的类加载器调用Class.forName方法:

ByteArrayClassLoader loader = new ByteArrayClassLoader(classes);

Class<?>cl=Class.forName(className, true, loader);

15.3 注解简介

注解是插入到源代码中使其他工具可以对其进行处理的标签。这些工具可以在源码层次上进行操作,或者可以处理编译器在其中放置了注解的类文件。

注解不会改变程序的编译方式。为了能够受益于注解,需要选择一个处理工具,通过处理工具来理解代码中插入的注解,再处理代码。

注解应用场景:

  • 附属文件的自动生成。
  • 测试、日志、事务语义等代码的自动生成。

注解举例:

public class AppTest{
    @Test//@Test用于注解start方法
    public void start(){}
}

在Java中注解是当作修饰符使用,一般位于被修饰项之前,没有分号间隔,且每个注解前都有@符号。

@Test注解本身不会做任何事情,他需要借助处理工具才能有作用。(比如需要JUnit4测试工具,才会识别这个注解)

注解可以注解方法、类、成员以及局部变量,可以存放在任何可以放置修饰符的地方。

每一个注解必须通过一个注解接口进行定义,这些接口中的方法与注解中的元素相对应。例如:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{
    long timeout() default 0L;
}

@interface声明创建了一个真正的Java接口。处理工具将接受实现了注解接口的对象,调用方法来获取特定注解的目标元素。

注解Target和Retention是元注解,注解了Test注解,将其标识为只能用在方法上的注解并加载到虚拟机上时,仍旧可以保留。

15.4 注解语法

15.4.1 注解接口

注解是由注解接口定义的:

modifiers @interface AnnotationName{
    elementDeclaration;
}

每个元素声明都具有一下形式:

// 1
type elementName();
// 2
type elementName() default value;

所有注解接口都隐式扩展java.lang.annotation.Annotation接口。

注解的元素类型为下列之一:

  • 基本类型:int,short,long,byte,char,double,float,boolean
  • String
  • Class:具有一个可选的类型参数,例如Class<? extends MyClass>
  • enum类型
  • 注解类型
  • 数组
15.4.2 注解

每个注解都具有这种格式:@AnnotationName(elementName1=value1,elementName2=value2...)。(元素的顺序无关)

特殊的注解使用方法:

  • 标记注解:如果使用的注解没有指定元素,或者所有元素都有默认值,就可以不适用括号

    @Test
    public void start(){};
    
  • 单值注解:如果一个元素具有特殊的名字,并且没有指定其他元素,就可以忽略掉这个元素名以及等号

    @GetMapper("/hello")//url=""
    public String welcome(){};
    

一个项可以有多个注解

15.4.3 注解各类声明

注解可以出现在很多地方,大体分为两类:声明注解和类型用法生声明注解。

声明注解可出现在下处:

  • 包、类、接口、方法、构造器、实例域、局部变量、参数变量、类型参数

对于类和接口,需将注解放在class和interface前面

  • @Entity public class User{}

对于变量需要将注解放置在类型前面

  • @SupperessWarnings("unchecked") List<User> users=...;

泛型类或方法可以将注解放在类型变量中

  • public class Cache<@Immutable V>{}
15.4.4 注解类型用法

声明注解提供了正在被声明的项的相关信息。例如:

public User getUser(@NonNull String uId)就断言了uId不为空。

类型用法注解可以出现在以下位置:

  • 与泛化类型参数一起使用:List <@NonNull String>
  • 数组中任意位置:@NonNull String[][] | String @NonNull [][] | String[] @NonNull []
  • 与超类和实现接口一起使用:class Warning extends @Localized Message
  • 与强制类型转换和instanceof检查一起使用:(@Localized String) text
  • 与异常规约一起使用:public String read() throws @Localized IOException
  • 与通配符和类型边界一起使用:List<@Localized ? extends Message>
  • 与方法和构造器引用一起使用:@Localized Message::getText

15.5 标准注解

在Java中定义了大量的注解接口,四种是元注解,用于描述注解接口的行为属性,其他的是规则接口,可以用他们注解源代码中的项。

注解接口应用场合目的
Deprecated全部将项标记为过时的
SuppressWarnings除了包和注解之外的所有情况阻止某个给定类型的警告信息
SafeVarargs方法和构造器断言 varargs 参数可安全使用
0verride方法检查该方法是否覆盖了某一个超类方法
FunctionalInterface接口将接口标记为只有一个抽象方法的函数式接口
PostConstruct PreDestroy方法被标记的方法应该在构造之后或移除之前立即被调用
Resource类、接口、方法、域在类或接口上:标记为在其他地方要用到的资源。在方法或域上:为“注入”而标记
Resources类、接口一个资源数组
Generated全部用于标识生成的代码或类是由哪个工具或程序自动生成的,一般是由代码生成器或自动化构建工具生成的代码所使用的。
Target注解指明可以应用这个注解的那些项
Retention注解指明这个注解可以保留多久
Documented注解指明这个注解应该包含在注解项的文档中
Inherited注解指明当这个注解应用于一个类的时候,能够自动被它的子类继承
Repeatable注解指明这个注解可以在同一个项上应用多次
15.5.1 用于编译的注解

@Deprecated注解可以被添加到任意不再鼓励使用的项上。所以,当使用一个已过时的项时,编译器将会发出警告。这个注解与Javadoc的标签@deprecated具有同等功效,但这个注解会持久化到运行时。

在这里插入图片描述

@SuppressWarnings注解会告知编译器组织特定类型的警告信息。如:@SuppressWarnings("unchecked")

@Override注解只能用在方法上,编译器会检查具有这种注解的方法是否真正覆盖了一个来自于超类的方法。如果声明了

class MyClass{
	@Override public boolean equals(...)
}

就会报错,因为equals方法没有覆盖Object类的equals方法。

在这里插入图片描述

@Generated注解的目的是供代码生成工具来使用。任何生成的源代码都可以被注解,从而与程序员提供的代码区分开。比如,代码编辑器可以隐藏生成的代码,或者代码生成器可以移除生成的代码的旧版本。每个注解都必须要包含一个表示代码生成器的唯一标识符,而日期字符串(ISO8601格式)和注释字符串是可选的。

如:

@Generated("com.horstmann.beanproperty","2008-01-04T12:08:56.235-0700");
15.5.2 用于管理资源的注解

@PostConstruct@PreDestroy注解用于控制对象生命周期环境中,如Web容器和应用服务器。标记了这些注解的方法应在对象被构建之后,或者在对象被移除之前,紧接着调用。

@Resource注解用于资源注入,当一个被注解的项的对象被构造时,容器就会注入一个对该数据源的引用。

@Resource属于 JDK 提供的注解,默认注入方式为byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType
@Resource 有两个比较重要且日常开发常用的属性:name(名称)、type(类型)。
如果仅指定name属性则注入方式为byName,如果仅指定type属性则注入方式为byType,如果同时指定nametype属性(不建议这么做)则注入方式为byType+byName
如:

// 报错,byName 和 byType 都无法匹配到 bean
@Resource
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Resource
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;

总结:

  • @AutowiredSpring提供的注解,@ResourceJDK提供的注解。
  • Autowired默认的注入方式为byType(根据类型进行匹配),@Resource默认注入方式为byName(根据名称进行匹配)。
  • 当一个接口存在多个实现类的情况下,@Autowired@Resource都需要通过名称才能正确匹配到对应的BeanAutowired可以通过@Qualifier注解来显式指定名称,@Resource可以通过name属性来显式指定名称。
  • @Autowired支持在构造函数、方法、字段和参数上使用。@Resource主要用于字段和方法上的注入,不支持在构造函数或参数上使用。
15.5.3 元注解

@Target注解可以应用于一个注解,以限制该注解可以应用到哪些项上。如:

@Target(ElementType.xxx)

@Target注解枚举类型ElementType的取值范围:

元素类型使用场合元素类型使用场合
ANNOTATION_TYPE注解类型声明FIELD成员域
PACKAGEPARAMETER方法或构造器参数
TYPE类和接口LOCAL_VARIABLE局部变量
METHOD方法TYPE_PARAMETER类型参数
CONSTRUCTOR构造器TYPE_USE类型用法

一条没有Target限制的注解可以用在任意位置的项上。编译器将检查你是否将一条注解只应用到某个允许的项上,否则会导致编译器错误。

在这里插入图片描述

@Retention注解用于指定一条注解应该保留多长时间。并且只能将其指定为下表中的值。

默认为RetentionPolicy.Class

保留规则描述
SOURCE不包括在类文件中的注解
CLASS包括在类文件中的注解,但虚拟机不需要将他们载入
RUNTIME包括在类文件中的注解,并由虚拟机载入,通过反射API得到他们

@Documented注解为向Javadoc这种归档工具提供提示。应像处理其他修饰符一样处理归档注解。

如果某个注解是暂时性的,就不要使用这个注解进行归档

@Inherited注解只能应用于对类的注解。如果一个类具有继承注解,那他的所有子类都自动具有同样的注解。比如:

// 注解
@Inherited
@interface Persistent{}
// 使用注解的类
@Persistent
class Employee{}
//子类
class Manager extends Employee

如果这个注解指明一个类的对象可以存储到数据库中,那么它的子类就会自动被注解为持久性的。

持久化机制去查找存储在数据库中的对象时,会自动去寻找Employee对象和Manager对象。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

iOS - Undefined symbols: 解决方法

Undefined symbols: 是让人苦恼的报错&#xff0c;如何知道是 哪个 symbols 不对呢&#xff1f; 今天探索到下面的方法&#xff1a; 1、点击导航上方 最右侧的按钮&#xff0c;查看历史报错 2、选中报错信息&#xff0c;右键选择 Expand All Transcripts 在出现的详细信息面…

【ARM Cortex-M3指南】4:存储器系统

文章目录 四、存储器系统4.1 存储器系统特性概述4.2 存储器映射4.3 存储器访问属性4.4 默认的存储器访问权限4.5 位段操作4.5.1 位段操作的优势4.5.2 不同数据宽度的位段操作4.5.3 C程序实现位段操作 4.6 非对称传输4.7 排他访问4.8 端模式 四、存储器系统 4.1 存储器系统特性…

【汇编语言】中断及外部设备操作

【汇编语言】中断及外部设备操作 文章目录 【汇编语言】中断及外部设备操作前言一、中断及其处理中断的概念8086内中断中断处理程序案例&#xff1a;系统中的0号中断中断过程 二、编制中断处理程序中断处理程序及其结构编制中断处理程序——以除法错误中断为例do0子程序应该放在…

Transformer中的数据输入构造

文章目录 1. 文本内容2. 字典构造2.1 定义一个类用于字典构造2.2 拆分文本2.3 构造结果 3. 完整代码 1. 文本内容 假如我们有如下一段文本内容&#xff1a; Optics It is the branch of physics that studies the behaviour and properties of light . Optical Science 这段…

【计算机科学速成课】笔记二

笔记一 文章目录 7.CPU阶段一&#xff1a;取指令阶段阶段二&#xff1a;解码阶段阶段三&#xff1a;执行阶段 8.指令和程序9.高级CPU设计——流水线与缓存 7.CPU CPU也叫中央处理器&#xff0c;下面我们要用ALU&#xff08;输入二进制&#xff0c;会执行计算&#xff09;、两种…

倪海厦(二)研究任何学问(东西)批判去看

大家好今天我们接着研究&#xff0c;倪海厦是如何思考问题的: 研究任何学问&#xff08;东西&#xff09;&#xff0c;批判去看&#xff0c;假设--验证--结果。以果决其行&#xff01;&#xff01;&#xff01;放空自己。学而后思&#xff0c;思学并进。 今天这一篇呢&#xf…

医疗器械软件相关的追溯关系

在医疗器械软件开发过程中&#xff0c;追溯性是确保产品质量和安全性的关键步骤之一。追溯性要求各个阶段的需求、设计、实现和测试之间能够清晰、连贯地关联起来&#xff0c;以便在整个开发周期中进行有效的跟踪和管理。IEC62304中明确输出的内容要有对应的追溯性&#xff0c;…

golang学习笔记(内存模型和分配机制)

操作系统的存储管理 虚拟内存管理 虚拟内存是一种内存管理技术&#xff0c;它允许操作系统为每个进程提供一个比实际物理内存更大的地址空间。这个地址空间被称为虚拟地址空间&#xff0c;而实际的物理内存则被称为物理地址空间。使用虚拟内存有以下几点好处&#xff1a; 内…

docker系列8:容器卷挂载(上)

目录 传送门 从安装redis说起 什么是容器卷挂载 操作系统的挂载 日志文件一般是"首恶元凶" 挂载命令 容器卷挂载 卷挂载命令 启动时挂载 查看挂载卷信息 容器卷管理 查看卷列表 创建容器卷 具名挂载与匿名挂载 具名挂载 传送门 docker系列1&#xff…

了解并学会使用反射

目录 一、反射的应用场景&#xff08;简单了解&#xff09; 二、反射的定义 三、关于反射的四个重要的类 四、反射的使用 1.Class获取一个class对象的方式 方式一&#xff1a;forName&#xff08;&#xff09;&#xff1a; 方式二&#xff1a;封装类.Class&#xff1a; …

天风证券:水电燃气价格上涨,能推动通胀么?

水电燃气价格上涨对PPI的影响更大&#xff0c;6%的平均价格上涨能够拉动CPI和PPI分别上涨0.3个和0.7个百分点。 近期&#xff0c;国内多地上调水电燃气价格 燃气价格上涨主要针对居民端。目前燃气价格实行居民用气价格限价波动非民用气市场化定价的双轨制&#xff0c;这使得居…

【Linux】目录和文件相关的命令,补充:centos7系统目录结构

【Linux】Linux操作系统的设计理念之一就是“一切皆文件”&#xff08;Everything is a file&#xff09;&#xff0c;即将设备、文件等都当作“文件”处理。 “文件”主要类型有&#xff1a;目录&#xff08;即文件夹&#xff09;&#xff0c;链接文档&#xff08;即快捷方式…

【Linux线程】

目录 线程是操作系统的一个执行流并发编程进程并发的优劣基于线程的并发编程Linux当中的线程 线程的创建使用pthread_createpthread_join对线程进行等待pthread_exit和pthread_cancelpthread_detach线程分离注意事项 原生线程库&#xff0c;详谈Linux的线程pthread库管理线程 线…

云端部署Stirling PDF:构建个人App的API调用指南(附Python源码)

今天发现一个Github的开源项目&#xff0c;Stirling PDF&#xff0c;项目地址如下&#xff1a;https://gitcode.com/Stirling-Tools/Stirling-PDFhttps://gitcode.com/Stirling-Tools/Stirling-PDF?utm_sourceartical_gitcode目前CSDN上已经有好几个up主都介绍了这个项目&…

cocos=》 预乘、混合(黑边、白色)

简介 预乘&#xff0c;指的是在数据提交给GPU之前&#xff0c;就对纹理的RGB分量与alpha值进行计算。 预乘计算 结果颜色 源颜色值 目标颜色值 * (1 - 源 alpha 值) result source.RGB dest.RGB * (1 - source.A); 对应的颜色混合函数设置为 gl.blendFunc(gl.ONE, gl.…

英语复习之英语形近词总结

最近在练习英语口语&#xff0c;有很好的练习场景&#xff0c;和数字人对练&#xff0c;还能纠错&#xff0c;不过开口的基础需要单词量的支撑以及语法的熟悉&#xff0c;因为英语的语法太简单了&#xff0c;没啥需要复习和注意的&#xff0c;音标发音的问题也可以后期再纠正&a…

Angular进阶-NVM管理Node.js实现不同版本Angular环境切换

一、NVM介绍 1. NVM简介 Node Version Manager&#xff08;NVM&#xff09;是一个用于管理多个Node.js版本的工具。它允许用户在同一台机器上安装和使用多个Node.js版本&#xff0c;非常适合需要同时进行多个项目的开发者。NVM是开源的&#xff0c;支持MacOS、Windows和Linux…

wechat_OCR项目打包以及如何使用

&#x1f4da;博客主页&#xff1a;knighthood2001 ✨公众号&#xff1a;认知up吧 &#xff08;目前正在带领大家一起提升认知&#xff0c;感兴趣可以来围观一下&#xff09; &#x1f383;知识星球&#xff1a;【认知up吧|成长|副业】介绍 ❤️感谢大家点赞&#x1f44d;&…

医学图像处理:nii格式转换(3D切片为2D)

目录 NIFTI文件结构 读取NII文件 ITK-SNAP安装 使用方法 NII转PNG NIFTI文件结构 NIFTI 格式&#xff0c;是一种用于存储和交换医学成像数据的文件格式&#xff0c;特别适用于神经影像学领域。NIFTI文件通常有两个扩展名&#xff1a;.nii&#xff08;用于图像数据&#xf…

42.WEB渗透测试-信息收集-域名、指纹收集(4)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;41.WEB渗透测试-信息收集-域名、指纹收集&#xff08;3&#xff09; 关于单域名收集内容…