Java Spring框架入门

Java Web开发中通常会用到经典的SSH后端框架,其中Spring是一个利用分层模块解决应用程序复杂性的轻量级框架。本篇笔记依然是整合资料的性质,来自Spring官方文档和《Spring揭秘》一书,其中有大多数名词都第一次见,看文档/书看得头大,有些英文表达也不知道对应的中文是什么就先保留了英文原文。

Spring的分层模块


1. Overview

Spring Framework Runtime
Spring框架大致有20个模块,分别归属于Core Container, Data Access/Integration, Web, AOP (Aspect Oriented Programming), Instrumentation, Messaging 以及 Test 六个大类。

2. Core Container

Core Container包含了如下模块:

  • spring-core & spring-beans: 提供了最基础的功能,包括IoC控制反转和DI依赖注入。其主要组件BeanFactory是工厂模式的一个实现,可将编程过程中依赖性的配置与实际的程序代码分离开来。
  • spring-context: 基于前面的Core&Beans模块,用于以框架的方式访问对象,支持internationalization, event propagation, resource loading, 透明化创建contexts,同时支持JavaEE的EJB(Enterprise JavaBean), JMX(Java Management Extensions), remoting等功能。其中的核心是ApplicationContext接口,spring-context-support则提供了集成第三方库如缓存、邮件、调度、模版引擎等。(尴尬,这些名词就没几个知道的…)
  • spring-expression: Spring内置的表达式语言,用于查询和制造对象。

3. AOP and Instrumentation

  • spring-aop: 内置了面向方面的编程,可以让由Spring框架管理的对象支持AOP,为这些对象提供了事物管理的服务。
  • spring-aspects: 集成了AspectJ。
  • spring-instrument: 提供了class instrumentation和classloader implementations,在特定的应用服务端使用。其中spring-instrument-tomcat包含了Spring对Tomcat的支持。

4. Messaging

  • spring-messaging: 包含了Message, MessageChannel, MessageHandler等的核心抽象,可为基于消息的应用服务。该模块同时提供了类似于SpringMVC的annotation编程模型,用于将消息映射到对应的方法。

5. Data Access/Integration

包含了JDBC(Java Data Base Connectivity), ORM(Object Relation Mapping), OXM(Object/XML Mapping), JMS(Java Message Service) 以及 Transaction模块:

  • spring-jdbc: 提供了JDBC抽象层,简化了繁杂的数据连接、错误处理的编程。
  • spring-tx: 管理实现了接口的类以及POJO(Plain Old Java Objects)类。
  • spring-orm: 提供object-relational mapping API的实现层,可使用JPA, Hibernate等框架完成OM映射。
  • spring-oxm: 提供Object/XML Mapping的抽象层,支持JAXB, Castor等实现。
  • spring-jms: 结合spring-messaging实现消息的产生和处理。

6. Web

  • spring-web: 提供了基本的网络编程实现,例如多文件上传、使用Servlet监听器初始化IoC容器,同时也包含了HTTP client和web相关的Spring remoting支持。
  • spring-webmvc: 又被称作Web-Servlet module,包含了Spring的model-view-controller和REST Web服务框架(强调以资源为中心)。SpringMVC框架将domain model code和web forms清晰地分离开来。
  • spring-webmvc-portlet: 又被称作Web-Portlet module,对应着mvc的功能在portlet环境中提供服务。

7. Test

  • spring-test: 使用JUnit或TestNG测试单元或整体功能,提供了ApplicationContext类和mock objects完成测试。

Spring核心技术之IoC


1. Inverse of Control概述

控制反转,又称DI(依赖性注入,Dependency Injection),其实就是将对象之间的依赖性重新整理以实现宽松耦合。在面向对象编程中,在实现特定功能时对象之间需要相互通信,尤其是相互之间的方法调用、属性访问,比如在这个过程中很可能会出现对象A内部实例化对象B的情况。下面就是这样的传统控制方式,显然由TopicManager类控制了TopicSaver类实例。

1
2
3
4
5
6
7
8
9
class TopicManager {
private TopicSaver topicSaver; // 作为成员变量的对象
public TopicManager() {
this.topicSaver = new TopicSaver(); // 实例化对象
...
}
...
}

而控制反转则允许在外部实例化之后再注入到别的类中,而不需要一开始就在构造函数中写死。在这个例子中TopicManager依赖的TopicSaver对象可以由外部传递给它(依赖注入),或者说TopicManager对TopicSaver对象的绝对控制权不复存在了(控制反转)。更抽象点看,控制反转强调不创建对象,但描述对象的创建方式;不直接让对象与服务连接,但在配置文件中描述哪一个组件需要哪一项服务。在Spring中IoC容器负责完成这项关联工作。

2. 三种依赖注入方式

构造方法注入、setter方法注入、接口注入。由于接口注入的方式具有侵入性(即强制被注入对象实现不必要的接口)现在已经淘汰了。

  • constructor injection: 被注入对象在其构造函数参数列表中体现有哪些依赖对象,则外部IoC容器就能知道它需要依赖谁。
  • setter injection: 不是在对象构造完成的同时完成依赖注入,而是在实例化之后使用setter函数显式地把依赖对象注入进去。
  • interface injection: 被注入对象需要实现某个接口,提供一个注入依赖对象的方法,在这个接口方法的参数中就是依赖对象,外部IoC容器就可以通过接口方法来了解对象之间的依赖关系。

3. IoC的好处

从主动获取依赖关系的方式转变为IoC方式,不单是方向上的改变,还可以帮助我们解耦各个业务对象依赖关系的对象绑定方式。举个例子,我们有个外汇新闻供应类FXNewsProvider,它依赖于IFXNewsListener来抓取新闻、IFXNewsPersister来存储新闻。假设默认是用DowJonesNewsListener和DowJonesNewsPersister来分别实现对应的监听和存储类,那么构造FxNewsProvider时只需传入对应的实例即可。后续如果引入了新的新闻源MarketWin24,则直接实现MarketWin24Listener然后传入即可,而不必因为新闻源更新而写多一个专门针对它的新的NewsProvider类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class FXNewsProvider {
private IFXNewsListener newsListener;
private IFXNewsPersister newsPersister;
// 构造方法注入
public FXNewsProvider(IFXNewsListener newsListener, IFXNewsPersister newsPersister) {
this.newsListener = newsListener;
this.newPersister = newsPersister;
}
}
FXNewsProvider fDowJones = new FXNewsProvider(new DowJonesNewsListener(), new DowJonesNewsPersister());
FXNewsProvider f = new FXnewsProvider(new MarketWin24NewsListener(), new MarketWin24NewsPersister());

IoC Service Provider


IoC Service Provider是一个抽象出来的概念,可以指代任何将IoC场景中的业务对象绑定到一起的实现方式,可能是一段代码,一组相关的类或者IoC框架或IoC容器实现。前面这个FXNewsProvider例子中,依赖性绑定的相关代码就可以看作是这个场景中的IoC Service Provider。在Spring中,IoC容器就是一个提供依赖注入服务的IoC Service Provider。

1. IoC Service Provider的职责

  • 业务对象的构建管理: 业务对象无需关心所依赖对象如何构建/取到,直接拿来用。因此IoC Service Provider需要将对象的构建逻辑从客户端对象中剥离出来。
  • 业务对象之间的依赖绑定: 业务对象需要及时得到依赖对象的响应。IoC Service Provider通过结合之前构建和管理的所有业务对象,以及各个业务对象间可以识别的依赖关系,将这些对象所依赖的对象注入绑定,从而保证每个业务对象在使用的时候,可以处于就绪状态。

2. IoC Service Provider对对象之间依赖关系的管理

  • 直接编码方式: 大部分IoC容器都支持直接编码,在容器启动之前就可以通过程序编码的方式建被注入对象和依赖对象注册到容器中,明确告知容器它们的依赖关系。如下代码,通过为相应的类指定对应的具体实例,当我们需要这种类型的对象时,IoC容器就会将注册的具体实例返回给我们。

    1
    2
    3
    4
    5
    6
    IoContainer container = ...;
    container.register(FXNewsProvider.class, new FXNewsProvider());
    container.register(IFXNewsListener.class, new DowJonesNewsListener());
    ...
    FXNewsProvider newsProvider = (FXNewsProvider)container.get(FXNewsProvider.class);
    newsProvider.someFunc();...
  • 配置文件的方式: 最普遍的方式是通过XML文件来管理对象注册和对象间依赖关系。例如Spring配置文件的方式如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <bean id="newsProvider" class="..FXNewsProvider">
    <property name="newsListener">
    <ref bean="djNewsListener"/>
    </property>
    <property name="newPersistener">
    <ref bean="djNewsPersister"/>
    </property>
    </bean>
    <bean id="djNewsListener"
    class="..impl.DowJonesNewsListener">
    </bean>
    <bean id="djNewsPersister"
    class="..impl.DowJonesNewsPersister">
    </bean>

    之后就可以通过newsProvider这个名字从容器中取得已经组装好的FXNewsProvider:

    1
    2
    3
    container.readConfigurationFiles(...);
    FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("newsProvider");
    newsProvider.getAndPersistNews();
  • 元数据方式: 代表是Google Guice,可以直接在类中使用元数据信息来标注各个对象之间的依赖关系,Guice框架将对象组装后交给客户端对象使用(即被注入对象)。具体工作的三个步骤是”使用Guice的Inject注解标注依赖关系加入到被注入类中”、”通过Module指定进一步的依赖注入相关信息”、”从Guice获取并用代码确定最终的注入关系”。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 使用Guice的注解标注依赖关系后的FXNewsProvider定义
    public class FXNewsProvider {
    private IFXNewsListener newsListener;
    private IFXNewsPersister newPersistener;
    @Inject // 通过Inject指明通过构造方法注入FXNewsProvider依赖的对象
    public FXNewsProvider(IFXNewsListener listener,IFXNewsPersister persister) {
    this.newsListener = listener;
    this.newPersistener = persister;
    }
    ...
    }
    // FXNewsProvider所使用的Module实现
    public class NewsBindingModule extends AbstractModule {
    @Override
    protected void configure() {
    bind(IFXNewsListener.class).to(DowJonesNewsListener.class).in(Scopes.SINGLETON);
    bind(IFXNewsPersister.class).to(DowJonesNewsPersister.class).in(Scopes.SINGLETON);
    }
    }
    // 从Guice获取并使用最终绑定完成的FXNewsProvider
    Injector injector = Guice.createInjector(new NewsBindingModule());
    FXNewsProvider newsProvider = injector.getInstance(FXNewsProvider.class);
    newsProvider.getAndPersistNews();

IoC容器之BeanFactory


1. Spring容器概述

Spring的IoC容器除了提供IoC支持,还有AOP框架支持、企业级服务集成、线程管理和对象声明周期管理等。IoC Service Provider是Spring容器的子集。Spring提供了两种容器类型:

  • BeanFactory: 基础类型IoC容器,提供完整的IoC支持。默认是lazy-load,即当客户端对象需要访问容器的某个受管对象时,才对该受管对象进行初始化和注入。适用于资源有限、功能要求不严格的场景。
  • ApplicationContext: 在BeanFactory的基础上构建(间接继承),除基础功能外,还提供了如事件发布、国际化等特性。默认是容器启动的同时就全部初始化并绑定完成,因此需要更多的系统资源。

2. BeanFactory概述

BeanFactory接受应用所需的所有业务对象,返回组装完成且可用的对象。BeanFactory肯定会提供获取组装完成的对象的方法接口(getBean)等。举回前面FXNews的例子。原本我们需要手动定义FXNewsProvider类以及相应的接口与具体类:

1
2
3
4
5
6
public class FXNewsProvider {...}
public interface IFXNewsListener {...}
public interface IFXNewsPersister {...}
public class DowJonesNewsListener implements IFXNewsListener {...}
public class DowJonesNewsPersister implements IFXNewsPersister {...}

而使用BeanFactory的XML配置方式指定业务对象之间的依赖关系就省去了上面的步骤:

1
2
3
4
5
6
7
8
9
10
11
<beans>
<bean id="djNewsProvider" class="..FXNewsProvider">
<constructor-arg index="0">
<ref bean="djNewsListener"/>
</constructor-arg>
<constructor-arg index="1">
<ref bean="djNewsPersister"/>
</constructor-arg>
</bean>
...
</beans>

在实例化对象时,Bean方式会负责生产:

1
2
3
4
5
6
7
8
// 使用前
FXNewsProvider newsProvider = FXNewsProvider();
newsProvider.someFunc();
// 使用后
BeanFactory container = new XmlBeanFactory(new ClassPathResource("配置文件路径"));
FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
newsProvider.someFunc();

3. BeanFactory的对象注册与依赖绑定方式

BeanFactory需要使用Configuration Megadata来明确管理各个业务对象和它们之间的依赖绑定关系。和前面IoC Service Provider提到的一样,有三种方式来定义。

  • 直接编码方式: 需要写比较完整的代码。使用BeanFactory接口的具体实现来完成Bean的注册和管理工作。而每一个受管对象,容器中都会有一个BeanDefinition(RootBeanDefinition和ChildBeanDefinition是其主要实现类)的实例与之对应,保存了对象的class类型、是否是抽象类、构造方法参数等。当客户端对象向BeanFactory请求相应对象的时候,就会利用这些信息返回完备可用的对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public static void main(String[] args) {
    // BeanFactory只是一个接口,需要一个实现类来取得实例对象
    DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
    // 注意这里的DefaultListableBeanFactory同时间接实现了BeanDefinitionRegistry接口才能这样直接传入默认转换类型
    BeanFactory container = (BeanFactory)bindViaCode(beanRegistry);
    FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
    newsProvider.getAndPersistNews();
    }
    public static BeanFactory bindViaCode(BeanDefinitionRegistry registry) {
    // 构造BeanDefinition
    AbstractBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class,true);
    AbstractBeanDefinition newsListener = new RootBeanDefinition(DowJonesNewsListener.class,true);
    AbstractBeanDefinition newsPersister = new RootBeanDefinition(DowJonesNewsPersister.class,true);
    // 将bean定义注册到容器中
    registry.registerBeanDefinition("djNewsProvider", newsProvider);
    registry.registerBeanDefinition("djListener", newsListener);
    registry.registerBeanDefinition("djPersister", newsPersister);
    // 指定依赖关系
    // 通过构造方法注入方式
    ConstructorArgumentValues argValues = new ConstructorArgumentValues();
    argValues.addIndexedArgumentValue(0, newsListener);
    argValues.addIndexedArgumentValue(1, newsPersister);
    newsProvider.setConstructorArgumentValues(argValues);
    return (BeanFactory)registry;
    }
  • 外部配置文件方式: 需要根据不同的外部配置文件格式给出相应的BeanDefinitionReader实现类,由其读取配置并映射到BeanDefinition,然后注册到一个BeanDefinitionRegistry,再完成Bean的注册和加载。
    -> Properties配置格式: Spring提供了org.springframework.beans.factory.support. PropertiesBeanDefinitionReader类来读取Properties格式的配置文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // Properties内容
    djNewsProvider.(class)=..FXNewsProvider
    # ----------通过构造方法注入的时候-------------
    djNewsProvider.$0(ref)=djListener # ref表示参数按引用传递,否则为String类型注入
    djNewsProvider.$1(ref)=djPersister
    # ----------通过setter方法注入的时候---------
    # djNewsProvider.newsListener(ref)=djListener
    # djNewsProvider.newPersistener(ref)=djPersister
    djListener.(class)=..impl.DowJonesNewsListener
    djPersister.(class)=..impl.DowJonesNewsPersister
    // Java
    public static void main(String[] args) {
    DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
    BeanFactory container = (BeanFactory)bindViaPropertiesFile(beanRegistry);
    FXNewsProvider newsProvider =
    (FXNewsProvider)container.getBean("djNewsProvider");
    newsProvider.getAndPersistNews();
    }
    public static BeanFactory bindViaPropertiesFile(BeanDefinitionRegistry registry) {
    // Spring提供的类来处理Properties配置文件
    PropertiesBeanDefinitionReader reader = new PropertiesBeanDefinitionReader(registry);
    reader.loadBeanDefinitions("classpath:../../binding-config.properties");
    return (BeanFactory)registry;
    }

    -> XML配置格式: Spring同样为XML格式的配置文件提供了现成的BeanDefinitionReader实现 ,即XmlBeanDefinitionReader。它负责读取Spring指定的XML格式配置文件,解析映射到BeanDefinition,然后加载到BeanDefinitionRegistry(DefaultListableBeanFactory)中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    // XML部分:
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
    <beans>
    <bean id="djNewsProvider" class="..FXNewsProvider">
    <constructor-arg index="0">
    <ref bean="djNewsListener"/>
    </constructor-arg>
    <constructor-arg index="1">
    <ref bean="djNewsPersister"/>
    </constructor-arg>
    </bean>
    <bean id="djNewsListener" class="..impl.DowJonesNewsListener">
    </bean>
    <bean id="djNewsPersister" class="..impl.DowJonesNewsPersister">
    </bean>
    </beans>
    // Java部分:
    public static void main(String[] args) {
    DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
    BeanFactory container = (BeanFactory)bindViaXMLFile(beanRegistry);
    FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("djNewsProvider");
    newsProvider.getAndPersistNews();
    }
    public static BeanFactory bindViaXMLFile(BeanDefinitionRegistry registry) {
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(registry);
    reader.loadBeanDefinitions("classpath:../news-config.xml");
    return (BeanFactory)registry;
    // 或者直接
    //return new XmlBeanFactory(new ClassPathResource("../news-config.xml"));
    }

    -> 注解方式: 使用@Autowired告知Spring容器需要为当前对象注入哪些依赖对象,标注出的是依赖对象的类。@Component需要结合Spring2.5中的classpath-scanning功能使用,标注出的是需要添加到容器中的所有类。如下面的配置文件中,<context:component-scan/>会到给定的package中把标注了@Component的类加入容器,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // Java各个类
    @Component
    public class FXNewsProvider {
    @Autowired
    private IFXNewsListener newsListener;
    @Autowired
    private IFXNewsPersister newPersistener;
    public FXNewsProvider(IFXNewsListener newsListner,IFXNewsPersister newsPersister) {
    this.newsListener = newsListner;
    this.newPersistener = newsPersister;
    }
    ...
    }
    @Component
    public class DowJonesNewsListener implements IFXNewsListener {...}
    @Component
    public class DowJonesNewsPersister implements IFXNewsPersister {...}
    // 配置文件:使用classpath-scanning功能
    <?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:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-2.5.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-2.5.xsd" >
    <!-- 以下这句 -->
    <context:component-scan base-package="cn.spring21.project.base.package"/>
    </beans>
    // 应用程序中使用加载好的客户端对象
    public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("配置文件路径");
    FXNewsProvider newsProvider = (FXNewsProvider)container.getBean("FXNewsProvider");
    newsProvider.getAndPersistNews();
    }

4. BeanFactory的XML配置详解

XML格式的容器信息管理方式是Spring最顶的方式。所有注册到容器的业务对象,在Spring中称之为Bean。所以,每一个对象在XML中的映射也自然而然地对应一个叫做<bean>的元素,多个<bean>也就组成了<beans>

  • <beans>: XML的最顶层元素,具有default-lazy-init, default-autowire, default-dependency-check, default-init-method 和 default-destroy-method 等属性,可以方便地为所包围的<bean>元素做统一设置。
  • <description>, <import>, <alias>: 分别为配置文件做描述、导入其他配置文件、为bean提供外号(比如为了偷懒少打点字)。
  • <bean>: 每个bean由id属性唯一标识,可添加name属性赋予别名。同时每个Bean都需要class属性指定其类型。接下来介绍的都是包围在bean元素内部的子(孙)元素。
  • <constructor-arg>: 告诉IoC容器要通过构造函数的方式为当前业务对象注入其所依赖的对象,所包含的ref元素(或简写为ref属性)指明了依赖对象的类名。此外还可以利用constructor-arg的type属性指定当前业务对象构造函数要传入参数的类型、value属性确定参数值、index属性确定参数传入位置

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id="djNewsProvider" class="..FXNewsProvider">
    <constructor-arg>
    <ref bean="djNewsListener"/>
    </constructor-arg>
    <constructor-arg>
    <ref bean="djNewsPersister"/>
    </constructor-arg>
    </bean>
  • <property>: 告诉IoC容器用setter的方式为当前业务对象注入依赖对象,用法和constructor-arg类似,用ref元素(或简写为ref属性)指定为name=xx的属性注入bean=xx的对象。

    1
    2
    3
    4
    5
    6
    7
    8
    <bean id="djNewsProvider" class="..FXNewsProvider">
    <property name="newsListener">
    <ref bean="djNewsListener"/>
    </property>
    <property name="newPersistener">
    <ref bean="djNewsPersister"/>
    </property>
    </bean>
  • constructor-arg和property的内嵌元素们: <value>可为主体对象注入简单数据类型;<ref>用于引用容器中其他对象实例;<idref>为当前对象注入所依赖对象的名称;内部<bean>为当前对象定义私有的对象;<list>按次序对应注入以collection形式声明的依赖对象;<set>则是无序注入一些值;<map>是注入键值对,以<entry>元素的属性key/key-ref给出键、以<entry>的子元素<value>给出值;<props>是朴素版的map,只支持String类型的键和值,以key属性给出键、props包围的字符为值;<null />当需要指定为null值的时候使用的特殊符,因为直接留空其实传入的是空字符串而不是null.

  • bean的depends-on属性: 要求容器在初始化自身实例之前首先实例化给定的类,根据id来确定具体是什么bean.
  • bean的autowire属性: 默认取值为no,即不进行自动绑定;byName则是根据客户端类定义中依赖对象的属性名来找匹配到id属性的bean;byType则是根据依赖对象类型找匹配到class属性的bean;constructor是根据客户端的构造函数参数类型来匹配class;autodetect是优先采用constructor再用byType把剩余属性绑定上,只适用于”原生类型、String类型以及Classes类型以外”的对象类型。若觉得为每个bean单独设置相同的autowire麻烦,可以在beans设置default-autowire属性。
  • bean的dependency-check属性: 对bean所依赖的对象做检查,与自动绑定结合使用,确认当自动绑定完成后,每个对象所依赖的对象是否按照预期的那样被注入了。
  • bean的lazy-init属性: 前面说过ApplicationContext在启动时会将容器中所有对象实例化,而lazy-init设为true就可以在不违反依赖性的情况下滞后实例化。
  • bean的parent属性: 根据parent属性指定的id确定类之间的继承关系。此外还有abstract属性。
  • bean的scope: BeanFactory容器还需要管理对象的生命周期。配置中的bean定义可以看作是模板,容器用这个模板来构造对象,但构造多少、存活多久,就需要scope确定。
    -> singelton: 容器可保证这种类型的bean在同一个容器中只存在一个共享实例,且从它首次被请求实例化后到容器销毁,该单例都会一直存在。
    -> prototype: 对于那些不能让请求方共享的对象类型,应当用prototype scope的bean定义,容器在收到这种类型对象的请求时,每次会重新生产实例返回给请求方,然后由请求方负责其后续生命周期管理工作。
    -> request: 只适用于Web应用程序。可视作prototype在Web中的特例,XmlWebApplicationContext会为每个HTTP请求创建独立的对象供当前请求使用。
    -> session: 只适用于Web应用程序,对应于Web中的session。与前面的request十分类似,可能实例会比request的bean会长一些。
    -> global session: 只适用于基于Portlet的Web应用程序,它映射到portlet的global范围的session。

5. 工厂模式和FactoryBean

  • 面向接口编程: 对象通过声明接口来避免对特定接口实现类的过度依赖,满足现实中接口”即插即用”的特性,下层只向上层暴露接口功能,上层对下层仅仅是接口依赖。使用接口的动机就是多态(即从某个更广的角度上看,对原本不同类事物不加区别的对待而统一处理),对于不稳定的问题领域,不在乎它究竟是个什么东西,直接通过接口定义的行为来认知它,规定了其子类应当实现的一组规则;而抽象类相较于接口,其描述的问题领域更为稳定,是从一般到特殊的关系。
  • 关联: 虽说使用接口是为了降低耦合,但总得有一种方式将声明依赖接口的对象与接口实现类关联起来。但我们不应该直接将该对象new出接口的实现类对象,而应当使用工厂模式。如果该类是我们设计开发的,那我们至少还能通过依赖注入让容器帮我们解除接口和实现类之间的耦合性;但若是第三方库,需要实例化其中的相关类,接口与实现类的耦合性需要用工厂模式来解除,即提供一个工厂类来实例化具体的接口实现类。如此一来,主体对象只需要依赖工厂类,具体使用的实现类有变动的话只需要对应修改工厂类,不用动主体对象了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public class Foo {
    private BarInterface barInstance;
    public Foo() {
    // 不要直接这样
    instance = new BarInterfaceImpl();
    // 应该这样
    barInstance = BarInterfaceFactory.getInstance();
    // 或 barInstance = new BarInterfaceFactory().getInstance();
    }
    }
  • 静态工厂方法: 将工厂类作为正常的bean写入配置文件注册到容器中。若需要传参,可用<constructor-arg>来指定。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class StaticBarInterfaceFactory {
    public static BarInterface getInstance() {
    return new BarInterfaceImpl();
    }
    }
    <bean id="foo" class="..Foo">
    <property name="barInterface">
    <ref bean="bar"/>
    </property>
    </bean>
    <bean id="bar" class="..StaticBarInterfaceFactory" factory-method="getInstance"/>
  • 非静态工厂方法: 客户端对象还是一样注册到bean中,但工厂方法factory-method不直接注册到工厂类的bean中了,而是分成两步。先指定非静态工厂类实例,然后利用factory-bean指定工厂方法所在的工厂类实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class NonStaticBarInterfaceFactory {
    public BarInterface getInstance() {
    return new BarInterfaceImpl();
    }
    }
    <bean id="foo" class="..Foo">
    <property name="barInterface">
    <ref bean="bar"/>
    </property>
    </bean>
    <bean id="barFactory" class="..NonStaticBarInterfaceFactory" />
    <bean id="bar" factory-bean="barFactory" factory-method="getInstance" />
  • FactoryBean: 它是一种可以扩展容器对象实例化逻辑的接口,与容器名称BeanFactory区分!我们可以实现接口中规定的三个方法来实现自定义FactoryBean类。而在XML文件中,配置bean并没有什么特别之处。特别的在于,在客户端的Java类中,声明依赖对象的类型不是XML中class给出的NextDayDateFactoryBean类,而是FactoryBean生产出的DateTime类。也就是说FactoryBean类型的bean定义,通过正常的id引用,容器返回的是FactoryBean所“生产”的对象类型,而非FactoryBean实现本身。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    import org.joda.time.DateTime;
    import org.springframework.beans.factory.FactoryBean;
    public class NextDayDateFactoryBean implements FactoryBean {
    public Object getObject() throws Exception {
    return new DateTime().plusDays(1);
    }
    public Class getObjectType() {
    return DateTime.class;
    }
    public boolean isSingleton() {
    return false;
    }
    }
    public class NextDayDateDisplayer {
    private DateTime dateOfNextDay;
    // 相应的setter方法
    // ...
    }
    <bean id="nextDayDateDisplayer" class="...NextDayDateDisplayer">
    <property name="dateOfNextDay">
    <ref bean="nextDayDate"/>
    </property>
    </bean>
    <bean id="nextDayDate" class="...NextDayDateFactoryBean" />

6. Spring的IoC容器启动过程

总的来看,容器加载Configuration Metadata、绑定整个系统的对象、组装成可用系统的这些过程,可以分为两个阶段。

  • 容器启动阶段: 借助某些工具类对配置信息进行解析和分析,将所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。
  • Bean实例化阶段: 当某个请求方通过容器的getBean方法明确地请求某个对象,或者因依赖关系容器需要隐式地调用getBean方法时,会触发容器检查初始化、根据注册信息实例化并注入依赖,最后返回给请求方。

IoC容器之ApplicationContext


ApplicationContext支持前面BeanFactory的所有功能,还进一步扩展了基本容器的功能,如特殊类型bean的自动识别、容器启动后bean实例的自动初始化、国际化、容器内事件发布等。Spring为ApplicationContext提供了几个基本实现如FileSystemXmlApplicationContext, ClassPathXmlApplicationContext, XmlWebApplicationContext.

1. 统一资源加载策略

  • URL: Uniform Resource Locator,基本上只限于网络形式发布的资源的查找和定位,而且资源查找和资源的表示的界限不清晰,导致资源返回时的形式多样而不是查找者希望的一个统一的接口。
  • Resource: Spring定义的所有资源的抽象和访问的接口,提供了一些简单的实现类如ByteArrayResource, ClassPathResource, FileSystemResource等。
  • ResourceLoader: 资源查找定位策略的统一抽象,具体的策略由实现类给出。例如DefaultResourceLoader实现类就是依次检测classpath, url路径来查找资源。

2. 国际化信息支持

让应用程序为世界各地的人们提供对应语言文字、货币形式、、日期时间格式等,在原声JavaSE中主要涉及两个类,而Spring在此基础上进一步抽象了国际化访问接口。

  • java.util.Locale: 不同的Locale代表了不同的国家和地区,使用zh_CN、en_US等内置静态常量代码,可帮助程序判断地区信息。
  • java.util.ResourceBundle: 用于保存特定于某个Locale的信息,通常会管理一组具有统一basename的信息序列,例如用一组properties文件分别保存不同国家地区的信息。
  • MessageSource接口: 统一了国际化信息的访问方式,传入Locale、资源的key、相关参数便能拿到国际化的信息,而不必从Locale到ResourceBundle再去查询了。有三个实现类,如ResourceBundleMessageSource。

Spring核心技术之AOP (TBC)


1.Aspect Oriented Programming

软件开发的目的最终是为了解决各种需求,包括业务需求和系统需求。运用OOP我们可以对业务需求等普通关注点进行很好的抽象和封装,并使之模块化。但是对于系统需求,例如在后期对系统进行监控需要打印log,或由于权限限制需要进行安全检查,这些系统需求就无法运用OOP“一个需求对应一个实现”这么方便就能集成到现有系统中去了,因为这些需求可能遍及所有的业务对象,因此OOP无法解决系统需求的实现散落在各处的问题。AOP则可以对类似Security和Logging等系统需求进行模块化的组织,简化系统需求和实现之间的对比关系,以模块化的形式对系统中的横切关注点(cross-cutting concern)进行封装。Aspect之于AOP,就如Class之于OOP。

2.Spring AOP

  • Spring AOP实现机制: 属于第二代AOP,采用动态代理机制和字节码生成技术实现,在运行期间为目标对象生成一个代理对象,将横切逻辑织入到这个代理对象,交由系统使用。
  • 动态代理模式: 为指定的接口在系统运行期间动态地生成代理对象,由Proxy类和InvocationHandler接口实现。例如现在的系统需求是在0~6点拒绝访问,要为ISubject和IRequestable两种类型提供相同横切逻辑的代理对象。来实现一个InvocationHandler:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class RequestCtrlInvocationHandler implements InvocationHandler {
    private static final Log logger = LogFactory.getLog(RequestCtrlInvocationHandler.class);
    private Object target;
    public RequestCtrlInvocationHandler(Object target) {
    this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (method.getName().equals("request")) {
    TimeOfDay startTime = new TimeOfDay(0, 0, 0);
    TimeOfDay endTime = new TimeOfDay(5, 59, 59);
    TimeOfDay currentTime = new TimeOfDay();
    if (currentTime.isAfter(startTime) && currentTime.isBefore(endTime)) {
    logger.warn("service is not available now");
    return null;
    }
    return method.invoke(target, args);
    }
    return null;
    }
    }

    然后就可以使用Proxy类使用上面这个RequestCtrlInvocationHandler的横切逻辑,为ISubject和IRequestable两个类提供相应的代理对象:

    1
    2
    3
    4
    5
    6
    7
    ISubject subject = (ISubject)Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(),
    new Class[]{ISubject.class}, new RequestCtrlInvocationHandler(new SubjectImpl()));
    subject.request();
    IRequestable requestable = (IRequestable)Proxy.newProxyInstance(ProxyRunner.class.getClassLoader(),
    new Class[]{IRequestable.class}, new RequestCtrlInvocationHandler(new RequestableImpl()));
    requestable.request();
  • AspectJ注解(这部分先不动了。。。)

Spring MVC


1.Servlet与JSP

  • Servlet: 是一个Java类,运行于Web容器中,提供了Session和对象声明周期管理等功能,可直接调用Java平台上的各种服务,如用JDBC进行数据访问。但最初没有JSP的时候,Servlet一人承担了太多的职责,流程控制逻辑、视图显示逻辑、业务逻辑、数据访问逻辑混杂在一起,使得后期维护十分困难。
  • JSP: JSP的出现让Servlet中的视图渲染逻辑能以标准化视图的形式输出,避免了在Servlet中写一堆out.print。由于JSP最终是编译成Servlet执行的,JSP拥有比其他1通用模板更强大的功能,例如可以直接在JSP中写Java代码,几乎任何逻辑都可以写入JSP,这让本该行使简单视图渲染功能的JSP也变得复杂。
  • Servlet与JSP的分工合作: JSP只负责视图的渲染工作,Servlet只负责请求处理流程的控制以及业务层的交互,而业务层逻辑交给JavaBean,最终形成了MVC三足鼎立的局面。
  • MVC雏形: JSP model2已经具有了Web应用架构的雏形。
    -> 控制器: 接受视图发送的请求并处理,根据请求条件去通知模型进行应用程序状态的更新,然后选择合适的视图返回给客户端。
    -> 模型: 封装了应用的逻辑以及数据状态。当控制器来通知模型进行状态更新的时候,模型内封装的逻辑就会被调用,执行完成后会通过事件机制通知视图状态更新完毕,从而让视图能显示最新的数据状态。
    -> 视图: 面向用户的接口。当用户通过视图发起某种请求的时候,视图会将请求转发给控制器。经过控制器、模型的处理后,视图最后会收到状态更新的事件通知,视图就会结合模型提供的数据更新自身的显示。但是对于Web应用来说,受限于协议和使用场景,现在的Web MVC其实是由控制器从模型中Pull数据给视图,而不是模型直接Push数据给视图。

2.Spring MVC的处理控制器

引入了Front Controller和Page Controller的概念来分离流程控制逻辑与具体的Web请求处理逻辑。在Spring MVC框架中,Front Controller对应DispatcherServlet,负责接收并处理所有的Web请求,针对不同的处理逻辑委派给下一级Page Controller去实现,即servlet.mvc.Controller。这个部分在练手项目中有涉及。

  • HandlerMapping: Web请求的处理协调人。DispatcherServlet作为FrontController,注定要服务一组Web请求,而不能一个请求对应一个Servlet。通常Web请求与处理类之间的映射通过“掐头去尾”的方式匹配。
  • org.springframework.web.servlet.Controller: Web请求的具体处理者。处理完成后会返回ModelAndView实例,包含了视图逻辑名称、模型数据等信息,DispatcherServlet可以开始渲染视图了。
  • HttpServletResponse输出视图步骤: 获取Model数据、获取视图模版文件(.jsp, .vm, *.xls等)、结合视图模板和模型数据,使用相应的视图API生成最终视图结果。其中这个API需要依赖ViewResolver来处理逻辑视图名和具体View实例之间的对应关系。

3.Spring MVC项目结构

(可参考练手项目)
WebContent-{[*.jsp] & [“WEB-INF”-(web.xml & controller-servlet.xml & applicationContext.xml …)] & [lib] & [classes]}。

  • web.xml: 整个Web应用程序的部署描述符文件,是所有基于Servlet规范的Web程序都有的。大致内容如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app ...>
    <display-name>xxxxx</display-name>
    <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
    <servlet-name>controller</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    </servlet>
    </web-app>
  • ContextLoaderListener与applicationContext.xml: ContextLoaderListener负责为整个Web程序加载顶层WebApplicationContext,其中注册了数据源定义DS、数据访问对象DAO定义等。而它正是由applicationContext.xml配置文件。不过通常会将这个配置文件进行分割细化,因此需要在web.xml中加入

    1
    2
    3
    4
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/applicationContext.xml, /WEB-INF/applicationContext-module1.xml</param-value>
    </context-param>
  • DispatcherServlet与XXX-servlet.xml: DispatcherServlet相当于struts中的ActionServlet,需要从配置文件中读取Spring MVC框架处理Web请求过程中涉及的各种组件,如HandlerMapping, Controller, ViewResolver的定义等。这个配置文件名称与web.xml中<servlet-name>一致。DispatcherServlet根据这个配置文件构建特定于自己的WebApplicationContext,将前面的顶层WebApplicationContext作为父容器。同样的,XXX-servlet.xml也可以拆分成多个模块:

    1
    2
    3
    4
    5
    6
    7
    8
    <servlet>
    <servlet-name>controller</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/controller-servlet.xml, /WEB-INF/module1-servlet.xml</param-value>
    </init-param>
    </servlet>

其他参考资料credit to
https://www.zhihu.com/question/21142149
https://www.ibm.com/developerworks/cn/java/wa-spring1/
https://www.ibm.com/developerworks/cn/java/j-lo-spring-principle/
http://www.iteye.com/blogs/subjects/springmvc-explore
https://zhuanlan.zhihu.com/p/20618651
https://zhuanlan.zhihu.com/tianmaying?topic=Spring