创建Spring的主要目的是用来替代更加重量级的企业级Java技术,尤其是EJB.
Spring框架由六个模块分类组成,但目前接触的主要是容器、aop,orm框架集成,spring MVC,还有spring boot等
Spring做的是简化开发具体表现为四个方面:
(1)基于POJO的轻量级和最小侵入性编程;
(2)通过依赖注入和面向接口实现松耦合;
(3)基于切面和惯例进行声明式编程;
(4)通过切面和模板减少样板式代码。
(1)最小侵入性编程:多框架通过强迫应用继承它们的类或实现它们的接口从而导致应用与框架绑死。
(2)保持pojo的独立性:Spring不会强迫你实现Spring规范的接口或继承Spring规范的类,相反,在基于Spring构建的应用中,它的类通常没有任何痕迹表明你使用了Spring。最坏的场景是,一个类或许会使用Spring注解,但它依旧是POJO。
代码示例:
//Spring的非侵入编程模型:一个普通的javabean,没有任何地方表明它是一个spring组件
//这个类在Spring应用和非Spring应用中都可以发挥同样的作用。
public class HelloWorldBean{
publicString sayHello(){
return"helloWorld";
}
}
(1)依赖注入:
1)为什么要实现依赖注入:
任何一个有实际意义的应用(肯定比HelloWorld示例更复杂)都会由
两个或者更多的类组成,这些类相互之间进行协作来完成特定的业务逻辑。
按照传统的做法,每个对象负责管理与自己相互协作的对象(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代码。
代码示例:
public class DamselRescuingKnight implementKnight{
privateRescueDamselQuest quest;
pulicDamselRescuingKnight(){
//RescueDamselQuest类与DamselRescuingKnight类紧密地耦合到了一起
//即骑士必救少女,如何需要执行其他的任务,那么就继续耦合,最后骑士的任务会变得异常繁重
//创建DamselRescuingKnight类,必创建RescueDamselQuest类
this.quest=newRescueDamselQuest();
}
//测试该方法时也会变得很困难,因为你无法保证quest.embark()方法被调用,因为quest可能并没有被创建
publicvoid embarkOnQuest(){
quest.embark();
}
}
2)什么是依赖注入:
通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。对象无需自行创建或管理它们的依赖关系。
代码示例:
//勇敢的骑士:想让他执行什么任务,就注入什么任务
public class BraveKnight implement Knight{
privateQuest quest;
//BraveKnight没有自行创建探险任务,而是在构造的时候把探险任务作为构造器参数传入。
//通过接口解耦合:传入的探险类型是Quest,也就是所有探险任务都必须实现的一个接口
//松耦合。如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。
pulicBraveKnight(Quest quest){//构造器注入
this.quest=quest;
}
publicvoid embarkOnQuest(){
quest.embark();
}
}
//如何将SlayDragonQuest注入BraveKnight?
public class SlayDragonQuest implementsQuest{
privatePrintStream stream;
publicSlaryDragonQuest(PrintStream stream){
this.stream=stream;
}
publicvoid embark(){
stream.println("Embarkingon quest to slay the dragon!");
}
}
3)实现注入:装配
创建应用组件之间协作的行为通常称为装配(wiring)。Spring有多种装配bean的方式。
1.采用xml进行装配:
代码示例:
//声明spring中的bean
//创建BraveKnight
//构造器方式,注入Questbean
//在构造时传入了对SlayDragonQuestbean的引用,将其作为构造器参数。
//创建SlayDragonQuest
//Spring表达式语言将System.out(这是一个PrintStream)传入到了SlayDragonQuest的构造器中。
2.基于java装配:
代码示例:
//基于java配置,实现注入
@Configuration
public class KnightConfig{
@Bean
publicKnight knight(){
//尽管BraveKnight依赖于Quest,但是它并不知道传递给它的是什么类型的Quest,也不知道这个Quest来自哪里。
returnnew BraveKnight(quest());
}
@Bean
publicQuest quest(){
returnnew SlayDragonQuest(System.out);
}
}
4)运行依赖注入的类:
Spring通过应用上下文(ApplicationContext)装载bean的定义并把它
们组装起来。Spring应用上下文全权负责对象的创建和组装。Spring
自带了多种应用上下文的实现,它们之间主要的区别仅仅在于如何加
载配置。
代码示例:
//运行类,观察其工作方式
public class KnightMain{
publicstatic void main(String args[])throws Exception{
//加载spring上下文,Spring自带了多种应用上下文的实现
//ClassPathXmlApplicationContext类加载位于应用程序类路径下的一个或多个XML配置文件
ClassPathXmlApplicationContextcontext=
newClassPathXmlApplicationContext(
"META_INF/spring/knight.xml"
);
//获取Knight Bean
Knightknight=context.getBean(Knight.class);
//使用knight,程序员无需知道是哪类knight和哪类quest
//只有knights.xml文件知道哪个骑士执行哪种探险任务。
knight.embarkOnQuest();
context.close();
}
}
(2)耦合的意义:
一方面,紧密耦合的代码难以测试、难以复用、难以理解,并且典型地表现出“打地鼠”式的bug特性(修复一个bug,将会出现一个或者更多新的bug)。另一方面,一定程度的耦合又是必须的——完全没有耦合的代码什么也做不了。为了完成有实际意义的功能,不同的类必须以适当的方式进行交互。总而言之,耦合是必须的,但应当被小心谨慎地管理。
面向切面编程(aspect-orientedprogramming,AOP)允许你把遍布应用各处的功能
分离出来形成可重用的组件。
(1)为什么要面向切面编程:
1)一些系统服务(横切关注点)会跨越系统的多个组件,如日志,事务管理等
2)如果将关注点分散到多个组件中,会产生以下问题:
1实现系统关注点功能的代码将会重复出现在多个组件中,修改困难
2组件会因为那些与自身核心业务无关的代码而变得混乱。
代码示例:
1.程序关注点:
//游吟诗人
public class Minstrel{
private PrintStreamstream;
publicMinstrel(PrintStream stream){
this.stream=stream;
}
public voidsingBeforQuest(){
stream.println("fala la ,the knight is so brave");
}
public voidsingAfterQuest(){
stream.println("teehee hee, the brave knight"+
"didembark on a quest");
}
}
2.程序组件:
public class BraveKnight implements Knight{
private Quest quest;
private Minstrelminstrel;
publicBraveKnight(Quest quest,Mintrel minstrel){
this.quest=quest;
this.minstrel=minstrel;
}
//骑士只负责冒险,不负责管理他的游吟诗人。代码开始变得复杂
public void embarkOnQuest()throwsQuestException{
minstrel.singBeforeQuest();
quest.embark();
minstrel.singAfterQuest();
}
}
(2)AOP效果:
我们可以把切面想象为覆盖在很多组件之上的一个外壳。应用是由那些实现各自业务功能的模块组成的。借助AOP,可以使用各种功能层去包裹核心业务层。这些层以声明的方式灵活地应用到系统中,你的核心应用甚至根本不知道它们的存在。
代码示例:
只需在spring配置文件中配置切入点即可:
//第一步:声明Minstrel bean
//第二步:aop配置
//定义切面,与bean配置结合
//定义切点
//配置expression属性来选择所应用的通知
//声明前置通知
//声明后置通知
//使用aop配置的优势:
//1.Minstrel仍然是一个POJO,没有任何代码表明它要被作为一个切面使用。
//2.Minstrel可以被应用到BraveKnight中,而BraveKnight不需要显式地调用它
(3)AOP的优势:
AOP能够使这些服务模块化,并以声明的方式将它们应用到它们需要影响的组件中去。这些组件会具有更高的内聚性并且会更加关注自身的业务,完全不需要了解涉及系统服务所带来复杂性。
切面内容见2.4,。
(1)问题:不断书写重复的样板式代码
(2)Spring拥有很多模板。如JdbcTemplate减少了JDBC书写中的代码重复
在基于Spring的应用中,你的应用对象生存于Spring容器(container)中。Spring容器负责创建对象,装配它们,配置它们并管理它们的整个生命周期,从生存到死亡。Spring容器,主要有两种,1.bean工厂;2.应用上下文(解析配置文件)。本文主要介绍应用上下文。
(1)应用上下文类别:
Spring自带多种类型的应用上下文:
AnnotationConfigApplicationContext:从一个或多个基于Java的配置类中加载Spring应用上下文。
AnnotationConfigWebApplicationContext:从一个或多个基于Java的配置类中加载SpringWeb应用上下文。
ClassPathXmlApplicationContext:从类路径下的一个或多个XML配置文件中加载上下文定义,把应用上下文的定义文件作为类资源。
FileSystemXmlapplicationcontext:从文件系统下的一个或多个XML配置文件中加载上下文定义。
XmlWebApplicationContext:从Web应用下的一个或多个XML配置文件中加载上下文定义。
(2)如何加载应用上下文:
1)从XML配置中加载应用上下文
ApplicationContext context=
new FileSystemXmlApplicationContext(
"c:/knight.xml");
2)从Java配置中加载应用上下文
ApplicationContext context=
new AnnotationConfigApplicationContext
("com.springinaction.knights.config.KnightConfig.class);
(3)Bean的生命周期:
理解Spring bean的生命周期非常重要,因为可以利用Spring提供的扩展点来自定义bean的创建过程。容器是根据bean实现的接口来执行生命周期的。
了解spring的过程,就是成为一个骑士的过程。愿大家都能成为自己生活中的骑士,勇往直前。