ramostear.comramostear.com 谭朝红的技术分享博客

格言 编程是一门技术,也是一门艺术 !

5个Spring Event奇技淫巧,你GET到了吗?

5个Spring Event奇技淫巧,你GET到了吗?

5个Spring Event奇技淫巧,你GET到了吗?

前言:

谈到事件,接触过前端或GUI编程(JavaScript,Swing)的同学应该有较深刻印象。如事件源、事件的监听、回调等概念应该耳熟能详。而在Web应用程序中则很少用到事件。但在Web应用程序中,也可以轻松实现面向事件的编程。

1、为什么需要面向事件编程

​ 在本文中,将基于Spring框架来谈谈关于面向事件编程的5个奇技淫巧。在开始主要内容之前,先了解一下为什么需要面向事件编程。首先看一个生活中的一个场景来帮助我们快速的了解面向事件编程所带来的好处。以客户到银行柜台取现为例,在较早以前,银行柜台取现需要一个接一个的排长龙等待柜台业务员处理取现业务,在此期间,客户不能离开队伍去做其他的事情,否则需要重新排队。这好比常规的面向过程的编程,需要依次执行每条逻辑语句,直到所有的语句都执行完毕,方法才返回结果。现在,银行柜台取现多了一台叫号机,需要办理取现业务的客户先通过叫号机领取一张业务号,如果等待人数过多的时候,客户可以先处理自身的事情,直到柜台叫到自己的号时,才到柜台办理取现业务。这就是一个典型(不太严谨)面向事件的处理过程。首先,客户通过叫号机注册一个取现的事件,此时的叫号机,相当于事件发布器(事件注册中心),客户相当于事件源,当柜台收到有客户需要取现的消息时,就会广播提示客户办理取现业务,柜台就相当于一个事件监听器。

​ 如上所述,面向事件的编程(也称作事件驱动编程)是基于对消息信号的反应的编程。面向事件编程需要满三个条件:

  • 1、事件源:消息的源头,如点击一个按钮、访问某个页面、新增一条数据等。
  • 2、事件注册中心:事件注册中心用于存储和发布事件。
  • 3、事件监听器:用于接收特定的消息,并作出对应的反应。

下面通过一张图更为直观的了解面向事件编程的基本原理:

图 1-1 事件处理机制

2、Spring Events

​ Spring Events 是Spring Framework的一部分,但在应用程序中并不经常使用,甚至被忽略。然而,Spring的Application拥有强大的事件发布并注册事件监听器的能力,它拥有一套完整的事件发布与处理机制。Spring 4.1开始引入@EventListener注解来监听具体类型的事件,它的出现,消除了使用Spring定义一个事件监听器的复杂操作。仅仅通过一个简单的注解,就可以完成一个监听器的定义,你不需要额外的进行其他的配置;这简直太棒了。在Spring Framework中,事件的发布是由ApplicationContext提供的,如果想完成一个完整的面向事件(也称为事件驱动编程)编程,你需要遵循以下三个基本的原则:

  • 1、定义一个事件,且扩展ApplicationEvent
  • 2、事件发布者应该注入一个ApplicationEventPublisher对象
  • 3、监听器实现ApplicationListener接口或者使用@EventListener注解

在开始介绍面向事件编程的具体实施细节之前,先通过一张类图了解一下Spring的事件机制设计类图:

图 2-1 Spring Event 类图

​ 通过类图,先快速对Spring Event事件类图做一个简单的介绍:

  • 1、ApplicationEventPublisher是Spring的事件发布接口,所有的事件都是通过该接口提供的publishEvent方法发布事件的。
  • 2、ApplicationEventMulticaster是Spring事件广播器,它将提供一个SimpleApplicationEventMulticaster实现类,如果没有提供任何可用的自定义广播器,Spring将采用默认的广播规则。
  • 3、ApplicationListener是Spring事件监听接口,所有的事件监听器都需要实现此接口,并通过onApplicationEvent方法处理具体的逻辑。
  • 4、在Spring Framework中,ApplicationContext本身就具备监听器的能力,我们也可以通过ApplicationContext发布先关的事件。

​ 通过分析,可以看到:当一个事件通过ApplicationEventPublisher发布出去之后,ApplicationEventMulticaster广播器就会开始工作,它将去ApplicationContext中寻找对应的事件监听器(ApplicationListener),并执行对应监听器中的onApplicationEvent方法,从而完成事件监听的全部逻辑。

3、自定义Spring Event

​ 本次内容将使用Spring Boot 2.1.5快速搭建所需要的环境。首先将演示如何自定义一个事件,并监听该事件且对监听到的事件做出反应。

​ 我们将使用IntelliJ IDEA创建一个Web项目,并在pom.xml文件中添加如下的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ramostear</groupId>
    <artifactId>spring-boot-event</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-event</name>
    <description>Demo project for Spring events</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

说明:

这将是一个完整的演示Spring Event事件处理机制的项目,因为期间需要演示事务管理的特性,所以加入了MySql数据库依赖。

​ 本次的内容将依据这样一个场景展开:当成功添加一条用户信息后,将发送一条邮件到用户的邮箱账户。首先,需要定义两个实体,用户实体和邮件详情实体。具体代码如下:

接下来是邮件详情实体类:

持久化类通过扩展JPA的JpaRepository类快速获得CRUD能力。用户持久化类和邮件详情持久化类分别如下:

UserRepository.java

package com.ramostear.repository;

import com.ramostear.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * Created by Ramostear on 2019/5/31 0031.
 */
public interface UserRepository extends JpaRepository<User,Long> {
}

EmailDetailRepository.java

package com.ramostear.repository;

import com.ramostear.domain.EmailDetail;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * Created by Ramostear on 2019/5/31 0031.
 */
public interface EmailDetailRepository extends JpaRepository<EmailDetail,Long> {
}

接下来,将定义一个用户业务接口,提供创建和查找用户两个方法,代码如下:

package com.ramostear.service;

import com.ramostear.domain.User;

/**
 * Created by Ramostear on 2019/5/31 0031.
 */
public interface UserService {

    User created(User user);


    User findOne(Long id);
}

基本的环境已经准备就绪,现在开始定义定义第一个事件类:SendEmailEvent.java,其代码如下所示

如之前所述,自定义事件需要扩展ApplicationEvent类,并提供自己的实现细节。这里的事件需要为其提供一个用户ID,在接下来的监听器中,通过此ID来甄别具体的用户,并发送邮件。

接下来,我们将使用@EventListener注解实现一个事件监听器,其源码如下:

说明:

在此类中,我们注入了EmailDetailRepository和UserRepository两个依赖项,并使用@EventListener注解对sendEmail方法进行了标注。在方法中,使用了Thread.sleep(1000*3)让程序休眠3秒,以模拟发送邮件时所耗费的时间。@Slf4j注解是Lombok提供的快速使用Logger的一个快捷方式。@Component注解用于标注此类是一个普通的组件,将被Spring自动扫描并管理。

​ 准备好了事件源以及事件监听器,现在来看看如何发布一个事件。发布事件将在用户业务实现类中进行操作。UserServiceImpl.java类的实现细节如下:

​ 如前面章节所讲,如果使用Spring发布一个事件,需要在具体的类中注入一个ApplicationEventPublisher对象。在用户业务实现类中,通过构造函数的方式注入ApplicationEventPublisher对象和UserRepository持久化对象。在created(User user)方法中,当成功保存用户数据后,通过发布一个SendEmailEvent事件,将邮件发送给用户的邮箱。与此同时,我们记录了程序执行完所耗费的事件,在下一小节中我们将使用到这个数据。

​ 最后,我们定义一个控制器,提供一个处理添加用户请求的方法,验证发送邮件事件是否被监听器成功监听。控制器代码如下:

启动项目,并使用PostMan工具对POST /users API进行测试:

图 3-1 Postman测试结果

经测试,成功返回了用户信息,且耗时3530ms。我们在看看控制台的日志信息:

图 3-2 日志记录信息

​ 至此,第一个基于Spring Event的面向事件的编程案例完成。在这个案例中,虽然我们解决了事件的发送和监听的编码实现问题,但可能会有同学会想,这样子的处理方式和将发布事件的代码替换成直接调用邮件持久化类保存邮件信息没有任何区别,如同一开始说的案例,虽然有叫号机叫号,解决排队的问题,但等待的时间依旧没有改变,这和直接排队在时间耗费上没有根本的变化。那么针对这样一个问题,我们在接下来的内容中将讲解如何实现异步事件。异步事件就好比你现在从普通储蓄客户变成了VIP会员,你可以不去银行柜台叫号,而是通过电话或者其他手段预约一个时间去办理业务,银行客户经理会给你预留相应的时间,当预约时间到达之后,你才去银行柜台办理业务。

4、Spring 异步事件

​ 在该小节的基础上,将原有的事件改造成异步事件,以节省客户端等待服务端响应的时间。使用Spring实现异步事件有两种方式,第一种方式是使用@Async注解对监听器进行标注,这是最为简单的一种实现方式;第二种是通过配置类对Spring的事件广播器进行配置,设置Multicaster对异步操作的支持,此方法比第一种方法稍微复杂一些,但可以拥有对事件广播器更多的控制权。接下来,将分别介绍这两种实现方式。

4-1、使用@Async注解实现异步事件

​ 在现有的工程中,我们需要对两个类进行改造。第一处改造是在项目主类上加入@EnableAsync注解,开启对异步操作的支持。代码如下:

package com.ramostear;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class SpringBootEventApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootEventApplication.class, args);
    }
}

第二步是改造监听器,在原来的方法上加入@Async注解,其余部分保持不变,代码如下:

    @Async
    @EventListener
    public void sendEmail(SendEmailEvent event){
       ...
    }

通过上述两个步骤,我们就将Spring默认的同步事件改造为异步事件。重新启动项目,依旧使用Postman对接口POST /users进行测试,结果如下:

图 4-1 @Async异步事件

通过上图我们可以看到,服务端成功返回用户信息,注意观察,从客户端发起请求到服务端响应结束总共耗时480ms,而在第一个案例中,请求和响应的总耗时为3530ms。最后我们再来看看控制台的日志信息,确定一下数据库是否成功保存用户邮件:

图 4-2 @Async异步事件控制台日志信息

通过上述的测试结果,我们可以看到,异步事件已经生效,在相同的环境下,同样的业务请求,使用同步事件和异步事件,服务端响应时间存在巨大的差异。同步事件耗时3530ms,异步事件耗时480ms,这就是异步事件带来的巨大优势。

4-2 、使用配置文件开启对异步事件的支持

​ 在4-1中,我们学会了如何使用@Async定义异步事件,那本小节将展示另外一种方式获取对异步事件的支持。首先需要定义一个配置类,该类用于配置SimpleApplicationEventMulticaster类对于异步事件的支持。其源码如下:

package com.ramostear.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.task.SimpleAsyncTaskExecutor;

/**
 * Created by Ramostear on 2019/5/31 0031.
 */
@Configuration
public class AsyncSpringEventConfig {

    @Bean(name = "applicationEventMulticaster")
    public ApplicationEventMulticaster applicationEventMulticaster(){
        SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
        multicaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return multicaster;
    }
}

​ 在此配置文件中,applicationEventMulticaster方法将返回一个SimpleApplicationEventMulticaster对象。在返回此事件发布器之前,我们为此事件发布器设置了一个任务执行器:SimpleAsyncTaskExecutor,当应用程序中有新的异步事件时,将会调用此异步任务执行器的submitListenable(Callable task)方法,发布异步事件任务,并选择相应的监听器处理事件。定义好配置类后,还需要将主类中的@EnableAsync和监听器中的@Async两个注解注释掉,其余部分保持不变。最后重启项目,并测试API接口。

图 4-3 异步事件发布器配置测试

如图4-3所示,测试成功返回用户信息,其请求和响应总耗时为492ms。通过配置的方式开启Spring对异步事件的支持已生效。

5、事件过滤

​ 如果我们想对用户事件进行限制,某一部分满足条件的用户才发送邮件,可以使用condition属性来指定限制的条件。这就好比银行柜台提供的VIP预约取现业务,它分不同等级的VIP,只有达到某个条件时,才享有预约服务。在原来的基础上,我们做如下的改动,当用户ID大于5时,才发送邮件给用户。代码如下:

    @EventListener(condition = "#event.userId > 5")
    public void sendEmail(SendEmailEvent event){
       ...
    }

现在,数据库中用户ID最大为3,我们先添加两个用数据,看邮件详情表是否会新增数据。启动项目,测试API接口:

图 5-1 条件过滤测试一

服务端成功返回结果,用户ID=4,现在看看数据库是否有用户邮件数据被添加:

图 5-2 数据库查询结果一

从查询结果可以看到,并未给ID=4的用户发送邮件。控制台也只输出了插入用户数据的SQL语句:

Hibernate: 
    insert 
    into
        t_user
        (email, first_name, last_name) 
    values
        (?, ?, ?)
2019-06-03 06:14:19.334  INFO 4652 --- [p-nio-80-exec-2] c.r.service.impl.UserServiceImpl         : total times :58

接下来我们再新增两条用户数据,观察当用户ID=6时,是否会为用户发送邮件。图5-3是当用户ID=6时,数据库的查询结果以及控制台的输出:

图 5-3 数据库查询结果二

此时,控制台的输出信息如下所示:

图 5-4 控制台日志信息

由此可以看出,我们的事件过滤条件已生效。

6、事务处理

​ 现在,Spring Event的相关使用技巧以及介绍了大部分,最后让我们来考虑这样一个问题,如果在保存用户的时候发生错误,事件将会怎么样?在监听器处理事件时,保存用户的事务还未提交会怎么样?按照常规的逻辑,当用户持久化事务未提交,或者保存过程中发生异常时,不应该给用户发送邮件。试想一下,如果本次案例的场景是发生在用户注册的业务中,如果用户尚未注册成功,就给用户发送一封邮件,那是多么尴尬的事情。这就涉及到事件的事务控制。在Spring中,可以使用@TransactionalEventListener注解对监听器进行标注,一旦使用此注解,被标注的方法将被纳入到事务管理的范围。此注解是@EventListener的扩展,它允许你将事件的监听绑定到某个事务阶段。可以通过phase属性对事务阶段进行设置。下面是phase可设置的所有事务阶段:

  • 1、AFTER_COMMIT(默认值),用于在事务成功完成后出发事件
  • 2、AFTER_ROLLBACK,用于事务回滚是出发事件
  • 3、AFTER_COMPLETION,用于事务完成后出发事件(AFTER_COMMIT和AFTER_ROLLBACK的一种模糊表述)
  • 4、BEFORE_COMMIT,用于事务提交前出发事件

最后给出一个简单的示例:

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT,condition = "#event.userId >=5")
    public void sendEmail(SendEmailEvent event){
        Optional<User> optional = userRepository.findById(event.getUserId());
        if(optional.isPresent()){
            User user = optional.get();
            log.info("User Detail:{ id:{},firstName:{},email:{}",user.getId(),user.getFirstName(),user.getEmail());
            try {
                Thread.sleep(1000*3);
            } catch (InterruptedException e) {
                log.error(e.getLocalizedMessage());
            }
            emailDetailRepository.save(EmailDetail.builder().
                    userId(user.getId()).
                    email(user.getEmail()).
                    sendTime(new Date()).
                    build());
            log.info("User email already send.");
        }
    }

其余代码保持不变。

总结

以上就是关于Spring Event的5个实用技巧,如果你想了解更多细节内容,我已将全部源码上传至Github仓库,你可以通过此链接获取全部源码。如有问题,请在文章最后给我留言。

(第二讲)Spring&Spring MVC&Spring Boot三者之间的区别与联系

(第二讲)Spring&Spring MVC&Spring Boot三者之间的区别与联系

Spring Framework的诞生让开发人员的工作从石器时代跨域到了工业时代,你是否还能记起手撸Servlet和JDBC的岁月?,你是否还对Struts1以及Struts2莫名其妙的404错误记忆犹新?从2004年3月Spring 1.0发布到至今,Spring的发展已经走过了15个年头,其创造的价值让人瞩目。今天,带着这样一个背景来梳理一下Spring Framework,Spring MVC和Spring Boot三者之间的区别。

我们使用Spring家族的系列产品这么长时间,不禁会问这样几个问题:Spring Framework是什么?Spring MVC是什么?Spring Boot又是什么?它们被设计出来的目的是什么?

你需要了解的知识

在接下来的内容中,将梳理这样几个知识点:

  • Spring Framework基本概述
  • Spring Framework主要解决的问题是什么?
  • Spring MVC基本概述
  • Spring MVC主要解决的问题是什么?
  • Spring Boot主要解决的问题是什么?
  • Spring,Spring MVC和Spring Boot三者之间的区别是什么?

Spring Framework 解决了哪些核心问题?

当你仔细思考这个问题的时候你会发现,很多地方它都有渗透到,貌似一个Spring就可以撑起开发的半边天,以至于很难一下子回答这个问题。那Spring Framework到底解决了哪些核心问题?

Spring Framework最重要也是最核心的特性是依赖注入。所有的Spring模块的核心就是DI(依赖注入)或者IoC(控制反转)。

依赖注入或控制反转是Spring Framework最大的特性,当我们正确使用DI(依赖注入)或IoC时,可以开发出一个高内聚低耦合的应用程序,而这一一个低耦合的应用程序可以轻松的对其实施单元测试。这就是Spring Framework解决的最核心的问题。

无依赖注入

请考虑这一一个案例:UserAction依赖于UserService来获取用户信息,在没有依赖注入的情况下,我们需要手动在UserAction中实例化一个UserService对象,这样的手工作业意味着UserAction和UserService必须精密的联系在一起,才能正常工作。如果一个Action需要多个Service提供服务,那实例化这些Service将是一个繁重的工作。下面我们给出一个不使用依赖注入的代码片段加以说明:

UserService.java

public interface UserService{
    User profile();
}

UserServiceImpl.java

public class UserServiceImpl implements UserService{
    @Override
    User profile(){
        // TODO
    }
}

UserAction.java

@RestController
public class UserAction{

    private UserService userService = new UserServiceImpl();
    // other services...

    @GetMapping("/profile")
    public User profile(){
        return userService.profile();
    }
}

引入依赖注入

引入依赖注入将会使整个代码看起来很清爽。为了能够开发出高内聚低耦合的应用程序,Spring Framework为我们做了大量的准备工作。下面我们使用两个简单的注解@Component@Autowired来实现依赖注入。

  • @Component : 该注解将会告诉Spring Framework,被此注解标注的类需要纳入到Bean管理器中。
  • @Autowired : 告诉Spring Framework需要找到一与其类型匹配的对象,并将其自动引入到所需要的类中。

在接下来的示例代码中,我们会看到Spring Framework将为UserService创建一个Bean对象,并将其自动引入到UserAction中。

UserService.java

public interface UserService{
    User profile();
}

UserServiceImpl.java

@Component
public class UserServiceImpl implements UserService{
    @Override
    User profile(){
        // TODO
    }
}

UserAction.java

@RestController
public class UserAction{
    @Autowired
    private UserService userService;
    // other services...

    @GetMapping("/profile")
    public User profile(){
        return userService.profile();
    }
}

对比上下两部分的代码,你是否发现了他们之间的区别?Action所依赖的Service的初始化工作全部交由Spring Framework来管理,我们只需要在适当的地方向Spring Framework索取想要服务即可。这就好比当我想要吃薯片的时候,我不需要自己亲自种土豆,施肥,收获…清洗,切片…一直到最后炸土豆片,想想都觉得累,而更简单的方法是直接去超市购买自己想要的薯片即可。

Spring Framework还有其他的核心特性吗?

#1:衍生的特性

Spring Framework的依赖注入是核心中的核心,在依赖注入核心特性的基础上,Spring Framework还衍生出了很多的高级模块:

  • Spring JDBC
  • Spring MVC
  • Spring AOP
  • Spring ORM
  • Spring JMS
  • Spring Test

对于这些新的高级模块,可能会产生这一一个问题:它们是否是一个全新的功能?答案是否定的,在不使用Spring Framework的情况下,我们依然能够使用JDBC连接数据库,依然能够对视图和数据模型进行控制,依然能够使用第三方的ORM框架。那Spring Framework干了什么?Spring Framework站在巨人的肩膀上,对这些原生的模块进行了抽象,而抽象可以带来这样一些好处:

  • 减少了应用中模板代码的数量
  • 降低了原生框架的技术门槛
  • 基于依赖注入特性,实现了代码的解耦,真正的高内聚、低耦合
  • 更细粒度的单元测试

这样的好处是显而易见的,比如与传统的JDBC相比,使用JDBCTemplate操作数据库,首先是代码量小了,其次是我们不需要再面对恐怖的try-catch。

#2:优秀的集成能力

Spring Framework还具备另外一个重要特性,那就是能够快速的与其他三方框架进行整合。与其自己造轮子,还不如想办法将好的轮子整合在一起,我想这句话应该可以用来概况Spring Framework这一特性。Spring Framework对于整合其他的框架,给出了不错的解决方案,下面将列举一些常见的方案:

  • 与Hibernate ORM框架整合
  • 与MyBatis 对象映射框架整合
  • 与Junit单元测试框架整合
  • 与Log4J日志记录框架整合

Spring MVC是什么?

Spring MVC提供了构建Web应用程序的全功能MVC模块,实现了Web MVC设计模式以及请求驱动类型的轻量级Web框架,即采用了MVC架构模式的思想,将Web层进行职责解耦。基于请求驱动指的是使用请求-响应模型,视图与数据模型分离,以简化Web应用的开发。

使用Spring MVC提供的Dispatcher Servlet,ModelAndView和ViewResolver等功能,可以轻松的开发出一个Web应用程序。

Spring Boot出现的原因是什么?

我们都知道,使用Spring Framework来开发应用程序,需要进行大量的配置工作以及依赖包的管理,工作繁重而且极易出现配置错误,尤为明显的是依赖包之间的版本冲突问题。

举一个简单的案例,当我们使用Spring MVC来开发Web应用程序时,我们大致需要经历这样几个步骤:

  • 1:配置组件扫描的包路径

  • 2:配置对应的Servlet程序

  • 3:配置视图解析器

  • 4:配置页面模板引擎(JSP、Freemarker等)

下面给出了一个小范围的举例:

...
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix">
        <value>/WEB-INF/pages</value>
    </property>
    <property name="suffix">
        <value>.jsp</value>
    </property>
</bean>
<mvc:resources mapping="/static/**" location="/static/"/>
...

此外,我们还需要配置先关的Servlet处理程序,它们大致是这样的:

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/todo-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

如果我们的应用程序还需要链接数据,则还需要配置数据源,实体对象管理器,事务管理器等众多配置:

<bean id="datasource" class="">
    ...
</bean>
<bean id="entityManagerFactory" class="">
    ...
</bean>
<bean id="transactionManager" class="">
    ...
</bean>
...

面对众多的配置文件,我们需要花费大量的时间去处理,这时你可能会问,我为什么要花费那么多的时间去管理Spring的配置工作?不是应该专注于应用本身的业务逻辑吗?现在,有了Spring Boot,这些烦心事就不需要你去操心了。

#1:Spring Boot的自动化配置能力

我为什么会把Spring Boot的自动化配置能力放在第一位,因为它极大的降低了我们使用Spring Framework所付出的成本。这是Spring Boot的自动化配置是一个最具价值的解决方案。

这难道不值得我们拍案叫好吗?如果你想要开发一个Web应用程序,你需要做的事情就是将Spring Boot Web包引入到项目的类路径下,Spring Boot就可以帮你解决后续的大多数配置工作。

  • 如果Hibernate的依赖被放到了类路径上,Spring Boot会自动配置数据源
  • 如果Spring MVC的依赖被放到了类路径上,Spring Boot又会自动配置Dispatcher Servlet

当Spring Boot检测到有新的依赖包添加到类路径上,Spring Boot会采用默认的配置对新的依赖包进行设置,如果我们想自己配置依赖包时,只需要手动覆盖默认的配置项即可。

  1. Spring Boot扫描类路径上可用的框架信息
  2. 获取应用程序现有的配置信息
  3. 如果应用程序没有提供框架的配置信息,Spring Boot将采用默认的配置来配置框架,这就是Spring Boot的自动配置特性(Auto Configuration)

#2:Spring Boot Starter项目

在传统模式的开发过程中,我们需要反复的确认应用程序所需要的第三方JAR包,以及这些JAR的版本和依赖关系。例如,现在我们打算开发一款Web应用程序,应用程序大概需要如下的一些依赖包:Spring MVC,Jackson Databind(用于数据绑定),Hibernate-Validator(用于服务端的数据校验)和Log4j(用于日志记录)。现在,我们需要去下载对应的jar包到应用程序中,并且还需要处理依赖包之间版本冲突的问题。

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>4.2.2.RELEASE</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.5.3</version>
</dependency>

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.0.2.Final</version>
</dependency>

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

这不是一件简单的事情,特别是发生版本冲突的时候,让应用程序能够正常运行起来就需要花费一定的时间。

Spring Boot Starter是一组用于管理依赖关系的描述符,通过这些描述符,我们可以在应用程序中轻松的管理依赖包,你可以以开箱即用的方式获取想要的依赖包,而无需去Maven仓库总检索对应的依赖,并将依赖配置复制粘贴到应用程序的pom文件中。例如,如果你想要使用Spring和JPA进行数据库访问,只需要在pom中添加spring-boot-starter-data-jpa依赖项就可以。

现在,如果我们想要开发一个Web应用程序,使用Spring Boot Starter Web依赖会是一个不错的选择。我们可以通过使用Spring INitializr快速构建一个Web应用程序,并将Spring Boot Starter Web添加到项目中。此时我们的pom文件只需要很少的配置:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Spring Boot Starter Web会为我们预装如下的一些依赖:

  • Spring : core,beans,context,aop
  • Web MVC : Spring MVC
  • Jackson : JSON Binding
  • Validation : Hibernate Validator,Validation API
  • Embedded Servlet Container: Tomcat
  • Logging : logback,slf4j

对于开发人员而言,我们不需要去担心这些依赖项的管理工作以及解决他们之间的兼容性问题,Spring Boot已经帮我们解决了。

Spring Boot的核心目标

Spring Boot的核心目标在于快速实现生产就绪的应用程序,这将包含这样几个部分:

  • 执行器 : 启用高级监控和跟踪应用程序功能
  • 嵌入式服务器:Spring Boot已经内置了多个Web服务器,如Undertow,jetty,tomcat,因此我们不需要再额外的配置服务器,就可以完成应用程序的调试工作。
  • 默认的异常处理机制
  • 开箱即用的依赖项管理机制
  • 自动化配置

总结

通过上述的梳理,我们可以看到,Spring Framework是一个提供了DI(依赖注入)和IoC(控制反转)的开发框架,使用Spring Framework可以帮助我们开发出高内聚,低耦合的应用程序,Spring MVC是在Spring Framework基础上发展出来的基于MVC模式的全功能Web开发框架,实现了Model,View和Controller之间的职责解耦;Spring Boot为我们提供了一个能够快速使用Spring Framework的优秀解决方案,通过最小化的配置,我们就可以使用Spring Framework,严格意义上讲,Spring Boot并不是某种框架,它只是为开发人员提供了一个更好的更方便的使用Spring Framework的解决方案。