sql注入冲突,语法错误:_依赖注入:语法糖胜于功能组合

sql注入冲突,语法错误:

引用依赖注入揭开神秘面纱

“依赖注入”是5美分概念的25美元术语。
*詹姆斯·肖尔(James Shore),2006年3月22日

依赖注入,在编写可测试,可组合和结构良好的应用程序时非常重要,它仅意味着具有带有构造函数的对象。 在本文中,我想向您展示依赖注入基本上是一种语法糖,它隐藏了函数的循环和组合。 不用担心,我们将非常缓慢地尝试解释为什么这两个概念非常相似。

设置器,注释和构造函数

Spring bean或EJB是Java对象。 但是,如果仔细观察,大多数bean在创建后实际上是无状态的。 在Spring bean上调用方法很少修改该bean的状态。 大多数时候,bean只是在类似上下文中运行的一系列过程的便捷命名空间。 调用invoice() ,我们不会修改CustomerService的状态,而只是委托给另一个对象,该对象最终将调用数据库或Web服务。 这已经远离面向对象的编程(我在这里讨论 )。 因此,从本质上讲,我们在命名空间的多层层次结构中拥有过程(稍后将介绍功能):它们所属的包和类。 通常,这些过程调用其他过程。 您可能会说他们在bean的依赖项上调用方法,但是我们已经了解到bean是一个谎言,它们只是过程组。

话虽如此,让我们看看如何配置Bean。 在我的职业生涯中,我有几集关于setter(以及XML中大量的<property name="..."> ),@ @Autowired字段和最后的构造函数注入的情节。 另请参见: 为什么应优先考虑构造函数的注入? 。 因此,我们通常拥有的对象具有对其引用的不可变引用:

Hello Rajeev, @Component
class PaymentProcessor {
 
    private final Parser parser;
    private final Storage storage;
 
    @Autowired
    public PaymentProcessor(Parser parser, Storage storage) {
        this.parser = parser;
        this.storage = storage;
    }
 
    void importFile(Path statementFile) throws IOException {
            try(Stream<string> lines = Files.lines(statementFile)) {
                lines
                        .map(parser::toPayment)
                        .forEach(storage::save);
            }
    }
 
}
 
 
@Component
class Parser {
    Payment toPayment(String line) {
        //om-nom-nom...
    }
}
 
 
@Component
class Storage {
 
    private final Database database;
 
    @Autowired
    public Storage(Database database) {
        this.database = database;
    }
 
    public UUID save(Payment payment) {
        return this.database.insert(payment);
    }
}
 
 
class Payment {
    //...
}</string>

取一个带有银行对账单的文件,将每一行解析为Payment对象并存储。 尽可能无聊。 现在让我们重构一下。 首先,我希望您意识到面向对象编程是一个谎言。 不是因为这只是名称空间(所谓的类)中的一堆过程(我希望您不是用这种方式编写软件)。 但是由于对象是使用隐式this参数实现为过程的, this ,当您看到: this.database.insert(payment)它实际上已编译为以下内容: Database.insert(this.database, payment) 。 不相信我吗

$ javap -c Storage.class
...
  public java.util.UUID save(com.nurkiewicz.di.Payment);
    Code:
       0: aload_0
       1: getfield      #2                  // Field database:Lcom/nurkiewicz/di/Database;
       4: aload_1
       5: invokevirtual #3                  // Method com/nurkiewicz/di/Database.insert:(Lcom/nurkiewicz/di/Payment;)Ljava/util/UUID;
       8: areturn

好吧,如果您很正常,这还不能证明您,所以让我解释一下。 aload_0 (代表this )后跟getfield #2this.database推送到操作数堆栈。 aload_1推第一个方法参数( Payment ),最后invokevirtual调用进程 Database.insert (有一些多态性这里涉及到,不相干在这种情况下)。 因此,我们实际上调用了两参数过程,其中第一个参数由编译器自动填充并命名为… this 。 在被调用方, this是有效的,并且指向Database实例。

忘记对象

让我们将所有这些变得更加明确,而忽略对象:

class ImportDependencies {
 
    public final Parser parser;
    public final Storage storage;
     
    //...
 
}
 
static void importFile(ImportDependencies thiz, Path statementFile) throws IOException {
    Files.lines(statementFile)
            .map(thiz.parser::toPayment)
            .forEach(thiz.storage::save);
}

疯了! 注意importFile 过程现在在外面PaymentProcessor ,我居然改名为ImportDependencies (原谅public修饰符字段)。 importFile可以是static因为所有依赖项都在thiz容器中显式给出,而不是使用this和instance变量隐式提供-并可在任何地方实现。 实际上,我们只是重构为编译过程中幕后已经发生的事情。 在这个阶段,您可能想知道为什么我们需要一个额外的容器来存储依赖关系,而不仅仅是直接传递它们。 当然,这毫无意义:

static void importFile(Parser parser, Storage storage, Path statementFile) throws IOException {
    Files.lines(statementFile)
            .map(parser::toPayment)
            .forEach(storage::save);
}

实际上,有些人更喜欢将依赖项显式传递给上述业务方法,但这不是重点。 这只是转型的又一步。

咖喱

下一步,我们需要将函数重写为Scala:

object PaymentProcessor {
 
  def importFile(parser: Parser, storage: Storage, statementFile: Path) {
    val source = scala.io.Source.fromFile(statementFile.toFile)
    try {
      source.getLines()
        .map(parser.toPayment)
        .foreach(storage.save)
    } finally {
      source.close()
    }
  }
 
}

它在功能上等效,因此无需多说。 请注意importFile()如何属于object ,因此它有点类似于Java中单例的static方法。 接下来,我们将参数分组

def importFile(parser: Parser, storage: Storage)(statementFile: Path) { //...

这一切都与众不同。 现在,您可以一直提供所有依赖项,或者可以更好地提供它,只需执行一次即可:

val importFileFun: (Path) => Unit = importFile(parser, storage)
 
//...
 
importFileFun(Paths.get("/some/path"))

上面的代码行实际上可以是容器设置的一部分,其中我们将所有依赖项绑定在一起。 设置完成后,我们可以在任何地方使用importFileFun ,而importFileFun担心其他依赖项。 我们所拥有的只是一个函数(Path) => Unit ,就像一开始的paymentProcessor.importFile(path)一样。

一直发挥作用

我们仍然将对象用作依赖项,但是如果您仔细看,我们既不需要parser也不需要storage 。 我们真正需要的是一个可以解析的函数parser.toPayment )和一个可以存储的函数storage.save )。 让我们再次重构:

def importFile(parserFun: String => Payment, storageFun: Payment => Unit)(statementFile: Path) {
  val source = scala.io.Source.fromFile(statementFile.toFile)
  try {
    source.getLines()
      .map(parserFun)
      .foreach(storageFun)
  } finally {
    source.close()
  }
}

当然,对于Java 8和lambda,我们可以做同样的事情,但是语法更加冗长。 我们可以提供任何用于解析和存储的功能,例如,在测试中,我们可以轻松创建存根。 哦,顺便说一句,顺便说一句,我们只是从面向对象的Java转变为功能组合,完全没有对象。 当然,仍然存在一些副作用,例如加载文件和存储,但是让我们这样吧。 或者,要使依赖项注入和函数组成之间的相似性更加显着,请查看Haskell中的等效程序:

let parseFun :: String -> Payment
let storageFun :: Payment -> IO ()
let importFile :: (String -> Payment) -> (Payment -> IO ()) -> FilePath -> IO ()
 
let simpleImport = importFile parseFun storageFun
// :t simpleImport
// simpleImport :: FilePath -> IO ()

首先,需要IO monad来管理副作用。 但是,您是否看到importFile高阶函数如何接受三个参数,但是我们只能提供两个参数并获得simpleImport ? 这就是我们在Spring或EJB中所谓的依赖注入。 但是没有语法糖。

翻译自: https://www.javacodegeeks.com/2015/09/dependency-injection-syntax-sugar-over-function-composition.html

sql注入冲突,语法错误:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值