《Spring 实战》笔记1:Spring 之旅

首先阐述几个概念

http://sishuok.com/forum/blogPost/list/2426.html

  1. 应用程序:是能完成我们所需要功能的成品,比如购物网站、 OA 系统。
  2. 框架:是能完成一定功能的半成品,比如我们可以使用框架进行购物网站开发;框架做一部分功能,我们自己做一部分功能,这样应用程序就创建出来了。而且框架规定了你在开发应用程序时的整体架构,提供了一些基础功能,还规定了类和对象的如何创建、如何协作等,从而简化我们开发,让我们专注于业务逻辑开发。
  3. 非侵入式设计:从框架角度可以这样理解,无需继承框架提供的类,这种设计就可以看作是非侵入式设计,如果继承了这些框架类,就是侵入设计,如果以后想更换框架之前写过的代码几乎无法重用,如果非侵入式设计则之前写过的代码仍然可以继续使用。
  4. 轻量级及重量级:轻量级是相对于重量级而言的,轻量级一般就是非入侵性的、所依赖的东西非常少、资源占用非常少、部署简单等等,其实就是比较容易使用,而重量级正好相反。
  5. POJO : POJO ( Plain Old Java Objects )简单的 Java 对象,它可以包含业务逻辑或持久化逻辑,但不担当任何特殊角色且不继承或不实现任何其它 Java 框架的类或接口。
  6. 容器:在日常生活中容器就是一种盛放东西的器具,从程序设计角度看就是装对象的的对象,因为存在放入、拿出等操作,所以容器还要管理对象的生命周期。
  7. 控制反转:即 Inversion of Control ,缩写为 IoC ,控制反转还有一个名字叫做依赖注入( Dependency Injection ),就是由容器控制程序之间的关系,而非传统实现中,由程序代码直接操控。
  8. Bean :一般指容器管理对象,在 Spring 中指 Spring IoC 容器管理对象。

简化 Java 开发

为了降低 Java 开发的复杂性,Spring 采取了以下 4 种 关键策略:

  • 基于 POJO 的轻量级和最小侵入性编程
  • 通过依赖注入和面向接口实现松耦合
  • 基于切面和惯例进行声明式编程
  • 通过切面和模板减少样板式代码

激发 POJO 的潜力

Spring 尽量避免让自己的 API 污染你的应用代码。Spring 不会强制要求开发人员实现某个 Spring 提供的接口或者继承某个 Spring 提供的类,在 Spring 应用中的 Java 类看起来和普通类一样,不过,Spring 现在经常使用注解来修饰 Java 类,但是这个类还是一个 POJO。

public class HelloWorldBean {
    public String sayHello() {
        return "Hello World";
    }
}

可以看出,这就是一个简单的 Java 类——POJO,没有什么特殊的标志表明它是一个 Spring 组件。Spring 这种非侵入式编程模型使得这个类在 Spring 和非 Spring 框架下具备相同的功能。

依赖注入

依赖注入这个词让人望而生畏,现在已经演变成一项复杂的编程技巧或设计模式理念

多个类之间进行协作来完成特定的业务。按照传统的方法,每个对象负责管理与自己相关的对象的引用,这将导致高度耦合和难以测试的代码。

public class DamselRescuingKnight implements Knight {
    private RescueDamselQuest quest;

    public DamselRescuingKnight() {
        // 紧耦合
        this.quest = new RescueDamselQuest();
    }

    @Override
    public void embarkOnQuest() {
        quest.embark();
    }
}

耦合具有两面性:

  • 紧密耦合的代码难以测试,难以复用,难以理解,并且典型地表现出“打地鼠”式的 BUG 特性,(修复一个 bug,将会出现新的 bug).
  • 一定的程度耦合又是必须的,完全没有耦合的代码什么都做不了。为了完成更有实际意义的功能,不同的类必须以适当的方式进行交互,总而言之,耦合是必须的,但需要谨慎对待

通过 DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定。对象无需自行创建或管理它们的依赖关系,如图,依赖关系将被自动注入到需要它们的对象中去。

依赖注入会将所依赖的关系自动交给目标对象,而不是让对象自己去获取依赖

BraveKnight 足够灵活,可以接受任何赋予他的探险任务。

public class BraveKnight implements Knight {
    private Quest quest;

    public BraveKnight(Quest quest) {  // Quest 被注入进来
        this.quest = quest;
    }

    @Override
    public void embarkOnQuest() {
        quest.embarck();
    }
}

BraveKnight 没有自行创建 Quest,而是在构造的时候把 Quest 作为构造参数传入。这是依赖注入的方式之一,即构造注入(constructor injection).

BraveKnight 没有有特定的 Quest 实现发生耦合。对他来说,被要求挑战的探险任务只要实现了 Quest 接口,那么具体的是那种类型就无关紧要了。这就是 DI 带来最大的收益——松耦合

将 Quest 注入到 Knight 中

public class SlayDragonQuest implements Quest{
    private PrintStream stream;

    public SlayDragonQuest(PrintStream stream) {
        this.stream = stream;
    }

    @Override
    public void embarck() {
        stream.println("SlayDragonQuest embark!");
    }
}

SlayDragonQuest 实现了 Quest 接口,这样就可以注入到 BraveKnight 中去了。SlayDragonQuest 在构造器中使用了更为通用的 PrintStream。

装配

创建应用组件之间协作的行为通常称为装配(wiring)

不管使用的是基于 XML 的配置还是基于 Java 的配置,DI 所带来的收益都是相同的。

使用 XML 装配
<!-- knights.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.xsd">

    <bean id="quest" class="com.bin.spring.SlayDragonQuest">
        <constructor-arg value="#{T(System).out}"/>
        <!--Spring表达式语言(Spring Expression Language),SpEL中, 使用T()运算符会调用类作用域的方法和常量-->
    </bean>
    <bean id="knight" class="com.bin.spring.BraveKnight">
        <constructor-arg ref="quest"/> <!-- 注入 Quest -->
    </bean>
</beans>

测试

public class KnightMain {
    public static void main(String[] args) {
        // 加载 Spring 上下文
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("knights.xml");
        Knight knight = context.getBean(Knight.class);
        knight.embarkOnQuest();
        context.close();
    }
}
// SlayDragonQuest embark!
使用 Java 装配
@Configuration
public class KnightConfig {
    @Bean
    public Quest quest() {
        return new SlayDragonQuest(System.out);
    }

    @Bean
    public Knight knight() {
        return new BraveKnight(quest());
    }
}

测试

public class KnightMainJava {
    public static void main(String[] args) {
        KnightConfig config = new KnightConfig();
        Knight knight = config.knight();
        knight.embarkOnQuest();
    }
}
// SlayDragonQuest embark!

应用切面

DI 能够让相互协作的软件组件保持松耦合,而面向切面编程(aspect-oriented programming AOP) 允许你把遍布应用各处的功能分离出来形成可重用的组件

面向切面编程往往被定义为促使软件系统实现关注点分离的一项技术。系统由许多不同的组件组成,每个组件各负责一块特定功能。除了自己的核心功能之外,这些组件还经常承担着额外的任务。如日志,事务管理和安全这样的系统服务经常融入到自身具有核心业务逻辑的组件中,这些系统服务通常被称为横切关注点,因为它们会跨越系统的多个组件

如果讲这些组件分散到多个组件中,则会带来双重的复杂性。

  • 代码会重复出现在多个组件中。
  • 组件会因为那些有自身的核心业务无关的代码而变得混乱。

AOP 能够使这些组件模块化,并以声明的方式将它们应用到它们需要影响的组件中去。这样这些组件就会具有更高的内聚性并且会更加关注自身的业务,完全不需要了解涉及系统服务所带来的复杂性。总之,AOP 能够确保 POJO 的简单性。

我们可以把切面想象为覆盖在很多组件之上的一个外壳。应用是有那些实现各自业务功能的模块组成的。借助 AOP,可以使用各种功能层去包裹核心业务层。这些层以声明的方式灵活地应用到系统中,你的核心应用甚至根本不知道它们的存在。这是一个非常强大的理念,可以将安全、事务和日志关注点和核心业务逻辑相分离

利用 AOP,系统范围内的关注点覆盖在它们所影响的组件之上

应用

使用咏游诗人这个服务类来记载骑士的所有事迹。

// 咏游诗人
public class Minstrel {
    private PrintStream stream;

    public Minstrel(PrintStream stream) {
        this.stream = stream;
    }

    public void singBeforeQuest() {
        stream.println("探险前!");
    }

    public void singAfterQuest() {
        stream.println("探险后!");
    }
}

以前做法

public class BraveKnight implements Knight {
    private Quest quest;
    private Minstrel minstrel;

    // 通过构造器来注入 Minstrel
    public BraveKnight(Quest quest, Minstrel minstrel) {
        this.quest = quest;
        this.minstrel = minstrel;
    }

    @Override
    public void embarkOnQuest() {
        minstrel.singBeforeQuest();
        quest.embarck();
        minstrel.singAfterQuest();
    }
}

这样可以达到预期效果。但 Knight 不应该管理咏游诗人。

利用 AOP,你可以声明咏游诗人必须歌颂骑士的探险事迹,而骑士本身不直接访问 Minstrel 的方法

对前文中 knights.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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
       http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="quest" class="com.bin.spring.SlayDragonQuest">
        <constructor-arg value="#{T(System).out}"/>
    </bean>
    <bean id="knight" class="com.bin.spring.BraveKnight">
        <constructor-arg ref="quest"/>
    </bean>
    <bean id="minstrel" class="com.bin.spring.Minstrel">
        <constructor-arg value="#{T(System).out}"/>
    </bean>

    <aop:config>
        <aop:aspect ref="minstrel">
            <!--定义切点,Aspect 切点表达式-->
            <aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/>
            <!--声明前置通知-->
            <aop:before method="singBeforeQuest" pointcut-ref="embark"/>
            <!--声明后置通知-->
            <aop:after method="singAfterQuest" pointcut-ref="embark"/>
        </aop:aspect>
    </aop:config>
</beans>

Minstrel 仍然是一个 POJO,没有任何代码表明它要被作为一个切面使用,其次最重要的是 Minstrel 可以被应用到 BraveKnight 中,而 BraveKnight 不需要显示的调用它,实际上,BraveKnight 完全不知道 MInstrel 的存在

再次运行 KnightMain 测试

探险前!
SlayDragonQuest embark!
探险后!

使用模板消除样板式代码

在编程过程中有没有感觉经常需要写重复无用的代码才能实现简单的功能,最经典的例子是 JDBC 的使用,这些代码就是样板式代码(boilerplate code)。

Spring 试图通过模板来消除重复代码,这里所用的是模板设计模式。对于 JDBC 接口,Spring 提供了 JdbcTemplate 模板来消除上面那个代码片段中的样板式代码,例子代码如下:

public Employee getEmployeeById(long id) {
    return jdbcTemplate.queryForObject(
            "select id, name from employee where id=?", // sql 查询
            new RowMapper<Employee>() {
                public Employee mapRow(ResultSet resultSet, int rowNum) throws SQLException {
                    // 将结果匹配为对象
                    Employee employee = new Employee();
                    employee.setId(resultSet.getLong("id"));
                    employee.setName(resultSet.getString("name"));
                    return employee;
                }
            }, id);  // 指定查询参数
}

容纳你的 Bean

在基于 Spring 的应用中,你的应用对象存在于 Spring 容器(container)中。Spring 负责创建对象,装配它们,配置它们并管理它们的整个生命周期,从生存到死亡(new 到 finalize())。

容器是 Spring 框架的核心。Spring 容器使用 DI 管理构成应用的组件,它会创建相互协作的组件之间的联系。

Spring 容器的两种类型:

  • bean 工厂(由 org.springframework.beans.factory.BeanFactory 接口定义)是最简单的容器,提供基本的 DI 支持。对于大多数应用来说太低级,一般不用。
  • 应用上下文(由 org.springframework.context.ApplicationContext 接口定义)基于 BeanFactory 构建,并提供应用框架级别的服务。

使用应用上下文

Spring 自带了多种应用上下文:

  • AnnotationConfigApplicationContext:从一个或多个基于 Java 的配置文件类中加载 Spring 应用上下文
  • AnnotationConfigWebApplicationContext:从一个或多个基于 Java 配置类加载 Spring Web 应用上下文
  • ClassPathXmlApplicationContext:从类路径下的一个或多个 XML 配置文件中加载上下文定义,把应用上下文的定义文件作为类资源
  • FileSystemXmlApplicationContext:从文件系统下的一个或多个 XMl 配置文件中加载上下文定义。
  • XmlWebApplicationContext:从 web 应用下的一个或多个 XML 配置文件中加载上下文定义。
  • XmlBeanFactory:BeanFactory 实现,提供基本的 IoC 容器功能,可以从 classpath 或文件系统等获取资源。

无论是从文件系统中装配应用上下文还是从类路径下装配应用上下文,将 bean 加载到 bean 工厂的过程都是相似的。

ApplicationContext context =
        new FileSystemXmlApplicationContext("c:/knight.xml");

APPlicationContext context2 =
        new ClassPathXmlApplicationContext("knight.xml");

// 使用 Java 配置中加载上下文
APPlicationContext context3 =
        new AnnotationConfigApplicationContext(com.bin.spring.KnightConfig.class);

File file = new File("knight.xml");
Resource resource = new FileSystemResource(file);
BeanFactory beanFactory = new XmlBeanFactory(resource);

Resource resource = new ClassPathResource("knight.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);

应用上下文准备就绪之后,我们就可以调用上下文的 getBean() 方法从 Spring 容器中获取 bean。

  • Object getBean(String name) 根据名称返回一个 Bean ,客户端需要自己进行类型转换;
  • T getBean(String name, Class<T> requiredType) 根据名称和指定的类型返回一个 Bean ,客户端无需自己进行类型转换,如果类型转换失败,容器抛出异常;
  • T getBean(Class<T> requiredType) 根据指定的类型返回一个 Bean ,客户端无需自己进行类型转换,如果没有或有多于一个 Bean 存在容器将抛出异常;
  • Map<String, T> getBeansOfType(Class<T> type) 根据指定的类型返回一个键值为名字和值为 Bean 对象的 Map ,如果没有 Bean 对象存在则返回空的 Map 。

bean 的生命周期

在bean工厂执行力若干启动步骤;

  1. Spring 对 bean 进行实例化;
  2. Spring 将值和 bean 的引用注入到 bean 对应的属性中;
  3. 如果 bean 实现了 BeanNameAware 接口,Spring 将 bean 的 ID 传给 setBeanName()方法;
  4. 如果 bean 实现了 BeanFactoryAware 接口,Spirng 将调用 setBeanFatory() 方法,将 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 将调用他的 destroy() 接口方法。同样,如果 bean 使用 destroy-mothod 声明销毁方法,该方法也会被调用。

Spring 也支持用 @PostConstruct@PreDestroy 注解来指定 initdestroy 方法,为了注解可以生效,需要在配置文件中定义 org.springframework.context.annotation.CommonAnnotationBeanPostProcessorcontext:annotation-config

Spring 的新功能

Spring 3.1 新特性

Spring 3.1 带来了多项有用的新特性和增强, 其中有很多都是关于如何简化和改善配置的。除此之外, Spring 3.1 还提供了声明式缓存的支持以及众多针对 SpringMVC 的功能增强。下面的列表展现了 Spring 3.1 重要的功能升级:

  • 为了解决各种环境下 (如开发、测试和生产) 选择不同配置的问题, Spring 3.1 引入了环境 profile 功能。借助于 profile, 就能根据应用部署在什么环境之中选择不同的数据源bean;
  • 在 Spring 3.0 基于 Java 的配置之上, Spring 3.1 添加了多个 enable 注解, 这样就能使用这个注解启用 Spring 的特定功能;
  • 添加了 Spring 对声明式缓存的支持, 能够使用简单的注解声明缓存边界和规则, 这与你以前声明事务边界很类似;
  • 新添加的用于构造器注入的 c 命名空间, 它类似于 Spring 2.0 所提供的面向属性的 p 命名空间, p 命名空间用于属性注入, 它们都是非常简洁易用的;
  • Spring 开始支持 Servlet 3.0, 包括在基于 Java 的配置中声明 Servlet 和 Filter, 而不再借助于 web.xml;
  • 改善 Spring 对 JPA 的支持, 使得它能够在 Spring 中完整地配置 JPA, 不必再使用 persistence.xml 文件。

Spring 3.1 还包含了多项针对 Spring MVC 的功能增强:

  • 自动绑定路径变量到模型属性中;
  • 提供了 @RequestMappingproduces 和 consumes 属性, 用于匹配请求中的 Accept 和 Content-Type 头部信息;
  • 提供了 @RequestPart 注解, 用于将 multipart 请求中的某些部分绑定到处理器的方法参数中;
  • 支持 flash 属性 (在 redirect 请求之后依然能够存活的属性) 以及用于在请求间存放 flash 属性的 RedirectAttributes 类型。

Spring 3.2 新特性

Spring 3.1 在很大程度上聚焦于配置改善以及其他的一些增强, 包括 SpringMVC 的增强, 而 Spring 3.2 是主要关注 Spring MVC 的一个发布版本。SpringMVC 3.2 带来了如下的功能提升:

  • Spring 3.2 的控制器 (Controller) 可以使用 Servlet 3.0 的异步请求, 允许在一个独立的线程中处理请求, 从而将 Servlet 线程解放出来处理更多的请求;
  • 引入了 Spring MVC 测试框架, 用于为控制器编写更为丰富的测试;
  • 除了提升控制器的测试功能, Spring 3.2 还包含了基于 RestTemplate 的客户端的测试支持, 在测试的过程中, 不需要往真正的 REST 端点上发送请求;
  • @ControllerAdvice 注解能够将通用的 @ExceptionHandler、@ InitBinder 和 @ModelAttributes 方法收集到一个类中, 并应用到所有控制器上;
  • 在 Spring 3.2 之前, 只能通过 ContentNegotiatingViewResolver 使用完整的内容协商 (full content negotiation) 功能。但是在 Spring 3.2 中, 完整的内容协商功能可以在整个Spring MVC 中使用, 即便是依赖于消息转换器 (message converter) 使用和产生内容的控制器方法也能使用该功能;
  • Spring MVC 3.2 包含了一个新的 @MatrixVariable 注解, 这个注解能够将请求中的矩阵变量 (matrix variable) 绑定到处理器的方法参数中;
  • 基础的抽象类 AbstractDispatcherServletInitializer 能够非常便利地配置 DispatcherServlet, 而不必再使用 web.xml。与之类似, 当你希望通过基于 Java 的方式来配置 Spring 的时候, 可以使用 Abstract-AnnotationConfigDispatcherServletInitializer 的子类;
  • 新增了 ResponseEntityExceptionHandler, 可以用来替代 DefaultHandlerException Resolver。ResponseEntityExceptionHandler 方法会返回 ResponseEntity<Object>, 而不是 ModelAndView;
  • RestTemplate 和 @RequestBody 的参数可以支持范型;
  • RestTemplate 和 @RequestMapping 可以支持 HTTP PATCH 方法;
  • 在拦截器匹配时, 支持使用 URL 模式将其排除在拦截器的处理功能之外。

虽然 Spring MVC 是 Spring 3.2 改善的核心内容, 但是它依然还增加了多项非 MVC 的功能改善。下面列出了 Spring 3.2 中几项最为有意思的新特性:

  • @Autowired、@Value 和 @Bean 注解能够作为元注解, 用于创建自定义的注入和 bean 声明注解;
  • @DateTimeFormat 注解不再强依赖 JodaTime。如果提供了 JodaTime, 就会使用它, 否则的话, 会使用 SimpleDateFormat;
  • Spring 的声明式缓存提供了对 JCache 0.5 的支持;
  • 支持定义全局的格式来解析和渲染日期与时间;
  • 在集成测试中, 能够配置和加载 WebApplicationContext;
  • 在集成测试中, 能够针对 request 和 session 作用域的 bean 进行测试。在本书的多个章节中, 都能看到 Spring 3.2 的特性, 尤其是在 Web 和 REST 相关的章节中。

Spring 4.0 新特性

  • Spring 提供了对 WebSocket 编程的支持, 包括支持 JSR-356——Java API for WebSocket;
  • 鉴于 WebSocket 仅仅提供了一种低层次的 API, 急需高层次的抽象, 因此 Spring 4.0 在 WebSocket 之上提供了一个高层次的面向消息的编程模型, 该模型基于 SockJS, 并且包含了对 STOMP 协议的支持;
  • 新的消息 (messaging) 模块, 很多的类型来源于 Spring Integration 项目。这个消息模块支持 Spring 的 SockJS/STOMP 功能, 同时提供了基于模板的方式发布消息;
  • 支持 Java 8 特性, 比如它所支持的 lambda 表达式。别的暂且不说, 这首先能够让使用特定的回调接口 (如 RowMapper 和 JdbcTemplate) 更加简洁, 代码更加易读;
  • 与 Java 8 同时得到支持的是 JSR-310——Date 与 Time API, 在处理日期和时间时, 它为开发者提供了比 java.util.Date 或 java.util.Calendar 更丰富的 API;
  • 为 Groovy 开发的应用程序提供了更加顺畅的编程体验, 尤其是支持非常便利地完全采用 Groovy 开发 Spring 应用程序。随这些一起提供的是来自于 Grails 的 BeanBuilder, 借助它能够通过 Groovy 配置 Spring 应用;
  • 添加了条件化创建 bean 的功能, 在这里只有开发人员定义的条件满足时, 才会创建所声明的 bean;
  • Spring 4.0 包含了 Spring RestTemplate 的一个新的异步实现, 它会立即返回并且允许在操作完成后执行回调;
  • 添加了对多项 JEE 规范的支持, 包括 JMS 2.0、JTA 1.2、JPA 2.1 和 Bean Validation 1.1。

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:《Spring 实战》笔记1:Spring 之旅

文章字数:5.5k

本文作者:Bin

发布时间:2019-04-13, 21:14:22

最后更新:2019-12-07, 22:11:10

原始链接:http://coolview.github.io/2019/04/13/Spring/%E3%80%8ASpring%20%E5%AE%9E%E6%88%98%E3%80%8B%E7%AC%94%E8%AE%B01%EF%BC%9ASpring%20%E4%B9%8B%E6%97%85/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录