当前位置: 首页 > news >正文

第四章:重新来认识你的老朋友Spring框架

欢迎查看Java开发之上帝之眼系列教程,如果您正在为Java后端庞大的体系所困扰,如果您正在为各种繁出不穷的技术和各种框架所迷茫,那么本系列文章将带您窥探Java庞大的体系。本系列教程希望您能站在上帝的角度去观察(了解)Java体系。使Java的各种后端技术在你心中模块化;让你在工作中能将Java各个技术了然于心;能够即插即用。本章我们来一起了解你的老朋友Spring框架。

Spring这个词对于开发者想必不会陌生,可能你每天都在使用Spring,享受着Spring生态提供的服务,理所当然的用着SpringIOC和SpringAOP去实现老板交给你的功能 ,唔 它就是这样使用的(类声明为Bean组件,然后注入),没错 能完成老板任务,没毛病。如果向你提问什么是Spring,Spring有什么核心功能呢,你会想:这太简单了,Spring就是框架嘛,Spring核心功能就是IOC和AOP,So Easy!!可是你真的了解Spring吗?带着这个疑问让我们一起来重新了解一下Spring!本文所有示例源码下载

Spring的起源和根本使命

Spring是一个开源框架,最早由RodJohnson创建,是为了解决企业级应用开发的复杂性而创建的。很多框架都宣称在某些方面针对Java开发做出了简化,但是Spring的目标是致力于全方位简化Java开发,这也是Spring的根本使命"简化Java开发"
在这里插入图片描述

Spring如何简化Java开发

为了降低Java开发的复杂性,Spring采取了以下4种关键策略,请务必牢记下面4中策略于心,这样会帮助你更深的理解Spring。下面我们来逐一解读每一条策略,你就能明白Spring是如何简化Java开发的。

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

1.基于POJO的轻量级和最小侵入性编程

Spring竭力避免因自身的API而弄乱你的代码。Spring不会强迫你实现Spring规范的接口或继承Spring规范的类。相反,在一个基于Spring构建的应用中,应用中的类没有任何痕迹表明你使用了Spring。最坏的情况就是你在应用的类中发现了Spring的相关注解,但它依然是POJO,下面我们来看一个在Spring中的一个POJO例子。example1实例源码下载

/**
 * @Author:jimisun
 * @Description:
 * @Date:Created in 20:32 2018-09-26
 * @Modified By:
 */
public class HelloSpringBean {
    public String sayHello() {
        return "Hello Spring!!!";
    }
}

你可以看到,这就是一个POJO(简单的JAVA类),没有任何地方表明它是Spring组件,Spring非侵入式编程模型意味着这个类在Spring应用和非Spring应用中都可以发挥同样的作用。尽管看起来很简单;但Spring通过IOC(Inversion of Control)管理这个POJO,然后通过DI(Dependency Inject)来注入他们,这个POJO就变的魔力十足;那么DI是如何帮助应用对象彼此之间保持松耦合的呢?

2.通过依赖注入和面向接口实现松耦合

来我们一起来看一下一个简单的POJO example2实例源码下载

/**
 * @Author:jimisun
 * @Description:
 * @Date:Created in 07:44 2018-09-27
 * @Modified By:
 */
public class BeautifulGirl implements Gril {

    private EatAction eat;

    public BeautifulGirl() {
        this.eat = new EatAction();
    }
    @Override
    public void action() {
        eat.action();
    }
}

在BeautifulGirl(可爱的小女孩)这个类中,在构造方法中创建一个EatAction(吃饭动作)。这样就极大限制了BeautifulGirl(可爱的小女孩)的能力;如果现在小女孩需要去玩耍呢?或者需要去睡觉呢?真是太抱歉了,BeautifulGirl(可爱的小女孩)只会吃东西这个动作。这是什么原因呢?这就是BeautifulGirl(可爱的小女孩)和EatAction(吃饭动作)这两个类紧紧耦合在了一起!紧密耦合同时会造成代码的难以测试,难以服用,难以理解,并且典型地表现出"打地鼠“式的Bug特性(修复一个Bug,将会出现一个或多个新Bug),所以我们可以知道耦合是必须的,但必须谨慎管理耦合。 但是怎么才算是谨慎处理耦合关系呢?
在这里插入图片描述

上图所示,DI(依赖注入)会将所依赖关系自动交给目标对象,而不是让对象本身创建所依赖对象。对象的依赖关系将由系统中负责协调个对象依赖关系的第三方组件(类似Spring)在创建对象的时候设定。现在我们明白了通过DI依赖注入可以将对象之间依赖关系轻松解耦,那么我们来看一下简单的实例吧。 example3实例源码下载

/**
 * @Author:jimisun
 * @Description:
 * @Date:Created in 07:44 2018-09-27
 * @Modified By:
 */
public class BeautifulGirl implements Gril {

    private Action action;


    public BeautifulGirl(Action action) {
        this.action = action;
    }

    @Override
    public void action() {
        action.action();
    }
}

从上面实例代码中可以看到BeautifulGirl本身并没有创建任何的动作,而是通过构造方法传入一个实现了Action(动作)接口的实现类即可,也就是说BeautifulGirl可以完成任意实现了Action接口的动作(睡觉啦...玩耍啦...旅行啦....)。
这里的要点是BeautifulGirl没有与任何特定的Action发生耦合。BeautifulGirl只需要的是一个实现Action接口就行,对象本身只是通过接口(而非具体实现或初始化过程)来表明依赖关系,那么这种依赖就能够在BeautifulGirl不知情的情况下替换不同的具体动作。好了我们现在明白了DI进行依关系解耦的原理了,下面我们看一下如何在Spring中应用DI。 example4实例源码下载

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean class="com.jimisun.spring.example4.BeautifulGirl" id="beautifulGirl">
        <constructor-arg ref="action"/>
    </bean>

    <bean class="com.jimisun.spring.example4.SleepAction" id="action"></bean>

</beans>

/**
 * @Author:jimisun
 * @Description:
 * @Date:Created in 07:53 2018-09-27
 * @Modified By:
 */
public class Main {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Spring.xml");
        BeautifulGirl beautifulGirl = (BeautifulGirl) context.getBean("beautifulGirl");
        beautifulGirl.action();
        context.close();
    }
}

这样执行Main方法,从Context中获取BeautifulGirl实例执行action方法。当然Spring提供了基于Java的配置,可作为XML配置文件的代替方案 example5实例源码下载

/**
 * @Author:jimisun
 * @Description:
 * @Date:Created in 08:40 2018-09-27
 * @Modified By:
 */
@Configuration
public class SpringConfig {

    @Bean
    public SleepAction sleepAction() {
        return new SleepAction();
    }

    @Bean
    public BeautifulGirl beautifulGirl() {
        return new BeautifulGirl(sleepAction());
    }
}

只不过Spring的上下文的实现应该使用AnnotationConfigApplicationContext进行加载配置

/**
 * @Author:jimisun
 * @Description:
 * @Date:Created in 07:53 2018-09-27
 * @Modified By:
 */
public class Main {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        SleepAction action = applicationContext.getBean(SleepAction.class);
        action.action();
    }
}
 

3.基于切面和惯例进行声明式编程

现在我们已经明白DI能够将互相协作的软件保持松耦合,而面向切面编程(aspect-oriented-programming,AOP)可以将遍布应用各处的功能分离出来形成可从用的组件。

我们仍然使用上面的例子,BeautifulGirl随心所欲的执行各种动作真的没问题吗?如果可爱的小女孩执行的动作是去河边玩耍,吃一些危险的东西呢?那么我们势必需要一个家长(Parent类)进行监督小女孩的动作是否是安全的,下面我们来看一下Parent类。example6实例源码下载

/**
 * @Author:jimisun
 * @Description:
 * @Date:Created in 09:32 2018-09-27
 * @Modified By:
 */
public class Parent {
    public void check() {
        System.out.println("检查动作是否安全.......");
    }
}

非常简单!Parent(家长类)只有一个方法就是check,那么现在就让Parent对BeautifulGirl的执行动作进行检查吧。

 <bean class="com.jimisun.spring.example6.BeautifulGirl" id="beautifulGirl">
        <constructor-arg ref="action"/>
        <constructor-arg ref="parent"/>
    </bean>

    <bean class="com.jimisun.spring.example6.SleepAction" id="action"></bean>
    <bean class="com.jimisun.spring.example6.Parent" id="parent"></bean>
</beans>
/**
 * @Author:jimisun
 * @Description:
 * @Date:Created in 07:44 2018-09-27
 * @Modified By:
 */
public class BeautifulGirl implements Girl {

    private Action action;

    private Parent parent;

    public BeautifulGirl(Action action, Parent parent) {
        this.action = action;
        this.parent = parent;
    }

    @Override
    public void action() {
        parent.check();
        action.action();
    }
}

我们非常聪明,使用了DI将Parent类注入到了BeautifulGirl类中,在BeautifulGirl执行action动作之前执行 parent.check()方法,这样我们要的效果就达到了。等等....好像有什么不对的地方?

  • 管理Parent家长的check动作真的是美丽的小女孩的职责吗?
  • 将Parent家长注入到美丽的小女孩类中不是将代码复杂化了吗?
  • 我们需不需要一个不需要家长注入的美丽的小女孩呢?
  • 如果注入的Parent为NULL我们是否应该在美丽的小女孩中进行校验呢?

真的是太糟糕了,注入一个Parent(家长)将会产生那么多的问题,为什么呢?因为Parent类的业务于BeautifulGirl无关;BeautifulGirl将会承担Parent类的职责。

在一个项目中,很多系统服务都会分布到各个业务模块中,这使得业务模块的逻辑变得十分复杂,而使用AOP能够使这些服务模块化,并以声明式的方式将他们应用到他们所需要影响的组件中,使得这些组件将会具有更高的内聚性并且更加关注自身的业务逻辑,完全不需要了解系统服务所带来的复杂性。

下面我们使用SpringAOP对上面代码进行改造 example7实例源码下载
 

 <!--声明Bean-->
    <bean class="com.jimisun.spring.example7.Parent" id="parent"></bean>

    <!--声明切面-->
    <aop:config>
        <aop:aspect ref="parent">
            <aop:pointcut id="girlAction" expression="execution(* com.jimisun.spring.example7.Action.*(..))"/>
            <aop:before pointcut-ref="girlAction" method="check"/>
        </aop:aspect>
    </aop:config>
 
/**
 * @Author:jimisun
 * @Description:
 * @Date:Created in 07:44 2018-09-27
 * @Modified By:
 */
public class BeautifulGirl implements Girl {

    private Action action;
    
    public BeautifulGirl(Action action) {
        this.action = action;
    }

    @Override
    public void girlAction() {
        action.action();
    }
}

我们只需要在Spring配置文件中声明Parent为Spring Bean,然后将配置在SpringAOP中即可,我们可以发现BeautifulGirl类中没有发现任何关于Parent的信息,这就是AOP的魅力所在——以声明的方式将组件应用到他们所需要影响的组件中!

4.通过切面和模板减少样版式代码

在通过JavaAPI进行编程,我们经常会编写很多重复的代码例如JDBC访问数据库,JMS,JNDI等场景下会重复编写大量的样版式代码,而Spring皆在通过模板封装来消除模板式代码,例如Spring针对JDBC访问数据库进行封装的JdbcTemplate等,这里就是Spring使用封装的模板来减少JavaAPI样版式代码。example8实例源码下载

/**
 * @Author:jimisun
 * @Description:
 * @Date:Created in 11:13 2018-09-27
 * @Modified By:
 */
public class Main {
    public static void main(String[] args) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.execute("select * from user");
    }
}
 

非常感谢大家耐心能看到这里,在本文中我尽可能地详细阐述Spring使如何简化Java开发?遵循哪些策略?以及为什么遵循的好处。

Java开发之上帝之眼系列教程其他文章

  • Java开发之上帝之眼系列教程前言和章节目录汇总

本文资料来源:

Spring IN ACTION
Spring 实战 (第四版)

勘误&感谢

  本系列文章资料来源很多出自于互联网和在下本身的见解,受限于个人技术能力水平和其他相关知识的限制,相关见解错误或者资料引用错误请各位帮助留言校正!引用资料多来自于互联网,在下在引用前会遵循各位前辈或者博主的引用说明表示感谢,但互联网资料多是转发再转发或存在遗漏请原作者内信联系指正。

相关文章:

  • Python知识点总结篇(三)
  • OSChina 周五乱弹 —— 三口气印度史(3)
  • linux文件的3个时间和7种文件类型
  • 域名被墙怎么办?域名被墙案例-解决办法
  • Grid Layout
  • 首屏时间从12.67s到1.06s,我是如何做到的?
  • Fabric遇到问题和解决方法总结
  • 如何在Pycharm中自动添加时间日期、作者等信息
  • vc枚举本机端口信息
  • 读ant design设计语言 记录
  • BZOJ[Usaco2017 Jan]Promotion Counting——线段树合并
  • Bokeh中数据的添加、修改和筛选 | Bokeh 小册子
  • 2018-2019-1 20165226 《信息安全系统设计基础》第3周学习总结
  • Spring boot初体验
  • Angular2入门教程-2 实现TodoList App
  • 【前端学习】-粗谈选择器
  • Android交互
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • Git同步原始仓库到Fork仓库中
  • javascript 哈希表
  • Java面向对象及其三大特征
  • java正则表式的使用
  • leetcode-27. Remove Element
  • leetcode98. Validate Binary Search Tree
  • Promise面试题2实现异步串行执行
  • 理解IaaS, PaaS, SaaS等云模型 (Cloud Models)
  • 巧用 TypeScript (一)
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 运行时添加log4j2的appender
  • 正则表达式
  • 你学不懂C语言,是因为不懂编写C程序的7个步骤 ...
  • 移动端高清、多屏适配方案
  • ​比特币大跌的 2 个原因
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (2/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (附源码)基于SSM多源异构数据关联技术构建智能校园-计算机毕设 64366
  • (四)Android布局类型(线性布局LinearLayout)
  • (转)h264中avc和flv数据的解析
  • (转)负载均衡,回话保持,cookie
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存
  • .NET core 自定义过滤器 Filter 实现webapi RestFul 统一接口数据返回格式
  • .NET Framework Client Profile - a Subset of the .NET Framework Redistribution
  • .net MySql
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter
  • .NET构架之我见
  • .net项目IIS、VS 附加进程调试
  • [ CTF ]【天格】战队WriteUp- 2022年第三届“网鼎杯”网络安全大赛(青龙组)
  • [2019.3.20]BZOJ4573 [Zjoi2016]大森林
  • [2544]最短路 (两种算法)(HDU)
  • [BZOJ] 2427: [HAOI2010]软件安装
  • [bzoj1912]异象石(set)
  • [C++]C++入门--引用
  • [HDU] 1054 Strategic Game 入门树形DP