Spring in Action——Spring之旅

本文深入解析Spring框架的核心理念,如依赖注入(DI)和面向切面编程(AOP),并探讨其如何简化Java开发,包括POJO编程、松耦合、声明式编程及模板化减少样板代码。同时介绍了Spring容器的不同实现,以及bean的生命周期管理。

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

根本使命

Spring的目标是致力于全方位的简化java开发:

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

POJO
Spring竭力避免因为自身API而弄乱你的应用代码。Spring不会强迫你实现Spring规范的接口或继承Spring规范的类,相反,在基于Spring构建的应用中,它的类通常没有任何痕迹表明你使用了Spring

依赖注入DI

public class DamseRescuingKnight implements Knight{
	private RescueDamseQuest quest;
	public DamseRescuingKnight(){
		this.quest=new RescueDamseQuest;
	}
	public void embarkOnQuest(){
		quest.embark();
	}
}

可以看到,它的构造函数自己创建了一个RescueDamseQuest,这使得耦合度太高。并且使得测试异常困难,在这样也测试中,你必须保证当骑士的embarkOnQuest()方法被调用的时候,探险的embark()方法也要被调用,但是没有一个简单明了的方式能够实现这一点。很遗憾,DamseRescuingKnight将无法进行测试。

那么实现了DI之后,如下:

public class BraveKnight implements Knight{
	private Quest quest;//变成了Quest,而不是它的实现(比如RescueDamseQuest或者SlayDragonQuest)
	public BraveKnight(Quest quest){//构造方法注入
		this.quest=quest;
	}
	public void embarkOnQuest(){
		quest.embark();
	}
}

这里的要点是BraveKnight 没有和 任何 特定的 Quest发生耦合,对他来说,只要是实现了Quest接口的探险任务就可以,具体是哪种无关紧要。
对依赖进行替换的一个常用方法就是在测试的时候使用mock实现,在使用DI后,我们可以轻松的测试BraveKnight:

public class BraveKnightTest{
	@Test
	public void knightShouldEmbarkOnQuest(){
		Quest mockQuest=mock(Quest.class);
		BraveKnight knight=new BraveKnight(mockQuest);//注入mockQuest
		knight.embarkOnQuest();
		verify(mockQuest,times(1).embark();)
	}
}

使用XML装配:

<beans>
	<bean id="knight" class="..BraveKnight">
		<constructor-arg ref="quest"/>
	</bean>
	<bean id="quest" class="..SlayDragonQuest"></bean>
</beans>

如果XML配置不符合你的喜好,那么还可以使用java:

@Configuration
public class KnightConfig{
	@Bean
	public Knight knight(){
		return new BraveKnight(quest());
	}
	@Bean
	public Quest quest(){
	return new SlayDragonQuest(System.out);
	}
}

Spring通过应用上下文(Application Context)装在bean的定义并且把它们组装起来。因为knights.xml中的bean是使用XML文件进行配置的,所以选择ClassPathXmlApplicationContext作为应用上下文相对是比较核实的。该类加载位于应用程序路径下的一个或多个XML配置文件:

public class KnightMain{
	public static void main(String[] args)throws Exception{
		ClassPathXmlApplicaitionContext context=new ClassPathXmlApplicationContext("META-INF/spring/knights.xml");
		Knight knight=context.getBean(Knight.class);//获取Knight bean
		knight.embarkOnQuest();//使用kngiht
		context.close();
	}
}

AOP
AOP使得诸如事务管理、安全模块、日志模块等服务模块化(因为如果不使用AOP,他们到处存在于java代码中)。所以可以使得组件会高内聚,并且更加关注自身的业务,而不需要关注系统服务而带来复杂性。
也就是AOP是的POJO简单。
为了演示Spring中如何应用AOP,我们重新回到骑士的例子:
假设吟游诗人用诗歌记载了骑士的事迹(日志)。

public class Minstrel{
	private PrintStream stream;
	public Minstrel(PrintStream stream){
		this.stream=stream;
	}
	public void singBeforeQuest(){//探险前调用
		stream.println("Fa la la, the knight is so brave");}
	public void SingAfterQuest(){//探险后调用
		stream.println("Tee hee hee, the brave knight"+
		"did embark on a quest !");}
}

Minstrel会通过一个PrintStream类来歌颂骑士的事迹,分别在探险前后都会歌颂。下面的程序尝试了吟游诗人和骑士的第一次组合:

public class BraveKnight implements Knight{
	private Quest quest;
	private Minstrel minstrel;
	public BraveKnight(Quest quest,Minstrel minstrel){
		this.quest=quest;
		this.minstrel=minstrel;
	}
	public void embarkOnQuest() throws QuestException{
		minstrel.singBeforeQuest();
		quest.embark();
		minstrel.singAfterQuest();
	}
}

Knight应该管理它的Minstrel么?显然不是,它脱离了Knight的职责,此外骑士需要知道吟游诗人,所以必须把它注入到自己的类中,会使得自己更加复杂。
要将Minstrel抽象为一个切面,所需要做的事情就是在一个Spring的配置文件里声明它:

<bean id="knight" class="..BraveKnight">
	<constructor-arg ref="quest"/>
</bean>
<bean id="quest" class="..SlayDragonQuest">
	<constructor-arg value="#(T(System).out)"/>
</bean>
<!--声明Minstrel bean-->
<bean id="minstrel" class="..Minstrel">
	<constructor-arg value="#(T(System).out)"/>
</bean>
<aop:config>
	<aop:aspect ref="minstrel">
		<!--定义切点-->
		<aop:pointcut id="embark" 
			expression="execution(* *.embarkOnQuest(..))"/>
		<aop:before pointcut-ref="embark"
			method="singBeforeQuest"/>
		<aop:after pointcut-ref="embark"
			method="singAfterQuest"/>
	</aop:aspect>
</aop:config>

这里使用了aop配置命名空间把Minstrel bean声明为一个切面。首先,需要把Minstrel声明为一个bean,然后在<aop:aspect>元素中引用该bean。为了进一步定义切面,声明(使用<aop:before>)在embarkOnQuest()放啊执行前调用Minstrel的singBeforeQuest()方法,这种方式称为前置通知。同时也有后置通知。
这两种方式中,pointcut-ref属性都引用了名字为embark的切入点,该切入点实在前面的<pointcut>元素中定义的,并配置expression属性来选择所应用的通知。表达式的语法采用的是AspectJ的切点表达式语言(现在毋须了解AspectJ或者编写AspectJ的切点表达式语言的细节,之后会重复)。

使用模版消除样板式代码
举个例子,如果你曾经使用过jdbc,相信你写过如下类似的代码:

public Employee getEmployeeById(long id){
	Connection conn=null;
	PreparedStatement stmt=null;
	ResultSet rs=null;
	try{
		conn=dataSource.getConnection();
		...
	}catch(SQLException e){...}
	finally{
	if(rs!=null){try{...}catch(...){...}}
	...
	}
}

正如你所看到,少量的查询代码淹没在一堆JDBC样板式代码中,建立链接关闭链接。JDBC不是产生样板式代码的唯一场景,JMS/JNDI/REST服务等等都会产生大量的重复代码。
Spring旨在通过模版封装来消除样板式代码,举个例子,使用Spring的JdbcTemplate重写getEmployeeById()方法仅仅关注获取员工数据的核心逻辑,而不需要迎合JDBC API的需求:

public Employee getEmployeeById(long id){
	return jdbcTemplate.queryForObject{
		"select id,firstname,lastname,salary form emplyee where id=?",
		new RowMapper<Employee>(){
			public Employee mapRow(ResultSet rs,int rowNom)throws SQLExcepion{
				Employee employee=new Employee();
				employee.setId(rs.getLong("id"));
				employee.setFirstName(rs.getString("firstname"));
				...//各种set
				return employee;
			}
		},
		id);//指定查询参数
}

新版本的getEmployeeById()简单多了,而且仅仅关注于从数据库中查询员工。模版的queryForObject()方法需要一个SQL查询语句,一个RowMapper对象(把数据映射为一个域对象),零个躲着多个查询参数。

容纳你的bean

Spring容器并不是只有一个,Spring自带了多个容器实现,可以归为两种不同的类型:BeanFactory是最简单的容器,提供基本的DI支持;ApplicationContext基于BeanFactory构建,并提供应用框架级别的服务。
但是BeanFactory对于大多数应用来说往往太低级了,我们这里主要讨论ApplicationContext的使用上。
使用ApplicaitonContext
下面罗列几个最有可能遇到的应用上下文:
AnnotationConfigApplicationContext:从一个或者多个基于Java配置类中加载Spring应用上下文。
AnnotationConfigWebApplicationContext:从一个或者多个基于Java配置类中加载Spring Web应用上下文。
XmlWebApplicationContext:从Web应用下的一个或者多个XML配置文件中加载上下文定义。
ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
FileSystemXmlApplicationContext:从文件系统下的一个或者多个XML配置文件中加载上下文定义。

我们之后会提到前三种ApplicaitonContxt。我们现在先简单使用ClassPathXmlApplicationContext和FileSystemXmlApplicationContext。无论是从文件系统还是类路径装在应用上下文,将bean加载到BeanFactory的过程是类似的,区别在于一个是从文件系统路径下查找xml文件,另一个是在所有的类路径下查找xml文件。

ApplicationContext context=new FileSystemXmlApplicationContext("C:/knight.xml");
//或者
ApplicationContext context=new ClassPathXmlApplicationContext("knight.xml");

而如果想从java配置中加载应用上下文:

ApplicationContext context=new  AnnotationConfigApplicationContext(com.springinaction.knights.config.KnightConfig.class);

bean的生命周期
在这里插入图片描述

Spring生态圈

spring模块
当我们下载Spring发布版本并查看其lib目录的时候,会发现里面有多个jar文件。在Spring4.0中,Spring框架的发布版本包括了20多个不同的模块。
这些模块根据功能可划分为6种。
在这里插入图片描述
总而言之,这些模块为企业级应用开发提供了所需的一切。
在这里插入图片描述
让我们逐一浏览Spring模块,看看它们是怎么构建起Spring生态圈的
Spring核心容器
容器是Spring框架最核心的部分,它掌管着Spring应用中bean的创建、配置和管理。包括了bean工厂——提供DI功能。基于bean工厂,我们还可以知道有Application Factory的存在。
Spring的AOP模块
AOP解耦合。
数据访问与集成
简化模版。
Web与远程调用
MVC模式是一种普遍被接受的构建Web应用的方法,它可以帮助用户将界面逻辑与应用逻辑分离。java从来不缺少MVC框架,SpringMVC我们将在后续学习到。
远程调用功能集成了RMI/Hessian/Burlap/JAX-WS等等,后面我们会学习到。
Instrumentation
Spring的Instrumentation模块提供了为JVM添加代理(agent)的功能。具体的来讲,它为Tomcat提供了一个织入代理,能够为Tomcat传递类文件,就像这些文件是被类加载器加载的一样。在本书中,我们不介绍该模块。
测试
监狱开发者自测的重要性,Spring提供了测试模块。通过该模块,Spring为使用JNDI、Servlet和Porlet编写单元测试提供了一系列mock对象实现。
TDD测试驱动开发。

Spring Portfolio

超出本书范围。暂时跳过。

目录 第一部分spring的核心 第1章开始spring之旅 1.1spring是什么 1.2开始spring之旅 1.3理解依赖注入 1.3.1依赖注入 1.3.2di应用 1.3.3企业级应用中的依赖注入 1.4应用aop 1.4.1aop介绍 1.4.2aop使用 1.5小结 第2章基本bean装配 2.1容纳你的bean 2.1.1beanfactory介绍 2.1.2使用应用上下文 2.1.3bean的生命 2.2创建bean 2.2.1声明一个简单的bean 2.2.2通过构造函数注入 2.3注入bean属性 2.3.1注入简单的数值 2.3.2使用其他的bean 2.3.3装配集合 2.3.4装配空值 2.4自动装配 2.4.1四种自动装配类型 2.4.2混合使用自动和手动装配 2.4.3何时采用自动装配 2.5控制bean创建 2.5.1bean范围化 2.5.2利用工厂方法来创建bean 2.5.3初始化和销毁bean 2.6小结 第3章高级bean装配 3.1声明父bean和子bean 3.1.1抽象基bean类型 3.1.2抽象共同属性 3.2方法注入 3.2.1基本的方法替换 3.2.2获取器注入 3.3注入非springbean 3.4注册自定义属性编辑器 3.5使用spring的特殊bean 3.5.1后处理bean 3.5.2bean工厂的后处理 3.5.3配置属性的外在化 3.5.4提取文本消息 3.5.5程序事件的解耦 3.5.6让bean了解容器 3.6脚本化的bean 3.6.1给椰子上lime 3.6.2脚本化bean 3.6.3注入脚本化bean的属性 3.6.4刷新脚本化bean 3.6.5编写内嵌的脚本化bean 3.7小结 第4章通知bean 4.1aop简介 4.1.1定义aop术语 4.1.2spring对aop的支持 4.2创建典型的spring切面 4.2.1创建通知 4.2.2定义切点和通知者 4.2.3使用proxyfactorybean 4.3自动代理 4.3.1为spring切面创建自动代理 4.3.2自动代理@aspectj切面 4.4定义纯粹的pojo切面 4.5注入aspectj切面 4.6小结 第二部分企业spring 第5章使用数据库 5.1spring的数据访问哲学 5.1.1了解spring数据访问的异常体系 5.1.2数据访问的模板化 5.1.3使用dao支持类 5.2配置数据源 5.2.1使用jndi数据源 5.2.2使用数据源连接池 5.2.3基于jdbc驱动的数据源 5.3在spring里使用jdbc 5.3.1处理失控的jdbc代码 5.3.2使用jdbc模板 5.3.3使用spring对jdbc的dao支持类 5.4在spring里集成hibernate 5.4.1选择hibernate的版本 5.4.2使用hibernate模板 5.4.3建立基于hibernate的dao 5.4.4使用hibernate3上下文会话 5.5spring和java持久api 5.5.1使用jpa模板 5.5.2创建一个实体管理器工厂 5.5.3建立使用jpa的dao 5.6spring和ibatis 5.6.1配置ibatis客户模板 5.6.2建立基于ibatis的dao 5.7缓存 5.7.1配置缓存方案 5.7.2缓存的代理bean 5.7.3注解驱动的缓存 5.8小结 第6章事务管理 6.1理解事务 6.1.1仅用4个词解释事务 6.1.2理解spring对事务管理的支持 6.2选择事务管理器 6.2.1jdbc事务 6.2.2hibernate事务 6.2.3jpa事务 6.2.4jdo事务 6.2.5jta事务 6.3在spring中编写事务 6.4声明式事务 6.4.1定义事务参数 6.4.2代理事务 6.4.3在spring2.0里声明事务 6.4.4定义注释驱动事务 6.5小结 第7章保护spring 7.1springsecurity介绍 7.2验证用户身份 7.2.1配置providermanager 7.2.2根据数据库验证身份 7.2.3根据ldap仓库进行身份验证 7.3控制访问 7.3.1访问决策投票 7.3.2决定如何投票 7.3.3处理投票弃权 7.4保护web应用程序 7.4.1代理springsecurity的过滤器 7.4.2处理安全上下文 7.4.3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值