Spring Framework基础知识:依赖注入与面向切面

本文通过《Spring in Action》一书中的理念,介绍了Spring框架的核心特性——依赖注入(DI)与面向切面编程(AOP)。通过《权力的游戏》中的人物角色,设计了一个编程实践案例,展示了依赖注入的实际应用。同时,探讨了AOP如何帮助分离关注点,提高代码复用性和清晰度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《Spring in Action》是一本很不错的Spring工具书,作者Craig Walls是Spring核心开发人员,特别喜欢用有趣的例子减少读者对枯燥理论和恐怖名词的畏惧感。受到他的影响,我在学习和做笔记的过程中,也试图不那么严肃,把自己喜欢的一些东西引入到编程实践中。

上一篇文章《用一个极简的Spring Demo开始记录2018年的满血转身》用一个很简单的Demo作为学习Spring的开始,今天开始用更多的编程实践来记录Spring更深入的知识。

Spring Framework为简化Java开发而生,它的每个机制都仅仅围绕着四个核心策略

  1. 基于POJO(Plain Ordinary Java Object)的轻量级和最小侵入性编程
  2. 通过依赖注入(DI)和面向接口实现松耦合
  3. 基于切面(AOP)进行声明式编程
  4. 通过切面减少样板式代码

Spring是轻量级的框架,而早期版本的Struts、WebWork等重量级框架存在的问题:强迫开发者按照框架接口要求编写大量冗余代码,导致应用被框架绑架,通常也难以编写测试代码

每当提起Spring,两个名词一定会脱口而出:依赖注入(DI)和面向切面编程(AOP),接下来分别说一说他们俩。

依赖注入

  • 如何理解耦合?耦合具有两面性:一方面,紧密耦合的代码难以测试、复用和理解;另一方面,一定程度的耦合是必须的,没有耦合的代码什么也做不了。因此,耦合是必须的,但应当小心谨慎的使用
  • 通过依赖注入的方式,为耦合提供了一种良好的解决手段。通过依赖注入,对象的依赖关系由负责协调系统中各个对象关系的第三方组件在创建对象时设定。对象本身无需自行创建和管理和其他对象的依赖关系。依赖注入是一个更抽象的名词--控制反转的(IoC)的一种实现方式
  • 通过依赖注入的方式,实现了对象间的松耦合。依赖注入推崇通过接口,而非具体实现或初始化的过程,来表明依赖关系。这种依赖能够在对象本身毫不知情的情况下,用不同的具体实现进行替换(正是利用了Java面向对象编程的动态绑定概念)
  • 创建对象之间的协作关系通常称为“装配”。Spring有多种装配方式,常见的是通过XML或者注解的方式

《Spring in Action》在介绍依赖注入时,用了骑士作为例子。骑士的例子很棒,但由于好多单词都不认识,我还是引用挚爱的美剧 《权力的游戏》中的人物作为依赖注入的编程实践

出场人物:“弑君者”詹姆·兰尼斯特,正义勇敢的艾德·斯塔克,“疯王”伊利斯·塔格里安(他其实只出现在打印中)

说明:应该尽可能习惯于面向接口的编程。面向接口是Spring推崇的,能够充分利用Java的动态绑定机制,悄无声息的改变运行态的对象类型,最大限度减少代码变更。

Knight接口,代表被各种各样的骑士,但这些骑士都要实现actionOnExplore()方法,代表其探险之旅干了什么

package com.study.spring.knights;

public interface Knight {
	public void actionOnExplore();
}

Explore接口,只有action()一个方法,代表探险具体的动作

package com.study.spring.knights;

public interface Explore {
	public void action();
}

HandsomeKnight,实现Kinght接口,代表帅哥类型的骑士

package com.study.spring.knights;

public class HandsomeKnight implements Knight {
	private Explore explore;
	
	public HandsomeKnight(Explore explore) {
		this.explore = explore;
	}
	
	@Override
	public void actionOnExplore() {
		explore.action();
	}
}

BraveKnight,实现Kinght接口,代表勇敢类型的骑士

package com.study.spring.knights;

public class BraveKnight implements Knight {
    private Explore explore;
	
	public BraveKnight(Explore explore) {
		this.explore = explore;
	}
	
	@Override
	public void actionOnExplore() {
		explore.action();
	}

}

KingslayerExplore,实现Explore接口,想必《权游》爱好者已经明白它要干什么了吧

package com.study.spring.knights;

public class KingslayerExplore implements Explore {

	@Override
	public void action() {
		System.out.println("Hi, I'm Jamie Lannister. Goodbye Aerys II Targaryen ...");
	}
}

OverthrowExplore,实现Explore接口,Overthrow啥意思?友情做个广告:有道词典,牛逼到不行不行的

package com.study.spring.knights;

public class OverthrowExplore implements Explore {

	@Override
	public void action() {
		System.out.println("Hi, I'm Eddard Stark. I overthrow Aerys II Targaryen with my brother Robert Baratheon...");
	}
}

然后就是在XML中定义Bean的依赖注入关系。

我将KingslayerExplore和OverthrowExplore以构造参数的方式分别注入到HandsomeKnight和BraveKnight中。当Spring Framework被调度起来后,会在其ApplicationContext容器中,创建上述四个实例,并根据注入关系建立起依赖关系。在HandsomeKnight和BraveKnight中,完全看不到new出一个Explore对象的语句。在运行过程中,不知不觉就被注入了具体的Explore。

如果我们觉得HandsomeKnight不应该被冠以Kingslayer之名,完全可以注入一个LoveSisterExplore,哈哈哈...

<?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-3.0.xsd">  
    
    
    <!-- Inject through constructor -->
    <bean id="jamieLannister" class="com.study.spring.knights.HandsomeKnight">
        <constructor-arg ref="kingslayer"/>
    </bean> 
    
    <bean id="eddardStark" class="com.study.spring.knights.BraveKnight">
        <constructor-arg ref="overthrow"/>
    </bean>
    
    <bean id="kingslayer" class="com.study.spring.knights.KingslayerExplore"/>
    
    <bean id="overthrow" class="com.study.spring.knights.OverthrowExplore"/>  
  
</beans>  

最后,写一个KnightMain,调度起Spring Framework,加在KnightXML

在main方法中,使用Spring框架接口,分别得到不同类型的Knight

package com.study.spring.knights;

import org.springframework.context.ApplicationContext; 
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class KnightMain {

	public static void main(String[] args) {
		
		//Create IoC container through XML
		ApplicationContext apc = new ClassPathXmlApplicationContext("Knight.xml");  
        
		//A Knight Jamie Lannister instance injected 
		Knight knight = (Knight)apc.getBean("jamieLannister");  
        
		//Invoke Jamie method
		knight.actionOnExplore();
		
		//A Knight Eddard Stark instance injected 
		knight = (Knight)apc.getBean("eddardStark");  
        
		//Invoke Eddard method
		knight.actionOnExplore();
		
	}
	
}

运行结果如期所至:

3月 08, 2018 10:20:43 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7fac631b: startup date [Thu Mar 08 22:20:43 CST 2018]; root of context hierarchy
3月 08, 2018 10:20:43 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [Knight.xml]
Hi, I'm Jamie Lannister. Goodbye Aerys II Targaryen ...
Hi, I'm Eddard Stark. I overthrow Aerys II Targaryen with my brother Robert Baratheon...


面向切面编程

  • 依赖注入让相互协作的软件组件保持松耦合,AOP则用来将遍布应用各处的功能分离出来形成可复用的组件。诸如日志、事件、事务管理、安全等系统服务经常要显示的融入到业务逻辑之中。这些系统服务通常称为横切关注点,因为它们总是跨越系统的多个组件
  • 关注点分散在多个组件中,对代码带来的明显损害是:1)关注点代码重复出现在系统的多个软件组件中,修改起来异常耗时;2)系统因这些和业务逻辑不强相关的代码变得异常混乱
  • 比如下图的示例,业务对象不仅知道要记录日志、进行安全控制和参与事务,还要亲自调用这些服务或接口,但它们并不是核心业务

  • 利用AOP,可以用这些功能层来包裹核心业务层。功能层以声明的方式应用到系统中,核心业务都可以不知道这些功能层的存在,是一种业务逻辑和非业务逻辑分离的编程模式

未完待续……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值