我使用Spring Boot创建了一个简单的MVC示例系统
第一次使用Spring Boot
我非常感到落后,但我开始学习Spring Boot。
在公司里,我们几乎都是使用自己的框架或者Struts2的系统。但是最近关于安全漏洞和微服务化的讨论越来越多,当参加了去年春天的JJUG CCC 2017 春季大会时,到处都在谈论Spring Boot。考虑到网络上有很多信息并且有很多相关的书籍,所以我决定尝试使用Spring Boot。
在实际使用中,我觉得首先可以立即开始并且很容易地添加功能,感觉非常方便。我一边阅读书籍,一边查看官方文档,再通过互联网上搜索信息,很快就能创建一个简单的系统。虽然也遇到了一些困难点…建议尝试运行一些适当的功能,但既然已经开始了,不如制作一个简单的样例系统来学习。
我已经在GitHub上公开了我编写的源代码,希望能对即将开始的人有所帮助。
https://github.com/ewai/spring-boot-mvc-template
※平时主要从事项目管理等工作,离编码有些遥远,可能存在一些不完善,请多多包涵。如果有更好的方法,请告诉我,我将不胜感激。
※设计不太好,请见谅。
待完成的任务
-
- Unit Test(Junit)
- Integration Test
我计划随时进行创建和更新。
我做了这样一个样本系统。
系统概述
管理图书信息的系统(类似简单的主数据管理系统)
-
- 検索、検索結果一覧
-
- 登録
-
- 更新
-
- 削除
- ログイン
需要登录才能操作数据。
只有管理员才能进行注册和更新。← TODO 权限方面存在问题。
画面过渡图

只有在经过认证的情况下,才能显示除首页之外的页面。
暂且观看一下演示。
目录结构
src
├─main
│ ├─java
│ │ └─info
│ │ └─ewai
│ │ └─sbmt
│ │ ├─config (Security周りの設定など
│ │ ├─domain (entity, repositoryなど
│ │ ├─service (service
│ │ └─web (controller, validator
│ │ └─form (form
│ └─resources
│ ├─static
│ │ ├─css
│ │ └─img
│ └─templates (thymleafのテンプレート
└─test
└─java
└─info
└─ewai
└─sbt (TODO Junit
我正在使用標準的軟件包結構。
如果不符合標準,就必須指定@ComponentScan(“xxx”),
起初我不知道這一點,所以自由地創建並陷入困境。
公式文件
http://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/#using-boot-using-the-default-package
在build.gradle文件中设置的库。
~
springBootVersion = '1.5.6.RELEASE'
~
compile("org.webjars:jquery:3.2.1")
compile("org.webjars:bootstrap:3.3.7")
compile('org.springframework.boot:spring-boot-starter-actuator')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-jetty')
compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE')
runtime('mysql:mysql-connector-java:5.1.43')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')
build.gradle 文件的 GitHub 源代码
出于公司使用Jetty的原因,尽管默认情况下使用Tomcat,但我们选择了Jetty。
环境
开发环境
-
- Java8
-
- Eclipse Oxygen
- windows10
图书馆
-
- Spring Boot 1.5.6
- Spring Framework 4.3.0 (上記を指定すると使われるバージョン)
应用服务器
- Jetty
数据库
-
- MySQL 5.7
- Docker
数据库环境
如果只需要查看源代码而不需要尝试运行的话是不必要的,但在尝试移动时则是必要的。
我根据Docker Hub上官方的MySQL镜像创建了一个Dockerfile,可以自动进行DDL和测试数据的注册。
通过从那个Dockerfile进行docker build来创建镜像,MySQL将会启动并包含DDL和测试数据,因此可以立即将系统投入运行。
只需提供一种选项:假设本地已安装Docker。
以下是一个相对简略的构建步骤。
初回
# こちらをcloneするなりダウンロードするなりする
https://github.com/ewai/docker-spring-boot-template-mysql
以下、コマンドの実行
# イメージ作成
docker build -t sbtdb .
# コンテナ作成
docker run -d --name sbtdb -p 3306:3306 sbtdb
これで起動されているはずです。
docker ps -a
statusがUPになっていればOK。
※DDLもテストデータも投入済みです
2回目以降
状態の確認
docker ps -a
statusがExitedになっていたらコンテナを起動する
docker start sbtdb
当status变为UP时,您可以尝试使用MySQL Workbench等工具进行连接。
接続情報
jdbc:mysql://localhost/sbtdb
ユーザー:sbt
パスワード:sbt

让它动起来试试看
-
- ローカルに上記のDB構築する
-
- Eclipseにソースをimportする(github)
-
- info.ewai.sbmt.SpringBootTemplateApplicationを実行する (gradle buildしてjarを実行してもOK)
http://localhost:8080/にアクセスする

关于源代码的说明
登陆认证
相关来源
+ compile('org.springframework.boot:spring-boot-starter-security')
我将使得能够使用Spring Security。
只需添加这个,基本身份验证就会自动应用。我已经创建了一个部分,用于实现登录验证。
package info.ewai.sbmt.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import info.ewai.sbmt.service.UserService;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/img/**", "/css/**", "/js/**", "/webjars/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("username")
.passwordParameter("password").permitAll().and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true).permitAll();
}
@Configuration
protected static class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {
@Autowired
UserService userService;
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
}
}
}
-
- 各静的ファイルが認証不要にする
-
- “/”へのアクセスのみ認証不要とする
-
- “/”以外へアクセスが来た場合は”/login”へ遷移させる
-
- “/logout”へアクセスが来た場合はJSESSIONIDを削除して”/”へ遷移させる
- “/login”へpostが来たらパラメータを元にuserServiceでユーザーを取得しパスワードをチェックする
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "login";
}
这个控制器只是用来跳转到登录页面的。我记得之前好像找到了能够仅通过配置完成的方法,但是忘记了,所以先将这类方法暂时集中在SimpleController.java中。
@Component
public class UserService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StringUtils.isEmpty(username)) {
throw new UsernameNotFoundException("Username is empty");
}
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found for name: " + username);
}
return user;
}
}
public interface UserRepository extends JpaRepository<User, Long> {
public User findByUsername(String username);
}
其他
信息.ewai.sbmt.领域.用户
信息.ewai.sbmt.领域.权限
登录.html
我已经实现了UserDetails并且几乎按照标准完成了,但是如果将内部系统的用户信息转换并同步,也许可以使用。
可以在样本系统内使用的用户权限
默认情况下插入的数据
搜索页面
@RequestMapping(value = "/book", method = RequestMethod.GET)
public String index(Model model) {
List<Book> list = this.bookservice.findAll();
model.addAttribute("booklist", list);
model.addAttribute("bookForm", new BookForm());
return "book";
}
public List<Book> findByBookNameLikeAndTagLike(String bookName, String tag) {
if (StringUtils.isEmpty(bookName) && (StringUtils.isEmpty(tag))) {
return this.findAll();
}
return this.bookRepository.findByBookNameLikeAndTagLike("%" + bookName + "%", "%" + tag + "%");
}
public interface BookRepository extends JpaRepository<Book, Long> {
public List<Book> findByBookNameLikeAndTagLike(String bookName, String tag);
}
使用Like方法指定可以进行模糊搜索,所以我试着使用了它。我以为参数会自动添加百分号%,但实际没添加,所以我加上了%。虽然标准功能中似乎可以轻松进行分页,但我目前还没有实现。
我认为实际上可能会遇到编写复杂SQL的情况,所以我打算创建自定义repository来编写JPQL或SQL。
@PersistenceContext
EntityManager entityManager;
~
Query query = entityManager.createQuery("from Book where id = :id")
编辑界面
输入检查
我正在创建自定义验证器并进行检查。
@Component
public class BookValidator implements Validator {
@Autowired
BookService bookService;
@Override
public boolean supports(Class<?> clazz) {
return BookForm.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
// required check
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "bookName", "field.required");
// TODO form check
// BookForm form = BookForm.class.cast(target);
// errors.rejectValue("field", "errorCode");
// global message
if (errors.hasErrors()) {
errors.reject("input.error");
}
}
}
ほぼBook.javaのEntityと同じ
如果想要进行基本的检查,例如必须检查和大小检查,只需要在表单中添加注释,就可以进行检查。但是,我希望能够重复使用表单,而且不希望将检查分散到表单和验证器中,所以我将检查集中在验证器中。
-
- グローバルに出すエラーメッセージ(画面全体へのメッセージ)
- 各フィールドに出すエラーメッセージ
正在设置错误消息。
错误消息存储在messages_ja.properties文件中。
@RequestMapping(value = "/book/save", method = RequestMethod.POST)
public String save(@Valid @ModelAttribute BookForm bookForm, BindingResult result, Model model) {
logger.info("save/" + bookForm.getBookId());
if (result.hasErrors()) {
return "book-edit";
}
try {
this.bookservice.save(new Book(bookForm));
} catch (Exception e) {
result.reject("exception.error");
result.reject("using defaultMessage", e.toString());
return "book-edit";
}
return "book-complete";
}
在新图书(bookForm)上,正在进行从表单到实体的替换。
有没有更好的方法呢?
显示全局消息
bindingResult.reject(“错误码”)
将错误消息显示到各个字段中。bindingResult.reject(“field”, “errorCode”)
在Controller中,使用BindingResult设置reject,
而在Validator中,使用Errors设置reject。
@Valid @ModelAttribute 图书表单 bookForm, BindingResult 结果
按照这个顺序编写参数部分的定义似乎是规则。
如果将BindingResult放在前面,将会出现错误。
我有点卡壳了。
如果使用@Valid,则会在调用此处之前通过验证器进行预先检查。
因此,我们只检查result.hasErrors()以确定是否有错误。
更新处理
@Transactional
public Book save(Book book) {
return this.bookRepository.save(book);
}
实际上,我认为其中可能涉及更复杂的处理,但目前只是简单地保存。如果加上@Transactional,当发生异常时将发生回滚。据说会回滚非检查异常(如RuntimeException)。
我已经参考了这个(指的是链接的内容)。
我本来想在Controller的方法上加上@Transactional,但由于页面控制的关系,我会捕获异常进行处理,但这样一来就无法回滚。所以我觉得应该把业务逻辑基本上都放在Service中,然后在这里加上@Transactional。
如果情况复杂,当然可以通过EntityManager来获取并控制Transaction,但基本上希望能够使用注解就可以了,不知道是否可行。
Thymeleaf模板的通用化
如果有对在每个画面都使用的源代码进行更改的情况,就需要对所有页面进行更改……我们已经将这种源代码进行了共通化。
下面是通用化的源代码。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.springframework.org/schema/security">
<head>
<!-- common head -->
<th:block th:fragment="head"><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
th:href="@{/webjars/bootstrap/3.3.7/css/bootstrap.min.css}" rel="stylesheet" />
<link href="/css/common.css"
th:href="@{/css/common.css}" rel="stylesheet" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"
th:src="@{/webjars/jquery/3.2.1/jquery.min.js}"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
th:src="@{/webjars/bootstrap/3.3.7/js/bootstrap.min.js}"></script></th:block>
</head>
<body>
<div th:fragment="header" class="container" id="header">
<h1><a href="/">Book</a></h1>
<p style="text-align:right;" th:if="${#httpServletRequest.remoteUser != null}">Hello
<span th:text="${#httpServletRequest.remoteUser}" /> |
<a href="/logout">ログアウト</a> |
<a href="https://github.com/ewai/spring-boot-mvc-template" target="_blank" alt="spring-boot-mvc-template"><img src="img/mark-github.svg" /></a>
</p>
</div>
<div th:fragment="footer" class="container" id="footer">
<ul>
<li><a href="https://github.com/ewai/spring-boot-mvc-template" target="_blank"><img src="img/mark-github.svg" alt="spring-boot-mvc-template"/></a> <a href="https://github.com/ewai/spring-boot-mvc-template">spring-boot-mvc-template</a></li>
<li><a href="https://github.com/ewai/docker-spring-boot-template-mysql">docker-spring-boot-template-mysql</a></li>
</ul>
</div>
</body>
</html>
我已经创建了三个模板,其中th:fragment标签是模板的内容。
-
- headの共通で読み込むJS,CSS
-
- header
- footer
由于这是共用文件,所以我原本考虑放在另一个目录中,但无法加载,所以我把它放在了“templates”文件夹中。
<head>
<th:block th:include="common::head"></th:block>
<title>Book Search</title>
</head>
<body>
<th:block th:replace="common::header"></th:block>
~~~コンテンツ~~~
<th:block th:replace="common::footer"></th:block>
</body>
</html>
我們將每個頁面都用在了這樣的方式上。
作为缺点,就是无法像html一样查看设计。个人而言,我会启动应用程序并在运行过程中进行确认。然而,如果设计师希望使用纯html进行确认,可能最好不使用这种方法。
权限相关
只在特定用户中显示
+ compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2.1.3.RELEASE')
起初我添加了以下内容,但是无法运行(sec:authorize=”hasRole(‘ROLE_ADMIN’)”会直接显示在html中),降低版本后就可以了。
编译(’org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE’)
只显示有权限的用户。
<li class="list-group-item" sec:authorize="hasRole('ROLE_ADMIN')"><a href="/book/create" th:href="@{/book/create}" class="btn btn-link" id="link">書籍登録</a></li>
为了让SEC可用,进行了追加。
xmlns:sec="http://www.springframework.org/schema/security">
将其转化为可执行的jar文件
springBoot {
executable = true
}
如果将此添加至编译,并构建可执行的jar文件。这意味着什么呢,那就是…
./spring-boot-mvc-template-0.0.1-SNAPSHOT.jar
可以以这样的方式执行。
当您希望在操作系统启动时自动启动某个程序时,可以使用此功能。
如果是CentOS 7的情况下
[Unit]
Description=sbt
After=syslog.target
[Service]
User=sbtuser
ExecStart=/xxx/xxx/spring-boot-mvc-template-0.0.1-SNAPSHOT.jar
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target
systemctl enable sbt.service
现在,在操作系统启动时会自动启动。
Spring Boot执行器
+ compile('org.springframework.boot:spring-boot-starter-actuator')
由于只需添加这个,就可以轻松地确认服务器的状态,所以我进行了添加。
在User(UserDetails)的getAuthorities()中,只有持有”ACUTIATOR”权限的用户可以查看,否则无法访问。
在这次设置中,将此权限授予了admin用户,因此使用admin/admin登录后可以查看。
{"status":"UP","diskSpace":{"status":"UP","total":247762329600,"free":125178765312,"threshold":10485760},"db":{"status":"UP","database":"MySQL","hello":1}}
可以看出服务器还在运行,数据库也在运行,并且还有可用的磁盘空间。
当以没有”ACUTIATOR”权限的用户进行参考时,
根据返回的{“status”:”UP”},似乎能够确认服务器是否正在运行。
在本地主机的8080端口下,若没有”ACUTIATOR”权限则无法访问环境变量功能。
发生了错误。
访问被拒绝。用户必须具备以下角色之一:执行器
http://localhost:8080/mappings
我觉得可以制作设计书。
"{[/book],methods=[GET]}":{
"bean":"requestMappingHandlerMapping",
"method":"public java.lang.String info.ewai.sbmt.web.BookController.index(org.springframework.ui.Model)"
},
"{[/book/edit/{bookId}],methods=[GET]}":{
"bean":"requestMappingHandlerMapping",
"method":"public java.lang.String info.ewai.sbmt.web.BookController.edit(info.ewai.sbmt.web.form.BookForm,org.springframework.validation.BindingResult,java.lang.Long,org.springframework.ui.Model)"
},
"{[/book/save],methods=[POST]}":{
"bean":"requestMappingHandlerMapping",
"method":"public java.lang.String info.ewai.sbmt.web.BookController.save(info.ewai.sbmt.web.form.BookForm,org.springframework.validation.BindingResult,org.springframework.ui.Model)"
},
除此以外,还可能存在其他终端点。
http://qiita.com/MariMurotani/items/01dafd2978076b5db2f3
似乎可以进行自定义和更改URL端口,但暂时保持原样。
我参考了以下的信息
Spring框架参考文档4.3.0.RELEASE
http://docs.spring.io/spring/docs/4.3.0.RELEASE/spring-framework-reference/htmlsingle/
Spring Boot参考指南1.5.6.RELEASE
http://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/
通过使用Spring框架开发Java应用程序,彻底入门了Spring。它详细解释了Spring的各种功能,帮助我理解了Spring的工作原理。还简要介绍了Spring Boot。
SpringBoot编程入门
最终了
虽然用Spring Boot可以非常简单且快速地创建项目,但是如果不了解规则的话可能会陷入一些困境。不过,我觉得官方文档、书籍和网上的信息都很丰富,解决问题相对容易。
这次我创建了一个简单的示例系统,但如果要构建实际可用的系统,我觉得还会有各种尝试和困难要克服,但我想继续使用它。
感谢那些在书籍和网上分享各种信息的人们。