在 Spring Tools 4 for Eclipse 中顺次抉择 File->New->Maven Project,而后在呈现的界面中按图所示减少相干信息。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
编写启动类,代码如下所示。
@SpringBootApplication
public class App {public static void main(String[] args) {SpringApplication.run(App.class, args);
}
}
启动类应用了 @SpringBootApplication 注解,这个注解示意该类是一个 Spring Boot 利用。间接运行 App 类即可启动,启动胜利后在控制台输入信息,默认端口是 8080,如图所示。举荐分布式架构源码
能够看到,咱们只在 pom.xml 中引入了一个 Web 的 Starter,而后创立一个一般的 Java 类,一个 Main 办法就能够启动一个 Web 我的项目。
与之前的应用形式相比,这种形式简略很多。以前须要配置各种 Spring 相干的包,还须要配置 web.xml 文件,还须要将我的项目放入 Tomcat 中去执行,搭建我的项目的过程还特地容易出错,会呈现各种 jar 包抵触。有了 Spring Boot 后这些问题都解决了。
咱们之所以可能通过一个 Main 办法启动一个 Web 服务,是因为 Sprig Boot 中内嵌了 Tomcat,而后通过内嵌的 Tomcat 来提供服务。当然,咱们也能够应用别的容器来替换 Tomcat,比方 Undertow 或 Jetty。
Spring Tools 4 for Eclipse 还为咱们提供了更加便捷的我的项目创立形式,在 File->New 选项中有 Spring Starter Project,能够间接抉择 Spring Boot 的版本以及须要依赖的第三方包,间接生成 Spring Boot 我的项目,不必再去手动配置 Maven 依赖。
这个性能和 https://start.spring.io/ 提供的是同一个性能,不便疾速搭建 Spring Boot 我的项目脚手架。举荐分布式架构源码
编写第一个 REST 接口
本节将创立一个控制器,编写第一个 REST 接口,拜访地址应用 /hello,代码如下所示。
@RestController
public class HelloController {@GetMapping("/hello")
public String hello() {return "hello";}
}
@RestController 是 @Controller 和 @ResponseBody 的组合注解,能够间接返回 Json 格局数据。
@GetMapping 其实就是 @RequestMapping(method=RequestMethod.GET),通过拜访 http://localhost:8080/hello 能够看到输入的后果“hello”。
读取配置文件
在以前的我的项目中咱们次要在 XML 文件中进行框架配置,业务的相干配置会放在属性文件中,而后通过一个属性读取的工具类来读取配置信息。
在 Spring Boot 中咱们不再须要应用这种形式去读取数据了。Spring Boot 中的配置通常放在 application.properties 中,读取配置信息十分不便,总共分为 3 种形式。
1)Environment
能够通过 Environment 的 getProperty 办法来获取想要的配置信息,代码如下所示。
@RestController
public class HelloController {
// 注入对象
@Autowired
private Environment env;
@GetMapping("/hello")
public String hello() {
// 读取配置
String port = env.getProperty("server.port");
return port;
}
}
2)@Value
能够注入具体的配置信息,代码如下所示。
@RestController
public class HelloController {
// 注入配置
@Value("${server.port}")
private String port;
@GetMapping("/hello")
public String hello() {return port;}
}
3)自定义配置类
prefix 定义配置的前缀,代码如下所示。
@ConfigurationProperties(prefix = "net.biancheng")
@Component
public class MyConfig {
private String name;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
读取配置的办法代码如下所示。
@RestController
public class HelloController {
@Autowired
private MyConfig myConfig;
@GetMapping("/hello")
public String hello() {return myConfig.getName();
}
}
定义配置 application.properties 的办法如下:
net.biancheng.name=zhangsan
profiles 多环境配置
在平时的开发中,我的项目会被部署到测试环境、生产环境,然而每个环境的数据库地址等配置信息都是不一样的。通过 profile 来激活不同环境下的配置文件就能解决配置信息不一样的问题。在 Spring Boot 中能够通过 spring.profiles.active=dev 来激活不同环境下的配置。
能够定义多个配置文件,每个配置文件对应一个环境,格局为 application- 环境.properties,如表 1 所示。
表 1 profile 多环境配置
application.properties 通用配置,不辨别环境
application-dev.properties 开发环境
application-test.properties 测试环境
application-prod.properties 生产环境
在开发环境中,能够通过批改 application.properties 中的 spring.profiles.active 的值来激活对应环境的配置,在部署的时候能够通过 java–jar xxx.jar–spring.profiles.active=dev 来指定应用对应的配置。
热部署
开发过程中常常会改变代码,此时若想看下成果,就不得不停掉我的项目而后重启。
对于 Spring Boot 我的项目来说,启动工夫是十分快的,在微服务的架构下,每个服务只关注本人的业务,代码量也十分小,这个启动工夫是能够容忍的。
对于那些臃肿的单体老我的项目,启动工夫几乎是节约生命。尽管 Spring Boot 启动很快,然而咱们还是要本人去重启。能不能做到有改变,它就会悄无声息地本人把改变的中央从新加载一遍?答案是必定的,通过 spring-boot-devtools 就能够实现。
只须要增加 spring-boot-devtools 的依赖即可实现热部署性能,代码如下所示。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
actuator 监控
Spring Boot 提供了一个用于监控和治理本身利用信息的模块,它就是 spring-boot-starter-actuator。该模块应用起来非常简单,只须要退出依赖即可,代码如下所示。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
启动我的项目咱们会发现在控制台输入的内容中减少了图 4 所示的信息。
下图所示的这些信息是 Actuator 模块提供的端点信息,具体如表 2 所示,通过拜访这些端点咱们能够失去很多监控信息。
比方,咱们拜访 /actuator/health 能够失去上面的信息:
{"status": "UP"}
表 2 Actuator 端点信息
UP 示意以后利用处于衰弱状态,如果是 DOWN 就示意以后利用不衰弱。减少上面的配置能够让一些衰弱信息的详情也显示进去:
management.endpoint.health.show-details=ALWAYS
再次拜访 /actuator/health,就能够失去衰弱状态的详细信息:
{
"status": "UP",
"diskSpace": {
"status": "UP",
"total": 491270434816,
"free": 383870214144,
"threshold": 10485760
}
}
大部分端点默认都不裸露进去,咱们能够手动配置须要裸露的端点。如果须要裸露多个端点,能够用逗号分隔,如下所示:
management.endpoints.web.exposure.include=configprops,beans
如果想全副端点都裸露的话间接配置成上面的形式:
management.endpoints.web.exposure.include=*
对于这些监控的信息不再赘述,大家能够自行理解。前面咱们会介绍如何应用 Spring Boot Admin 在页面上更加直观地展现这些信息,目前都是 Json 格局的数据,不不便查看。
自定义 actuator 端点
在很多场景下,咱们须要自定义一些规定来判断利用的状态是否衰弱,能够采纳自定义端点的形式来满足多样性的需要。如果咱们只是须要对利用的衰弱状态减少一些其余维度的数据,能够通过继承 AbstractHealthIndicator 来实现本人的业务逻辑。代码如下所示。
@Component
public class UserHealthIndicator extends AbstractHealthIndicator {
@Override
protected void doHealthCheck(Builder builder) throws Exception {builder.up().withDetail("status", true);
// builder.down().withDetail("status", false);
}
}
通过 up 办法指定利用的状态为衰弱,down 办法指定利用的状态为不衰弱。withDetail 办法用于增加一些详细信息。拜访 /actuator/health,能够失去咱们自定义的衰弱状态的详细信息:
{
"status": "UP",
"details": {
"user": {
"status": "UP",
"details": {"status": true}
},
"diskSpace": {
"status": "UP",
"details": {
"total":
249795969024,
"free": 7575375872,
"threshold": 10485760
}
}
}
}
下面咱们是在框架自带的 health 端点中进行扩大,还有一种需要是齐全开发一个全新的端点,比方查看以后登录的用户信息的端点。自定义全新的端点很简略,通过 @Endpoint 注解就能够实现。代码如下所示。
@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", 1001);
map.put("userName", "zhangsan");
list.add(map);
return list;
}
}
拜访 /actuator/user 能够看到返回的用户信息如下:
[
{
"userName": "zhangsan",
"userId": 1001
}
]
对立异样解决
对于接口的定义,咱们通常会有一个固定的格局,比方:
{
"status": true,
"code": 200,
"message": null,
"data": [
{
"id": "101",
"name": "jack"
},
{
"id": "102",
"name": "jason"
}
]
}
然而,如果调用方在申请咱们的 API 时把接口地址写错了,就会失去一个 404 谬误:
{
"timestamp": 1492063521109,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/rest11/auth"
}
后端服务会通知咱们哪个地址没找到,其实也挺敌对。然而因为咱们下面自定义的数据格式跟上面的不统一,所以当用户拿到这个返回的时候是无奈辨认的,其中最显著的是 status 字段。
咱们自定义的是 boolean 类型,用来示意申请是否胜利,这里返回的就是 Http 的状态码,所以咱们须要在产生这种零碎谬误时也能返回咱们自定义的那种格局,那就要定义一个异样解决类(代码如下所示),通过这个类既能够返回对立的格局,也能够对立记录异样日志。
@ControllerAdvice
public class GlobalExceptionHandler {private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseData defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {logger.error("", e);
ResponseData r = new ResponseData();
r.setMessage(e.getMessage());
if (e instanceof org.springframework.web.servlet.NoHandlerFoundException) {r.setCode(404);
} else {r.setCode(500);
}
r.setData(null);
r.setStatus(false);
return r;
}
}
ResponseData 是咱们返回格局的实体类,其产生谬误时也会被捕捉到,而后封装好返回格局并返回给调用方。最初要害的一步是,在 Spring Boot 的配置文件中加上如下代码所示配置。
# 呈现谬误时, 间接抛出异样
spring.mvc.throw-exception-if-no-handler-found=true
# 不要为咱们工程中的资源文件建设映射
spring.resources.add-mappings=false
而后当咱们调用一个不存在的接口时,返回的错误信息就是咱们自定义的那种格局了:
{
"status": false, "code": 404,
"message": "No handler found for GET /rest11/auth", "data": null
}
最初贴上 ResponseData 的定义,代码如下所示。
public class ResponseData {
private Boolean status = true;
private int code = 200;
private String message;
private Object data;
// get set ...
}
异步执行
异步调用就是不必期待后果的返回就执行前面的逻辑;同步调用则须要期待后果再执行前面的逻辑。
通常咱们应用异步操作时都会创立一个线程执行一段逻辑,而后把这个线程丢到线程池中去执行,代码如下所示。
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(() -> {
try {// 业务逻辑} catch (Exception e) {e.printStackTrace();
} finally {}});
这种形式只管应用了 Java 的 Lambda,但看起来没那么优雅。在 Spring 中有一种更简略的形式来执行异步操作,只须要一个 @Async 注解即可,代码如下所示。
@Async
public void saveLog() {System.err.println(Thread.currentThread().getName());
}
咱们能够间接在 Controller 中调用这个业务办法,它就是异步执行的,会在默认的线程池中去执行。须要留神的是,肯定要在内部的类中去调用这个办法,如果在本类调用则不起作用,比方 this.saveLog()。最初在启动类上开启异步工作的执行,增加 @EnableAsync 即可。
另外,对于执行异步工作的线程池咱们也能够自定义,首先咱们定义一个线程池的配置类,用来配置一些参数,具体代码如下所示。
@Configuration
@ConfigurationProperties(prefix = "spring.task.pool")
public class TaskThreadPoolConfig {
// 外围线程数
private int corePoolSize = 5;
// 最大线程数
private int maxPoolSize = 50;
// 线程池保护线程所容许的闲暇工夫
private int keepAliveSeconds = 60;
// 队列长度
private int queueCapacity = 10000;
// 线程名称前缀
private String threadNamePrefix = "FSH-AsyncTask-";
// get set ...
}
而后咱们从新定义线程池的配置,代码如下所示。
@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());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initia lize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
// 异步工作中异样解决
return new AsyncUncaughtExceptionHandler() {
@Override
public void handleUncaughtException(Throwable arg0, Method arg1, Object... arg2) {logger.error("==========================" + arg0.getMessage() + "=======================", arg0);
logger.error("exception method:" + arg1.getName());
}
};
}
}
配置完之后咱们的异步工作执行的线程池就是咱们自定义的了,咱们能够在属性文件外面配置线程池的大小等信息,也能够应用默认的配置:
spring.task.pool.maxPoolSize=100
最初讲一下线程池配置的回绝策略。当咱们的线程数量高于线程池的处理速度时,工作会被缓存到本地的队列中。队列也是有大小的,如果超过了这个大小,就须要有回绝的策略,不然就会呈现内存溢出。目前反对两种回绝策略:
AbortPolicy:间接抛出 java.util.concurrent.RejectedExecutionException 异样。
CallerRunsPolicy:主线程间接执行该工作,执行完之后尝试增加下一个工作到线程池中,这样能够无效升高向线程池内增加工作的速度。
倡议大家用 CallerRunsPolicy 策略,因为当队列中的工作满了之后,如果间接抛异样,那么这个工作就会被抛弃。如果是 CallerRunsPolicy 策略,则会用主线程去执行,也就是同步执行,这样操作最起码工作不会被抛弃。
随机端口
在理论的开发过程中,每个我的项目的端口都是定好的,通过 server.port 能够指定端口。
当一个服务想要启动多个实例时,就须要扭转端口,特地是在咱们前面进行 Spring Cloud 学习的时候,服务都会注册到注册中心里去,为了可能让服务随时都能够扩容,在服务启动的时候能随机生成一个能够应用的端口是最好不过的。
在 Spring Boot 中,能够通过 ${random} 来生成随机数字,咱们能够这样应用:
server.port=${random.int[2000,8000]}
通过 random.int 办法,指定随机数的拜访,生成一个在 2000 到 8000 之间的数字,这样每次启动的端口就都不一样了。
其实下面的办法尽管可能达到预期的成果,然而也会存在一些问题:如果这个端口曾经在应用了,那么启动必然会报错。所以咱们能够通过代码的形式来随机生成一个端口,而后检测是否被应用,这样就能生成一个没有被应用的端口。
编写一个启动参数设置类,代码如下所示。
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]);
}
}
}
通过对启动参数进行遍历判断,如果有指定启动端口,后续就不主动生成了;如果没有指定,就通过 ServerPortUtils 获取一个能够应用的端口,而后设置到环境变量中。在 application.properties 中通过上面的形式获取端口:
server.port=${server.port}
对于获取可用端口的代码如下所示。
public static int getAvailablePort() {
int max = 65535;
int min = 2000;
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;}
}
获取可用端口的次要逻辑是指定一个范畴,而后生成随机数字,最初通过 NetUtils 来查看端口是否可用。如果获取到可用的端口则间接返回,没有获取到可用的端口则执行回调逻辑,从新获取。检测端口是否可用次要是用 Socket 来判断这个端口是否能够被链接。
最初在启动类中调用端口即可应用,代码如下所示。
public class FshHouseServiceApplication {public static void main(String[] args) {
// 启动参数设置, 比方主动生成端口
new StartCommand(args);
SpringApplication.run(FshHouseServiceApplication.class, args);
}
}
编译打包
传统的 Web 我的项目在部署的时候,是编译出一个 war 包放到 Tomcat 的 webapps 目录下。而在 Spring Boot 构建的 Web 我的项目中则突破了这一传统部署的形式,它采纳更加简略的内置容器形式来部署应用程序,只须要将利用编译打包成一个 jar 包,间接能够通过 java–jar 命令启动利用。
在我的项目的 pom.xml 中减少打包的 Maven 插件,代码如下所示。
<build>
<plugins>
<!-- 打包插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
<mainClass>net.biancheng.spring_boot_example.App</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>
mainClass 配置的是咱们的启动入口类,配置实现后能够通过 Maven 的 mvn clean package 命令进行编译打包操作。编译实现后在 target 目录下会生成对应的 jar 包,部署的时候间接调用 java–jar xx.jar 即可启动利用。
举荐分布式架构源码