(6)java Spring Cloud+Spring boot+mybatis企业快速开发架构之SpringCloud-Spring Boot项目详细搭建步骤

2021年09月16日 阅读数:9
这篇文章主要向大家介绍(6)java Spring Cloud+Spring boot+mybatis企业快速开发架构之SpringCloud-Spring Boot项目详细搭建步骤,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

 在 Spring Tools 4 for Eclipse 中依次选择 File->New->Maven Project,而后在出现的界面中按图所示增长相关信息。
在这里插入图片描述
html

<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>

 

 编写启动类,代码以下所示。java

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

 

启动类使用了 @SpringBootApplication 注解,这个注解表示该类是一个 Spring Boot 应用。直接运行 App 类便可启动,启动成功后在控制台输出信息,默认端口是 8080,如图所示。推荐分布式架构源码web

 

能够看到,咱们只在 pom.xml 中引入了一个 Web 的 Starter,而后建立一个普通的 Java 类,一个 Main 方法就能够启动一个 Web 项目。spring

与以前的使用方式相比,这种方式简单不少。之前须要配置各类 Spring 相关的包,还须要配置 web.xml 文件,还须要将项目放入 Tomcat 中去执行,搭建项目的过程还特别容易出错,会出现各类 jar 包冲突。有了 Spring Boot 后这些问题都解决了。数据库

咱们之因此可以经过一个 Main 方法启动一个 Web 服务,是由于 Sprig Boot 中内嵌了 Tomcat,而后经过内嵌的 Tomcat 来提供服务。固然,咱们也可使用别的容器来替换 Tomcat,好比 Undertow 或 Jetty。apache

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”。mvc

读取配置文件
在之前的项目中咱们主要在 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 便可启动应用。

推荐布式架构源码