I. 简介

FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

模板编写为FreeMarker Template Language (FTL)。 在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。 这种方式通常被称为 MVC (模型 视图 控制器) 模式,对于动态网页来说,是一种特别流行的模式。

上图如果Template换成Jsp,对于大多数JavaEE开发者来说,会显得非常熟悉。FreeMarker最初的设计,是被用来在MVC模式的Web开发框架中生成HTML页面的,它没有被绑定到 Servlet或HTML或任意Web相关的东西上。它也可以用于非Web应用环境中。 它完全可以替代Jsp,实现页面的静态化。

II. 入门程序

需求

利用FreeMarker 实现一个网站用户登录显示用户信息。

流程

  • 添加Jar包
  • 创建Configration对象
  • 创建模板
  • 获取模板
  • 创建模板需要的数据
  • 合并模板与数据

创建工程

FreeMarker 不依赖于web容器,所以普通的java项目也可以使用FreeMarker 。FreeMarker 的依赖包下载可以在官网进行获取,目前最新为FreeMarker 2.3.28版本。

利用IDEA创建工程,如果是普通项目则将 freemarker.jar 添加的 lib 目录下。如果创建为Maven项目,则可以在pom文件中添加:

<dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.28</version>
</dependency>

如果是结合Spring使用FreeMarker,则需要额外添加依赖:

<dependency><groupId>org.springframework</groupId><artifactId>spring-context-support</artifactId><version>${spring.version}</version>
</dependency>

创建Configration对象

首先,需要创建一个 freemarker.template.Configuration 实例, 然后对其属性进行设置。Configuration 实例是存储 FreeMarker 应用级设置的核心部分。同时,它也处理创建和缓存预解析模板(比如 Template 对象)的工作。

Configuration 的创建代价很高,不需要重复创建实例,尤其是会丢失缓存。Configuration 实例就是应用级别的单例,不管一个系统有多少独立的组件来使用 FreeMarker, 它们都会使用他们自己私有的 Configuration 实例。

只需要在应用(可能是servlet)生命周期的开始执行一次

// 创建Configuration实例,指定版本
Configuration configuration = new Configuration(Configuration.getVersion());
try {// 指定configuration对象模板文件存放的路径configuration.setDirectoryForTemplateLoading(new File("/where/you/store/templates"));// 设置config的默认字符集,一般是UTF-8configuration.setDefaultEncoding("UTF-8");// 设置错误控制器configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
} catch (IOException e) {e.printStackTrace();
}

关于错误控制器,FreeMarker 四个预先编写的错误控制器:

  • DEBUG_HANDLER:打印堆栈信息和重新抛出异常,这是默认的异常控制器。
  • HTML_DEBUG_HANDLER:和DEBUG_HANDLER相同,但是可以格式化堆栈跟踪信息。HTML页面,建议使用它而不是DEBUG_HANDLER。
  • IGNORE_HANDLER:简单地忽略所有异常。它对处理异常没有任何作用,也不会重新抛出异常。
  • RETHROW_HANDLER: 简单重新抛出所有异常而不会做其他的事情。这个控制器对Web应用很好,因为它在生成的页面发生错误的情况下,给了对Web应用的更多的控制权。

创建模板

/where/you/store/templates 文件夹下创建模板文件 hello.ftl

<html>
<head><title>欢迎  ${user.username}!</title>
</head>
<body><h1>欢迎 ${user.username}!</h1><h2>年龄: ${user.age}</h2><h2>${user.record.id}:${user.record.name}</h2>
</body>
</html>

获取模板

模板代表了 freemarker.template.Template 实例。 典型的做法是直接使用 Configuration 实例的 getTemplate() 方法获取一个 Template 实例,例如:

// 获取模版
Template template = configuration.getTemplate("hello.ftl");

当调用这个方法的时候,将会创建一个 hello.ftlTemplate 实例,通过读取 */where/you/store/templates/hello.ftl 文件,之后解析(编译)它。Template 实例以解析后的形式存储模板, 而不是以源文件的文本形式。

Configuration 会缓存 Template 实例,当需要再次获得 hello.ftl 的时候,它可能不再读取和解析模板文件了, 而只是返回第一次获取的 Template实例。

准备数据

需要给模板的数据往往来自真实的业务数据,可能从数据库、文件等获得。在JavaEE中往往有单独的Service和Dao层帮助我们准备好需要的数据。数据的包装形式往往是JavaBean。

  • 使用 java.lang.String 来构建字符串。
  • 使用 java.lang.Number 来派生数字类型。
  • 使用 java.lang.Boolean 来构建布尔值。
  • 使用 java.util.List 或Java数组来构建序列。
  • 使用 java.util.Map 来构建哈希表。
  • 使用自定义的bean类来构建哈希表,bean中的项和bean的属性对应。

这里我们定义两个类,一个是User类,一个是用户记录Record类。

public class User {private String username;private int age;private Record record;/** setter and getter **/
}public class Record {private long id;private String name;/** setter and getter **/
}

下面是构建这个数据模型的Java代码片段:

// 准备数据
Map<String, User> map = new HashMap<>();Record record = new Record();
record.setId(1L);
record.setName("记录一");User user = new User();
user.setUsername("小明");
user.setAge(18);
user.setRecord(record);map.put("user", user);

合并模板与数据

数据模型+模板=输出,这一过程是由模板的 process 方法完成的。它需要数据和 Writer 对象两部分作为参数,然后向 Writer 对象写入产生的内容。这里创建一个Writer对象,指定生成的文件保存的路径及文件名:

// 创建一个Writer对象,指定生成的文件保存的路径及文件名
Writer writer = new FileWriter(new File("/where/you/store/outputs"));
template.process(map, writer);

这里用到了Java IO相关的操作,基于 out 对象,必须保证 out.close() 最后被调用。典型的Web应用程序中,不能关闭 out 对象,对此FreeMarker会在模板执行成功后调用 out.flush(),所以不必担心 (这一功能也可以在 Configuration 中禁用) 。

还有一点,一旦获得了 Template 实例, 就能将它和不同的数据模型进行不限次数的合并。此外, 当 Template实例创建之后的模板文件 (hello.ftl) 才能访问,而不是在调用 process 方法时。

结果

III. 书写模板

布尔型format

FreeMarker 无法直接对布尔型的值直接输出True、False,需要对其format才能输出。

${布尔变量名?string('yes', 'no')}

日期format

FreeMarker 对Java的Date类型对象取值分两种情形,当用的sql包下的Date可以直接取值;当用的是util包下的Date则需要进行format才能输出。因为Java API通常不区别 java.util.Date 只存储日期部分, 时间部分,或两者都存。 为了用本文正确显示值,FreeMarker必须知道 java.util.Date 的日期究竟要显示哪一部分。

${sqldate}
${utildate?string("yyyy/MM/dd HH:mm:ss")}

null或missing

FreeMarker 不可以输出null,类似于空指针异常。同样,如果通过 ${变量名} 表达式取值,压根不存在变量名,FreeMarker也会报出错误。 FreeMarker 对此加入了判空操作,利用 可以在null或missing的情况下不输出或输出默认文本。

${nullVar!'默认输出'}
${missingVar!}

变量定义赋值运算

通过assign标签给变量进行赋值,赋值完的变量可以在 ${} 中进行相应的运算。

<#assign a=100 />
<#assign b=2 />
a + b = ${a + b}
a - b = ${a - b}
a x b = ${a * b}
a / b = ${a / b}
a % b = ${a % b}

遍历List

FreeMarker 可以采用list标签遍历Java中的List集合数据来输出文本,极为方便。

<#list numList as item>list:${item}
</#list>

FreeMarker 也可以利用list标签来遍历map集合。需要取到map中的key,再通过key来取出相应的value。取map中的key可以通过 map?keys 来获取:

<#list objectMap?keys as key>${key}:${objectMap[key].username}
</#list>

if-else

FreeMarker 提供了逻辑判断,采用if和else标签实现。满足条件则输出,不满足则忽略。

<#list numList as item><#if item!=2 >list:${item}</#if>
</#list>

<#list numList as item><#if item &gt; 3 ><font color="red">list:${item}</font><#elseif item == 3><font color="blue">list:${item}</font><#else><font color="green">list:${item}</font></#if>
</#list>

其中&gt;是大于的转义,同样&lt;是小于的转义。利用双问号 ?? 或者 ?exists 可以对对象进行判空,例如:

<#if item??>
<#if item?exists>

同样FreeMarker 也支持多条件判断,用 &&|| 进行连接。

switch

FreeMarker 的switch和Java中的switch十分类似,同样由switch、case、break和default标签组成。switch标签支持数值和字符串两种类型。

<#assign str="java" />
<#switch str><#case "python"> 学习python <#break><#case "java"> 学习java <#break><#default> 学习别的。。。
</#switch><#assign str="11.1" />
<#switch str><#case "11.1"> 学习python <#break><#case "11.11"> 学习java <#break><#default> 学习别的。。。
</#switch>

IV. 使用函数

字符串内建函数

<#assign a="hello"/>
<#assign b="world"/>
<li>连接</li>
${a + b}
<li>截取</li>
${(a + b)?substring(5, 8)}
<li>长度</li>
${(a + b)?length}
<li>大写</li>
${(a + b)?upper_case}
<li>小写</li>
${(a + b)?lower_case}
<li>index</li>
${(a + b)?index_of('o')}
<li>last_index</li>
${(a + b)?last_index_of('o')}
<li>替换</li>
${(a + b)?replace('o', 'xx')}

list内建函数

<#assign myList=[3, 4, 5, 6, 1, 3, 7, 9, 2] />
mySize大小:${myList?size}
mySize第三个元素:${myList[3]}
顺序:
<#list myList?sort as item>${item_index} : ${item}
</#list>
逆序:
<#list myList?sort?reverse as item>${item_index} : ${item}
</#list>

常用内建函数

处理字符串:

  • substring cap_first ends_with contains
  • date datetime time (字符串转日期时间)
  • starts_with index_of last_index_of split trim

处理数字:

  • string x?string(“0.##”) (保留两位小数)
  • round floor ceiling

处理list:

  • first last seq_contains seq_index_of
  • size reverse sort sort_by
  • chunk(分块处理)

其他:

  • is函数:is_string is_number is_method
  • has_content
  • eval求值

V. 自定义功能

在FreeMarker内部中可用的变量都是实现了freemarker.template.TemplateModel 接口的Java对象,而我们可以使用基本的Java集合类作为变量,是因为FreeMarker提供了一种对象包装的功能特性,我们用的基本的Java集合类变量会在内部被替换为适当的 TemplateModel 类型。

在自定义函数和指令中,我们需要使用FreeMarker中的数据类型,而非直接使用Java中的数据类型,所以有必要先熟悉FreeMarker中定义了哪些数据类型。

标量

FreeMarker中定义了四种类型标量:布尔值,数字,字符串以及日期。每一种标量都是 TemplateXxxModel 接口的实现,Xxx 是Java中相关类型的名称。比如 TemplateBooleanModel 。这些接口中都只定义了一个方法用于转换类型为Java类型:getAsXxx() 。在名称上,只有字符串标量有些例外,字符串标量的接口是 TemplateScalarModel,而不是TemplateStringModel

除了 SimpleBoolean 类型,这些接口的一个简单的实现是 freemarker.template 包下的 SimpleXxx 类。为了代表布尔值, 可以使用 TemplateBooleanModel.TRUETemplateBooleanModel.FALSE 来单独使用。 同样,字符串标量的实现类是 SimpleScalar,而不是 SimpleString

容器

除了标量,对于Java的集合数组类型FreeMarker也定义了相关的数据类型称为容器。容器包括哈希表序列集合三种类型。

  • 哈希表

    哈希表是实现了 TemplateHashModel 接口的Java对象。TemplateHashModel 有两个方法: TemplateModel get(String key) 方法根据给定的名称返回子变量, boolean isEmpty()方法表明哈希表是否含有子变量。get 方法当在给定的名称没有找到子变量时返回null。

    TemplateHashModelEx 接口扩展了 TemplateHashModel。它增加了更多的方法,使得可以使用内建函数 values 和 keys 来枚举哈希表中的子变量。

    经常使用的实现类是 SimpleHash,该类实现了 TemplateHashModelEx 接口。从内部来说,它使用一个 java.util.Hash 类型的对象存储子变量。 SimpleHash 类的方法可以添加和移除子变量。 这些方法应该用来在变量被创建之后直接初始化。

  • 序列(数组)

    序列是实现了 TemplateSequenceModel 接口的Java对象。它包含两个方法:TemplateModel get(int index)int size()

    经常使用的实现类是 SimpleSequence。该类内部使用一个 java.util.List 类型的对象存储它的子变量。 SimpleSequence 有添加子元素的方法。 在序列创建之后应该使用这些方法来填充序列。

  • 集合

    集合是实现了 TemplateCollectionModel 接口的Java对象。这个接口定义了一个方法: TemplateModelIterator iterator()TemplateModelIterator 接口和 java.util.Iterator 相似,但是它返回 TemplateModels 而不是 Object, 而且它能抛出 TemplateModelException 异常。

    通常使用的实现类是 SimpleCollection

自定义函数

有时候FreeMarker内建函数不一定能够满足我们的处理需要,我们可以自定义函数来处理数据并展示。FreeMarker自定义函数需要自定义处理数据的类,该类需要实现 TemplateMethodModel 接口 ,接口中的 TemplateModel exec(java.util.List arguments) 方法也需要我们重新实现。当调用自定义函数时,自定义类的exec 方法将会被调用。 形参将会包含FTL方法调用形参的值。exec 方法的返回值给出了FTL方法调用表达式的返回值。

我们以自定义一个数组排序的函数为例,首先新建一个 MySortMethod 类实现 TemplateMethodModel 接口,并重写 exec 方法。

public class MySortMethod implements TemplateMethodModelEx {/*** 自定义函数需要实现的方法* @param list 在.ftl模板中调用自定义方法传的参数* @return 返回结果* @throws TemplateModelException*/@Overridepublic Object exec(List list) throws TemplateModelException {// 接收传入的ListDefaultListAdapter defaultListAdapter = (DefaultListAdapter) list.get(0);List<Integer> arrayList = (List<Integer>) defaultListAdapter.getAdaptedObject(Integer.class);// 接收传入的升序还是降序布尔值boolean asc = "yes".equals(((SimpleScalar) list.get(1)).getAsString()) ? true : false;Collections.sort(arrayList, new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {if(asc) {return o1 - o2;}else {return o2 - o1;}}});return arrayList;}
}

在调用函数前,我们需要在模板数据里添加:

List<Integer> list = new ArrayList<>();
for (int i = 0; i < 5; i++) {list.add(i+1);
}
map.put("numList", list);
map.put("my_sort", new MySortMethod());

在模板中调用自定义函数 my_sort ,传入两个参数:

顺序:
<#list my_sort(numList, true?string('yes', 'no')) as item>
${item_index} : ${item}
</#list>
逆序:
<#list my_sort(numList, false?string('yes', 'no')) as item>
${item_index} : ${item}
</#list>

输出结果为:

注:新版本的Api好像不太一样,在 MySortMethod.java 的13、14行中获取传入的list参数部分,我这里的写法是临时摸索出来的,有知道正确的获取list数据姿势的请不吝赐教。

自定义指令

类似自定义函数,我们也可以自定义指令,类似于if-else、assign这样的指令。自定义指令的需要使用 @ 符号,而不是 # 符号。可以使用 TemplateDirectiveModel 接口在Java代码中实现自定义指令。TemplateDirectiveModel 在 FreeMarker 2.3.11 版本时才加入, 来代替快被废弃的 TemplateTransformModel

下面举个栗子,自定义用来校验用户名密码的指令:

public class MyValidationDirective implements TemplateDirectiveModel {/**** @param environment 环境变量(实现复杂功能时可能会用)* @param map 在.ftl模板中使用自定义指令传的参数(key-value形式)* @param templateModels 返回值,数组形式* @param templateDirectiveBody 指令内容* @throws TemplateException* @throws IOException*/@Overridepublic void execute(Environment environment,Map map,TemplateModel[] templateModels,TemplateDirectiveBody templateDirectiveBody)throws TemplateException, IOException {SimpleScalar username = (SimpleScalar) map.get("username");SimpleScalar password = (SimpleScalar) map.get("password");if("admin".equals(username.getAsString()) && "123456".equals(password.getAsString())) {templateModels[0] = TemplateBooleanModel.TRUE;} else {templateModels[0] = TemplateBooleanModel.FALSE;}List<String> rights = new ArrayList<>();rights.add("insert");rights.add("delete");rights.add("update");rights.add("select");templateModels[1] = new SimpleSequence(rights);templateDirectiveBody.render(environment.getOut());}
}

模板编写:

利用 <@role /> 标签需要传入模版数据前进行添加:

map.put("role", new MyValidationDirective());

或者在模板文件中使用内建函数 new() 将指令放到一个FTL库中:

<#assign role="directive.MyValidationDirective"?new() />

输出结果:

macro宏指令

macro语法:

<#macro 指令名称 param1 param2 param3 paramN>template_code 可以获取参数${param1}<#nested />
</#macro>

调用语法:

<@指令名称 param1="xxx" param2="xxx" /><@指令名称 param1="xxx" param2="xxx">nested_template
</@指令名称>

下面举一些例子。

<h2>无参数的macro</h2>
<#macro test1>我是无参数的macro
</#macro>
<@test1 /><h2>有参数的macro</h2>
<#macro test2 param1 param2>我是有参数的macro,参数是${param1}和${param2}
</#macro>
<@test2 param1="hello" param2="world"/><h2>有默认参数的macro</h2>
<#macro test3 param1 param2="world">我是有默认参数的macro,参数是${param1}和${param2}
</#macro>
<@test3 param1="hello" />
<@test3 param1="hello" param2="earth"/><h2>有多个参数的macro</h2>
<#macro test4 param1 param2 paramExt...>我是有多个参数的macro,参数是${param1}、${param2}、${paramExt['param3']}和${paramExt['param4']}
</#macro>
<@test4 param1="hello" param2="world" param3="hi" param4="man"/><h2>有nested的macro</h2>
<#macro test5 param1 param2="hi" paramExt...>我是有多个参数的macro,固定参数是${param1}和${param2}<#nested paramExt['param3'] paramExt['param4']/>
</#macro>
<@test5 param1="hello" param2="world" param3="hi" param4="man"; loopVar1, loopVar2>可变参数为${loopVar1}和${loopVar2}
</@test5>

function方法

function语法:

<#function 方法名 param1 param2><#return param1和param2的操作>
</#function>

调用语法:

${方法名(param1, param2)}

下面举一些例子。

<#function doAdd param1 param2><#return param1+param2 />
</#function>
${doAdd(2, 3)}

输出为5。

VI. 结合Spring

以商城项目生成商品详情页面为例,一般访问url中每个商品id有个静态页面,例如京东商品页面。每个商品静态页面可以利用FreeMarker来生成。

配置Configration

Spring要结合FreeMarker需要在配置文件中进行配置Configration。

<!--配置FreeMarker-->
<bean id="freemarkerConfig"class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer"><property name="templateLoaderPath" value="/WEB-INF/ftl/" /><property name="defaultEncoding" value="UTF-8" />
</bean>

静态文件生成时机

  1. 当用户第一次访问时生成静态文件。此方案当高并发时,会出现生成一半的页面显示。不推荐该方案。
  2. 提前生成好静态页面。当后台管理员添加、编辑商品时生成静态网页。

生成页面

/*** 生成静态页面Service*/
public class StaticPageServiceImpl implements StaticPageService {@Autowiredprivate ItemService itemService;@Autowiredprivate FreeMarkerConfig freeMarkerConfig;@Value("${STATIC_PAGE_PATH}")private String STATIC_PAGE_PATH;@Overridepublic TaotaoResult getHtml(Long itemId) throws IOException, TemplateException {// 获取商品基本信息、详细介绍和商品参数TbItem tbItem = itemService.getItemById(itemId);String desc = itemService.getItemDescById(itemId);String param = itemService.getItemParamById(itemId);// 生成静态页面Configuration configuration = freeMarkerConfig.getConfiguration();Template template = configuration.getTemplate("item.ftl");Map<String, Object> root = new HashMap<>();root.put("item", tbItem);root.put("itemDesc", desc);root.put("itemParam", param);Writer writer = new FileWriter(new File(STATIC_PAGE_PATH + itemId + ".html"));template.process(root, writer);writer.flush();writer.close();return TaotaoResult.ok();}
}

模版文件

模版文件的准备仅仅需要将jsp或html文件修改成ftl模板即可。

利用Nginx访问静态资源

生成好的静态html文件,可以利用Nginx服务器进行部署,这样性能比Tomcat要好,其次也不需要各种延迟加载等操作。

JavaEE进阶——FreeMarker模板引擎相关推荐

  1. Spring Boot 系列(五)web开发-Thymeleaf、FreeMarker模板引擎

    前面几篇介绍了返回json数据提供良好的RESTful api,下面我们介绍如何把处理完的数据渲染到页面上. Spring Boot 使用模板引擎 Spring Boot 推荐使用Thymeleaf. ...

  2. Freemarker模板引擎

    模板引擎的实质就是将页面结构提前写好,然后将数据渲染到模板上生成一个静态页面,这样一来,下次就可以 直接访问静态文件,不用进行额外的获取数据的操作(例如:访问数据库),这样大大提升了网站的访问速度. ...

  3. 利用freemarker模板引擎进行word导出

    FreeMarker是一个用Java语言编写的模板引擎,它基于模板来生成文本输出.FreeMarker与Web容器无关,即在Web运行时,它并不知道Servlet或HTTP.它不仅可以用作表现层的实现 ...

  4. Java Email 发HTML邮件工具 采用 freemarker模板引擎渲染

    Java Email 发HTML邮件工具 采用 freemarker模板引擎 1.常用方式对比 Java发送邮件有很多的实现方式 第一种:Java 原生发邮件mail.jar和activation.j ...

  5. Java之利用Freemarker模板引擎实现代码生成器,提高效率

    开心一笑 [1.你以为我会眼睁睁的看着你去送死?我会闭着眼睛.2.给你讲个故事,从前有个笨蛋,他非常笨,别人问他问题他只会回答"没有",这个故事你听过吗?] 视频教程 大家好,我录 ...

  6. Java项目中利用Freemarker模板引擎导出--生成Word文档

    应邀写的一篇文章:Java项目中利用Freemarker模板引擎导出--生成Word文档 资源下载:https://download.csdn.net/download/weixin_41367523 ...

  7. freemarker模板引擎,页面404,没有任何错误信息提示

    一.问题描述 freemarker模板引擎,页面404,没有任何错误信息提示,如下图所示,404首先排除页面是否存在,这个肯定是存在的,在一个排除下路径是否写错了,这个也没有问题,前面都能访问好好的, ...

  8. struts2 html模板,使用FreeMarker模板引擎作为Struts2的视图技术

    FreeMarker是一个非常优秀的模板引擎,这个模板引擎可用于任何场景,FreeMarker负责将数据模型中的数据合并到模板中,从而生成标准输 出.FreeMarker可以提供昜好的团队协作,对于界 ...

  9. Freemarker模板引擎学习,生成html里的动态表格,可合并单元格

    需求:现有html模板,需动态填充数据,并且包含表格,表格大小不固定,根据数据多少确定表格大小. 解析:两种方案: 1.java代码实现:将模板文件读出为StringBuffer,找到特定位置,循环生 ...

最新文章

  1. 制药行业的GxP代表什么?
  2. 基于SSM实现校友录管理平台
  3. 动态规划之----最长公共子序列
  4. OpenCASCADE可视化:应用交互服务之交互式上下文
  5. laravel 5.1 添加第三方扩展库
  6. (06)VHDL实现计数器
  7. 【原】[webkit移动开发笔记]之空链接是使用javascript:void(0)还是使用#none
  8. 用python写一个程序来验证每个数字的生成概率是否相同_Python实现简单生成验证码功能【基于random模块】...
  9. 记一次Pr安装转场插件导致AE报错问题解决
  10. android 编译太慢,如何解决android studio运行编译速度慢
  11. 计算机设备不能正常启动怎么办,电脑没有找到可引导设备怎么办
  12. 小胖子学spring-aop
  13. 渗透工具SharpXDecrypt:Xshell全版本凭证一键恢复工具,针对Xshell全版本在本地保存的密码进行解密
  14. 今天分享给你几个绘制Excel表格的技巧
  15. 国二python是什么意思_国二都考什么啊
  16. 南卡Runner Pro4发布!「響」科技令其问鼎全球最强骨传导耳机旗舰机皇!
  17. FME2018软件安装
  18. 银行服务器销售业务,利用呼叫中心进行远程销售
  19. 安全狗防护引擎安装失败
  20. First-chance exception at 0x774CEB23 (ntdll.dll) in XXX.exe: 0xC0000005: Access violation writing

热门文章

  1. 音乐推荐系统搭建试验
  2. Android Studio开发工具的设置
  3. 机器人 python 路径规划_基于Q-learning的机器人路径规划系统(matlab)
  4. win32com为word添加页码(示例)
  5. npm 安装依赖报错解决方法总结
  6. 互联网IT 校招与内推:软实力的技巧
  7. hdmi接口有什么用_路由器USB接口有什么用 路由器USB接口作用介绍【详解】
  8. hyper-v 安装 openwrt x86 squashfs
  9. 【对抗攻击论文笔记】Enhancing the Transferability of Adversarial Attacks through Variance Tuning
  10. ImageMagick convert多张照片JPG转成pdf格式,pdfunite合并PDF文件