Spring之旅
1、简化Java开发
- what
Spring是一个开源框架,最早由Rod Johnson创建 - why
Spring是为了解决企业级应用开发的复杂性而创建的,使用Spring可以让简单的JavaBean实现之前只有EJB才能完成的事情。
Spring不仅局限于服务器的开发,任何Java应用都能在简单性、可测试性和松耦合等方面从Spring中获益。 - how
为了降低Java开发的复杂性,Spring采取了一下4种关键策略:- 基于POJO的轻量级和最小侵入性编程
- 通过依赖注入和面向接口实现松耦合
- 基于切面和惯例进行声明式编程
- 通过切面和模版减少样板式代码
1.1激发POJO的潜能
Spring竭力避免因自身的API而弄乱你的应用代码。
Spring的非侵入编程模型意味着这个类在Spring应用和非Spring应用中都可以发挥作用。Spring赋予POJO魔力的方式之一就是通过DI来装配他们。
1.2依赖注入
按照传统做法,每个对象负责管理与自己协作的对象(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代码;
public class BraveKnight implements Knight {
private RescueDamselQuest quest;
public BraveKnight() {
this.quest = new RescueDamselQuest();//与RescueDamselQuest紧耦合
}
public void embarkOnKnight(){
quest.embark();
}
}
通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。对象无需自行创建或管理他们的依赖关系。
依赖注入会将所依赖的关系自动交给目标对象,而不是让对象自己去获取依赖。
/**
* 依赖注入的方式之一:构造器注入
*/
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest) {//Quest被注入进来
this.quest = quest;
}
public void embarkOnKnight(){
quest.embark();
}
}
//BraveKnight没有与任何特定的Quest实现发生耦合。
如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。
把特定的Quest实现传给BraveKnight
public class SlayDragonQuest implements Quest {
private PrintStream printStream;
public SlayDragonQuest(PrintStream printStream) {
this.printStream = printStream;
}
public void embark() {
printStream.println("SlayDragonQuest");
}
}
创建应用之间协作的行为通常叫做装配。
Spring有多种装配bean的方式,采用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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="knight" class="com.gaogao.pojo.BraveKnight">
<constructor-arg ref="quest"/>
</bean>
<bean id="quest" class="com.gaogao.pojo.SlayDragonQuest">
<constructor-arg value="#{T(System).out}"/>
</bean>
</beans>
BraveKnight和 SlayDragonQuest 被声明为bean。就BraveKnight bean来讲,它在构造时传入了对SlayDragonQuest bean的引用,将其作为构造器参数。
SlayDragonQuest bean的声明使用了Spring表达式语言(SpEL),将System.out(PrintStream)传入到了SlayDragonQuest的构造器当中。
Spring还支持使用Java来描述配置。
@Configuration
public class KnightConfig {
@Bean
public Knight knight(){
return new BraveKnight(quest());
}
@Bean
public Quest quest() {
return new SlayDragonQuest(System.out);
}
}
声明了BraveKnight和Quest的关系后,只需要装载XML配置文件,并把应用启动起来。
Spring通过应用上下文(Application Context)装载bean的定义并把它们组装起来。Spring应用上下文全权负责对象的创建和组装。
使用XML文件进行配置的,选择ClassPathXmlApplicationContext作为应用上下文比较合适(使用基于Java的配置,使用AnnotationConfigApplicationContext)。该类加载位于应用程序路径下的一个或多个XML配置文件。
public class MyTest {
public static void main(String[] args) {
// //获取spring的上下文对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Knight knight = (Knight) context.getBean("knight");
knight.embarkOnKnight();
context.close();
}
}
这里的main方法基于beans.xml文件创建了Spring应用上下文。随后它调用该应用上下文获取一个name为knight的bean.
1.3 应用切面
DI能够让相互协作的软件组件保持松散耦合,而面向切面编程(AOP)允许你把遍布应用各处的功能分离出来形成可重用的组件。
面向切面编程往往被定义为促使软件系统实现关注点分离的一项技术。系统由许多不同的组件组成,每个组件各负责一块特定功能。
AOP能够使服务模块化,并以声明的方式将它们应用到它们需要影响的组件中去。所造成的结果就是这些组件会具有更高的内聚性并且会更加关注自身的业务,完全不需要了解涉及系统服务所带来的复杂性。
==示例:==
需求:声明吟游诗人必须歌颂骑士的探险事迹,而骑士本身并不用直接访问Minstrel的方法
public class Minstrel {
private PrintStream printStream;
public Minstrel(PrintStream printStream) {
this.printStream = printStream;
}
//探险前调用
public void singBefore(){
printStream.println("brave is ready");
}
//探险后调用
public void singAfter(){
printStream.println("sucessful");
}
}
将Minstrel声明为一个切面
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="knight" class="com.gaogao.pojo.BraveKnight">
<constructor-arg ref="quest"/>
</bean>
<bean id="quest" class="com.gaogao.pojo.SlayDragonQuest">
<constructor-arg value="#{T(System).out}"/>
</bean>
<bean id="minstrel" class="com.gaogao.pojo.Minstrel">
<constructor-arg value="#{T(System).out}"/>
</bean>
<aop:config>
<aop:aspect ref="minstrel">
<!--定义切点-->
<aop:pointcut id="embark" expression="execution(* *.embarkOnKnight(..))"/>
<!--声明前置通知-->
<aop:before pointcut-ref="embark" method="singBefore"/>
<!--声明后置通知-->
<aop:after pointcut-ref="embark" method="singAfter"/>
</aop:aspect>
</aop:config>
</beans>
从示例中可以获知:1.Minstrel仍然是一个POJO,没有任何代码表明它要被作为一个切面使用。当我们按照上面那样配置以后,在Spring的上下文中,Minstrel实际上已经变成一个切面了。2.Minstrel可以被应用到BraveKnight中,而BraveKnight不需要显示地调用它。实际上,BraveKnight完全不知道Minstrel的存在
2、容纳你的Bean
- what
在基于Spring的应用中,你的应用对象生存于Spring容器中。Spring容器负责创建对象,装配它们,配置它们并管理它们的生命周期,从生存到死亡。容器是Spring框架的核心。 - why
Spring容器使用DI管理构成应用的组件,它会创建相互协作的组件之间的关联。这些对象更简单干净,更易于理解,更易于重用并且更易于进行简单的测试。 - how
Spring自带了多个容器实现,可以归为两种类型。bean工厂(由org.springframework.beans.factory.BeanFactory接口定义)是最简单的容器,提供基本的DI支持。应用上下文(由org.springframework.context.ApplicationContext 接口定义)基于BeanFactory构建,并提供应用框架级别的服务。
bean工厂对大多数应用来说往往比较低级,因此应用上下文使用更为广泛。
2.1使用应用上下文
Spring自带了多种类型的应用上下文:
- AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文
- AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载Spring Web应用上下文
- ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源
- FileSystemXmlApplicationContext:从文件系统下的一个或多个XML配置文件中加载上下文定义
- XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义
2.2bean的生命周期
下图展示了bean装载到Spring应用上下文的一个典型的生命周期过程:
在bean准备就绪之前,bean工厂执行了若干启动步骤:
1.Spring对bean进行实例化
2.Spring将值和bean 的引用注入到bean对应的属性中
3.如果bean实现了BeanNameAware接口,Spring将bean的ID传递给setBeanName()方法
4.如果bean实现了BeanFactoryAware接口,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
5.如果bean实现了ApplicationContextAware接口,Spring将调用setApplicationContext()方法,将bean所在的应用上下文的引用传入进来
6.如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessBeforeInitialization()方法
7.如果bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
8.如果bean实现了BeanPostProcessor接口,Spring将调用它们的postProcessAfterInitialization()方法
9.此时,bean已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,知道该应用上下文被销毁
10.如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法。同样,如果bean使用destory-method声明了销毁方法,该方法也会被调用。
3、Spring模块
Spring框架由6个定义良好的模块分类组成