做一个Spring Boot小例子

2022-05-05 20:07:25 浏览数 (1)

在我的《Spring Boot 框架介绍和使用》里介绍了Spring Boot,但是没有例子。所以这一篇的主要内容就是来做一个小例子。结合我上面那篇一起看效果更佳。

运行项目

创建项目和上篇文章一样,我用了2.0的快照版本的Spring Boot,因为现版本1.5的Thymeleaf还是2.1的版本,比较旧。在Idea中运行Spring Boot项目不如Spring Tool Suite简单,因为在STS中直接保存文件即可触发devtools的重启,而在IDEA中只能手动点击build project命令。

在这里还遇到一点小情况。我原来不明白IDEA中有一个delegate to gradle有什么作用,就胡乱选上了。现在才发现,原来选中这个选项之后,在点击构建项目的时候不会调用IDEA自己的构建工具,而是使用gradle的构建。所以速度会更慢。如果使用IDEA的构建命令,速度会更快一些。

当然这样感觉还是稍微比STS慢一点。所以我又找到了另外一种方法,就是利用gradle的持续构建选项。首先打开一个终端,输入gradle assemble --continuous,这样gradle就会一直编译项目。如果检测到文件发生更改,就会自动重复编译项目,直到我们手动关闭了终端。然后在用gradle bootRun命令正常调试运行项目,当文件发生更改的时候gradle会自动编译项目,从而触发devtools自动重启服务器。

Spring Boot项目在运行的时候支持LiveReload,我们只要在浏览器上安装相应的插件并启用,服务器自动重启之后便会自动刷新浏览器。我们不必每次更改之后手动刷新了。

MVC

多个视图解析器

在Spring Boot中,错误页面可以放在下面的文件夹下。在使用Thymeleaf的时候,情况就变的稍微有点复杂了。

代码语言:javascript复制
src/
  - main/
      - java/
     |     <source code>
      - resources/
          - templates/
              - error/
             |    - 404.html
              - <other templates>

我们希望将重复的模板代码抽出来组合成单独的文件,让其他页面引用。这样以后修改的时候只需要修改一处就可以更改所有页面的效果。但是Thymeleaf默认的代码块导入只能支持同级页面,像下面这样错误页面在单独一个文件夹、公用页面也在单独一个文件夹下的情况,默认的配置不能满足我们的需要。这时候就需要覆盖Spring Boot的自动配置了。

经过一番查阅,我找到了解决办法。这种情况下需要配置的多个视图解析器。在Spring Boot中很简单,我们只需要定义自己的视图解析器,Spring就会自动屏蔽默认配置的。

配置代码如下。我们为代码段单独配置一个视图解析器。然后将这些视图解析器都添加到视图引擎中。这些必须都配置为Spring Bean。如果直接在templateEngine()中new视图解析器并添加,就会抛出ApplicationContext为空的异常。

最后要注意setCheckExistence方法也必须设置。不然的话视图解析器就会认为视图总是存在,所以渲染页面的时候会出现找不到视图文件的情况。所以设置了这个选项,解析器就会先检查文件是否存在,不存在的话就直接返回。这样另一个视图解析器就会寻找视图,最后我们两个文件夹下的视图就可以都找到了。

代码语言:javascript复制
@Configuration
public class TemplateConfig {
    private boolean cacheable = false;
    private String templatesPrefix = "classpath:templates/";
    private String fragmentsPrefix = "classpath:templates/fragments/";
    private String suffix = ".html";
    private TemplateMode templateMode = TemplateMode.HTML;
    private String encoding = "UTF-8";
    private boolean checkExistence = true;

    @Bean
    public SpringResourceTemplateResolver templateResolver() {
        SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
        resolver.setPrefix(templatesPrefix);
        resolver.setSuffix(suffix);
        resolver.setCharacterEncoding(encoding);
        resolver.setCacheable(cacheable);
        resolver.setCheckExistence(checkExistence);
        return resolver;
    }

    @Bean
    public SpringResourceTemplateResolver fragmentResolver() {
        SpringResourceTemplateResolver fragmentResolver = new SpringResourceTemplateResolver();
        fragmentResolver.setPrefix(fragmentsPrefix);
        fragmentResolver.setSuffix(suffix);
        fragmentResolver.setCacheable(cacheable);
        fragmentResolver.setTemplateMode(templateMode);
        fragmentResolver.setCharacterEncoding(encoding);
        fragmentResolver.setCheckExistence(checkExistence);
        return fragmentResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.addTemplateResolver(templateResolver());
        templateEngine.addTemplateResolver(fragmentResolver());
        templateEngine.setEnableSpringELCompiler(true);
        templateEngine.addDialect(new Java8TimeDialect());
        return templateEngine;
    }
}

MVC配置

spring自动配置的MVC基本够最基本的使用了,但是在做项目的话肯定要添加自己的配置。我们用Java配置的话也很简单。下面的例子很简单,添加了几个视图控制器,直接将请求和视图连在一起;还定义了两个格式化器。不知道为何Spring没有对这些新日期类的支持,所以我们只能自己写格式化器了。

如果我们只需要向下面添加几个自己的格式化器之类的,向下面这样继承WebMvcConfigurerAdapter即可。如果想完全控制Mvc的设置,可以添加@EnableMvc,这样Spring Boot的自动配置就会完全取消。

在此叨叨两句,如果是旧项目的话就算了。如果使用新项目的话我们在处理日期和时间的时候务必使用Java 8提供的新类,LocalDate、LocalDateTime这些,这些新类符合新标准,提供的新方法也更好用。

代码语言:javascript复制
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/system-beans").setViewName("beans");
        registry.addViewController("/addUser").setViewName("addUser");
        registry.addViewController("/mvc").setViewName("mvc");
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new LocalDateFormatter());
        registry.addFormatter(new LocalDateTimeFormatter());
    }
}

class LocalDateFormatter implements Formatter<LocalDate> {

    @Override
    public LocalDate parse(String text, Locale locale) throws ParseException {
        return LocalDate.parse(text);
    }

    @Override
    public String print(LocalDate object, Locale locale) {
        return object.toString();
    }
}

class LocalDateTimeFormatter implements Formatter<LocalDateTime> {

    @Override
    public LocalDateTime parse(String text, Locale locale) throws ParseException {
        return LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }

    @Override
    public String print(LocalDateTime object, Locale locale) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return object.format(formatter);

    }
}

Spring Data

多数据源

在开发的时候我们一般有测试数据库和生产数据库,在测试的时候连接到测试数据库,部署的时候改为生产数据库。所以我们这里首先来配置一下多数据源。

application-test.properties中。其实这里什么也不写也可以,Spring 检测到H2 、HSQLDB或Derby的话就会自动创建一个内存嵌入式数据源。

代码语言:javascript复制
# 数据库设置
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:test
spring.datasource.username=sa

application-product.properties中,生产数据库为MySQL。

代码语言:javascript复制
# 数据库设置
spring.datasource.username=root
spring.datasource.password=12345678
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

然后在application.properties中,激活配置即可。以后开发完成之后,改为product即可。

代码语言:javascript复制
spring.profiles.active=test

使用Hikari连接池

Spring Boot会按照tomcat、HikariCP、DBCP2的顺序查找和使用连接池。不过我看了一下好像HikariCP的性能最好,所以这里我们直接使用HikariCP。

首先需要添加HikariCP的依赖。Spring Boot也包含了对HikariCP的版本号管理,不过它的版本比较低一点,所以我就干脆直接指定了最新的。

代码语言:javascript复制
compile group: 'com.zaxxer', name: 'HikariCP', version: '2.6.1'

然后添加spring.datasource.type=com.zaxxer.hikari.HikariDataSource即可。

H2 web控制台

如果嵌入式数据库选择了H2,而且项目中添加了spring-boot-devtools。那么Spring还会启用H2的web控制台功能。

如果不需要这个功能可以直接关闭。

代码语言:javascript复制
spring.h2.console.enabled=false

启用审计

最后我希望使用Spring Data的审计功能来帮我设置用户的注册时间。但是审计不是Spring Boot自动配置的内容。所以我们需要手动开启。

代码语言:javascript复制
@Configuration
@EnableJpaAuditing
public class JpaConfig {
}

然后在实体类上添加EntityListeners注解。

代码语言:javascript复制
@Entity
@EntityListeners(AuditingEntityListener.class)
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    @Column(nullable = false, unique = true)
    private String username;
    @Column(nullable = false)
    private String password;
    @Column
    private String nickname;
    @Column
    private LocalDate birthday;
    @Column
    @CreatedDate
    private LocalDateTime registerTime;
}

这样,当我们处理数据的时候,注册时间就会由Spring自动填充了。

代码语言:javascript复制
@Transactional
public interface UserRepository extends CrudRepository<User, Integer> {
    Optional<User> findByUsername(String username);
}

Actuator

在项目中添加Actuator的依赖项即可。这样Spring就会自动添加相关的路径映射。

代码语言:javascript复制
compile("org.springframework.boot:spring-boot-starter-actuator")

为了保证安全,这些都需要验证才能访问。我们可以简单的关闭它,management.security.enabled=false。不过为了安全起见,实际开发中应该设置密码来保护这些敏感信息。

Beans可视化

本来我想将这些端点全做成可视化的,不过看了一些,大部分端点返回来的JSON都比较复杂,是个多层结构,所以最后只做了一个Beans的可视化。推荐一个Chrome插件,json-formatter。安装好之后,在查看这些端点的JSON,就不是糊成一团的了,而是格式化并且语法高亮的形式了。

Beans端点返回的JSON稍微有些奇怪,它是个类似下面这样的对象,也就是个数组,所以获取到数据之后必须使用data[0]这样的语法才能获取里层的对象。

代码语言:javascript复制
[
  {
    context: "xxx",
    parent: "xxx",
    beans: [
        ...
...

然后页面就可以写成下面这样的。"[[@{/beans}]]"是Thymeleaf的语法,Thymeleaf引擎遇到它会转换为实际的URL。然后jquery获取到对象之后,使用了Knockout将数据绑定到页面上。详细使用方法请参考jQuery和Knockout的官方文档。

代码语言:javascript复制
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="_header::header('系统监控')">
</head>
<body>
<nav th:replace="_navbar::navbar"></nav>
<div class="container">
    <div class="row">
        <div class="col-md-12">
            <h2>Beans</h2>
            <h4><a th:href="@{/beans}">点击查看原格式</a></h4>
            <p><strong>context:</strong><span data-bind="text: context"></span></p>
            <p><strong>parent:</strong><span data-bind="text: parent"></span></p>
            <p><strong>数量:</strong>共有<span data-bind="text: beans.length"></span>个Beans</p>
            <table class="table table-bordered table-hover table-responsive table-striped">
                <thead>
                <tr>
                    <th>Beans</th>
                    <th>resource</th>
                    <th>scope</th>
                    <th>type</th>
                </tr>
                </thead>
                <tbody data-bind="foreach: beans">
                <tr>
                    <td data-bind="text: bean"></td>
                    <td data-bind="text: resource"></td>
                    <td data-bind="text: scope"></td>
                    <td data-bind="text: type"></td>
                </tr>
                </tbody>
            </table>
        </div>

    </div>
</div>
<div th:replace="_footer::footer"></div>
<script>
    $(document).ready(function () {
        $.getJSON("[[@{/beans}]]", function (data) {
            ko.applyBindings(data[0])
        })
    })
</script>
</body>
</html>

Hypermedia方式查看数据

如果我们添加 Spring HATEOAS的依赖,也就是下面这个。

代码语言:javascript复制
compile 'org.springframework.hateoas:spring-hateoas'

然后启用Hypermedia功能。

代码语言:javascript复制
endpoints.hypermedia.enabled=true

Spring就会在默认/actuator路径下生成一个发现页面,返回所有可用的端点和相应的URL。这个发现页面的URL可以使用endpoints.actuator.path设置,这个发现功能可以使用endpoints.actuator.enabled打开或关闭。

如果还存在HAL Browser的jar包,也就是添加下面的依赖,那么Spring还会开启一个可视化页面覆盖/actuator的JSON形式。可视化页面其实也没啥功能,会把所有的端点和输出以格式化之后的JSON形式输出,如果没安装JSON美化浏览器插件的话这个功能还是挺有用的。

代码语言:javascript复制
 compile group: 'org.springframework.data', name: 'spring-data-rest-hal-browser', version: '2.6.1.RELEASE'

主页

用Markdown格式化

想了想也没啥写的了。最后就来说说Markdown把。我用的是marked。然后在resouces/statis/md/下建了markdown格式文件。然后页面可以写成类似这样的。同样是通过jQuery获取数据,然后转换为HTML。

代码语言:javascript复制
<div class="container" id="content">

</div>
<div th:replace="_footer::footer"></div>
<script>
    $(document).ready(function () {
        $.get("[[@{/md/index.md}]]", function (data) {
            $("#content").html(marked(data));
        })
    })
</script>

完整的Spring Boot小例子代码在CSDN代码库上了,有兴趣的同学可以看看。由于项目是Gradle项目,所以可能有些同学不好编译打包。这里我还上传了完整的二进制程序Spring Boot小例子程序,可以直接使用java -jar XXX.jar来运行。

0 人点赞