J'ai l'impression d'être en retard, mais j'ai commencé à étudier Spring Boot. La plupart des entreprises utilisaient leurs propres frameworks et systèmes Struts2, mais les vulnérabilités qui faisaient du bruit ces jours-ci et la tendance aux microservices, et quand j'ai participé au JJUG CCC 2017 Spring au printemps, partout Spring Boot J'ai décidé d'utiliser Spring Boot car il y a beaucoup d'histoires à ce sujet, et il y a beaucoup d'informations et de livres sur le net.
En pensant à l'utiliser, j'ai réalisé que c'était pratique car je pouvais le démarrer immédiatement et ajouter des fonctions facilement. Si vous lisez le livre, regardez le document officiel et recherchez les informations sur le net, un système simple sera complété en un rien de temps. Cependant, il y avait quelques points addictifs ... Il est normal de déplacer les fonctions de manière appropriée, mais j'ai décidé de créer un système d'échantillonnage simple et de l'étudier.
La source créée est publiée sur github, donc j'espère qu'elle sera utile pour ceux qui commencent à partir de maintenant. https://github.com/ewai/spring-boot-mvc-template
Nous prévoyons de le créer et de le mettre à jour de temps en temps.
Système de gestion des informations sur les livres (Système qui effectue une gestion simple du maître)
Vous devez vous connecter pour travailler avec les données Seul l'administrateur peut s'inscrire / mettre à jour ← L'autorité TODO ne fonctionne pas bien
En dehors de l'écran supérieur, il ne peut être affiché que lorsqu'il a été authentifié.
https://ewai.info/sbt/
utilisateur | mot de passe | Autorité |
---|---|---|
sbt | sbt | Autorité utilisateur normale (système de référence uniquement) |
admin | admin | Droits d'administrateur (les données peuvent être mises à jour) |
src
├─main
│ ├─java
│ │ └─info
│ │ └─ewai
│ │ └─sbmt
│ │ ├─config (paramètres relatifs à la sécurité, etc.
│ │ ├─domain (entity,référentiel etc.
│ │ ├─service (service
│ │ └─web (controller, validator
│ │ └─form (form
│ └─resources
│ ├─static
│ │ ├─css
│ │ └─img
│ └─ modèles (modèles de feuilles de thymel
└─test
└─java
└─info
└─ewai
└─sbt (TODO Junit
Il a une configuration de package standard. Il semble que vous deviez spécifier @ComponentScan ("xxx") s'il ne respecte pas la norme. Je l'ai fait librement sans le savoir au début.
Documentation officielle 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')
source github build.gradle
Par défaut, tomcat est utilisé, mais Jetty est utilisé car l'entreprise utilise Jetty.
Bibliothèque
Serveur d'application
DB
Sur la base du MySQL officiel de Docker Hub, j'ai créé un Dockerfile qui enregistre automatiquement le DDL et les données de test. Si vous construisez docker à partir de là et créez une image, elle contiendra DDL et des données de test. Depuis le démarrage de MySQL, vous pouvez démarrer le système immédiatement.
Ce qui suit est une procédure de construction assez approximative.
Première fois
#Cloner ou télécharger ceci
https://github.com/ewai/docker-spring-boot-template-mysql
Ci-dessous, exécutez la commande
#Création d'image
docker build -t sbtdb .
#Création de conteneurs
docker run -d --name sbtdb -p 3306:3306 sbtdb
Il devrait maintenant fonctionner.
docker ps -a
OK si l'état est UP.
* DDL et les données de test ont déjà été saisies.
Après la deuxième fois
Vérifiez l'état
docker ps -a
Démarrez le conteneur si le statut est Quitté
docker start sbtdb
Lorsque le statut devient UP, vous pouvez vous connecter, alors essayez de vous connecter avec MySQL Workbench. sbtdb
Informations de connexion
jdbc:mysql://localhost/sbtdb
Utilisateur: sbt
Mot de passe: sbt
Exemple) MySQL Workbench
build.gradle
+ compile('org.springframework.boot:spring-boot-starter-security')
Active Spring Security.
Ajoutez simplement ceci et l'authentification de base sera appliquée automatiquement. J'ai fait cette partie parce que je veux authentifier la connexion.
SecurityConfig.java
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());
}
}
}
SimpleController.java
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login() {
return "login";
}
C'est un contrôleur qui passe uniquement à la page de connexion. Je pense que j'ai trouvé que ce genre de chose ne peut être fait que par réglage, mais j'ai oublié, alors Une fois, je les ai rassemblés dans SimpleController.java.
UserService.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;
}
}
UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
public User findByUsername(String username);
}
Autre info.ewai.sbmt.domain.User info.ewai.sbmt.domain.Authorities login.html
J'ai implémenté UserDetails et l'ai fait presque comme standard, Il peut être possible de l'utiliser en convertissant et en synchronisant les informations utilisateur du système interne.
Entrée de données par défaut
utilisateur | mot de passe | Autorité | Contenu de l'autorisation |
---|---|---|---|
sbt | sbt | ROLE_USER | Autorité utilisateur normale (système de référence uniquement) |
admin | admin | ROLE_ADMIN | Droits d'administrateur (les données peuvent être mises à jour) |
admin | admin | ACTUATOR | Autorisation d'utiliser Spring Boot Actuator |
BookController.java
@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";
}
BookService.java
public List<Book> findByBookNameLikeAndTagLike(String bookName, String tag) {
if (StringUtils.isEmpty(bookName) && (StringUtils.isEmpty(tag))) {
return this.findAll();
}
return this.bookRepository.findByBookNameLikeAndTagLike("%" + bookName + "%", "%" + tag + "%");
}
BookRepository.java
public interface BookRepository extends JpaRepository<Book, Long> {
public List<Book> findByBookNameLikeAndTagLike(String bookName, String tag);
}
Si vous spécifiez Like dans la méthode, vous pouvez effectuer une recherche ambiguë, j'ai donc essayé de l'utiliser. Je pensais que% serait ajouté automatiquement au paramètre, mais je ne pouvais pas, alors j'ai ajouté%. Il semble que vous puissiez facilement paginer avec des fonctions standard, mais je ne l'ai pas fait pour le moment.
En fait, je pense qu'il est possible de créer du SQL compliqué, donc je pense que je vais créer un référentiel personnalisé et écrire JPQL et SQL.
@PersistenceContext
EntityManager entityManager;
~
Query query = entityManager.createQuery("from Book where id = :id")
Je crée un Varidator personnalisé et je le vérifie.
BookValidator.java
@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");
}
}
}
BookForm.java
Presque livre.Identique à l'entité Java
Des vérifications simples telles que des vérifications obligatoires et des vérifications de taille peuvent être effectuées en annotant le formulaire. Il est possible de vérifier, mais je voulais réutiliser Form et je ne voulais pas distribuer le chèque à Form et Validator Les chèques sont regroupés dans Validator.
Le message d'erreur est défini.
Le message d'erreur se trouve dans messages_ja.properties.
BookController.java
@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";
}
new Book(bookForm) Je passe de la forme à l'entité à. N'y a-t-il pas un bon moyen?
// Afficher le message global bindingResult.reject("errorCode")
// Afficher un message d'erreur pour chaque champ bindingResult.reject("field", "errorCode")
Dans Controller, définissez le rejet dans BindingResult et Dans Validator, définissez le rejet dans Erreurs.
@Valid @ModelAttribute BookForm bookForm, BindingResult result
Il semble être une règle d'écrire la définition de la partie argument dans cet ordre. Mettre le BindingResult au premier plan entraînera une erreur. J'y suis entré un peu.
Si @Valid est ajouté, il sera appelé dans un état qui a été vérifié à l'avance par Validator. Je ne vérifie donc que result.hasErrors () pour les erreurs.
BookService.java
@Transactional
public Book save(Book book) {
return this.bookRepository.save(book);
}
En fait, je pense qu'un traitement plus compliqué arrivera, mais il s'agit simplement d'économiser. Si @Transactional est ajouté, il sera annulé lorsqu'une exception se produit. Il semble que les exceptions non contrôlées (RuntimeException, etc.) soient annulées.
J'ai été autorisé à faire référence) http://qiita.com/NagaokaKenichi/items/a279857cc2d22a35d0dd
J'ai pensé à ajouter @Transactional à la méthode Controller, En raison du contrôle de l'écran, l'exception est interceptée et traitée, mais elle n'a pas été annulée, donc Je me demande si toute la logique métier doit être attachée ici en la mettant en service J'ai pensé.
Bien sûr, si c'est compliqué, il semble que vous allez retirer Transaction d'EntityManager et la contrôler, Je me demande s'il est acceptable d'utiliser des annotations comme base ou non.
S'il y a un changement dans la source qui est couramment utilisée sur chaque écran, toutes les pages doivent être changées ... J'ai partagé la source comme.
Ce qui suit est une source courante.
common.html
<!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">Se déconnecter</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>
Le modèle se trouve à l'intérieur de la balise ** th: fragment **. J'en ai fait trois.
Puisqu'il s'agit d'un fichier commun, j'ai pensé que je devrais le mettre dans un répertoire différent, mais il n'a pas été lu, il est donc placé directement sous les modèles.
book.html
<head>
<th:block th:include="common::head"></th:block>
<title>Book Search</title>
</head>
<body>
<th:block th:replace="common::header"></th:block>
~~~contenu~~~
<th:block th:replace="common::footer"></th:block>
</body>
</html>
J'utilise chaque page comme ça.
L'inconvénient est que vous ne pouvez pas vérifier la conception au format HTML. Personnellement, je lance l'application et la vérifie en l'exécutant, Il vaut peut-être mieux ne pas l'utiliser lorsque le concepteur le crée avec du html pur et le vérifie.
Lors de l'affichage uniquement pour un utilisateur spécifique
build.gradle
+ compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2.1.3.RELEASE')
Au début, j'ai ajouté ce qui suit, mais cela n'a pas fonctionné (sec: authorize = "hasRole ('ROLE_ADMIN')" était affiché en html tel quel) et cela a fonctionné lorsque j'ai abaissé la version.
compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE')
Lors de l'affichage uniquement des utilisateurs autorisés
index.html
<li class="list-group-item" sec:authorize="hasRole('ROLE_ADMIN')"><a href="/book/create" th:href="@{/book/create}" class="btn btn-link" id="link">Inscription au livre</a></li>
Ajouté pour que sec puisse être utilisé
index.html
xmlns:sec="http://www.springframework.org/schema/security">
https://github.com/thymeleaf/thymeleaf-extras-springsecurity
build.gradle
springBoot {
executable = true
}
Si vous ajoutez ceci et que vous le construisez, il devient un fichier jar exécutable. Qu'est-ce que ça veut dire?
./spring-boot-mvc-template-0.0.1-SNAPSHOT.jar
Il est possible de l'exécuter ainsi.
Il peut être utilisé lorsque vous souhaitez le démarrer automatiquement au démarrage du système d'exploitation.
Pour centos7
/etc/systemd/system/sbt.service
[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
cmd
systemctl enable sbt.service
Maintenant, il démarrera automatiquement au démarrage du système d'exploitation.
Spring Boot Actuator
build.gradle
+ compile('org.springframework.boot:spring-boot-starter-actuator')
Je l'ai ajouté car vous pouvez facilement vérifier l'état du serveur simplement en ajoutant ceci. Un utilisateur qui a l'autorité "ACUTIATOR" dans getAuthorities () de User (UserDetails) Il semble que vous ne puissiez pas le voir tant que vous ne vous êtes pas connecté. Cette fois, l'utilisateur admin a cette autorisation, vous pouvez donc la voir en vous connectant en tant qu'admin / admin.
http://localhost:8080/health
/health
{"status":"UP","diskSpace":{"status":"UP","total":247762329600,"free":125178765312,"threshold":10485760},"db":{"status":"UP","database":"MySQL","hello":1}}
Vous pouvez voir que le serveur est actif, que la base de données est active et que l'espace disque est toujours libre.
Quand j'ai essayé de me référer à lui comme un utilisateur sans autorité "ACUTIATOR", Puisque {"status": "UP"} est renvoyé, vous pouvez voir si le serveur est opérationnel.
http://localhost:8080/env Comme prévu, si vous ne disposez pas des droits "ACUTIATOR", vous ne pouvez pas faire référence au système de variables d'environnement. J'ai une erreur.
Access is denied. User must have one of the these roles: ACTUATOR
http://localhost:8080/mappings Il semble qu'un document de conception puisse être réalisé.
/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)"
},
Il y aura probablement également d'autres points de terminaison. http://qiita.com/MariMurotani/items/01dafd2978076b5db2f3
Il semble que vous puissiez le personnaliser et changer le port de l'URL, mais laissez-le tel quel.
Spring Framework Reference Documentation 4.3.0.RELEASE http://docs.spring.io/spring/docs/4.3.0.RELEASE/spring-framework-reference/htmlsingle/
Spring Boot Reference Guide 1.5.6.RELEASE http://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/
Présentation détaillée du développement d'applications Spring Java avec Spring Framework Il a été utile d'expliquer et de comprendre les fonctions de Spring en général. Il y a aussi un peu écrit sur Spring Boot.
Introduction à la programmation Spring Boot
C'est une botte de printemps qui est très facile à fabriquer, mais il y avait des moments où je ne connaissais pas les règles. Cependant, j'ai estimé que c'était relativement facile à résoudre en raison de l'abondance d'informations sur les documents officiels, les livres et Internet. Cette fois, j'ai fait un système d'échantillonnage simple, donc je pense qu'il y aura divers essais, erreurs et addictions lorsque je fais un système pratique, mais j'ai senti que j'aimerais l'utiliser. Merci à toutes les personnes qui ont publié diverses informations dans des livres et en ligne.
Recommended Posts