zoukankan      html  css  js  c++  java
  • 什么是spring框架?spring框架到底有什么用?spring框架到底做了些什么?

    什么是spring框架,spring框架究竟有什么用呢?我们可以用spring框架来做些什么呢?这是我今天要说的内容。

    当然,百度spring框架会出现一大堆spring框架的介绍,以及IOC和AOP。但是这些官方的语言,看书都有解释,关键是我不知道为什么要用spring,spring框架和不用框架到底区别在哪,还有它到底是通过什么来表明我用的框架就是spring框架的?

    spring很抽象,spring是框架,框架的主要目的是什么呢?大概所有框架的目的都一样吧,那就是简化开发。而它存在的目的也是为了简化java开发。

    它是怎样来简化开发的呢?让我们看看spring采取的关键策略。

    • 基于POJO的轻量级和最小侵入性编程;
    • 通过依赖注入和面向接口实现松耦合;
    • 基于切面和惯例进行声明式编程;
    • 通过切面和模板减少样板式代码。

    这就是spring框架的四种关键策略。我们下面详细谈谈。

    spring框架的最小侵入性

    Spring竭力避免因自身的API而弄乱你的应用代码。 Spring不会强迫你实现Spring规范的接口或继承Spring规范的类, 相反, 在基于Spring构建的应用中, 它的类通常没有任何痕迹表明你使用了Spring。只能通过xml配置文件来看出spring的思想。

    最坏的场景是, 一个类或许会使用Spring注解, 但它依旧是POJO。

    那可能有人要问了,不侵入如何实现spring框架强大的功能,spring框架不是很强大吗?在这里我要说明,spring框架是很强大,但它的强大在于它的理念,它的思想,而不是它实现了多强大的功能。

    当然不侵入有不侵入的解决方法,在我的理解里它的解决方法也算是spring的核心吧!那就是配置文件或者注解。

    让我们来看一个javabean。它是一个普通的java类。

    1.  
      public class exampleBean(){
    2.  
      public String testPrint(){
    3.  
      System.out.println("===test===");
    4.  
      }
    5.  
      }

    Spring的非入侵式就是不强制类要实现Spring的任何接口或类,没有任何地方表明它是一个Spring组件。 意味着这个类在Spring应用和非Spring应用中都可以发挥同样的作用。

    那这个类在不耦合的情况下是如何发挥作用的呢?这就要提到spring框架的DI(依赖注入)了。

    依赖注入

    任何一个有实际意义的应用都会由两个或者更多的类组成, 这些类相互之间进行协作来完成特定的业务逻辑。 按照传统的做法, 每个对象负责管理与自己相互协作的对象(即它所依赖的对象) 的引用, 这将会导致高度耦合和难以测试的代码。而spring框架利用依赖注入恰恰解决了这一难题。

    耦合具有两面性 。 一方面, 紧密耦合的代码难以测试、 难以复用、 难以理解, 并且典型地表现出“打地鼠”式的bug特性 。 另一方面, 一定程度的耦合又是必须的——完全没有耦合的代码什么也做不了。 为了完成有实际意义的功能, 不同的类必须以适当的方式进行交互。 总而言之, 耦合是必须的, 但应当被小心谨慎地管理。

    1.  
      public void DealData(){
    2.  
      public Data data;
    3.  
      public void setData(Data data){
    4.  
      this.data=data;
    5.  
      }
    6.  
      public void add(){
    7.  
      data.add();
    8.  
      }
    9.  
       
    10.  
      }

    如上图,DealData类和Data类耦合了。

    通过DI, 对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。 对象无需自行创建或管理它们的依赖关系。

    应用切面

    DI能够让相互协作的软件组件保持松散耦合, 而面向切面编程(aspect-oriented programming, AOP) 允许你把遍布应用各处的功能分离出来形成可重用的组件。

    面向切面编程往往被定义为促使软件系统实现关注点的分离一项技术。 系统由许多不同的组件组成, 每一个组件各负责一块特定功能。 除了实现自身核心的功能之外, 这些组件还经常承担着额外的职责。 诸如日志、 事务管理和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中去, 这些系统服务通常被称为横切关注点, 因为它们会跨越系统的多个组件。

    如果将这些关注点分散到多个组件中去, 你的代码将会带来双重的复杂性。

    • 实现系统关注点功能的代码将会重复出现在多个组件中。 这意味着如果你要改变这些关注点的逻辑, 必须修改各个模块中的相关实现。
    • 即使你把这些关注点抽象为一个独立的模块, 其他模块只是调用它的方法, 但方法的调用还是会重复出现在各个模块中。
    • 组件会因为那些与自身核心业务无关的代码而变得混乱。 一个向地址簿增加地址条目的方法应该只关注如何添加地址, 而不应该关注它是不是安全的或者是否需要支持事务

    图1.2展示了这种复杂性。 左边的业务对象与系统级服务结合得过于紧密。 每个对象不但要知道它需要记日志、 进行安全控制和参与事务, 还要亲自执行这些服务。

    image.png

    在整个系统内, 关注点(例如日志和安全)的调用经常散布到各个模块中, 而这些关注点并不是模块的核心业务

    AOP能够使这些服务模块化, 并以声明的方式将它们应用到它们需要影响的组件中去。 所造成的结果就是这些组件会具有更高的内聚性并且会更加关注自身的业务, 完全不需要了解涉及系统服务所带来复杂性。 总之, AOP能够确保POJO的简单性。

    如图1.3所示, 我们可以把切面想象为覆盖在很多组件之上的一个外壳。 应用是由那些实现各自业务功能的模块组成的。 借助AOP, 可以使用各种功能层去包裹核心业务层。 这些层以声明的方式灵活地应用到系统中, 你的核心应用甚至根本不知道它们的存在。 这是一个非常强大的理念, 可以将安全、 事务和日志关注点与核心业务逻辑相分离。

    image.png

    利用AOP, 系统范围内的关注点覆盖在它们所影响组件之上

    为了示范在Spring中如何应用切面, 让我们重新回到骑士的例子, 并为它添加一个切面。

    每一个人都熟知骑士所做的任何事情, 这是因为吟游诗人用诗歌记载了骑士的事迹并将其进行传唱。 假设我们需要使用吟游诗人这个服务类来记载骑士的所有事迹。 程序清单1.9展示了我们会使用的Minstrel类。

    1.  
      1
    2.  
      2
    3.  
      3
    4.  
      4
    5.  
      5
    6.  
      6
    7.  
      7
    8.  
      8
    9.  
      9
    10.  
      10
    11.  
      11
    12.  
      12
    13.  
      13
    14.  
      14
    15.  
      15
    16.  
      16
    17.  
      17
    18.  
      18
    19.  
      19
    20.  
      20
    21.  
      21
    22.  
      22
    1.  
      package sia.knights;
    2.  
       
    3.  
      import java.io.PrintStream;
    4.  
       
    5.  
      public class Minstrel {
    6.  
       
    7.  
      private PrintStream stream;
    8.  
       
    9.  
      public Minstrel(PrintStream stream) {
    10.  
      this.stream = stream;
    11.  
      }
    12.  
       
    13.  
      public void singBeforeQuest() {
    14.  
      stream.println("Fa la la, the knight is so brave!");
    15.  
      }
    16.  
       
    17.  
      public void singAfterQuest() {
    18.  
      stream.println("Tee hee hee, the brave knight " +
    19.  
      "did embark on a quest!");
    20.  
      }
    21.  
       
    22.  
      }

    正如你所看到的那样, Minstrel是只有两个方法的简单类。 在骑士执行每一个探险任务之前, singBeforeQuest()方法会被调用; 在骑士完成探险任务之后, singAfterQuest()方法会被调用。 在这两种情况下, Minstrel都会通过一个PrintStream类来歌颂骑士的事迹, 这个类是通过构造器注入进来的。

    把Minstrel加入你的代码中并使其运行起来, 这对你来说是小事一桩。 我们适当做一下调整从而让BraveKnight可以使用Minstrel。 程序清单1.10展示了将BraveKnight和Minstrel组合起来的第一次尝试。

    1.  
      1
    2.  
      2
    3.  
      3
    4.  
      4
    5.  
      5
    6.  
      6
    7.  
      7
    8.  
      8
    9.  
      9
    10.  
      10
    11.  
      11
    12.  
      12
    13.  
      13
    14.  
      14
    15.  
      15
    16.  
      16
    17.  
      17
    18.  
      18
    19.  
      19
    1.  
      package sia.knights;
    2.  
       
    3.  
      public class BraveKnight implements Knight {
    4.  
       
    5.  
      private Quest quest;
    6.  
      private Minstrel minstrel;
    7.  
       
    8.  
      public BraveKnight(Quest quest, Minstrel minstrel) {
    9.  
      this.quest = quest;
    10.  
      this.minstrel = minstrel;
    11.  
      }
    12.  
       
    13.  
      public void embarkOnQuest() {
    14.  
      minstrel.singBeforeQuest();
    15.  
      quest.embark();
    16.  
      minstrel.singAfterQuest();
    17.  
      }
    18.  
       
    19.  
      }

    这应该可以达到预期效果。 现在, 你所需要做的就是回到Spring配置中, 声明Minstrel bean并将其注入到BraveKnight的构造器之中。

    但是, 请稍等……

    我们似乎感觉有些东西不太对。 管理他的吟游诗人真的是骑士职责范围内的工作吗? 在我看来, 吟游诗人应该做他份内的事, 根本不需要骑士命令他这么做。 毕竟, 用诗歌记载骑士的探险事迹, 这是吟游诗人的职责。 为什么骑士还需要提醒吟游诗人去做他份内的事情呢?此外, 因为骑士需要知道吟游诗人, 所以就必须把吟游诗人注入到BarveKnight类中。 这不仅使BraveKnight的代码复杂化了, 而且还让
    我疑惑是否还需要一个不需要吟游诗人的骑士呢? 如果Minstrel为null会发生什么呢? 我是否应该引入一个空值校验逻辑来覆盖该场景?

    简单的BraveKnight类开始变得复杂, 如果你还需要应对没有吟游诗人时的场景, 那代码会变得更复杂。 但利用AOP, 你可以声明吟游诗人必须歌颂骑士的探险事迹, 而骑士本身并不用直接访问Minstrel的方法。

    要将Minstrel抽象为一个切面, 你所需要做的事情就是在一个Spring配置文件中声明它。 程序清单1.11是更新后的knights.xml文件, Minstrel被声明为一个切面。

    1.  
      1
    2.  
      2
    3.  
      3
    4.  
      4
    5.  
      5
    6.  
      6
    7.  
      7
    8.  
      8
    9.  
      9
    10.  
      10
    11.  
      11
    12.  
      12
    13.  
      13
    14.  
      14
    15.  
      15
    16.  
      16
    17.  
      17
    18.  
      18
    19.  
      19
    20.  
      20
    21.  
      21
    22.  
      22
    23.  
      23
    24.  
      24
    25.  
      25
    26.  
      26
    27.  
      27
    28.  
      28
    29.  
      29
    30.  
      30
    31.  
      31
    32.  
      32
    33.  
      33
    34.  
      34
    35.  
      35
    1.  
      <?xml version="1.0" encoding="UTF-8"?>
    2.  
      <beans xmlns="http://www.springframework.org/schema/beans"
    3.  
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4.  
      xmlns:aop="http://www.springframework.org/schema/aop"
    5.  
      xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    6.  
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    7.  
       
    8.  
      <bean id="knight" class="sia.knights.BraveKnight">
    9.  
      <constructor-arg ref="quest" />
    10.  
      </bean>
    11.  
       
    12.  
      <bean id="quest" class="sia.knights.SlayDragonQuest">
    13.  
      <constructor-arg ref="fakePrintStream" />
    14.  
      </bean>
    15.  
       
    16.  
      <bean id="minstrel" class="sia.knights.Minstrel">
    17.  
      <constructor-arg ref="fakePrintStream" />
    18.  
      </bean>
    19.  
       
    20.  
      <bean id="fakePrintStream" class="sia.knights.FakePrintStream" />
    21.  
       
    22.  
      <aop:config>
    23.  
      <aop:aspect ref="minstrel">
    24.  
      <aop:pointcut id="embark"
    25.  
      expression="execution(* *.embarkOnQuest(..))"/>
    26.  
       
    27.  
      <aop:before pointcut-ref="embark"
    28.  
      method="singBeforeQuest"/>
    29.  
       
    30.  
      <aop:after pointcut-ref="embark"
    31.  
      method="singAfterQuest"/>
    32.  
      </aop:aspect>
    33.  
      </aop:config>
    34.  
       
    35.  
      </beans>

    这里使用了Spring的aop配置命名空间把Minstrel bean声明为一个切面。 首先, 需要把Minstrel声明为一个bean, 然后在元素中引用该bean。 为了进一步定义切面, 声明(使用) 在embarkOnQuest()方法执行前调用Minstrel的singBeforeQuest()方法。 这种方式被称为前置通知(before advice) 。 同时声明(使用)在embarkOnQuest()方法执行后调用singAfter Quest()方法。 这种方式被称为后置通知(after advice) 。

    首先, Minstrel仍然是一个POJO, 没有任何代码表明它要被作为一个切面使用。 当我们按照上面那样进行配置后, 在Spring的上下文中, Minstrel实际上已经变成一个切面了。

    其次, 也是最重要的, Minstrel可以被应用到BraveKnight中, 而BraveKnight不需要显式地调用它。 实际上, BraveKnight完全不知道Minstrel的存在。

    必须还要指出的是, 尽管我们使用Spring魔法把Minstrel转变为一个切面, 但首先要把它声明为一个Spring bean。 能够为其他Spring bean做到的事情都可以同样应用到Spring切面中, 例如为它们注入依赖。

    使用模板消除样板式代码

    你是否写过这样的代码, 当编写的时候总会感觉以前曾经这么写过? 我的朋友, 这不是似曾相识。 这是样板式的代码(boilerplate code) 。 通常为了实现通用的和简单的任务, 你不得不一遍遍地重复编写这样的代码。

    遗憾的是, 它们中的很多是因为使用Java API而导致的样板式代码。 样板式代码的一个常见范例是使用JDBC访问数据库查询数据。 举个例子, 如果你曾经用过JDBC, 那么你或许会写出类似下面的代码。

    image.png

    正如你所看到的, 这段JDBC代码查询数据库获得员工姓名和薪水。 我打赌你很难把上面的代码逐行看完, 这是因为少量查询员工的代码淹没在一堆JDBC的样板式代码中。 首先你需要创建一个数据库连接, 然后再创建一个语句对象, 最后你才能进行查询。

    为了平息JDBC可能会出现的怒火, 你必须捕捉SQLException, 这是一个检查型异常, 即使它抛出后你也做不了太多事情。

    最后, 毕竟该说的也说了, 该做的也做了, 你不得不清理战场, 关闭数据库连接、 语句和结果集。 同样为了平息JDBC可能会出现的怒火, 你依然要捕SQLException。

    程序清单1.12中的代码和你实现其他JDBC操作时所写的代码几乎是相同的。 只有少量的代码与查询员工逻辑有关系, 其他的代码都是JDBC的样板代码。

    JDBC不是产生样板式代码的唯一场景。 在许多编程场景中往往都会导致类似的样板式代码, JMS、 JNDI和使用REST服务通常也涉及大量的重复代码。

    Spring旨在通过模板封装来消除样板式代码。 Spring的JdbcTemplate使得执行数据库操作时, 避免传统的JDBC样板代码成为了可能。

    举个例子, 使用Spring的JdbcTemplate(利用了 Java 5特性的JdbcTemplate实现) 重写的getEmployeeById()方法仅仅关注于获取员工数据的核心逻辑, 而不需要迎合JDBC API的需求。 程序清单1.13展示了修订后的getEmployeeById()方法。

    image.png

    正如你所看到的, 新版本的getEmployeeById()简单多了, 而且仅仅关注于从数据库中查询员工。 模板的queryForObject()方法需要一个SQL查询语句, 一个RowMapper对象(把数据映射为一个域对象) , 零个或多个查询参数。 GetEmp loyeeById()方法再也看不到以前的JDBC样板式代码了, 它们全部被封装到了模板中。

    我已经向你展示了Spring通过面向POJO编程、 DI、 切面和模板技术来简化Java开发中的复杂性。 在这个过程中, 我展示了在基于XML的配置文件中如何配置bean和切面。

    但这些文件是如何加载的呢? 它们被加载到哪里去了? 让我们再了解下Spring容器, 这是应用中的所有bean所驻留的地方。下次聊。

    参考自:https://blog.csdn.net/huanghanqian/article/details/79340762

  • 相关阅读:
    MongoDB笔记: 安装和常见问题
    Spring Boot方式的Dubbo项目
    Centos7安装Redis5.0.5并加入Systemd服务
    生成Nginx服务器SSL证书和客户端证书
    OpenSSL的证书, 私钥和签名请求(CSRs)
    迁移Git项目到Gitlab
    Ubuntu18.04 Server安装Nginx+Git服务和独立的svn服务
    ESXi6.5上的Ubuntu虚机在远程SSH时宕机
    Nginx访问路径添加密码保护
    从阿里云DATAV GeoAtlas接口抽取行政区划数据
  • 原文地址:https://www.cnblogs.com/coder-ahao/p/14225747.html
Copyright © 2011-2022 走看看