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

简介: 欢迎查看Java开发之上帝之眼系列教程,如果您正在为Java后端庞大的体系所困扰,如果您正在为各种繁出不穷的技术和各种框架所迷茫,那么本系列文章将带您窥探Java庞大的体系。本系列教程希望您能站在上帝的角度去观察(了解)Java体系。

欢迎查看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开发之上帝之眼系列教程其他文章

本文资料来源:

Spring IN ACTION
Spring 实战 (第四版)

勘误&感谢

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

目录
相关文章
|
2月前
|
安全 Java Ruby
我尝试了所有后端框架 — — 这就是为什么只有 Spring Boot 幸存下来
作者回顾后端开发历程,指出多数框架在生产环境中难堪重负。相比之下,Spring Boot凭借内置安全、稳定扩展、完善生态和企业级支持,成为构建高可用系统的首选,真正经受住了时间与规模的考验。
248 2
|
1月前
|
安全 前端开发 Java
《深入理解Spring》:现代Java开发的核心框架
Spring自2003年诞生以来,已成为Java企业级开发的基石,凭借IoC、AOP、声明式编程等核心特性,极大简化了开发复杂度。本系列将深入解析Spring框架核心原理及Spring Boot、Cloud、Security等生态组件,助力开发者构建高效、可扩展的应用体系。(238字)
|
3月前
|
XML JSON Java
Spring框架中常见注解的使用规则与最佳实践
本文介绍了Spring框架中常见注解的使用规则与最佳实践,重点对比了URL参数与表单参数的区别,并详细说明了@RequestParam、@PathVariable、@RequestBody等注解的应用场景。同时通过表格和案例分析,帮助开发者正确选择参数绑定方式,避免常见误区,提升代码的可读性与安全性。
|
4月前
|
Java Spring
聊聊你对SpringBoot框架的理解 ?
SpringBoot是Spring家族中流行的子项目,旨在简化Spring框架开发的繁琐配置。它主要提供三大功能:starter起步依赖简化依赖管理,自动配置根据条件创建Bean,以及内嵌Web服务器支持Jar包运行,极大提升了开发效率。
167 0
|
1月前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
117 8
|
2月前
|
监控 Kubernetes Cloud Native
Spring Batch 批处理框架技术详解与实践指南
本文档全面介绍 Spring Batch 批处理框架的核心架构、关键组件和实际应用场景。作为 Spring 生态系统中专门处理大规模数据批处理的框架,Spring Batch 为企业级批处理作业提供了可靠的解决方案。本文将深入探讨其作业流程、组件模型、错误处理机制、性能优化策略以及与现代云原生环境的集成方式,帮助开发者构建高效、稳定的批处理系统。
356 1
|
4月前
|
安全 Java 微服务
Java 最新技术和框架实操:涵盖 JDK 21 新特性与 Spring Security 6.x 安全框架搭建
本文系统整理了Java最新技术与主流框架实操内容,涵盖Java 17+新特性(如模式匹配、文本块、记录类)、Spring Boot 3微服务开发、响应式编程(WebFlux)、容器化部署(Docker+K8s)、测试与CI/CD实践,附完整代码示例和学习资源推荐,助你构建现代Java全栈开发能力。
518 0
|
3月前
|
Cloud Native Java API
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
668 0
|
4月前
|
NoSQL Java 数据库连接
SpringBoot框架
Spring Boot 是 Spring 家族中最流行的框架,旨在简化 Spring 应用的初始搭建与开发。它通过自动配置、起步依赖和内嵌服务器三大核心功能,大幅减少配置复杂度,提升开发效率。开发者可快速构建独立运行的 Web 应用,并支持多种数据访问技术和第三方集成。
|
4月前
|
缓存 安全 Java
第五章 Spring框架
Spring IOC(控制反转)通过工厂模式管理对象的创建与生命周期,DI(依赖注入)则让容器自动注入所需对象,降低耦合。常见注解如@Component、@Service用于声明Bean,@Autowired用于注入。Bean默认单例,作用域可通过@Scope配置,如prototype、request等。Spring通过三级缓存解决循环依赖问题,但构造函数循环依赖需用@Lazy延迟加载。AOP通过动态代理实现,用于日志、事务等公共逻辑。事务通过@Transactional实现,需注意异常处理及传播行为。
84 0

热门文章

最新文章