1. 简化java开发
为了降低java开发的复杂性,spring采取了以下四种关键策略:
(1)基于POJO的轻量级和最小侵入性编程。
(2)通过依赖注入和面向接口实现松耦合。
(3)通过切面和惯例进行声明示编程。
(4)通过切面和模板减少样板式代码。
1.1 激发POJO的潜能
spring不会强迫你实现spring规范的接口或者继承spring规范的类,相反,在基于spring构建的应用中,它的类通常没有任何痕迹表明你使用了spring。
尽管形式看起来很简单,但POJO一样可以具有魔力,sping赋予POJO魔力的方式之一就是通过DI来装配他们。
1.2 依赖注入
任何一个有实际意义的应用,都会由两个或者更多的类组成,这些类相互之间进行协作来完成特定的业务逻辑,按照传统的做法,每个对象负责管理与自己相互协作的对象的引用,这将会导致高度耦合和难以测试的代码。
public class DamselRescuingKnight implements Knight {
private RescueDamselQuest quest;
public DamselRescuingKnight() {
this.quest = new RescueDamselQuest();
}
public void embarkOnQuest() {
quest.embark();
}
}
从上述的代码可以看出,DamselRescuingKnight自行创建了RescueDamselQuest, 这使得他们紧密的耦合到一起。
耦合具有两面性,一方面,紧密耦合的代码难以测试,难以复用,难以理解,并且典型的表现出打地鼠式的bug特性,另一方面,一定程度的耦合又是必须的,完全没有耦合的代码什么也做不了。
通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。对象无须自行创建或者管理它们的依赖关系。
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {
this.quest = quest;
}
public void embarkQuest() {
quest.embark();
}
}
如上述代码,BraveKnight没有自行创建探险任务,而是在构造的时候把探险任务作为构造器参数传入,这是依赖注入的方式之一,即构造器注入。更重要的是,传入的探险类型是Quest,也就是所有探险任务都必须实现的一个接口。
如果一个对象只通过接口来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下, 用不同的具体实现代替。
1.2.1 将Quest注入到Knight中
(1) xml配置
<?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-2.5.xsd">
<bean id = "knight" class = "BraveKnight">
<constructor-arg ref="quest"/>
</bean>
<bean id = "quest" class = "SlayDragonQuest">
<constructor-arg value="#(T(System).out)"/>
</bean>
</beans>
(2)java配置
@Configuration
public class KnightConfig {
@Bean
public Knight knight() {
return new BraveKnight();
}
@Bean
public Quest quest() {
return new SlayDragonQuest(System.out);
}
}
1.2.2 观察它如何工作
spring通过应用上下文装配bean的定义并把他们组装起来。spring应用上下文全权负责对象的创建和组装。
public class KnightMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("knight.xml");
BraveKnight knight = (BraveKnight) applicationContext.getBean("knight");
knight.embarkOnQuest();
applicationContext.close();
}
}
1.3 应用切面
DI能够用相互协作的软件组件保持松散耦合,而面向切面编程允许你把遍布应用各处的功能分离出来形成可重用的组件。
AOP能够使这些服务模块化,并以声明的方式将他们应用到它们需要影响的组件中去,所造成的结果就是这些组件会具有更高的内聚性并且会更加关注自身的业务。完全不需要了解涉及系统服务所带来的复杂性。总之,AOP能确保POJO的简单性。
(1)AOP应用
public class Minstrel {
private PrintStream printStream;
public Minstrel(PrintStream printStream) {
this.printStream = printStream;
}
public void singBeforeQuest() {
System.out.println("战斗开始");
}
public void singAfterQuest() {
System.out.println("战斗结束");
}
}
public class BraveKnight implements Knight {
private Quest quest;
private Minstrel minstrel;
public BraveKnight(Quest quest) {
this.quest = quest;
}
public void embarkOnQuest() {
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
如上所示,这样做是BraveKnight和Minstrel耦合在了一起,同时,如果有多个Knight,会有大量的重复代码产生,这时候,AOP的作用就体现出来了。
要将Minstrel抽象成一个切面,你所需要做的事情就是在spring的配置文件中声明它。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="knight" class="BraveKnight">
<constructor-arg ref="quest"/>
</bean>
<bean id="quest" class="SlayDragonQuest">
<constructor-arg value="#(T(System).out)"/>
</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>
</beans>
首先,Minstrel仍然是一个POJO,没有任何代码表明它要被作为一个切面使用,但是通过上述的配置后,Minstrel已然变成了一个切面。同时Minstrel已经被应用到BraveKnight中了, 但是BraveKnight并不知道有它的存在。
1.4 使用模板消除样板式代码
通常为了实现通用的和简单的任务,你不得不一遍遍地重复编写相同的代码。样板式代码的一个常见范例是使用JDBC访问数据库查询数据。而Spring旨在通过模板封装来消除样板式代码,Spring的JdbcTemplate使得执行数据库操作时,避免传统的JDBC样板代码成为了可能。
2. 容纳你的Bean
容器是spring框架的核心,spring容器并不是只有一个,srping自带了多个容器实现,可以归为2种不同的类型,bean工厂是最简单的容器,提供基本的DI支持。应用上下文基于BeanFactory
构建,并提供应用框架级别的服务,例如从属性文件解析文本信息以及发布事件给感兴趣的事件监听者。
2.1 使用应用上下文
spring自带了多种应用上下文,下面的几种是最常见的:
(1)AnnotationConfigApplicationContext
从一个或者多个基于java的配置类中加载spring应用上下文
(2)AnnotationConfigWebAppApplicationContext
从一个或者多个基于java的配置类中加载spring web应用上下文
(3)ClassPathXmlApplicationContext
从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
(4)FileSystemXmlApplicationContext
从文件系统下的一个或多个XML配置文件中加载上下文定义。
(5)XmlWebApplicationContext
从Web应用下的一个或多个XML配置文件中加载上下文定义。
2.2 bean的生命周期
在传统的java应用中,bean的生命周期很简单,使用java关键字new进行bean实例化,然后该bean就可以使用了,一旦该bean不再被使用,则由java自动进行垃圾回收。
相比之下,spring容器中bean的生命周期就显得相对复杂多了。