• Day04 Spring和SpringBoot


    《SpringBoot 实战》


    第二章  开发第一个应用程序


    任务: 使用springbooot构建一个简单的阅读列表应用程序

    技术栈:

    • Spring MVC来处理Web请求,
    • Thymeleaf来定义Web视图,
    • Spring Data JPA来把阅读列表持久化到数据库里,姑且先用嵌入式的H2数据库

    注:

    有了起步依赖就不需要指定版本号,起步依赖本身的版本是由正在使用的Spring Boot的版本来决定的,而起步依赖则会决定它们引入的传递依赖的版本。


    1.新建SpringBoot项目,勾选Web、Thymeleaf和JPA、H2

    打开启动类:

    1. @SpringBootApplication //开启组件扫描和自动配置
    2. public class ChapterTestApplication {
    3. public static void main(String[] args) {
    4. SpringApplication.run(ChapterTestApplication.class, args);//负责启动引导应用程序
    5. }
    6. }

    @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注解,说明这个字段是实体的唯一标识,并且这个字段的值是自动生成的。

    1. package com.ggqq.chaptertest;
    2. import javax.persistence.Entity;
    3. import javax.persistence.GeneratedValue;
    4. import javax.persistence.GenerationType;
    5. import javax.persistence.Id;
    6. @Entity
    7. public class Book {
    8. @Id
    9. @GeneratedValue(strategy = GenerationType.AUTO)
    10. private Long id;
    11. private String reader;
    12. private String isbn;
    13. private String title;
    14. private String author;
    15. private String description;
    16. public Long getId() {
    17. return id;
    18. }
    19. public void setId(Long id) {
    20. this.id = id;
    21. }
    22. public String getReader() {
    23. return reader;
    24. }
    25. public void setReader(String reader) {
    26. this.reader = reader;
    27. }
    28. public String getIsbn() {
    29. return isbn;
    30. }
    31. public void setIsbn(String isbn) {
    32. this.isbn = isbn;
    33. }
    34. public String getTitle() {
    35. return title;
    36. }
    37. public void setTitle(String title) {
    38. this.title = title;
    39. }
    40. public String getAuthor() {
    41. return author;
    42. }
    43. public void setAuthor(String author) {
    44. this.author = author;
    45. }
    46. public String getDescription() {
    47. return description;
    48. }
    49. public void setDescription(String description) {
    50. this.description = description;
    51. }
    52. }

     5.Service层:

    定义用于把Book对象持久化到数据库的仓库:因为用了Spring Data JPA,所以要做的就是简单地定义一个接口,扩展一下Spring Data JPA的JpaRepository接口

    1. package com.ggqq.chaptertest;
    2. import org.springframework.data.jpa.repository.JpaRepository;
    3. import java.util.List;
    4. public interface ReadingListRepository extends JpaRepository {
    5. List findBookByReader(String reader);
    6. }

    6.控制层(Controller层)

    ReadingListController使用了@Controller注解,这样组件扫描会自动将其注册为Spring应用程序上下文里的一个Bean。它还用了@RequestMapping注解,将其中所有的处理器方法都映射到了“/”这个URL路径上

    1. package com.ggqq.chaptertest;
    2. import org.springframework.beans.factory.annotation.Autowired;
    3. import org.springframework.stereotype.Controller;
    4. import org.springframework.ui.Model;
    5. import org.springframework.web.bind.annotation.RequestMapping;
    6. import org.springframework.web.bind.annotation.RequestMethod;
    7. import java.util.List;
    8. @Controller
    9. @RequestMapping("/readingList")
    10. public class ReadingListController {
    11. public static final String reader = "ggqq";
    12. private ReadingListRepository readingListRepository;
    13. @Autowired
    14. public ReadingListController(ReadingListRepository readingListRepository) {
    15. this.readingListRepository = readingListRepository;
    16. }
    17. @RequestMapping(method = RequestMethod.GET)
    18. public String readerBooks(Model model){
    19. List readingList = readingListRepository.findBookByReader(reader);
    20. if(readingList != null){
    21. model.addAttribute("books",readingList);
    22. }
    23. return "readingList";
    24. }
    25. @RequestMapping(method = RequestMethod.POST)
    26. public String addToReadingList(Book book){
    27. book.setReader(reader);
    28. readingListRepository.save(book);
    29. return "redirect:/readingList";
    30. }
    31. }

    该控制器有两个方法。 
    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的文件:

    1. <html>
    2. <head>
    3. <title>Reading Listtitle>
    4. <link rel="stylesheet" th:href="@{/style.css}">link>
    5. head>
    6. <body>
    7. <h2>Your Reading Listh2>
    8. <div th:unless="${#lists.isEmpty(books)}">
    9. <dl th:each="book : ${books}">
    10. <dt class="bookHeadline">
    11. <span th:text="${book.title}">Titlespan> by
    12. <span th:text="${book.author}">Authorspan>
    13. (ISBN: <span th:text="${book.isbn}">ISBNspan>)
    14. dt>
    15. <dd class="bookDescription">
    16. <span th:if="${book.description}"
    17. th:text="${book.description}">Descriptionspan>
    18. <span th:if="${book.description eq null}">
    19. No description availablespan>
    20. dd>
    21. dl>
    22. div>
    23. <div th:if="${#lists.isEmpty(books)}">
    24. <p>You have no books in your book listp>
    25. div>
    26. <hr/>
    27. <h3>Add a bookh3>
    28. <form method="POST">
    29. <label for="title">Title:label>
    30. <input type="text" name="title" size="50">input><br/>
    31. <label for="author">Author:label>
    32. <input type="text" name="author" size="50">input><br/>
    33. <label for="isbn">ISBN:label>
    34. <input type="text" name="isbn" size="15">input><br/>
    35. <label for="description">Description:label><br/>
    36. <textarea name="description" cols="80" rows="5">textarea><br/>
    37. <input type="submit">input>
    38. form>
    39. body>
    40. html>

    8.实际效果:

     现在理解一下SpringBoot的自动配置:

    自动配置会做出以下配置决策,它们和之前的例子息息相关。 

    •  因为Classpath里有H2,所以会创建一个嵌入式的H2数据库Bean,它的类型是javax.sql.DataSource,JPA实现(Hibernate)需要它来访问数据库。 
    •  因为Classpath里有Hibernate(Spring Data JPA传递引入的)的实体管理器,所以自动配置会配置与 Hibernate相关的 Bean,包括 Spring的LocalContainerEntityManager- FactoryBean和JpaVendorAdapter。 
    • 因为Classpath里有Spring Data JPA,所以它会自动配置为根据仓库的接口创建仓库实现。 
    •  因为Classpath里有Thymeleaf,所以Thymeleaf会配置为Spring MVC的视图,包括一个Thymeleaf的模板解析器、模板引擎及视图解析器。视图解析器会解析相对于Classpath根目录的/templates目录里的模板。 
    • 因 为 Classpath 里 有 Spring MVC ( 归 功 于 Web 起 步 依 赖 ) , 所 以 会 配 置 Spring 的DispatcherServlet并启用Spring MVC。 
    • 因为这是一个Spring MVC Web应用程序, 所以会注册一个资源处理器, 把相对于Classpath根目录的/static目录里的静态内容提供出来。 (这个资源处理器还能处理/public、 /resources和/META-INF/resources的静态内容。 ) 
    • 因为Classpath里有Tomcat (通过Web起步依赖传递引用) , 所以会启动一个嵌入式的Tomcat容器,监听8000端口。

    第三章  自定义配置


    本章内容:两种影响自动配置的方式——使用显式配置进行覆盖和使用属性进行精细化配置。另外还有如何使用Spring Boot提供的钩子引入自定义的错误页。

    3.1 覆盖 Spring Boot 自动配置 

    3.1.1 保护应用程序 

    安全工作:配置Spring Security起步依赖

     现在在浏览器里打开该应用程序,马上就会看到HTTP基础身份验证对话框。此处的用户名是user,密码是在应用程序每次运行时随机生成后写入日志的,需要查找日志消息(默认写入标准输出) ,找到此类内容: 

     

     

    这太粗糙了,对用户不友好,所以引出了希望修改Spring Security的一些配置,至少要有一个好看一些的登录页,还要有一个基于数据库或LDAP(Lightweight Directory Access Protocol)用户存储的身份验证服务。 

    3.1.2 创建自定义的安全配置

    覆盖自动配置很简单,就当自动配置不存在,直接显式地写一段配置。

    在Spring Security的场景下,这意味着写一个扩展了WebSecurityConfigurerAdapter的配置类:

    1. package com.ggqq.chaptertest;
    2. import org.springframework.beans.factory.annotation.Autowired;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    5. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    6. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    7. import org.springframework.security.core.userdetails.UserDetails;
    8. import org.springframework.security.core.userdetails.UserDetailsService;
    9. import org.springframework.security.core.userdetails.UsernameNotFoundException;
    10. @Configuration
    11. public class SecurityConfig extends WebSecurityConfigurerAdapter {
    12. @Autowired
    13. private ReaderRepository readerRepository;
    14. @Override
    15. protected void configure(HttpSecurity http) throws Exception {
    16. http
    17. .authorizeRequests()
    18. .antMatchers("/").access("hasRole('READER')")//要求登录者有READER角色
    19. .antMatchers("/**").permitAll()
    20. .and()
    21. .formLogin()
    22. .loginPage("/login") //设置登录表单的路径
    23. .failureUrl("/login?error=true");
    24. }
    25. @Override
    26. protected void configure(
    27. AuthenticationManagerBuilder auth) throws Exception {
    28. auth
    29. .userDetailsService(new UserDetailsService() { //定义自定义UserDetailsService
    30. @Override
    31. public UserDetails loadUserByUsername(String username)
    32. throws UsernameNotFoundException {
    33. UserDetails userDetails = readerRepository.getOne(username);
    34. if (userDetails != null) {
    35. return userDetails;
    36. }
    37. throw new UsernameNotFoundException("User '" + username + "' not found.");
    38. }
    39. });
    40. }
    41. }

    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 通过属性文件外置配置

    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属性: 

     3.3 定制应用程序错误页面 

    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

    1. <html>
    2. <head>
    3. <title>Oops!title>
    4. <link rel="stylesheet" th:href="@{/style.css}">link>
    5. head>
    6. <html>
    7. <div class="errorPage">
    8. <span class="oops">Oops!span><br/>
    9. <img th:src="@{/MissingPage.png}">img>
    10. <p>There seems to be a problem with the page you requested
    11. (<span text="${path}">/readingListspan>).p>
    12. <p th:text="${'Details: ' + message}">p>
    13. div>
    14. html>
    15. html>

    其中有两处特别的信息需要呈现:错误的请求路径和异常消息。但这还不是错误页上的全部细节。默认情况下,Spring Boot会为错误视图提供如下错误属性:

    • timestamp:错误发生的时间。 
    • status:HTTP状态码。 
    • error:错误原因。 
    • exception:异常的类名。 
    • message:异常消息(如果这个错误是由异常引起的) 。 
    • errors:BindingResult异常里的各种错误(如果这个错误是由异常引起的) 。
    • trace:异常跟踪信息(如果这个错误是由异常引起的) 。 
    • path:错误发生时请求的URL路径。

  • 相关阅读:
    「运维有小邓」通过审计用户活动和AD域内部变化来确保GDPR合规
    移动app安全检测报告有什么作用?
    数据库模式笔记 --- 如何在开发中选择合适的数据库+关系型数据库是谁发明的?
    jsp页面编码解析规则
    Dubbo路由规则:静态标签的使用与扩展
    自动化测试框架有哪几种?搭建的思路是什么?完整指南奉上!
    自己实现 SpringMVC 底层机制 系列之-实现任务阶段 4- 完成自定义@Service 注解功能
    kafka集群下线broker节点实践方法
    智慧化工园区信息化整体解决方案:PPT全53页,附下载
    52基于MATLAB的希尔伯特Hilbert变换求包络谱
  • 原文地址:https://blog.csdn.net/Mcdull__/article/details/126541822