每日进度2

Spring框架学习

11月28日

Spring Framework Overview

Spring是开发java application的通用框架,分为多个模块(modules),核心是core container,包括configuration model(配置模型)和dependency injection(依赖注入)Spring还可以为多种应用架构(application architecture)提供支持,包括messaging,transaction data, persistence(持久化),web。Spring也提供Servlet-based Spring MVC web framework和Spring WebFlux reactive web framework。

History

Spring最早在2003年,由于J2EE过于复杂而被开发出来的。有人认为Spring和Java EE是竞争关系,但Spring更像是对Java EE的补充。Spring整合了一些EE的标准:

  • Servlet API
  • WebSocket API
  • Concurrency Utilities(并发性)
  • JSON Binding API 简介
  • Bean Validation(数据校验) 简介
  • JPA
  • JMS
  • Dependency Injection and Common Annotations

Java EE在app开发中的角色在随时间变化。早期的时候,javaEE和Spring开发的应用是部署在application server上的,今天,在Spring Boot的帮助下开发变得友好且更加云端化(devops and cloud-friendly),嵌入Servelet容器,非常容易改变。在Spring Framework5中,一个webflux应用甚至不需要Servlet API并可以运行在不含Servlet容器的server上。

Spring projects目前在逐渐丰富,建立在Spring Framework上的projects有Spring Boot,Spring Security,Spring Data,Spring Cloud,Spring Batch...

Spring的design philosophy

  • Provide choice at every level 尽可能允许不改动code的情况下变更design
  • Accommodate diverse perspectives 允许设计的灵活性
  • Maintain strong backward compatibility 对JDK和第三方库的高兼容性
  • Care about API design API被设计地简单易用
  • Set high standards for code quality 注意代码的整洁

Core Technology

IoC Container

Introduction

IoC: Inversion of Control(控制反转) 也可称为dependency injection(依赖注入), 定义: it is a process whereby objects define their dependencies (that is, the other objects they work with) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. 容器随后在创建bean时将依赖注入(把需要的对象传入)。这个过程事实上是对bean自身控制实例化或依赖定位(通过直接初始化类或类似Service Locator Pattern机制)的inverse,这就是为什么叫Inversion of Control。

org.springframework.beansorg.springframework.context两个包是Spring Framework的IoC容器的基础。BeanFactory接口提供了能管理任何类型对象的高级配置机制。简单来说BeanFactory提供了框架配置和基本的功能。ApplicationContextBeanFactory的子接口,它添加了更多特性企业级应用的功能。在Spring中,塑造应用骨架并被IoC容器管理的对象称为bean。一个bean是一个被IoC容器实例化(instantiated),组装(assembled),管理(managed)的对象,bean只是应用中众多对象中的一个。bean和它周围的依赖都会被容器的配置影响。

Container Overview

org.springframework.context.ApplicationContext接口说明了IoC容器,容器负责实例化,配置,集成beans。容器通过阅读配置文件(configuration metadata)知道实例化(或配置,集成)哪些beans。configuration metadata可以是XML,Java annotations(注释)或java code。IoC可以传递对象以使应用更简练并丰富对象间的依赖。

Spring也提供ApplicationContext接口的一些实现,在实践中经常会创建ClassPathXmlApplicationContextFileSystemXmlApplicationContext的实例。XML是传统的定义configuration metadata的方法。你可以通过一些XML配置(修改配置为支持额外的格式)来让容器使用注释或代码作为metadata。(原文较难理解:you can instruct the container to use Java annotations or code as the metadata format by providing a small amount of XML configuration to declaratively enable support for these additional metadata formats)

在大部分application scenario中IoC容器实例化多个实例并不需要详尽的用户代码,比如说网页应用中创建一个boilerplate只需要几行代码的descriptor。如果使用Spring Tool Suite就甚至只需要鼠标点几下或者键盘敲几下。

how Spring works(图)
Your application classes are combined with configuration metadata so that, after the ApplicationContext is created and initialized, you have a fully configured and executable system or application.

Spring IoC

Configuration Metadata

传统上Configuration metadata是简单易理解的XML(xml-based configuration),除此外还有annotation-based configuration和Java-based configuration(后面有详细的)。XML中一般用<beans>...</beans>把具体的配置括进去。java-based config中一般用@Configuration注释类,用@Bean注释方法。

These bean definitions correspond to the actual objects that make up your application.一般来说有service layer objects, data access objects (DAOs), presentation objects such as Struts Action instances, infrastructure objects such as Hibernate SessionFactories, JMS Queues, and so forth。通常我们不会在容器中配置一个非常详细的(fine-grained)domain objects,因为根据设计原则应该是由DAO和business logic来创建和调用domain objects。(然而Spring也可以集成AspectJ来配置IoC容器外创建的对象)
xml-based configuration file结构:

<?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.xsd">

    <bean id="..." class="...">   
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <bean id="..." class="...">
        <!-- collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions go here -->

</beans>

Instantiating a Container

初始化container时要给出metadata的路径(resource strings that let container load configuration metadata from a variety of external resources)(resources含义丰富,除了local path外还可以是读取inputstream等)。xml名称前不需要加slash。可以,但不推荐使用相对地址(../),可以使用绝对地址(file:C;/config/services.xml或classpath:/config/services.xml),但这样就只有特定的地址。推荐使用类似'${...}':的占位符,再替换地址,这样就可以在任何JVM上使用。

ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

一个例子,services.xml的config file(关于service layer objects):

<?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.xsd">

    <!-- services -->

    <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
        <property name="accountDao" ref="accountDao"/>
        <property name="itemDao" ref="itemDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for services go here -->

</beans>

daos.xml:(for data access objects)

<?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.xsd">

    <bean id="accountDao"
        class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>

    <!-- more bean definitions for data access objects go here -->

</beans>

很多情况下我们让一个XML对应一个逻辑层或者一个module。

Groovy文件也可以作为config metadata,基本等效于xml。

Using the container

ApplicationContext 接口的多态对象可以作为registry保存不同的beans和他们的依赖。例子:

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

事实上完全可以不用getbean方法,这样就不会产生对spring API的依赖。Spring和web框架的integration可以对web框架的组件注射依赖,因此只需要在metadata中声明依赖就可以了。

Bean Overview

Beans通过metadata定义。在容器内bean被表示为BeanDefinition对象,其包括了如下的metadata:

  1. 含包class name(package-qualified class name): typically是bean的actual implementation class
  2. Bean行为配置的元素(bean behavioral configuration elements),表明bean在容器内的行为(scope, lifecycle callbacks...)
  3. 对其他beans的引用,也被称作collaborators或dependencies
  4. 其他创建对象时的config setting,比如pool的size limit,bean内管理pool的connections的使用次数等
    metadata会设置这些properties:


    The bean definition

除了通过bean definitions,ApplicationContext实现类也可以允许用户在容器外创建的对象进行注册。需要用getBeanFactory()方法来访问ApplicationContext的BeanFactory,会返回DefaultListableBeanFactory实现。DefaultListableBeanFactory支持通过registerSingleon(..)registerBeanDefinition(..)方法注册但是typical applications只能和通过metadata定义的beans一起工作(需要时再查

Bean metadata和manually supplied singleton instances需要尽可能早注册,以便容器在autowiring和其他instropection steps中可以properly reason about(does not have unintended side effects)。运行时注册新的beans是not officially supported的,可能会导致concurrent access exceptions和inconsistent state in the bean container。

Naming Beans

每个bean可以有一个或多个identifiers(一般只有一个,多个的话可以被认为是aliases),identifiers必须在host container中唯一。在XML中可以用idname来标识identifiers。id只有一个,name可以有多个,用逗号或分号或空格隔开。也可以不指定identifiers,容器可以自动产生。如果我们指定了名称,就可以通过ref或lookup来查找和引用bean。一般inner beans和autowiring collaborators不必提供名称。

java beans的命名习惯:小写开头camel-cased。Spring为未命名组件创建的名字也是同样规则,一般就是把类名的第一个字母小写。但如果有超过一个前面字母大写(unusual)就保留这个名字不变。java beans可以命名别名,为何有这个需要呢?比如说一个application有很多components,每一个components可能有不同的命名习惯,如果某一通用的bean要被众多组件引用,那么它可以起一个component-specific的名字(为了美观方便阅读云云)。别名也可以在definition以外的地方被创建(好处是显而易见的,在需要的时候命名,不会每次都改前面定义bean的metadata,尤其是产品规模很大的时候)在xml-based config metadata中可以这样写:

<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>

Instantiating Beans

(很棒的定义) bean本质上是创建一个或多个对象的食谱(recipe) 容器看着bean的食谱(封装在config metadata中的bean定义)创建对象。

在xml-based file中,object类型定义在<bean/>element中的classattribute, 大部分情况下这是必须要有的定义(For exceptions, see Instantiation by Using an Instance Factory Methodand Bean Definition Inheritance.) 两种方法使用Class property:

  1. 容器创建bean时直接调用用类的构造函数(为了确定bean的类型)

  2. 为了确认是哪个类含有 static factory method(创建对象时被调用) ,还有一个更少见的情况是调用该方法创建bean。该静态方法返回的对象类型可能是另一个类

inner class names
有时候如果bean定义是在一个static nested class(内部静态类)时,内部类要使用binary name
比如说如果有一个类SomeThingcom.example package中,Something 类又有一个内部静态类OtherThing,那么bean定义中class attribute的value就是com.example.SomeThing$OtherThing

Instantiation with a Constructor
通过构造函数创建的bean不需要任何特殊的接口或多余代码,只要确认bean class就够了。Spring IoC可以管理任何类(只要你愿意),不限于真正的JavaBeans。大部分Spring的使用者倾向于在容器中使用只有default contructor,有getter和setter的java bean。

instantiation with a static factory method
通过static factory method实例化,需要class来确认factory method自身的name(因为要调用这个方法)。你应该调用这个方法并返回一个对象,整个过程看起来好像是调用了构造函数创造的,但并不是(这就是为什么需要class,不然不知道去哪里调用这个方法)。这个方法的本质目的是把实例的创建过程封装起来。

Instantiation by Using an Instance Factory Method
跟上面类似,区别是调用了非静态方法。过程是一个已经存在的bean调用非静态方法创建了这个bean。使用这种mechanism,class要leave empty(上面说的极少数情况), 在Factory-bean中确定当前(或father,ancestor)的容器中包括了实例方法的bean的name(哪个bean创建的这个bean),以及factory-method(方法名称)

一个factory class也可以有多个factory method
例子:
factory类:

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    private static AccountService accountService = new AccountServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }

    public AccountService createAccountServiceInstance() {
        return accountService;
    }
}

XML:

<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<bean id="clientService"
    factory-bean="serviceLocator"
    factory-method="createClientServiceInstance"/>

<bean id="accountService"
    factory-bean="serviceLocator"
    factory-method="createAccountServiceInstance"/>

这个方法显示了factory bean自身可以通过DI被管理和配置。factory bean在Spring中指的是容器中配置并可以通过实例方法或静态方法创造对象的bean。

Dependencies

Dependency Injection依赖注射

Dependency injection (DI) is a process whereby objects define their dependencies (that is, the other objects with which they work) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. 依赖注射是指对象决定其依赖的过程,这个过程只通过构造函数参数工厂方法参数在被工厂方法构造或返回的对象实例上的成员 (三种方法)。

DI的好处是让代码更加clean,并且在对象提供他们的依赖时耦合(decoupling)会更加有效。对象不会查看自身的依赖,也不知道依赖的位置和类。这样类会更容易测试,尤其是当依赖在接口或抽象基类上,这样允许单元测试中使用stub或mock。

DI有两种主要的方法:Constructor-based dependency injectionSetter-based dependency injection

Constructor-based dependency injection

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容