SpringBoot入门

SpringBoot入门

第一节 Spring Boot 简介

  Spring Boot设计目的是为了简化Spring应用的初始搭建和开发过程,;

1.1 功能

  • 快速搭建:习惯优于配置,搭建项目过程简单,可以一分钟即完成初始配置。
  • 独立运行:单独Spring Boot项目以jar包方式独立运行。
  • 内嵌Servlet:可以使用内嵌的Tomcat、Jetty或Undertow,项目可以直接启动而无需再通过war包部署在应用服务器中。
  • Starter简化配置:通过Starter轻松集成第三方框架,而不需再一个个的管理第三方依赖。
  • 自动配置:通过starter pom,Spring Boot会自动配置绝大部分需要的Bean。
  • 应用监控:提供基于http、ssh、telnet对运行时项目进行监控。
  • 注解代替XML:取消XML配置的方式,完全用注解代替。

1.2 优点

  • 快速搭建项目
  • 无配置集成
  • 项目可独立运行
  • 提高了开发、部署效率
  • 与微服务和云计算的适配
  • 运行时应用监控
  • 轻松集成Spring系列框架

1.3 启动类

  通过注解@SpringBootApplication表示此类是Spring Boot应用入口,main函数是入口方法。只需要执行main方法便可以启动一个Web服务,相比旧的Spring要简单和安全很多。

1.4 配置文件

  Spring Boot采用一个全局配置文件:application.properties或application.yml,取代繁琐的XML配置文件。

读取配置文件的方式有三种:

  • Environment,通过其getProperty方法读取配置信息。
  • @Value,通过注解获取配置信息。
  • 自定义配置类,使用@ConfigurationProperties注解,参数prefix对应配置的前缀,通过类的getter获取配置信息。

1.5 profiles多环境

  项目开发中会部署到测试和生产环境,往往每个环境对应的数据库等信息都是不同的,通过profile激活不同环境下的配置文件从而灵活应对环境的改变。

1
spring.profiles.active=dev

  分别创建application.properties、application-dev.properties、application-test.properties、application-prod.properties创建不同环境下的配置文件,application.properties是全局配置不区分环境。

1.6 日志

  Spring Boot支持Java Util Logging、Log4J、Log4J2和Logback作为日志框架,默认使用Logback。

1
logging.level.root=info

1.7 热部署

  通过devtools可以实现热部署功能,自动重新加载改动的部分。Idea启动热部署需要开启自动编译功能。

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>

1.8 统一异常处理

  项目中对异常的处理机制的设计一般会统一定义一组错误编码,一种固定的异常消息格式,我们往往需要在开发过程中手动的实现异常的捕获和处理。Spring Boot应用可以@ControllerAdvice注解实现一个全局异常处理类,即使是发生一些系统异常也可以进行自定义处理,当然也刻意统一记录异常日志。

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
@ControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

@ResponseStatus(HttpStatus.NOT_FOUND)
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseData defaultErrorHandler(HttpServletRequest req,Exception e)throws Exception{
logger.error("捕获",e);
ResponseData data;
if(e instanceof NoHandlerFoundException){
data = new ResponseData(HttpStatus.NOT_FOUND.value(),"未找到请求路径");
}else {
data = new ResponseData(HttpStatus.INTERNAL_SERVER_ERROR.value(),"未知错误");
}
return data;
}
}

/**
* 统一返回格式
*/
@Data
public class ResponseData {
private boolean status;
private int code;
private String msg;
private Object data;
private Integer count;

public ResponseData(Integer code, String msg) {
this.code = code;
this.msg = msg;
}

public ResponseData(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}

1.9 异步执行

  通常Java开发使用异步操作要创建一个新线程,虽然Java引入了流和Lambda表达式来对多线程进行简化和优化,但仍显得不是那么优雅。而在Spring Boot中只需要一个注解@Async就可以实现异步代码,然后在Controller中调用就是异步执行的(调用者一定要是外部类),最后在启动类上通过注解@EnableAsync开启即可。

1
2
3
4
@Async
public void save(){
......
}

  也可以通过配置类来对线程池进行管理和自定义

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
44
45
46
47
48
49
50
@Configuration
@ConfigurationProperties(prefix = "spring.task.pool")
@Data
public class TaskThreadPoolConfig {
// 核心线程数
private int corePoolSize = 5;
// 最大线程数
private int maxPoolSize = 50;
// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = 60;
// 队列长度
private int queueCapacity = 10000;
// 线程名称前缀
private String threadNamePrefix = "LAI-AsyncTask-";
}

@Configuration
public class AsyncTaskExecutePool implements AsyncConfigurer {
private Logger logger = LoggerFactory.getLogger(AsyncTaskExecutePool.class);
@Autowired
private TaskThreadPoolConfig config;

@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(config.getCorePoolSize());
executor.setMaxPoolSize(config.getMaxPoolSize());
executor.setQueueCapacity(config.getQueueCapacity());
executor.setKeepAliveSeconds(config.getKeepAliveSeconds());
executor.setThreadNamePrefix(config.getThreadNamePrefix());
//线程池拒绝策略,当线程数量太大,任务会被缓存到本地队列,超过队列大小时就需要拒绝策略
//AbortPolicy-直接抛出异常RejectedExecutionHandler
//CallerRunsPolicy-主线程直接执行此任务,执行完后尝试添加下一任务到线程池,这样可以有效降低向线程场添加任务的速度
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable throwable, Method method, Object... objects) {
//异步任务异常处理
logger.error("============" + throwable.getMessage() + "============",throwable);
logger.error("exception method:" + method.getName());
}
};
}
}

线程池配置有两种拒绝策略:

  • AbortPolicy:直接抛出java.util.RejectExecutionException异常
  • CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,这样可以有效降低向线程池内添加任务的速度

  建议使用CallerRunsPolicy,因为当队列任务满了以后,如果直接抛出异常,这个任务就会被丢弃,而CallerRunsPolicy策略则会用主线程去执行,也就是同步执行。

1.10 随机端口

  开发项目时端口号往往是确定的,但如果一个服务想要启动多个实例,特别是在Spring Cloud中,服务要注册到注册中心,为了使服务能够随时扩容,在启动时能生成一个随机端口是很重要的。

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
44
45
46
47
48
49
50
51
public class StartCommand {
private Logger logger = LoggerFactory.getLogger(StartCommand.class);

public StartCommand(String[] args){
Boolean isServerPort = false;
String serverPort = "";
//判断是否指定端口
if(args != null){
for(String arg : args){
if(StringUtils.hasText(arg) && arg.startsWith("--server.port")){
isServerPort = true;
serverPort = arg;
break;
}
}
}
//没有指定端口,则随机生成一个可用端口
if(!isServerPort){
int port = ServerPortUtils.getAvailablePort();
logger.info("current server.port=" + port);
System.setProperty("server.port",String.valueOf(port));
}else {
logger.info("current server.port=" + serverPort.split("=")[1]);
System.setProperty("server.port",serverPort.split("=")[1]);
}
}
}

public class ServerPortUtils {
public static int getAvailablePort(){
int max = 65535;
int min = 200;
Random random = new Random();
int port = random.nextInt(max)%(max-min+1) + min;
boolean using = NetUtils.isLoclePortUsing(port);
if(using){
return getAvailablePort();
}else {
return port;
}
}
}

@SpringBootApplication
public class IboxApplication {
public static void main(String[] args) {
//启动参数设置
new StartCommand(args);
SpringApplication.run(IboxApplication.class, args);
}
}

1.11 编译打包

  传统Web项目需要编译出一个war包然后部署到Tomcat等应用服务器去执行,而Spring Boot因为内嵌服务器的原因,只需要打包成jar,就可以直接通过执行java -jar命名来启动应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<build>
<plugins>
<!-- 打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
<mainClass>top.linyishui.ibox.IboxApplication</mainClass>
</configuration>
</plugin>

<!-- 编译插件,指定JDK版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>

第二节 Spring Boot Starter

  用来简化jar包依赖,执行引入一个Starter,然后在属性文件配置一些值,整个集成过程就完成了。所以通过引入各种Spring Boot Starter包可以款速的搭建出一个项目的脚手架。

常用的Spring Boot Starter如:

  • spring-boot-starter-web:快速搭建基于Spring MVC的Web项目,使用Tomcat作为默认嵌入式容器。
  • spring-boot-starter-redis:操作Redis。
  • spring-boot-starter-mongodb:操作Mongodb。
  • spring-boot-starter-jpa:操作Mysql。
  • spring-boot-starter-activemq:操作Activemq。
  • ……

2.1 自定义Starter

  尝试自定义一个Starter,从而了解Starter的工作流程。

  首先创建一个项目,并引入starter-web和lombok依赖。

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

  创建一个配置类,用来在属性文件中配置值。

1
2
3
4
5
@Data
@ConfigurationProperties("spring.user")
public class UserProperties {
private String name;
}

  再定义一个客户端类,定义一个用于获取配置中值的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class UserClient {
private UserProperties userProperties;

public UserClient() {

}

public UserClient(UserProperties p) {
this.userProperties = p;
}

public String getName() {
return userProperties.getName();
}
}

  最基础的Starter包定义好了,但还不能自动构建UserClient的实例,所以我们要定义一个自动创建UserClient实例的类。

1
2
3
4
5
6
7
8
9
@Configuration
@EnableConfigurationProperties(UserProperties.class)
public class UserAutoConfigure {
@Bean
@ConditionalProperty(prefix = "spring.user", value = "enabled", havingValue = "true")
public UserClient userClient(UserProperties userProperties) {
return new UserClient(userProperties);
}
}

  Spring Boot会默认扫描和启动类平级的包,如果我们的Starter和启动类不在同一个包下,如何使UserAutoConfigure生效? 在resources下创建一个META-INF文件夹,然后创建一个spring.factories文件,文件内指定自动配置的类,Spring Boot启动时会读取此文件,根据配置激活对应的配置类。

1
2
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.xxx.UserAutoConfigure

  接着就可以尝试在其他项目中引用这个Starter包,成功引入后就可以直接使用UserClient。

1
2
spring.user.name=xxx
spring.user.enable=true

2.2 使用注解开启Starter自动构建

  有时我们希望由用户指定是否使用自动配置,而不是默认引入包后就要初始化,这时会如@EnableAsync这样的注解来开启调用方法异步执行的功能。

1
2
3
4
5
6
7
8
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({UserAutoConfigure.class})
public interface EnableUserClient{

}

  这个组合注解的核心是@Import({UserAutoConfigure.class}),通过导入的方式实现把UserAutoConfigure实例加入SpringIOC容器中,来开启自动配置。然后就可以在启动类中添加此注解来开启功能了。

2.3 使用配置开启Starter自动构建

  有些场景下,UserAutoConfigure需要配置多个对象,这些对象我们不希望全部配置,而是在用户指定需要开启配置时再去构建对象,可以通过@ConditionalOnProperty

1
2
3
4
5
@Bean
@ConditionalOnProperty(prefix = "spring.user", value = "enabled", havingValue = "true")
public UserClient userClient(UserProperties userProperties) {
return new UserClient(userProperties);
}

  这样只有启动类添加@EnableUserClient注解,并在配置文件中添加spring.user.enable=true后才会自动配置UserClient。

2.4 配置Starter内容显示

  在META-INF文件夹中创建spring-configuration-metadata.json文件

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"properties": [
{
"name": "spring.user.name",
"defaultValue": "cxytinadi"
},
{
"name": "spring.user.enable",
"type": "java.lang.Boolean",
"defaultValue": false
}
]
}

第三节 Spring Boot Actuator

  Spring Boot提供了actuator模块用来监控和管理自身应用信息

1
2
3
4
5
# actuator监控配置
# 显示一些详细信息
management.endpoint.health.show-details=always
# 暴漏一些隐藏端点,*表示所有
management.endpoints.web.exposure.include=health,configprops,beans,httptrace

3.1 自定义端点

  有些时候我们需要自定义一些规则来判断应用是否健康,如果我们只是需要对应用的健康状态增加一些其他维度的数据,可以通过继承AbstractHealthIndicator来实现。

1
2
3
4
5
6
7
8
9
@Component
public class UserHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Health.Builder builder) {
//withDetail用来添加信息
builder.up().withDetail("status",true);
//builder.down().withDetail("status",false);
}
}

  up方法指定应用状态为健康,down方法指定应用状态为不健康。访问/actuator/health可以得到自定义的健康状态的信息。

  有时我们需要开发一个新的端点,比如查看当前登陆用户信息,通过@Endpoint注解来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
@Endpoint(id = "user")
public class UserEndpoint {
@ReadOperation
public List<Map<String,Object>> health(){
List<Map<String,Object>> list = new ArrayList<>();
Map<String,Object> map = new HashMap<>();
map.put("userId",10001);
map.put("userName","linyishui");
list.add(map);
return list;
}
}

  访问/actuator/user来查看用户信息。


参考博客和文章书籍等:

《Spring Cloud微服务-入门、实战和进阶》

《Spring Boot实战》

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容