java平台脚本+java编译器API

【0】README

0.1)本文文字描述转自 core java volume 2, 旨在学习  java平台脚本+java编译器API 的 基础知识;

------------------------------------------------------------------------------

【1】java平台的脚本 

1)脚本语言定义: 脚本语言是一种通过在运行时解释程序文本,从而避免使用通常的编辑/编译/链接/运行循环的语言。(干货——脚本语言定义,如JavaScript)
2)脚本语言有很多优势(merit):
m1)便于快速变更,鼓励不断试验;
m2)可以修改运行着的程序的行为;
m3)支持程序用户的定制化;
3)problem+solution:
3.1)problem:大多数脚本语言都缺乏可以使编写复杂应用受益的特性,如,强类型,封装和模块化;
3.2)solution:因此人们在尝试将脚本语言和传统语言的优势相结合。。脚本API 使你可以在 java 平台上实现这个目的, 他支持在java程序中使用 JavaScript, Groovy, Ruby, 甚至更奇异的注入 Scheme 和 Haskell 等语言编写的脚本进行调用;(干货——java脚本API的作用)

【1.1】获取脚本引擎
1)脚本引擎: 脚本引擎是一个可以执行用某种特定语言编写的脚本的类库。当虚拟机启动时, 他会发现这些引擎;(干货——脚本引擎定义)
2)为了枚举这些引擎:需要构造一个 ScriptEngineManager , 并调用 getEngineFactories 方法。 可以向每个引擎工厂去询问他们所支持的引擎名, MIME类型和文件扩展名;如下表所示:

3)获取引擎:可以直接通过名字, MIME 类型 或文件扩展名来请求它:
<span style="font-family:Microsoft YaHei;font-size:14px;">ScriptEngine engine = manager.getEngineByName("JavaScript");</span>

【1.2】 脚本赋值与绑定
1)利用引擎调用脚本: Object result = engine.eval(script);
2)如果脚本存储在文件中, 那么需要打开一个 Reader, 然后调用: Object result = engine.eval(reader);
3)可以在同一个引擎上调用多个脚本: 
<span style="font-family:Microsoft YaHei;font-size:14px;">engine.eval("n=1728");
Object result = engine.eval("n+1")
返回 1729;</span>
Attention) 要想知道在多个线程中并发执行脚本是否安全, 可以调用:
Object param = factory.getParameter("THREADING"); 返回值有: 
A1)null:(不安全);
A2)MULTITHREADED: 并发执行安全; 
A3)THREAD-ISOLATED:除了MULTITHREADED外,会为每个线程维护不同的变量绑定;
A4)STATELESS: 除了THREAD-ISOLATED外, 脚本不会改变变量绑定;
4) 向引擎中添加变量绑定:
<span style="font-family:Microsoft YaHei;font-size:14px;">engine.put(k, 100);
Object result = engine.eval("k+1");</span>
5)获取由脚本语句绑定的变量:
<span style="font-family:Microsoft YaHei;font-size:14px;">engine.eval("n=100");
Object result = engine.get("n");</span>
6)除了向引擎或全局作用域添加绑定外,还可以将绑定收集到一个类型为 Bindings 的对象中, 然后将其传递给 eval 方法:
<span style="font-family:Microsoft YaHei;font-size:14px;">Bindings scope = engine.createBindings();
scope.put(b, new Butten());
engine.eval(script, scope);</span>

<span style="font-family:Microsoft YaHei;font-size:14px;">package com.corejava.chapter10;import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;public class ScriptTest
{public static void main(String[] args) throws ScriptException, FileNotFoundException{// 脚本引擎管理器//ScriptEngineManager manager = new ScriptEngineManager();/*List<ScriptEngineFactory> list = manager.getEngineFactories();for(ScriptEngineFactory engine : list){System.out.println(engine.getEngineName());}*/// 脚本引擎管理器ScriptEngineManager manager = new ScriptEngineManager();ScriptEngine engine = manager.getEngineByName("JavaScript");// 向引擎中添加变量绑定engine.eval("n=1728");Object result = engine.eval("n+1"); // 1729.0System.out.println(result);// 获取由脚本语句绑定的变量 engine.put("k", 100);result = engine.eval("k+1"); // 101System.out.println(result);// 除了向引擎或全局作用域添加绑定外,还可以将绑定收集到一个类型为 Bindings 的对象中, // 然后将其传递给 eval 方法:Bindings scope = engine.createBindings();scope.put("a", "hello, ");result = engine.eval("a+ 'world!'", scope); // hello, world!System.out.println(result);// 重定向输入和输出// 任何用 js 的 print 和 println函数产生的输出都会发送到 writerStringWriter writer = new StringWriter();engine.getContext().setWriter(new PrintWriter(new File("com/corejava/chapter10/output.txt")));engine.eval("print('hello world, this msg is from java app')");}
}</span>
Attention)你可能希望除了引擎作用域和全局作用域外还有其他的作用域。但这需要你自己去解决。你需要实现一个类, 他实现了 ScriptContext 接口, 并管理着一个作用域集合。每个作用域都是由一个整数标识的, 而且越小的数字应该越先被搜索;

【1.3】重定向输入和输出
1)重定向输入和输出的实现:通过调用脚本上下文的 setReader 和 setWriter 方法来实现;
<span style="font-family:Microsoft YaHei;font-size:14px;">StringWriter writer = new StringWriter();
engine.getContext().setWriter(new PrintWriter(writer, true));</span>
对以上代码的分析(Analysis):  在上例中, 任何用 js 的 print 和 println函数产生的输出都会发送到 writer;
Warning)可以向setWriter 方法传递任何Writer, 但是如果传递的不是 PrintWriter, Rhino 引擎就会抛出异常;

<span style="font-family:Microsoft YaHei;font-size:14px;">// 重定向输入和输出(同上)// 任何用 js 的 print 和 println函数产生的输出都会发送到 writerStringWriter writer = new StringWriter();engine.getContext().setWriter(new PrintWriter(new File("com/corejava/chapter10/output.txt")));engine.eval("print('hello world, this msg is from java app')");</span>

【1.4】调用脚本的函数和方法
1)提供调用脚本的函数和方法的 脚本引擎实现了 Invocable 接口;
2)如何调用一个函数? 需要用函数名来调用 invokeFunction 方法,函数名后面是函数的参数:
<span style="font-family:Microsoft YaHei;font-size:14px;">if(engine implemens Invocable)(Invocable)engine.invockFunction("aFunction", param1, param2);</span>
3) 如果脚本是面向对象的, 像下面这样调用方法:
<span style="font-family:Microsoft YaHei;font-size:14px;">    (Invocable)engine.invockMethod(implicitParam, "aMethod", explicitParam1, explicitParam2);</span>
3.1)这里, implicitParam对象是用脚本语言编写的对象的一个代理, 他必须是前一个脚本引擎调用的结果;
4)让脚本引擎去实现一个 java 接口, 然后就就可以用 java 方法调用的语法来调用该脚本函数;
4.1)看个荔枝:
step1)java接口:
<span style="font-family:Microsoft YaHei;font-size:14px;">public interface Greeter
{String greet(String whom);
}</span>
step2)在脚本引擎如Rhino中, 可以提供下面的函数:
<span style="font-family:Microsoft YaHei;font-size:14px;">function greet(x)
{return "hello" + x + "!";
}</span>
step3)以上代码必须先计算,然后调用:
<span style="font-family:Microsoft YaHei;font-size:14px;">Greeter g = ((Invocable)engine).getInterface(Greeter.class);</span>
step4) 产生一个普通的java 方法调用:
<span style="font-family:Microsoft YaHei;font-size:14px;">String result = g.greet("world");</span>

<span style="font-family:Microsoft YaHei;font-size:14px;">public class InvocableTest
{public static void main(String[] args) throws FileNotFoundException, ScriptException{// 脚本引擎管理器ScriptEngineManager manager = new ScriptEngineManager();ScriptEngine engine = manager.getEngineByName("JavaScript");engine.eval(new FileReader("com/corejava/chapter10/test.js"));Greeter g = ((Invocable)engine).getInterface(Greeter.class);String result = g.greet("World");System.out.println(result);// hello, World}
}</span>
5)在面向对象的脚本语言中, 可以通过相匹配的 java 接口来访问一个脚本类。
5.1)看个荔枝: 
step1)下面的代码定义了一个 SimpleGreeter类,
<span style="font-family:Microsoft YaHei;font-size:14px;">function SimpleGreeter(salutation) {this.salutation = salutation};
SimpleGreeter.prototype.greet = function(whom){return this.salutation + ", " + whom + "!" ;}</span>
step2) 在对 JavaScript的类定义计算后, 可以调用:
<span style="font-family:Microsoft YaHei;font-size:14px;">Object greeter = engine.eval("new SimpleGreenter('goodbye')");
Greeter g = ((Invocable)engine).getInterface(greeter , Greeter.class);</span>
step3) 当调用 g.greet("world")时, greet 方法会在 js 对象 greenter 上被调用。 其结果是 “goodbye”;

<span style="font-family:Microsoft YaHei;font-size:14px;">public class SimpleGreeter
{public static void main(String[] args) throws FileNotFoundException, ScriptException{// 脚本引擎管理器ScriptEngineManager manager = new ScriptEngineManager();ScriptEngine engine = manager.getEngineByName("JavaScript");engine.eval(new FileReader("com/corejava/chapter10/test.js"));Object goodbyeGreeter = engine.eval("new SimpleGreeter('goodbye')");Greeter g = ((Invocable)engine).getInterface(goodbyeGreeter, Greeter.class);String result = g.greet("World");System.out.println(result);// goodbye, World}
}</span>
<span style="font-family:Microsoft YaHei;font-size:14px;">function greet(x)
{return "hello, " + x + "!";
}function SimpleGreeter(salutation)
{this.salutation = salutation;
}
SimpleGreeter.prototype.greet = function(whom)
{return this.salutation + ", " + whom + "!" ;
}</span>
Conclusion) 如果你希望从 java 中调用脚本代码, 同时又不想因这种脚本语言的语法而受到困扰, 那么Invocable 接口就很有用;

【1.5】 编译脚本
Attention) 将脚本进行编译后,再执行;
1)应用背景:某些脚本引擎处于对执行效率的考虑,可以将脚本代码编译为某种中间格式。这些引擎实现了 Compilable 接口;
2)看个荔枝:
<span style="font-family:Microsoft YaHei;font-size:14px;">Reader reader = new FileReader("script.js");
CompiledScript script = null;
if(engine implements Compilable)script = ((Compilable )engine).compile(reader);</span>
3)一旦脚本被编译,就可以执行它,如果引擎不支持编译, 则执行原始脚本, 如:
<span style="font-family:Microsoft YaHei;font-size:14px;">if(script != null)script.eval();
elseengine.eval(reader);</span>
Attention) 当然,只有需要重复执行代码时, 我们才希望编译脚本;(干货——为什么编译脚本?——只有需要重复执行代码时, 我们才希望编译脚本)

<span style="font-family:Microsoft YaHei;font-size:14px;">public class CompileTest
{public static void main(String[] args) throws FileNotFoundException, ScriptException{// 脚本引擎管理器ScriptEngineManager manager = new ScriptEngineManager();ScriptEngine engine = manager.getEngineByName("JavaScript");// 读取器Reader reader = new FileReader("com/corejava/chapter10/test.js");CompiledScript script = null;if(engine instanceof Compilable){script = ((Compilable)engine).compile(reader); // 编译脚本}if(script != null){script.eval();System.out.println("执行编译后的脚本");}else{engine.eval(reader);System.out.println("执行脚本源码");}Object goodbyeGreeter = engine.eval("new SimpleGreeter('goodbye')");Greeter g = ((Invocable)engine).getInterface(goodbyeGreeter, Greeter.class);String result = g.greet("World");System.out.println(result);// goodbye, World}
}</span>
Attention) for complete source code about instances  above, please visit https://github.com/pacosonTang/core-java-volume/tree/master/coreJavaAdvanced/chapter10/10_1  

【2】编译器API
1)有许多工具都需要调用java 编译器,如(tools):
t1) 开发环境;
t2) java 教学和辅导程序;
t3)自动化的构建和测试工具;
t4)处理java 代码段的模板工具, 如 java server page(JSP);
2) 在过去, 应用程序是通过在 jdk/lib/tools.jar类库中未归档的类调用 java 编译器的。
3)从java SE6 开始, 用于编译的一个公共API 成为 java 平台的一部分,并且它再也不需要使用 tools.jar 了;

【2.1】编译便捷之法
1)调用编译器的荔枝:
<span style="font-family:Microsoft YaHei;font-size:14px;">JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
OutputStream out = ...
OutputStream err = ...
int result = compiler.run(null, out, err, "-sourcepath", "src", "Test.java"); // 返回值为0 表示编译成功;</span>

package com.corejava.chapter10_2;import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;public class JavaCompilerTest
{public static void main(String[] args) throws FileNotFoundException{JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();OutputStream out = new FileOutputStream("out.txt");OutputStream err = new FileOutputStream("err.txt");System.out.println(compiler);System.out.println(System.getProperty("java.home"));int result = compiler.run(null, out, err, "com/corejava/chapter10_2/Hello.java");if(result == 0){System.out.println("bingo");}else{System.out.println("oops");}}
}
对以上代码的分析(Analysis):
A1) 编译器会向 提供给他 的流发送输出和错误消息;
A2)如果将这些参数设置为null, 就会使用 System.out 和 System.err;
A3) run 方法的第一个参数是输入流, 由于编译器不会接受任何控制台输入, 因此总是应该让其保持为null;
A4) 如果在命令行调用 javac, 那么run 方法其余的参数就会作为 变量传递给 javac;

【2.2】使用编译工具(for detailed info, please visit http://blog.csdn.net/pacosonswjtu/article/details/50718602)
1)可以通过使用 CompilationTask 对象来对编译过程进行更多的控制, 如(controls):
c1)控制程序代码的来源;
c2)控制类文件的位置;
c3)监听在编译过程中产生的错误和警告消息;
c4)在后台运行编译器;
2)源代码和类文件的位置:由 JavaFileManager 控制的, 它负责确定源代码和类文件的 JavaFileObject 实例;
3) 为了监听错误消息, 需要安装一个 DiagnosticListener: 这个监听器在编译器报告警告或错误消息时会收到一个 Diagnostic 对象。 DiagnosticCollector类实现了这个接口, 他将收集所有的 诊断信息, 使得你可以在编译完成之后遍历这些信息;
4)通过调用JavaCompiler 类的 getTaksk 方法 来获得 CompilationTask 对象, 这时需要指定(sepecification):
s1)一个用于所有编译器输出的Writer: 如果是null, 则使用 System.err;
s2)一个 JavaFileManager:  如果是null, 则使用编译器的标准文件管理器;
s3)一个 DiagnosticListener;
s4)选项字符串:  如果没有选项, 则为null;
s5)用于注解处理的类名字: 如果没有指定类名字, 则为null;
s6)用于源文件的 JavaFileObject 实例;
5)需要为最后3个参数提供 Iterable 对象, 如:
Iterable<String> options = Arrays.asList("-g", "-d", "classes");
6)如果希望编译器从磁盘读取源文件, 可以让 StandardJavaFileManager 将文件 名字符串或 File 对象转译成 JavaObject 实例, 如:
<span style="font-family:Microsoft YaHei;font-size:14px;">StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null) ;
Iterable<JavaFileObject> fileObjects = fileManager.getJavaFileObjectsFromStrings(filename);</span>
7) 如果希望 编译器从磁盘之外的地方读取 源代码: 可以提供自己的 JavaFileObject 的子类;
8)CompilationTask接口: 扩展了Callable<Boolean>接口,可以将其传递给一个 Executor, 使其可以在另一个线程中执行, 或者可以直接调用call 方法。返回值如果是 false, 则调用失败;
<span style="font-family:Microsoft YaHei;font-size:14px;">Callable<Boolean> task = new JavaCompiler.compilationTasks(null, fileManager, diagnostics,  options, null, fileObjects);
if(!task.call())println("compilation failed");</span>
9)problem+solution:
9.1)problem:事实证明,要告知编译器的文件管理器去使用这些文件对象还是比较棘手的,因为类库没有提供实现了 StandardJavaFileManager 接口的类;
9.2)solution:我们需要子类化 ForwardingJavaFileManager 类, 该类将所有的调用都代理给了给定的文件管理器。
Conclusion)
C1) 总之,如果只是想以常规的方式调用编译器, 那就只需要调用 JavaCompiler 任务的run方法, 去读写磁盘文件;
C2)如果想对文件处理和错误报告进行更多 的控制, 可以使用 CompilationTask 类;


 

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

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

相关文章

Java 多线程总结

一、多线程实现方式 &#xff08;1&#xff09;继承Thread类&#xff0c;覆盖run方法 &#xff08;2&#xff09;实现Runnable接口&#xff0c;覆盖run方法&#xff0c;将对象传人Thread对象中实现Runnable接口比继承Thread类所具有的优势&#xff1a; 1&#xff09;适合多个相…

古巴比伦乘法_古巴:为生产做准备

古巴比伦乘法“它可以在我的本地机器上运行&#xff01;” 如今&#xff0c;它听起来像模因&#xff0c;但是“开发环境与生产环境”的问题仍然存在。 作为开发人员&#xff0c;您应始终牢记&#xff0c;您的应用程序有一天将在生产环境中开始运行。 在本文中&#xff0c;我们将…

java编译器API——使用编译工具

【0】README 0.1&#xff09;以下内容转自&#xff1a; http://suntips.iteye.com/blog/69002 0.2&#xff09;for basic java compiler API, please visit http://blog.csdn.net/pacosonswjtu/article/details/50718494 1&#xff09;当你需要更好的处理这些结果时,你可以使…

Java 线程同步总结

一、synchronized&#xff08;1&#xff09;synchronized方法 &#xff08;2&#xff09;synchronized块 二、Lock 注意&#xff1a;及时释放Lock锁&#xff0c;否则会出现死锁&#xff0c;通常在finally代码释放锁 &#xff08;1&#xff09;ReentrantLock 实现…

aws es方案_AWS Elasticsearch后模式

aws es方案碰巧我们在SaaS版本的LogSentinel上遇到了生产问题–我们的Elasticsearch停止了对新数据编制索引。 由于Elasticsearch只是辅助存储&#xff0c;因此没有数据丢失&#xff0c;但这给我们的客户带来了一些问题&#xff08;他们无法在其仪表板上看到实时数据&#xff0…

java注解总结

【0】README 0.1&#xff09;本文主要对 java 注解做总结&#xff1b; 【1】处理注解的级别 level1&#xff09; 在运行期级别处理注解&#xff1a; http://blog.csdn.net/pacosonswjtu/article/details/50719361level2&#xff09;在源码级别处理注解&#xff1a; http://b…

Java 类加载总结

一、类加载过程 装载链接验证准备解析初始化二、类初始化情况 1&#xff09;创建类的实例&#xff0c;也就是new一个对象 2&#xff09;访问某个类或接口的静态变量&#xff0c;或者对该静态变量赋值 3&#xff09;调用类的静态方法 4&#xff09;反射&#xff08;Class.…

maven依赖管理_依赖管理和Maven

maven依赖管理Maven伟大而成熟。 几乎所有事物都总有解决方案。 您可能在组织项目上遇到的主要情况是依赖管理。 而不是每个项目都具有自己的依赖关系&#xff0c;您需要一种集中的方式来继承那些依赖关系。 在这种情况下&#xff0c;您可以在父舞会上声明托管依赖项。 在我的…

JavaBean 持久化

【0】README 0.1&#xff09;本文文字描述转自 core java volume 2&#xff0c;旨在学习 JavaBean 持久化 的基础知识&#xff1b; 0.2&#xff09;本文所有源代码荔枝均为原创&#xff1b; 0.3&#xff09; for complete souce code, please visit https://github.com/pa…

apache kafka_Apache Kafka消费者再平衡

apache kafka消费者重新平衡决定哪个消费者负责某个主题的所有可用分区的哪个子集。 例如&#xff0c;您可能有一个包含20个分区和10个使用者的主题。 在重新平衡结束时&#xff0c;您可能希望每个使用者都从2个分区中读取数据。 如果关闭了这些使用者中的10个&#xff0c;则可…

Java 注解总结

一、注解定义 注解早在J2SE1.5就被引入到Java中&#xff0c;主要提供一种机制&#xff0c;这种机制允许程序员在编写代码的同时可以直接编写元数据。 二、元注解 Target 说明了被修饰的注解的应用范围&#xff0c;也就是被修饰的注解可以用来注解哪些程序元…

编译原理三大经典书籍(龙书 虎书 鲸书)

以下内容转自&#xff1a; http://blog.csdn.net/skymingst/article/details/7436892 1、龙书&#xff08;Dragon book&#xff09; 英文名&#xff1a;Compilers: Principles,Techniques,and Tools 作者&#xff1a;Alfred V.Aho,Ravi Sethi,Jeffrey D.Ullman 中文名&…

两个时间之间是多少小时_那是两个小时我不会回来

两个时间之间是多少小时正如我之前关于linting主题所说的 &#xff0c;花时间修改代码的好处很有限&#xff0c;因为自动工具告诉您这样做。 更糟糕的是&#xff0c;这些工具并非万无一失。 例如&#xff0c;我们一直在针对完美无害的try-with-resources构造周围的SpotBugs警告…

java的类载入器

【0】README 0.1&#xff09;本文文字转自&#xff1a; 深入剖析tomcat&#xff0c; 旨在 理解 jvm 的类载入器&#xff1b; 【1】 jvm的类载入器相关 1&#xff09;jvm 使用了3种类载入器来载入所需要的类&#xff1a;分别是引导类载入器&#xff08;bootstrap class load…

Java 代理总结

一、代理 为其他对象提供一种代理以便控制对这个对象的访问。 &#xff08;1&#xff09;静态代理 &#xff08;2&#xff09;动态代理 1&#xff09;JDK自带的动态代理 2&#xff09;javaassist字节码操作库实现 3&#xff09;CGLIB 4&#xff09; ASM&#xff08;底层…

分解因数 递归_递归分解WAR文件

分解因数 递归抽象 是否曾经需要分解WAR文件以及分解WAR文件中的所有JAR文件&#xff1f; 是的&#xff0c;我也是&#xff01; 我写了ferris-war-exploder来爆炸&#xff1a; 一个JAR文件 一个WAR文件&#xff0c;它找到的每个JAR文件也会爆炸。 包含每个JAR文件&#xff…

jvm(2)-java内存区域

【0】README 0.1&#xff09;本文转自 深入理解jvm&#xff0c; 旨在学习 java内存区域 的基础知识&#xff1b; 【1】运行时数据区域 1&#xff09;jvm 所管理的内存将会包括以下几个运行时数据区域 1.1&#xff09;方法区&#xff1b;&#xff08;线程共享&#xff09; 1.2&…

Java Socket编程总结

一、网络API InetAddress     用于标识网络上的硬件资源&#xff0c;主要是IP地址 URL         统一资源定位符&#xff0c;通过URL可以直接读取或写入网络上的数据Sockets      使用TCP协议实现的网络通信Socket相关的类Datagram     使用UDP协议&am…

java插入排序_Java程序要插入排序

java插入排序Java程序插入示例的排序。 显示了示例仿真以及时间复杂度。 插入排序是一种简单的排序算法&#xff0c;可以一次构建一个最终的排序数组&#xff08;或列表&#xff09;。 它比冒泡排序有效得多&#xff0c;并且在大型列表上的效率比快速排序 &#xff0c;堆排序或…

jvm(2)-JVM内存的设置(解决eclipse下out of memory问题)

【0】README 0.1&#xff09;本文转自&#xff1a; http://blog.csdn.net/sjf0115/article/details/8889201 一、JVM内存的设置的原理 默认的java虚拟机的大小比较小&#xff0c;在对大数据进行处理时java就会报错&#xff1a;java.lang.OutOfMemoryError。 设置jvm内存的方…