Spring之旅
作者:@houkai
本文为作者原创,转载请注明出处:https://www.cnblogs.com/houkai/p/4691436.html
Java使得以模块化构建复杂应用系统成为可能,它为Applet而来,但为组件化而留。
Spring是一个开源的框架,最早由Rod Johnson创建。Spring是为了解决企业级应用开发的复杂性而创建的,但Spring又不仅仅局限于服务器端开发,任何Java应用都能在简单性、可测试性和松耦合性等方面从Spring中获益。
Spring可以做很多事情,但归根结底,支撑Spring的仅仅是少许的基本理念,所有的理念都可以追溯到Spring最根本的使命:简化Java开发。
为了降低Java开发的复杂性,Spring采取了一下4个关键策略:
- 基于POJO的轻量级和最小侵入性编程
- 通过依赖注入和面向接口实现松耦合
- 基于切面和惯例进行声明室编程
- 通过切面和模板减少样式代码
1、基于POJO的最小侵入式编程
在Java编程中,很多框架通过强迫应用继承它的类或实现它的接口从而让应用和框架绑死。典型的例子实现一个EJB2 的无状态回话Bean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package com.test.session; import javax.ejb.SessionBean; import javax.ejb.SessionContext; public class HelloWorldBean implements SessionBean { public void ejbActive(){} public void ejbPassivate(){} public void ejbRemove(){} public void setSessionContext(SessionContext ctx){} public String sayHello(){ return "Hello World" ; } public void ejbCreate(){} } |
SessionBean接口允许你实现若干个生命周期的回调方法以便参与到EJB的生命周期内。HelloWorldBean的大部分代码是为了EJB而编写的。这些重量级的框架都存在一个问题:强迫开发者编写大量的冗余代码、应用和框架绑定,并且难以测试代码。
Spring竭力避免因自身API而扰乱你的应用代码。Spring 不会强迫你实现Spring规范的接口或继承Spring规范的类。在Spring框架中,它的类没有任何痕迹表明你使用了Spring。最坏的场景是,一个类或许会使用Spring注解,但它依然是POJO。
采用Spring把HelloWorldBean重写,应该是这样子的。
1 2 3 4 5 6 7 | package com.test.session; public class HelloWorldBean { public String sayHello() { return "Hello World" ; } } |
HelloWorldBean没有实现、继承或导入任何与Spring API相关的东西,HelloWorldBean只是一个普通的Java对象。
尽管形式简单,但是POJO一样可以拥有魔力。Spring赋予POJO魔力的方式之一就是通过依赖注入来装配它们。
2、依赖注入
依赖注入 可能让人望而生畏,事实证明依赖注入并不像它听上去复杂。在项目中应用依赖注入,你会发现代码更简单、更易理解和测试。
任何有实际意义的应用都是由两个或更多地类组成的,它们之间相互协作来完成特定的业务逻辑。通常,每个对象负责管理和自己协作的对象(它依赖的对象)引用,这就难免导致了高度耦合和难以测试。
例如knights类
1 2 3 4 5 6 7 8 9 10 11 12 13 | package com.test.knights; public class DamselRescuingKnight implements Knight { private RescueDamselQuest quest; public DsmselRescuingKnight() { quest = new RescueDamselQuest(); } public void embarkOnQuest() throws QuestException { quest.embrak(); } } |
DamselRescuingKnight在构造函数中自行创建了RescueDamselQuest,这使得二者耦合在一起;更糟糕的是,为这个DamselRescuingKnight编写单元测试将出奇的困难。在这样的测试中,你必须保证当embarkOnQuest方法被调用时,embrak 方法也被调用,但是没有一个简明的方式能够实现这点。
耦合的两面性:一方面耦合的代码难以测试、复用和理解;另一方面,一定的耦合又是必须的,为了完成特定功能不同的类必须以适当的方式进行交互。
通过依赖注入,对象的依赖关系将由负责协调系统中各个对象的第三方组件在创建对象时设定。对象无需创建和管理它们的依赖关系——依赖关系会被自动注入到需要它们的对象中去。
1 2 3 4 5 6 7 8 9 10 11 12 13 | package com.test.knights; public class BraveKnight implements Knight { private Quest quest; public BraveKnight(Quest quest) { this .quest = quest; } public void embarkOnQuest() throws QuestException { quest.embrak(); } } |
此时的BraveKnight并没有自行创建Quest,而是在构造函数时把探险任务作为构造器参数传入,这是依赖注入的方式之一,即构造器注入。
更重要的是,它传入的类型是Quest,这可以是个接口,所以BraveKnight可以相应RescueDamselQuest、SlayDragonQuest...等任何Quest实现。
对依赖进行替换的最常用方法之一,就是测试的时候使用mock实现。你无法充分测试DamselRescuingKnight,因为它是紧耦合的,但可以轻松测试BraveKnight,只需要给它提供一个Quest的mock实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package com.test.knights; import static org.mockito.Mockito.*; import org.junit.Test; public class BraveKnightTest { @Test public volid knightShouldEmbrakOnQuest() throws QuestException { Quest mockQuest = mock(Quest. class ); BraveKnight knight = new BraveKnight(mockQuest); knight.embarkOnQuest(); verify(mockQuest, time( 1 )).embrak(); } } |
通过现有的mock对象,你可以创建一个新的BraveKnight实例,通过构造器注入mock Quest。当调用embarkOnQuest()方法时,你可以要求Mockito框架验证Quest的mock实现的embrack 方法仅仅被调用了一次。
创建应用插件之间协作的行为通常称之为装配。Spring有多种装配Bean的方式,采用XML是最常用的方式。
1 2 3 4 5 6 7 8 9 10 | <? 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.xsd"> < bean id = "knight" class = "com.test.knight.BraveKnight"> < constructor-arg ref = "quest" /> </ bean > < bean id = "quest" class = "com.test.knight.SlayDragonQuest" /> </ beans > |
现在已经声明了BraveKnight和Quest的关系,你只需要装在XML配置文件,并把应用启动起来。
Spring通过应用上下文(Application Context)装载Bean的定义并将它们组装起来。
1 2 3 4 5 6 7 8 9 10 11 12 | package com.test.knights; import org.springframework.context.ApplicationContext; import org.springframework.context.ClassPathXmlApplicationContext; public class KnightMain { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext( "knights.xml" ); Knight knight = (Knight) context.getBean( "knight" ); knight.embarkOnQuest(); } } |
注意Knight这个类型完全没有意识到它所要执行的quest(例如com.test.knight.SlayDragonQuest),只有knights.xml文件知道具体的组装配置。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架