使用代理模式编写服务器是个好主意吗?

4

为了一个学校项目,我需要用Java编写一个简单的服务器,不断监听一个传入目录,并将文件从该目录移动到其他地方。服务器需要记录信息和错误消息,所以我想可以使用代理模式来实现。因此,我创建了以下ServerInterface:

public interface ServerInterface extends Runnable {

    public void initialize(String repPath, ExecutorInterface executor, File propertiesFile) throws ServerInitException;

    public void run();

    public void terminate();

    public void updateHTML();

    public File[] scanIncomingDir();

    public List<DatasetAttributes> moveIncomingFilesIfComplete(File[] incomingFiles);

}

然后我创建了一个实现了真实对象的Server类和一个代理类ProxyServer。此外,Server还有一个工厂方法,用于创建一个ProxyServer对象,但将其返回为ServerInterface

代理对象上的run方法如下所示:

@Override
    public void run(){

        log(LogLevels.INFO, "server is running ...");

        while( !stopped ){

            try {

                File[] incomingContent = scanIncomingDir();
                moveIncomingFilesIfComplete(incomingContent);
                updateHTML();
                pause();

            } catch (Exception e) {
                logger.logException(e, new Timestamp(timestampProvider.getTimestamp()));
                pause();
            }

        }

        log(LogLevels.INFO, "server stopped");
    }

try语句中调用的函数只是记录并传递调用给真实对象。到目前为止,一切都很好。但现在我已经以这种方式在代理对象中实现了run方法,真实对象中的run方法变得过时,因此为空(terminate方法也是如此)。
所以我问自己:这样可以吗?代理模式应该是这样实现的吗?
我认为,我混淆了“真实”和“代理”的行为……通常,真实服务器应该被困在while循环中,而不是代理服务器,对吧?我试图避免混淆这一点,但两种方法都不令人满意:
- 我可以在真实对象中实现run方法,然后将代理对象交给真实对象,以便在while循环期间仍能记录日志。但这样真实对象就会做一些记录,这正是我写代理的原因所在。 - 我可以说,只有代理服务器是可运行的,因此从接口中删除runterminate,但这将破坏代理模式。
我应该使用其他的设计吗?还是我看到了没有问题?

很棒的问题(为什么要关闭投票?) - Jasper Blues
1
对于学校作业,我会直接记录行内,除非作业的重点是普遍应用模式。 - millimoose
我同意@millimoose的观点——保持简单,虽然你考虑这些事情很好。 - Jasper Blues
@millimoose 我同意对于学校作业,我可以很容易地找到另一个解决方案。我提出这个问题的原因也是为了更好地理解代理模式的应用方式。 - kafman
4个回答

2

你的想法是正确的,你提到了一个有趣的概念。

像你描述的日志记录等特性是我们在面向切面编程中所称的横切关注点的一个例子。

  • 横切关注点是一种在许多对象中都会使用的要求。

因此,它们往往会破坏面向对象编程。这是什么意思呢?

  • 如果你试图创建一个专门从A地方移动文件到B地方的类,并且实现方法首先谈论日志记录(然后是事务,然后是安全性),那就不太符合OO原则了。它违反了单一职责原则。

进入面向切面编程

这就是为什么我们有AOP的原因——它存在于将这些横切关注点模块化和封装起来。它的工作方式如下:

  • 定义所有我们想要应用横切特性的地方。
  • 使用拦截器设计模式来"编织"该特性。

我们可以使用AOP"编织"需求的方式

  • 一种方法是使用像你描述的Java动态代理。例如Spring框架中的默认方式。这仅适用于接口。
  • 另一种方法是使用字节码工程库,如asm、cglib、Javassist——它们拦截类加载器以在运行时提供一个新的子类。
  • 第三种方法是使用编译时编织——在编译时更改代码(或字节码)。
  • 还有一种方法是使用Java代理(JVM的参数)。

后两种选项在AspectJ中得到支持。

总之:

听起来你正在向面向切面编程(AOP)迈进,所以请了解一下这个。请注意,Spring Framework具有许多功能,可简化应用AOP,但在你的情况下,考虑到这是一个学校作业,最好深入了解AOP本身的核心概念。

NB:如果你正在构建一个生产级别的服务器,日志记录可能是一个完整的功能,因此值得使用AOP……在其他情况下,直接内联可能足够简单。


1
需要注意的是,OP试图做的事情无法通过运行时织入来实现。 (因为代理无法拦截对'this'的调用,这是OP已经发现的情况,并且与Spring AOP拦截器相同)。它需要加载时织入或编译时织入,这两者都需要进行设置。我不确定您在字节码工程建议中所指的意思-如果您不使用JVM代理,他们如何拦截类加载器? - millimoose
1
我不确定所有细节,但关键点是它们不需要Java代理。我的理解是在加载类的时候,它们会返回一个新的受检测子类。. asm和cglib在字节码级别工作,而Javassit非常有趣,因为它使用反编译器!这使得它易于使用,尽管也非常慢。. . Spring的具体类事务管理使用(或曾经使用)cglib。. (虽然现在你也可以使用AspectJ)。 - Jasper Blues
感谢您的详细回答!我会查看您提到的一些概念。 - kafman

0
您可以使代理意识到真实对象。基本上,您的代理将委托调用运行方法到真实实现。
在委派之前,代理首先记录启动。委派后,代理记录“关闭”:
// Snapshot from what should look like the run method implementation
// in your proxy.

public ServerInterfaceProxy(ServerInterface target){
    this.proxiedTarget = target;
}

public void run(){
    log(LogLevels.INFO, "server is running ...");
    this.proxiedTarget.run();
    log(LogLevels.INFO, "server is running ...");
}

这个实现也可以被看作是一个装饰器模式的实现。在我看来,代理和装饰器在某种程度上(就实现而言)是等价的:它们都拦截/捕获目标的行为。


当服务器在您的实现中运行时,您将如何记录某些内容? - kafman
好的,你的服务器服务也可以通过日志修饰器进行封装。现在,这取决于你的项目规模,可能会变得繁琐。在复杂的服务器假设下,我同意声明性解决方案更为合适。所以总结一下,一切都取决于你的项目规模(根据你的问题基本上很简单)! - yechabbi

0

在这种情况下,您应该使用观察者模式

观察者模式是一种软件设计模式,其中一个对象(称为主题)维护其依赖项列表(称为观察者),并通过调用它们的方法之一自动通知它们任何状态更改。

您的Observable将通过时间轮询或如此已经建议的WatchService来观察目录中的更改。目录的更改将通知Observer,后者将采取移动文件的操作。 ObservableObserver都应记录其操作。

您还应该知道,观察者模式通过实现java.util.Observablejava.util.Observer成为Java JDK的一部分。


问题是要求如何从实现移动文件的任何内容中提取日志记录。在您的情况下,“Server”将是实现“Observer”的类,并因此必须“记录其操作”,这是OP试图从“Server”中获取的内容。 - millimoose

-1

看看Java 7的WatchService类。

对于这个来说,使用代理行为几乎肯定是过度设计了。


-1:完全没有理解问题的要点。代理用于处理日志记录,服务器实际上做什么以及如何完成这个过程都是次要的。 - millimoose
从某个评论是“内联日志”的人来看,这种说法至少是虚伪的。 - kittylyst
我知道“内联日志”是一种解决方法或旁敲侧击的建议。这就是我将其发布为评论而不是答案的原因。此外,这并不是您的唯一问题。(例如,您甚至没有提供到Javadocs的链接,更不用说演示如何使用WatchService来避免需要日志拦截器的需求了。) - millimoose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接