核心J2EE模式 - 截取过滤器

背景

呈现层请求处理机制接收许多不同类型的请求,这些请求需要不同类型的处理。一些请求被简单转发到适当的处理程序组件,而其他请求必须在进一步处理之前进行修改,审核或未压缩。

存在的问题

需要预处理和后处理客户端Web请求和响应。

当请求进入Web应用程序时,它通常必须在主处理阶段之前通过几个入口测试。例如,

  • 客户端是否经过身份验证?

  • 客户端是否有有效的会话?
  • 客户端的IP地址是否受信任的网络?
  • 请求路径是否违反任何约束?
  • 客户端使用什么编码来发送数据?
  • 我们是否支持客户端的浏览器类型?

其中一些检查是测试,导致是或否答案,决定处理是否继续。其他检查将输入的数据流处理成适合处理的形式。

经典的解决方案包括一系列条件检查,任何失败的检查都会中止请求。嵌套if / else语句是一种标准策略,但是这种解决方案导致代码脆弱性和复制和粘贴风格的编程,因为过滤流程和过滤器的操作被编译到应用程序中。

以灵活和不显眼的方式解决这个问题的关键是拥有一个简单的机制,用于添加和删除处理组件,其中每个组件完成特定的过滤操作。

军队

  • 诸如检查数据编码方案或关于每个请求的记录信息的公共处理按照每个请求完成。

  • 需要集中共同的逻辑。
  • 服务应该容易地添加或删除不引人注目,而不影响现有组件,以便它们可以以各种组合使用,例如
    • 日志和身份验证
    • 调试和转换特定客户端的输出
    • 解压缩和转换输入的编码方案

创建可插拔过滤器,以标准方式处理公共服务,而不需要更改核心请求处理代码。过滤器拦截传入请求和传出响应,允许预处理和后处理。我们能够不引人注意地添加和删除这些过滤器,而不需要更改现有的代码。

实际上,我们能够通过各种常见的服务(如安全性,日志记录,调试等)来装饰我们的主要处理。这些过滤器是独立于主应用程序代码的组件,它们可以以声明方式添加或删除。例如,可以修改部署配置文件以设置一个过滤器链。相同的配置文件可能包括特定URL到此过滤器链的映射。当客户端请求与此配置的URL映射匹配的资源时,链中的过滤器在调用所请求的目标资源之前都按顺序进行处理。

结构体

图7.1表示截取滤波器模式。

 
图7.1截取过滤器模式类图

参与者和责任

图7.2表示截取滤波器模式。


图7.2截取滤波器序列图

FilterManager

FilterManager管理过滤器处理。它以正确的顺序创建具有相应过滤器的FilterChain,并启动处理。

FilterChain

FilterChain是独立过滤器的有序集合。

FilterOne,FilterTwo,FilterThree

这些是映射到目标的各个过滤器。FilterChain协调其处理。

目标

目标是客户端请求的资源。

策略

自定义过滤策略

过滤器是通过开发人员定义的自定义策略实现的。这不如首选标准过滤策略的灵活性和功能更低,这在下一节中介绍,仅适用于支持2.3 servlet规范的容器。自定义过滤器策略的功能不那么强大,因为它不能以标准和便携的方式提供请求和响应对象的包装。另外,请求对象不能修改,如果过滤器要控制输出流,必须引入某种缓冲机制。要实现自定义过滤策略,开发人员可以使用Decorator模式[GoF]来围绕核心请求处理逻辑包装过滤器。例如,可能会有一个包装认证过滤器的调试过滤器。实施例7.1和实施例7。

示例7.1实现过滤器 - 调试过滤器

 public class DebuggingFilter implements Processor {
private Processor target;// 处理器目标; public DebuggingFilter(processor myTarget){
target = myTarget;
} public void execute(ServletRequest req,
ServletResponse res) throws IOException,
ServletException {
//在这里做一些过滤处理,比如
//显示请求参数
target.execute(req,res);
}
}

示例7.2实现过滤器 - 核心处理器

 public class CoreProcessor implements Processor {
private Processor target;
public CoreProcessor() {
this(null);
} public CoreProcessor(Processor myTarget) {
target = myTarget;
} public void execute(ServletRequest req,
ServletResponse res) throws IOException,
ServletException {
//在这里做核心处理
} }

在servlet控制器中,我们委托一个被调用processRequest来处理传入请求的方法 ,如例7.3所示。

示例7.3处理请求

public void processRequest(ServletRequest req,
ServletResponse res)
throws IOException, ServletException {
Processor processors = new DebuggingFilter(
new AuthenticationFilter(new CoreProcessor()));
processors.execute(req, res);
//然后调度到下一个资源,这可能是
//要显示的视图
dispatcher.dispatch(req, res); }

仅作为示例目的,假设每个处理组件在执行时都会写入标准输出。例7.4显示了可能的执行输出。

示例7.4写入标准输出的消息

调试过滤器预处理完成...
认证过滤处理完成...
核心处理完成...
调试过滤器后处理完成...

一系列处理器按顺序执行。每个处理器,除了链中的最后一个,被认为是一个过滤器。最终的处理器组件是我们封装我们要为每个请求完成的核心处理的地方。考虑到这种设计,当我们要修改处理请求的方式时,我们需要更改CoreProcessor类以及任何过滤器类中的代码。

图7.3是描述使用示例7.1,示例7.2和示例7.3的过滤器代码时的控制流程的序列图。

 
图7.3自定义过滤策略的序列图,装饰器实现

请注意,当我们使用装饰器实现时,每个过滤器直接调用下一个过滤器,尽管使用通用接口。或者,可以使用FilterManager和FilterChain实现此策略。在这种情况下,这两个组件协调和管理过滤器处理,并且各个过滤器不能直接相互通信。这个设计近似于servlet 2.3兼容的实现,尽管它仍然是一个自定义策略。示例7.5是仅仅创建FilterChain的FilterManager类的列表,如例7.6所示。FilterChain以适当的顺序向链中添加过滤器(为了简洁起见,这在FilterChain构造函数中完成,但通常会被替换为注释),处理过滤器,并最终处理目标资源。

 
图7.4自定义过滤策略的顺序图,非分配器实现

示例7.5 FilterManager - 自定义过滤器策略

public class FilterManager {
public void processFilter(Filter target,
javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException,
java.io.IOException {
FilterChain filterChain = new FilterChain(); //过滤器管理器在此构建过滤器链
//如有必要 //通过过滤器链管理请求
filterChain.processFilter(request, response); //进程目标资源
target.execute(request, response);
}
}

示例7.6 FilterChain - 自定义过滤器策略

public class FilterChain {
//过滤器链 filter chain
private Vector myFilters = new Vector(); // 创建新的 FilterChain
public FilterChain() {
// 插件默认过滤服务为例
// 只要。这通常会在
// FilterManager,但是在这里完成了例子目的
addFilter(new DebugFilter());
addFilter(new LoginFilter());
addFilter(new AuditFilter());
} public void processFilter(
javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response)
throws javax.servlet.ServletException,
java.io.IOException {
Filter filter; // 应用过滤器
Iterator filters = myFilters.iterator();//迭代器过滤器
while (filters.hasNext())
{
filter = (Filter)filters.next();
//通过各种请求和响应
//筛选器
filter.execute(request, response);
}
} public void addFilter(Filter filter) {
myFilters.add(filter);
}
}

这个策略不允许我们创建与我们想要的一样灵活或强大的过滤器。一个,过滤器以编程方式添加和删除。虽然我们可以编写专有的机制来处理通过配置文件添加和删除过滤器,但我们仍然无法包装请求和响应对象。另外,没有复杂的缓冲机制,这种策略不能提供灵活的后处理。

标准过滤策略提供了解决这些问题的解决方案,利用了2.3 Servlet规范的功能,该规范为过滤困境提供了一个标准的解决方案。

注意

在撰写本文时,Servlet 2.3规范是最终草案。

标准过滤策略

过滤器使用部署描述符进行声明性控制,如servlet规范版本2.3中所述,在本文​​中,它是最终草案形式。servlet 2.3规范包括用于构建过滤器链的标准机制,并且不引人注目地从这些链中添加和去除过滤器。过滤器围绕接口构建,并通过修改Web应用程序的部署描述符以声明方式添加或删除。

我们的这个策略的例子是创建一个过滤器来预处理任何编码类型的请求,以便每个请求可以在我们的核心请求处理代码中类似处理。为什么这样做是必要的?包含文件上传的HTML表单使用与大多数表单不同的编码类型。因此,随着上传的表单数据不能通过简单的 getParameter()调用获得。因此,我们创建两个预处理请求的过滤器,将所有编码类型转换为单一一致的格式。我们选择的格式是将所有表单数据作为请求属性使用。

一个过滤器处理类型的标准表单编码, application/ x-www-form-urlencoded另一个处理较不常见的编码类型 multipart/form-data,用于包含文件上传的表单。过滤器将所有表单数据转换为请求属性,因此核心请求处理机制可以以相同的方式处理每个请求,而不是用于不同编码的特殊外壳。

示例7.8显示了使用通用应用程序表单编码方案转换请求的过滤器。例7.9显示了处理使用多部分格式编码方案的请求的转换的过滤器。这些过滤器的代码基于servlet规范版本2.3的最终草案。也使用基本过滤器,这两个过滤器都从该过滤器继承(参见“过滤策略”一节)。基本过滤器(如示例7.7所示)为标准过滤器回调方法提供了默认行为。

示例7.7基本过滤器 - 标准过滤器策略

public class BaseEncodeFilter implements
javax.servlet.Filter {
private javax.servlet.FilterConfig myFilterConfig; public BaseEncodeFilter() { } public void doFilter(
javax.servlet.ServletRequest servletRequest,
javax.servlet.ServletResponse servletResponse,
javax.servlet.FilterChain filterChain)
throws java.io.IOException,
javax.servlet.ServletException {
filterChain.doFilter(servletRequest,
servletResponse);
} public javax.servlet.FilterConfig getFilterConfig()
{
return myFilterConfig;
} public void setFilterConfig(
javax.servlet.FilterConfig filterConfig) {
myFilterConfig = filterConfig;
}
}

示例7.8 StandardEncodeFilter - 标准过滤器策略

public class StandardEncodeFilter
extends BaseEncodeFilter {
//创建新的 StandardEncodeFilter
public StandardEncodeFilter() { } public void doFilter(javax.servlet.ServletRequest
servletRequest,javax.servlet.ServletResponse
servletResponse,javax.servlet.FilterChain
filterChain)
throws java.io.IOException,
javax.servlet.ServletException { String contentType =
servletRequest.getContentType();
if ((contentType == null) ||
contentType.equalsIgnoreCase(
"application/x-www-form-urlencoded")) {
translateParamsToAttributes(servletRequest,
servletResponse);
} filterChain.doFilter(servletRequest,
servletResponse);
} private void translateParamsToAttributes(
ServletRequest request, ServletResponse response)
{
Enumeration paramNames =
request.getParameterNames(); while (paramNames.hasMoreElements()) {
String paramName = (String)
paramNames.nextElement(); String [] values; values = request.getParameterValues(paramName);
System.err.println("paramName = " + paramName);
if (values.length == )
request.setAttribute(paramName, values[]);
else
request.setAttribute(paramName, values);
}
}
}

示例7.9 MultipartEncodeFilter - 标准过滤器策略

 public class MultipartEncodeFilter extends
BaseEncodeFilter {
public MultipartEncodeFilter() { }
public void doFilter(javax.servlet.ServletRequest
servletRequest, javax.servlet.ServletResponse
servletResponse,javax.servlet.FilterChain
filterChain)
throws java.io.IOException,
javax.servlet.ServletException {
String contentType =
servletRequest.getContentType();
// 只有当这个请求是多部分的时候才过滤这个请求编码
if (contentType.startsWith(
"multipart/form-data")){
try {
String uploadFolder =
getFilterConfig().getInitParameter(
"UploadFolder");
if (uploadFolder == null) uploadFolder = "."; /** The MultipartRequest class is:
* Copyright (C) 2001 by Jason Hunter
* <jhunter@servlets.com>. All rights reserved.
**/
MultipartRequest multi = new
MultipartRequest(servletRequest,
uploadFolder,
* * );
Enumeration params =
multi.getParameterNames();
while (params.hasMoreElements()) {
String name = (String)params.nextElement();
String value = multi.getParameter(name);
servletRequest.setAttribute(name, value);
} Enumeration files = multi.getFileNames();
while (files.hasMoreElements()) {
String name = (String)files.nextElement();
String filename = multi.getFilesystemName(name);
String type = multi.getContentType(name);
File f = multi.getFile(name);
//在这一点上,做一些事情
//文件,必要时
}
}
catch (IOException e)
{
LogManager.logMessage(
"error reading or saving file"+ e);//读取或保存文件错误
}
} // end if
filterChain.doFilter(servletRequest,
servletResponse);
} // end method doFilter()
}

示例7.10中的以下摘录来自包含此示例的Web应用程序的部署描述符。它显示了这两个过滤器如何注册,然后映射到资源,在这种情况下是一个简单的测试servlet。此外,该示例的序列图如图7.5所示。

示例7.10部署描述符 - 标准过滤器策略

.
.
.
<filter>
<filter-name>StandardEncodeFilter</filter-name>
<display-name>StandardEncodeFilter</display-name>
<description></description>
<filter-class> corepatterns.filters.encodefilter.
StandardEncodeFilter</filter-class>
</filter>
<filter>
<filter-name>MultipartEncodeFilter</filter-name>
<display-name>MultipartEncodeFilter</display-name>
<description></description>
<filter-class>corepatterns.filters.encodefilter.
MultipartEncodeFilter</filter-class>
<init-param>
<param-name>UploadFolder</param-name>
<param-value>/home/files</param-value>
</init-param>
</filter>
.
.
.
<filter-mapping>
<filter-name>StandardEncodeFilter</filter-name>
<url-pattern>/EncodeTestServlet</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>MultipartEncodeFilter</filter-name>
<url-pattern>/EncodeTestServlet</url-pattern>
</filter-mapping>
.

 
图7.5拦截滤波器,标准滤波器策略编码转换示例的序列图

当客户端向控制器servlet发出请求时,StandardEncodeFilter和MultiPartEncodeFilter拦截控制。容器通过调用它们的doFilter方法来满足过滤器管理器和向量控件对这些过滤器的作用 。在完成处理之后,每个过滤器将控制权传递到其包含的FilterChain,FilterChain指示执行下一个过滤器。一旦两个过滤器已经接收并随后放弃了控制,接收控制的下一个组件就是实际的目标资源,在这种情况下是控制器servlet。

在servlet规范2.3版中支持的过滤器也支持包装请求和响应对象。该功能提供了比使用自定义过滤策略建议的自定义实现可以构建的功能更强大的机制。当然,组合这两种策略的混合方法也可以定制,但仍然缺乏Servlet规范支持的标准过滤策略的功能。

基本过滤策略

基本过滤器用作所有过滤器的公共超类。通用特性可封装在基本过滤器中,并在所有过滤器之间共享。例如,基本过滤器是在“声明过滤器策略”中包含容器回调方法的默认行为的好地方。例7.11显示了如何做到这一点。

示例7.11基本过滤器策略

 public class BaseEncodeFilter implements
javax.servlet.Filter {
private javax.servlet.FilterConfig myFilterConfig; public BaseEncodeFilter() { } public void doFilter(javax.servlet.ServletRequest
servletRequest,javax.servlet.ServletResponse
servletResponse, javax.servlet.FilterChain
filterChain) throws java.io.IOException,
javax.servlet.ServletException { filterChain.doFilter(servletRequest,
servletResponse);
} public javax.servlet.FilterConfig getFilterConfig() {
return myFilterConfig;
} public void
setFilterConfig(javax.servlet.FilterConfig
filterConfig) {
myFilterConfig = filterConfig;
}
}

模板过滤策略

使用所有其他继承的基本过滤器(请参阅本章中的“基本过滤策略”)允许基类提供模板方法[Gof]功能。在这种情况下,基本过滤器用于指定每个过滤器必须完成的一般步骤,同时留下如何将该步骤完成到每个过滤器子类的细节 。通常,这些将被粗略定义,简单地在每个模板上施加有限结构的基本方法。此策略也可以与任何其他过滤策略相结合。示例7.12和示例7.13中的列表显示了如何将此策略与“声明过滤策略”配合使用。

示例7.12显示了一个名为TemplateFilter的基本过滤器,如下所示。

示例7.12使用模板过滤器策略

 public abstract class TemplateFilter implements
javax.servlet.Filter {
private FilterConfig filterConfig; public void setFilterConfig(FilterConfig fc) {
filterConfig=fc;
} public FilterConfig getFilterConfig() {
return filterConfig;
} public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 所有过滤器的通用处理可以在这里
doPreProcessing(request, response, chain); // 所有过滤器的通用处理可以在这里
doMainProcessing(request, response, chain); // 所有过滤器的通用处理可以在这里
doPostProcessing(request, response, chain); // 所有过滤器的通用处理可以在这里 // 将控制权传递给链中的下一个过滤器
// 到目标资源
chain.doFilter(request, response);
}
public void doPreProcessing(ServletRequest request,
ServletResponse response, FilterChain chain) {
} public void doPostProcessing(ServletRequest request,
ServletResponse response, FilterChain chain) {
} public abstract void doMainProcessing(ServletRequest
request, ServletResponse response, FilterChain
chain);
}

给定TemplateFilter的这个类定义,每个过滤器都被实现为只能实现该doMainProcessing方法的子类。但是,如果需要,这些子类可以选择实现所有三种方法。示例7.13是实现一个强制性方法(由我们的模板过滤器指定)和可选的预处理方法的过滤器子类的示例。另外,使用该策略的序列图如图7.6所示。

示例7.13调试过滤器

public class DebuggingFilter extends TemplateFilter {
public void doPreProcessing(ServletRequest req,
ServletResponse res,FilterChain chain){
//在这里做一些预处理
} public void doMainProcessing(ServletRequest req,
ServletResponse res,FilterChain chain){
//做主要处理;
}
}

 
图7.6截取过滤器,模板过滤器策略序列图

在图7.6的序列图中,过滤器子类(如DebuggingFilter)通过覆盖抽象doMainProcessing方法(可选) doPreProcessing和(和) 来定义特定的处理 doPostProcessing。因此,模板过滤器对每个过滤器的处理强加了一个结构,并为每个过滤器提供了一个封装代码的地方。

后果

  • 集中控制与松散耦合处理器 
     过滤器提供了处理多个请求处理的中心位置,控制器也是如此。过滤器更适合按照目标资源(如控制器)进行最终处理的按摩请求和响应。此外,控制器经常将诸如认证,记录,加密等的许多不相关的公共服务的管理联系在一起,而过滤允许可以以各种组合组合的更多松散耦合的处理程序。

  • 改进可重用性 
     过滤器可以促进更清洁的应用程序分区,并鼓励重用。这些可插拔拦截器从现有代码中透明地添加或删除,并且由于它们的标准接口,它们可以任意组合工作,并且可重复使用于不同的演示。
  • 声明性和灵活性配置 
     无需重新编译核心代码库,大量服务就以不同的排列组合。
  • 信息共享是无效的 
     过滤器之间的共享信息可能是低效的,因为根据定义,每个过滤器松散耦合。如果过滤器之间必须共享大量的信息,那么这种方法可能被证明是昂贵的。

相关模式

  • 前端控制器 
     控制器解决了一些类似的问题,但更适合处理核心处理。

  • 装饰器[GoF] 
     拦截过滤器模式与Decorator模式相关,该模式提供动态可插入的包装器。
  • 模板方法[GoF] 
     模板方法模式用于实现模板过滤策略。
  • Interceptor [POSA2] 
     拦截过滤器模式与Interceptor模式相关,允许透明地添加服务并自动触发。
  • 管道和过滤器[POSA1] 
     拦截过滤器模式与管道和过滤器模式相关。

核心J2EE模式 - 截取过滤器的更多相关文章

  1. MVC模式 - 理解J2EE模式

        MVC模式Model-View-Controller头字母的缩写,中文翻译为“模型-视图-控制器” 模式(或者模型).该模式把一个GUI应用划分 业务逻辑处理(M),画面表示(V),控制(C) ...

  2. J2EE中的过滤器和拦截器

    过滤器和拦截器的相似之处就是拦截请求,做一些预处理或者后处理. 而过滤器和拦截器的区别在于过滤器是相对HTTP请求而言的,而拦截器是相对Action中的方法的. 过滤器:访问web服务器的时候,对一个 ...

  3. ActiveRecord模式整理

    DAO Data Access Object,数据访问对象 DAO是一个软件设计的指导原则,在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应用,应该将所有对数据源的访问操作抽象 ...

  4. DAL、DAO、ORM、Active Record辨析

    转自:http://blog.csdn.net/suiye/article/details/7824943 模型 Model 模型是MVC中的概念,指的是读取数据和改变数据的操作(业务逻辑).一开始我 ...

  5. DAO是什么技术

    DAO是Data Access Object数据访问接口,数据访问:故名思义就是与数据库打交道.夹在业务逻辑与数据库资源中间. 在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应 ...

  6. 【JDBC 笔记】

    JDBC 笔记 作者:晨钟暮鼓c个人微信公众号:程序猿的月光宝盒 对应pdf版:https://download.csdn.net/download/qq_22430159/10754554 没有积分 ...

  7. jdbc-DAO的实现

    什么是 DAO DAO(Data Access Object)是一个数据访问接口,夹在业务逻辑与数据库资源中间. 在核心J2EE模式中是这样介绍DAO模式的:为了建立一个健壮的J2EE应用,应该将所有 ...

  8. 架构-数据库访问-SQL语言进行连接数据库服务器-DAO:DAO

    ylbtech-架构-数据库访问-SQL语言进行连接数据库服务器-DAO:DAO DAO(Data Access Object) 数据访问对象是一个面向对象的数据库接口,它显露了 Microsoft ...

  9. 每个Java开发人员都应该知道的4个Spring注解

    这是每个Java开发人员都应该知道的最重要的Spring注解.感谢优锐课老师对本文提供的一些帮助. 随着越来越多的功能被打包到单个应用程序或一组应用程序中,现代应用程序的复杂性从未停止增长.尽管这种增 ...

随机推荐

  1. Python入门教程(1)

    人生苦短,我用Python! Python(英语发音:/ˈpaɪθən/), 是一种面向对象.解释型计算机程序设计语言,由Guido van Rossum于1989年底发明,第一个公开发行版发行于19 ...

  2. Java并发之任务的描述和执行

    简单概念 <Java编程思想>对并发概念的重要性阐述: Java是一种多线程语言,并且提出了并发问题,不管你是否意识到了.因此,有很多使用中的Java程序,要么只是偶尔工作,要么是在大多数 ...

  3. Android布局管理详解(1)—— LinearLayout 线性布局

    Android的布局方式共有6种,分别是LinearLayout(线性布局).TableLayout(表格布局).FrameLayout(帧布局).RelativeLayout(相对布局).GridL ...

  4. 3016: [Usaco2012 Nov]Clumsy Cows

    3016: [Usaco2012 Nov]Clumsy Cows Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 91  Solved: 69[Submi ...

  5. 1232: [Usaco2008Nov]安慰奶牛cheer

    1232: [Usaco2008Nov]安慰奶牛cheer Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 612  Solved: 431[Submi ...

  6. 2017java预备作业2

    1 安装git • 到Git官网https://www.git-scm.com/ 下载Git客户端 • 安装时选择默认即可. • 安装完成后在桌面的快捷菜单中选择Git Bash Here 或者在开始 ...

  7. JQuery和原生JS跨域加载JSON数据或HTML。

    前提:有时候需要在网页上,加载另一个网站上的数据.或者加载另一个网站上的一个页面.Js的Ajax请求不具备跨域功能,可以使用JQuery来实现. 网页端JS代码: $(function () { $. ...

  8. Java开发过程中开发工具Eclipse中导入jar包的过程

    欢迎欣赏我的第二篇随笔.我们在创建好一个动态网站项目之后,如果没有部署maven的情况下,你可以按照以下的方法,直接把要用的jar包导入你的工程中,而不用再部署maven. 例如在使用JDBC编程时需 ...

  9. fastlane安装流程和fastlane match同步证书和PP文件方法

    分步指南 安装fastlane:  ① Make sure you have the latest version of the Xcode command line tools installed: ...

  10. Windows上安装Kafka需要注意的几点

    1.不能安装在有空格的路径上 比如:D:\Program Files 2.设置日志路径时,要用"/",不能用Windows上的"\",比如: # A comma ...