《SpringBoot 实战》
技术栈:
注:
有了起步依赖就不需要指定版本号,起步依赖本身的版本是由正在使用的Spring Boot的版本来决定的,而起步依赖则会决定它们引入的传递依赖的版本。
1.新建SpringBoot项目,勾选Web、Thymeleaf和JPA、H2
打开启动类:
- @SpringBootApplication //开启组件扫描和自动配置
- public class ChapterTestApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(ChapterTestApplication.class, args);//负责启动引导应用程序
- }
-
- }
@SpringBootApplication开启了Spring的组件扫描和Spring Boot的自动配置功能。实际
上,@SpringBootApplication将三个有用的注解组合在了一起。
- Spring的@Configuration:标明该类使用Spring基于Java的配置。
- Spring的@ComponentScan:启用组件扫描,这样你写的Web控制器类和其他组件才能被自动发现并注册为Spring应用程序上下文里的Bean。
- Spring Boot 的 @EnableAutoConfiguration :就是这一行配置开启了Spring Boot自动配置的魔力,从此用再写成篇的配置了。
2.application.properties文件,配置属性
3.首次使用Gradle构建项目:(Gradle或Maven都一样,我第一次使用Gradle)
构建插件的主要功能是把项目打包成一个可执行的超级JAR (uber-JAR) , 包括把应用程序的所有依赖打入JAR文件内,并为JAR添加一个描述文件,其中的内容能让你用java -jar来运行应用程序。
起步依赖:
4. 实体类(POJO)
Book类:@Entity注解表明它是一个JPA实体,id属性加了@Id和@GeneratedValue注解,说明这个字段是实体的唯一标识,并且这个字段的值是自动生成的。
- package com.ggqq.chaptertest;
-
- import javax.persistence.Entity;
- import javax.persistence.GeneratedValue;
- import javax.persistence.GenerationType;
- import javax.persistence.Id;
-
- @Entity
- public class Book {
- @Id
- @GeneratedValue(strategy = GenerationType.AUTO)
- private Long id;
- private String reader;
- private String isbn;
- private String title;
- private String author;
- private String description;
-
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getReader() {
- return reader;
- }
-
- public void setReader(String reader) {
- this.reader = reader;
- }
-
- public String getIsbn() {
- return isbn;
- }
-
- public void setIsbn(String isbn) {
- this.isbn = isbn;
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public String getAuthor() {
- return author;
- }
-
- public void setAuthor(String author) {
- this.author = author;
- }
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
- }
5.Service层:
定义用于把Book对象持久化到数据库的仓库:因为用了Spring Data JPA,所以要做的就是简单地定义一个接口,扩展一下Spring Data JPA的JpaRepository接口
- package com.ggqq.chaptertest;
-
- import org.springframework.data.jpa.repository.JpaRepository;
-
- import java.util.List;
-
- public interface ReadingListRepository extends JpaRepository
{ - List
findBookByReader(String reader); - }
6.控制层(Controller层)
ReadingListController使用了@Controller注解,这样组件扫描会自动将其注册为Spring应用程序上下文里的一个Bean。它还用了@RequestMapping注解,将其中所有的处理器方法都映射到了“/”这个URL路径上
- package com.ggqq.chaptertest;
-
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.Model;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
-
- import java.util.List;
-
- @Controller
- @RequestMapping("/readingList")
- public class ReadingListController {
- public static final String reader = "ggqq";
- private ReadingListRepository readingListRepository;
-
- @Autowired
- public ReadingListController(ReadingListRepository readingListRepository) {
- this.readingListRepository = readingListRepository;
- }
-
- @RequestMapping(method = RequestMethod.GET)
- public String readerBooks(Model model){
- List
readingList = readingListRepository.findBookByReader(reader); - if(readingList != null){
- model.addAttribute("books",readingList);
- }
- return "readingList";
- }
-
- @RequestMapping(method = RequestMethod.POST)
- public String addToReadingList(Book book){
- book.setReader(reader);
- readingListRepository.save(book);
- return "redirect:/readingList";
- }
- }
该控制器有两个方法。
1.readersBooks():处理/{reader}上的HTTP GET请求,根据路径里指定的读者,从(通
过控制器的构造器注入的)仓库获取Book列表。随后将这个列表塞入模型,用的键是
books,最后返回readingList作为呈现模型的视图逻辑名称。
2. addToReadingList():处理/{reader}上的HTTP POST请求,将请求正文里的数据绑定
到一个Book对象上。该方法把Book对象的reader属性设置为读者的姓名,随后通过仓
库的save()方法保存修改后的Book对象,最后重定向到/{reader}(控制器中的另一个方
法会处理该请求) 。
7.前端:Thymeleaf模板
readersBooks()方法最后返回readingList作为逻辑视图名, 为此必须创建该视图。
在src/main/ resources/templates里创建一个名为readingList.html的文件:
- <html>
- <head>
- <title>Reading Listtitle>
- <link rel="stylesheet" th:href="@{/style.css}">link>
- head>
-
- <body>
- <h2>Your Reading Listh2>
- <div th:unless="${#lists.isEmpty(books)}">
- <dl th:each="book : ${books}">
- <dt class="bookHeadline">
- <span th:text="${book.title}">Titlespan> by
- <span th:text="${book.author}">Authorspan>
- (ISBN: <span th:text="${book.isbn}">ISBNspan>)
- dt>
- <dd class="bookDescription">
- <span th:if="${book.description}"
- th:text="${book.description}">Descriptionspan>
- <span th:if="${book.description eq null}">
- No description availablespan>
- dd>
- dl>
- div>
- <div th:if="${#lists.isEmpty(books)}">
- <p>You have no books in your book listp>
- div>
-
-
- <hr/>
-
- <h3>Add a bookh3>
- <form method="POST">
- <label for="title">Title:label>
- <input type="text" name="title" size="50">input><br/>
- <label for="author">Author:label>
- <input type="text" name="author" size="50">input><br/>
- <label for="isbn">ISBN:label>
- <input type="text" name="isbn" size="15">input><br/>
- <label for="description">Description:label><br/>
- <textarea name="description" cols="80" rows="5">textarea><br/>
- <input type="submit">input>
- form>
-
- body>
- html>
8.实际效果:
现在理解一下SpringBoot的自动配置:
自动配置会做出以下配置决策,它们和之前的例子息息相关。
本章内容:两种影响自动配置的方式——使用显式配置进行覆盖和使用属性进行精细化配置。另外还有如何使用Spring Boot提供的钩子引入自定义的错误页。
安全工作:配置Spring Security起步依赖
现在在浏览器里打开该应用程序,马上就会看到HTTP基础身份验证对话框。此处的用户名是user,密码是在应用程序每次运行时随机生成后写入日志的,需要查找日志消息(默认写入标准输出) ,找到此类内容:
这太粗糙了,对用户不友好,所以引出了希望修改Spring Security的一些配置,至少要有一个好看一些的登录页,还要有一个基于数据库或LDAP(Lightweight Directory Access Protocol)用户存储的身份验证服务。
覆盖自动配置很简单,就当自动配置不存在,直接显式地写一段配置。
在Spring Security的场景下,这意味着写一个扩展了WebSecurityConfigurerAdapter的配置类:
- package com.ggqq.chaptertest;
-
- 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.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
-
- @Configuration
- public class SecurityConfig extends WebSecurityConfigurerAdapter {
-
- @Autowired
- private ReaderRepository readerRepository;
-
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- .authorizeRequests()
- .antMatchers("/").access("hasRole('READER')")//要求登录者有READER角色
- .antMatchers("/**").permitAll()
- .and()
- .formLogin()
- .loginPage("/login") //设置登录表单的路径
- .failureUrl("/login?error=true");
- }
-
- @Override
- protected void configure(
- AuthenticationManagerBuilder auth) throws Exception {
- auth
- .userDetailsService(new UserDetailsService() { //定义自定义UserDetailsService
- @Override
- public UserDetails loadUserByUsername(String username)
- throws UsernameNotFoundException {
- UserDetails userDetails = readerRepository.getOne(username);
- if (userDetails != null) {
- return userDetails;
- }
- throw new UsernameNotFoundException("User '" + username + "' not found.");
- }
- });
- }
-
- }
SecurityConfig是个非常基础的Spring Security配置, 尽管如此, 它还是完成了不少安全定
制工作。通过这个自定义的安全配置类,我们让Spring Boot跳过了安全自动配置,转而使用我们的安全配置。
扩展了WebSecurityConfigurerAdapter的配置类可以覆盖两个不同的configure()方法。在SecurityConfig里,第一个configure()方法指明, “/” (ReadingListController的方法映射到了该路径)的请求只有经过身份认证且拥有READER角色的用户才能访问。其他的所有请求路径向所有用户开放了访问权限。 这里还将登录页和登录失败页 (带有一个error属性)指定到了/login。
Spring Security为身份认证提供了众多选项,后端可以是JDBC 、LDAP和内存用户存储。在这个应用程序中,我们会通过JPA用数据库来存储用户信息。第二个configure()方法设置了一个自定义的UserDetailsService,这个服务可以是任意实现了UserDetailsService的类,用于查找指定用户名的用户。提供了一个匿名内部类实现, 简单地调用了注入ReaderRepository (这是一个Spring Data JPA仓库接口) 的findOne()方法。
3.2.1 自动配置微调
1.禁用模板缓存 :spring.thymeleaf.cache设置为false(实时变更)
2.设置server.port属性
3.配置日志:
默认情况下,Spring Boot会用Logback(http://logback.qos.ch)来记录日志,并用INFO级别输出到控制台。一般来说,你不需要切换日志实现;Logback能很好地满足你的需要。但是,如果决定使用Log4j或者Log4j2,那么你只需要修改依赖,引入对应该日志实现的起步依赖,同时排除掉
Logback。
在Gradle里,在configurations下排除该起步依赖是最简单的办法
然后 在Gradle里可以这样添加Log4j:
要设置日志级别你可以创建以logging.level开头的属性,后面是要日志名称。如果根日志级别要设置为WARN,但Spring Security的日志要用DEBUG级别,可以在application.yml里加入以下内容:
4.配置数据库
eg:
3.2.2 应用程序 Bean 的配置外置
3.2.3 使用 Profile 进行配置
Profile是一种条件化配置,基于运行时激活的Profile,会使用或者忽略不同的Bean或配置类。
这里用的@Profile注解要求运行时激活production Profile,这样才能应用该配置。如果production Profile没有激活,就会忽略该配置,而此时缺少其他用于覆盖的安全配置,于是应用自动配置的安全配置。
也可以向application.yml里添加spring.profiles.active属性:
Spring Boot自动配置的默认错误处理器会查找名为error的视图,如果找不到就用默认的白标
错误视图,如下图所示。
因此,最简单的方法就是创建一个自定义视图,让解析出的视图名为error。
这一点归根到底取决于错误视图解析时的视图解析器。
- 实现了Spring的View接口的Bean,其 ID为error(由Spring的BeanNameViewResolver所解析) 。
- 如果配置了Thymeleaf,则有名为error.html的Thymeleaf模板。
- 如果配置了FreeMarker,则有名为error.ftl的FreeMarker模板。
- 如果配置了V elocity,则有名为error.vm的V elocity模板。
- 如果是用JSP视图,则有名为error.jsp的JSP模板。
eg:在本例中:error.html
- <html>
- <head>
- <title>Oops!title>
- <link rel="stylesheet" th:href="@{/style.css}">link>
- head>
-
- <html>
- <div class="errorPage">
- <span class="oops">Oops!span><br/>
- <img th:src="@{/MissingPage.png}">img>
- <p>There seems to be a problem with the page you requested
- (<span text="${path}">/readingListspan>).p>
- <p th:text="${'Details: ' + message}">p>
- div>
- html>
-
- html>
其中有两处特别的信息需要呈现:错误的请求路径和异常消息。但这还不是错误页上的全部细节。默认情况下,Spring Boot会为错误视图提供如下错误属性: