如何基于Spring Boot搭建一个完整的项目
前言
使用Spring Boot做后台项目开发也快半年了,由于之前有过基于Spring开发的项目经验,相比之下觉得Spring Boot就是天堂,开箱即用来形容是绝不为过的。在没有接触Spring Boot 之前,以为Spring Boot 是一个新的框架体系。正好Spring Boot出现先的时候,也是微服务特别火的时候,大家不约而同把Spring Boot 和微服务等同起来了,但其实并不是如此,那么到底什么是Spring Boot ? 基于Spring Boot 又是如何开发的呢 ? 带着这两个问题,大家继续往下看。
Spring Boot 特点
Our primary goals are:
- Provide a radically faster and widely accessible getting started experience for all Spring development.
- Be opinionated out of the box, but get out of the way quickly as requirements start to diverge from the defaults.
- Absolutely no code generation and no requirement for XML configuration.
上述是从Spring Boot官方文档上摘录的,简述言之Spring Boot框架的目的就是开箱即用、去除配置。Spring Boot并不是重复去造就一个轮子,它的出现是为Spring的开发带来一种全新的体验,从而大大的降低Spring框架的使用门槛。
下面主要从几个特性介绍Spring Boot是如何简化开发的。
Spring Boot启动器
使用过Spring Boot的同学都知道,快速编写一个Spring Boot的Web Demo需要在pom文件中加入如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
Spring Boot 提供了很多像spring-boot-starter-web这样的启动器,名称都是spring-boot-starter-* 。
截图部分来自Spring Boot官网,只是Spring Boot启动器中小部分,更多启动器可以到官网上查找。
启动器为何物? 以spring-boot-starter-web 这个web开发用到的启动器为例。
从上图可以看出spring-boot-starter-web.jar包里面没有任何代码,对没有任何代码。只有一个pom文件。what ? 其中包中pom.xml的内容如下所示:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
以前在基于Spring框架下进行web开发通常需要在模块的pom文件中引入spring-webmvc、spring-web、jackson、tomcat等依赖模块。如果使用Spring Boot的启动器,只需要引入spring-boot-starter-web模块就可以。看到这里聪明的读者应该明白了为嘛启动器的包中没有代码只有pom文件了吧。 简言之Spring Boot的启动器就是将不同的第三方组件依赖进行了套件封装,为开发Spring应用提供了一个易用的套件库。开发者再也不用在pom中引入那么多模块了,一个套件搞定一个相关的功能所需要的所有依赖库,这个体验很棒吧。
继续解锁下一个Spring Boot的牛逼特性。
内嵌Web容器(Tomcat或者Jetty)
早期基于Spring框架开发的时候,Web容器是不可以缺少的一个步骤。通常把代码编译成war包,然后扔到tomcat容器中进行运行。 这时候需要单独的下载tomcat容器,有点小麻烦。Spring Boot直接把通过内嵌web容器,直接运行。 那么Spring Boot是如何内嵌的web容器的呢? 其中以tomcat容易为例。
上面介绍启动器的时候,有介绍spring-boot-starter-web启动器,这是进行web开发的时候需要在pom中引入的模块。进入到这个启动器的内部pom文件,如下描述:
<description>
Starter for building web, including RESTful, applications using
Spring MVC. Uses Tomcat as the default embedded container
</description>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
看了spring-boot-starter-web启动器的内部pom描述,很容易明白。tomcat是通过spring-boot-starter-web启动器引入的,并且在Spring Boot 中默认的Web容器是tomcat,当然也可以换成更轻量级的Jetty,这里不做介绍 。关键在于spring-boot-starter-tomcat 这个tomcat启动器,引入它之后,所有tomcat相关的jar包都引入进来了。在启动Spring Boot服务的时候,就会调用tomcat容器的启动。为了提高点文章的逼格来点源码分析。
在启动Spring Boot的时候,控制台的日志打印其实很好的告知了我们Spring Boot在启动的时候,框架做了哪些事情。选出两条和Tomcat相关的日志:
s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8094 (http)
s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8094 (http)
通过日志发现了一个类 TomcatEmbeddedServletContainer,搜寻到这个类属spring-boot.jar包里面的。通过下图所示的spring-boot-1.5.9.RELEASE.jar包目录结构和包名很容易猜测到Web容器的启动就是这个包来控制的。
核心接口 EmbeddedServletContainer,其中三个容器分别对接口进行了实现,对方法进行了重写。 接口代码如下:
public interface EmbeddedServletContainer {
/**
* Starts the embedded servlet container.
*/
void start() throws EmbeddedServletContainerException;
/**
* Stops the embedded servlet container
*/
void stop() throws EmbeddedServletContainerException;
/**
* Return the port this server is listening on.
*/
int getPort();
}
其实除了内嵌web容器的启动和spring-boot包有关外,整个Spring Boot的服务启动,都是在这个包中实现的,感兴趣的同学可以跟这个包死磕到底,深入下去。
完全去配置文件化
Spring Boot之所以号称降低了Spring的开发门槛其实是有杀手锏的,完全去配置化就是重量级的杀手锏。 早起完成Spring的开发,最烦的就是各种xml配置文件。在Spring Boot的项目工程中确实看不到了那些烦人的xml,它们到哪去了?
回到Spring Boot的项目工程,其中还有两处变化的没有介绍。
- Resource资源文件下的四个属性文件。application.properties 、application-dev.properties、application-prd.properties、application-stg.properties。
- spring-boot-autoconfigure-*.jar(省去了版本号).
属性文件这里就不介绍了,配置相关的属性的地方,根据不同的环境(测试、开发、生产)提供了不同名称的属性文件。
spring-boot-autoconfigure-*.jar 很明显从名字就知道,自动配置相关的jar包。没错,Spring Boot的属性自动配置全部都是由这个包实现的。
打开spring-boot-autoconfigure-*.jar,包名称就告诉了我们一切。在这个包中集成了我们大部分平时开发需要的中间件,比方说: amqp 消息中间件、cache缓存中间件、data数据持久化相关的中间件。基于Spring的框架,如果要集成各种常用中间件, 少不了一个使用了 @Configuration注解的配置类和各种属性配置。但是使用Spring Boot框架,@Configuration注解的配置类,全部不需要了。只要在application.properties 配置属性值就可以直接使用。
这里把集成Redis作为一个例子。查看spring-boot-autoconfigure-*.jar 中data包目录下的redis包可以看到几个类,其中重点在于RedisAutoConfiguration 和 RedisProperties 这两个类。
- RedisProperties类
通过 @ConfigurationProperties(prefix = "spring.redis")注解,把在application.properties 中配置的redis 相关的名称为spring.redis.*的属性全部注入到 RedisProperties类的属性变量中。
- RedisAutoConfiguration类
在这个类中主要定义了几个Bean, 主要是JedisConnectionFactory 和 JedisPoolConfig、StringRedisTemplate等。以前我们基于Spring框架,需要自己定义Bean全部都被Spring Boot框架自己定义了,其他的中间件也是如此。 再结合Spring Boot中使用的是3.0+版本的Servlet包,使用注解完成各种Listener、Filter、Servlet 以及Bean的定义,让无xml化不是梦了。
思考: 如何修改基于框架下的自动配置的属性值?
完全依赖框架的自动配置,其实也是件蛮不安全的事情。有次生产环境上,redis一直抛出一个异常。但是没多久又好了,持续了很多天。从异常可以判断是客户端使用了redis连接池提供的无效链接。通过Jedis的源码可以知道,使用的连接池是apache-common-pool2组件。其中相关的参数有BaseObjectPoolConfig类中的
private boolean testOnBorrow = false;
private boolean testWhileIdle = false;
这两个参数值默认都是false。其中testOnBorrow 代指获取连接池的连接前,检查链接的有效性。 testWhileIdle 是空闲的时候检查链接的有效性。 需要把这两个参数都设置为true。因为默认的自动配置值都是false,但是这两个属性值又不在RedisProperties类中,所以不能通过application.properties文件来修改。那怎么修改呢? 现在项目重写了JedisConnectionFactory 和 JedisPoolConfig、StringRedisTemplate这三个Bean,通过重写Bean来修改属性值。异常没有了,但是总觉得这不是最好的方式,大家如果有更好的方式,欢迎留言,谢谢!
等等: 说好的是工程介绍,怎么全是文字,自己都不想看了。好吧,回到正题,搭建基于Spring Boot的工程项目需要哪些东西?
整个项目的结构
<modules>
<module>pa-market-update</module>
<module>pa-market-delete</module>
<module>pa-market-common</module>
<module>pa-market-add</module>
<module>pa-market-schedule</module>
</modules>
完整的项目一般都是根据业务进行功能划分的。上面是主pom文件的内容,common是公共模块,schedule是定时任务模块(每个项目应该都少不了定时任务吧),其他的各自业务模块,名字随便取的。
单个模块下包目录结构
在前后端分离的项目中,通常要求基于Restful风格编写接口,基于这种风格,项目每个子模块的包目录结构其实是可以固定。
- aspect 包: 基于AOP的拦截器一般都放在这个包。
- consts包 : 常量包
- dto包:表现层和业务层的中间产物
- entity包: 对应表结构的实体类
- service包: 业务层,主要实现业务逻辑的Service类
- utils包: 工具类
- web包: Controller控制层,也是一个项目的入口,通过它可以知道有多少接口
- Application 类: Spring Boot的启动类,注意它放的位置是有讲究的
集成Mybatis框架
DAO层在一个项目中是必不可以少的,在这里采用常用的ORM框架Mybatis。 在pom文件中需要加入的模块。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
mybatis-spring-boot-starter这个启动器不是Spring Boot官方提供的,是Mybatis团队自己集成的,所以名字跟官方的启动器名字不一样。
再加上 @Mapper注解的Mapper类和Mapper.xml文件,就可以了。当然少不了在application.properties中配置数据库的基本信息。使用ORM框架就这么简单。其他第三方框架也是如此,这里就不重复了。
日志框架
日志打印是一个项目必不可少的部分,在这里介绍log4j2的日志框架。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
log4j2不支持属性文件,只支持xml文件或者json文件。这里介绍xml文件的配置。
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="INFO" packages="com.pa.market" >
<appenders>
<!--这个输出控制台的配置 -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss} %-5p - %msg%n" />
<!-- 设置级别 -->
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="NEUTRAL" />
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY" />
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
<RollingFile name="RollingFile" fileName="/data/logs/market/market-inquiry.log"
filePattern="/data/logs/market/market-inquiry.%d{yyyy-MM-dd}.%i.log">
<PatternLayout>
<charset>UTF-8</charset>
<Pattern>%d{HH:mm:ss} %-5p - %msg%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="256 MB" />
</Policies>
</RollingFile>
</appenders>
<loggers>
<!-- 将业务dao接口填写进去,并用控制台输出即可 -->
<logger name="com.pa.market.common.mapper" level="DEBUG" additivity="false">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFile" />
</logger>
<AsyncRoot level="info">
<appender-ref ref="Console" />
<appender-ref ref="RollingFile" />
</AsyncRoot>
</loggers>
</configuration>
如果需要打印Mybatis框架中的日志,可以配置 loggers 标签。每个标签的含义,在官网上可以查询到,这里不做介绍。
打包部署
Spring Boot 框架下如果想打包相应的模块代码,需要在资源文件下添加一个assembly.xml文件。文件内容如下:
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>release</id>
<formats>
<format>tar.gz</format>
</formats>
<fileSets>
<fileSet>
<directory>${project.basedir}\src\main\resources</directory>
<outputDirectory>conf</outputDirectory>
</fileSet>
<fileSet>
<directory>${project.basedir}\src\script</directory>
<outputDirectory>script</outputDirectory>
<fileMode>0755</fileMode>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<useProjectArtifact>true</useProjectArtifact>
<outputDirectory>lib</outputDirectory> <!-- 将scope为runtime的依赖包打包到lib目录下。 -->
<scope>runtime</scope>
</dependencySet>
</dependencySets>
</assembly>
通过这个xml文件,maven执行mvn package命令的时候,会出现一个release包,其中包含conf、lib两个文件夹。其中conf中存放的是模块下Resource目录下存放的资源文件,lib包下是模块所有依赖的jar包。在部署到linux服务器的时候,这两个文件很重要。
maven的很多功能是通过插件来实现的,所以基于assembly.xml的打包功能,需要在pom文件中配置如下插件。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<excludes>
<exclude>*</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions> <!--执行器 mvn assembly:assembly -->
<execution>
<id>make-zip</id><!--名字任意 -->
<phase>package</phase><!-- 绑定到package生命周期阶段上 -->
<goals>
<goal>single</goal><!-- 只运行一次 -->
</goals>
<configuration>
<descriptors> <!--描述文件路径 -->
<descriptor>src/main/resources/assembly.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
总结
通过后面介绍如何搭建工程的大篇幅粘贴,终于把文章的篇幅给填充上去了, 哈哈,这里再多唠叨几句。Spring Boot 框架的出现,让Spring 开发变得异常的简单,对于开发者来说是好事也是坏事。好处,开发门槛低了,可以更多的关注业务代码的书写。坏处,框架封的越简单,框架层面出现问题后越傻眼,对底层的框架原理的理解也越少。所以作为有技术追求的你,懂得应该怎么做吧。小编在使用框架的时候,遇到了不少疑问,大部分也都记录下来了。待把疑问解决后,后续会和大家继续分享,敬请期待! 从Spring 到 Spring Boot 再到 Spring Cloud ,东西太多,大家一起加油!
许多年前 你有一双清澈的双眼
想看遍这世界 去最遥远的远方
感觉有双翅膀 能飞越高山和海洋