Spring Boot学习笔记

一、Spring Boot入门

1.Spring Boot简介

  • 主要作用:简化Spring的开发,免去了SpringMVC各种配置文件的繁琐,并且部署项目不需要打成war

2.微服务

  • 2014,Martin Fowler

  • In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.

    James Lewis and Martin Fowler (2014)

  • 每一个应用都被拆解成一组小型服务,服务之间可以交互,通常用HTTP进行;

  • 每一个服务都独立部署,可独立升级和替换

3.环境准备

1.IDEA

2.maven

4.Spring Boot HelloWorld

1.新建maven项目

2.配置pom.xml,导入Spring Boot相关依赖

3.编写main函数,用来启动SpringBootApplication

/**
 *@SpringBootApplication 标注此函数是一个SpringBootApplication
 * */
@SpringBootApplication
public class HelloSpringBoot {
    public static void main(String[] args) {
        SpringApplication.run(HelloSpringBoot.class,args);

    }
}

4.编写Contorller

@Controller
public class HelloController {

    @ResponseBody
    @RequestMapping("/hello")
    public String hello(){
        return "Hello World!";
    }

}

5.运行 main

​ 浏览器中输入http://localhost:8080/hello ,浏览器即可得到response(“Hello World!”)

6.将项目打成jar包,如下

在这里插入图片描述

7.一键部署

在这里插入图片描述

8.SpringBootApplication-HelloWorld源码解析

  • /org/springframework/boot/spring-boot-autoconfigure/2.3.3.RELEASE/spring-boot-autoconfigure-2.3.3.RELEASE.jar包下配置了javaEE的整合解决方案

  • /org/springframework/boot/spring-boot-autoconfigure/2.3.3.RELEASE/spring-boot-autoconfigure-2.3.3.RELEASE.jar/META-INF/spring.factories实现自动配置功能组件,免去了手动配置的工作

9.使用Spring Initializer快速创建Spring Boot项目

  • main已经写好,只需要我们写一下Controller
  • resources文件夹中目录结构
    • static 保存静态文件
    • Templates 保存页面模版(默认不支持jsp,Spring Boot默认 jar包使用嵌入式的Tomcat);可使用模版引擎(freemarker、thymeleaf)
    • application.properties 配置Spring Boot各种参数,可修改一些默认设置(如 server.port=8081 可将服务器端口号换成8081)
  • 【注】
    • 问题:IDEA可能卡在reading pom.xml
    • 原因:默认从Apache的服务器取jar包,服务器在美国,出现卡顿
    • 解决方案:1)删除.mvn文件夹 2)配置使用本地的maven及setting.xml

二、配置文件

1.配置文件(修改Spring Boot自动配置的默认值)

Spring Boot使用全局的配置文件(文件名固定)

  • application.properties
  • application.yml(yml 全称 YMAL : Ain’t a Markup langauage,比json、xml更简洁)

yml:

server:
  port: 8081

xml

<server>
	<port>8081</port>
</server>

2.YAML基本语法

1.基本语法

  • k: v 键值对
  • 空格的缩进控制层级关系;(左对齐的为同一层级)
  • 大小写敏感

2.值的写法

  • 数字、字符串、布尔等普通值

    • k: v 直接写
    • 字符串默认不加单引号和双引号,如果加了
      • “” :“a \n b” 输出 a (换行) b; \n会生效
      • ‘’ : ‘a \n b’ 输出 a \n b ; \n 不生效
  • 对象、Map

    1. 对象
    Friends:
      lastName: zhangsan
      age: 20
    

    or

    Friends: {lastName: zhangsan,age: 20}
    
    1. 数组
    pets:
     - cat
     - dog
     - pig
    

    or

    pets: [cat,dog,pig]
    

    3.在配置文件中配置类的值

  • 首先在要配置的类开头加上如下注解

/*
* ConfigurationProperties: 告诉SpringBoot将本类中的所有属性 在 配置文件中进行配置
* Component:容器 只有这个组件放到容器中,才能使用上述的配置功能
* */
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    String name;
    String password;
    Dog dog;
  • 在引用使用person的注解时,需要加上@Autowired
/*
*
@Autowired 保证person能够联系上配置文件中的内容
* */
@SpringBootTest
class QuickStartSpringbootapplicationApplicationTests {
  
    @Autowired
    Person person;

下面给出两种配置文件

  • yml
person:
  name: zhangsan
  passwrod: 18
  dog:
    name: dog1
  • properties
person.name=jack
person.password=1234
person.dog.name=dog1

1.properties文件中文乱码问题

  • 勾选以下配置,重建properties文件(防止不自动刷新)
    在这里插入图片描述

2.@Value和@ConfigurationProperties

@ConfigurationProperties@Value
功能批量配置一个个配置
松散语法支持:如lastName可写成last-name不支持
SpEL不支持支持:如11*2可算出结果
复杂类型封装支持不支持
  • @Value只能赋予基本类型的值
  • @Value的优先级较@ConfigurationProperties高

使用场景

  • 需要单个配置文件中的值,使用@Value
  • 需要配置某个javaBean,使用@ConfigurationProperties

@Value示例

@Component
//@ConfigurationProperties(prefix = "person")
public class Person {
    @Value("${person.name}")
    String name;
    @Value("#{11*22}")//@Value的优先级较@ConfigurationProperties高
    String password;
    //@Value("${person.dog}")
    Dog dog;
@RestController
public class HelloController {

    @Value("${person.name}")
    private String name;

    @RequestMapping("/sayHello")
    public String sayHello(){return "hello "+this.name;}

}

3.@PropertySource和@ImportResource

  1. @PropertySource加载指定的**.properties**配置文件(@ConfigurationProperties默认从全局配置文件获取值)
@PropertySource(value = {"classpath:person.properties"})//从指定配置文件获取数据
@Component
@ConfigurationProperties(prefix = "person")//从applicaiton.properties(全局配置文件)获取数据
public class Person {
    //@Value("${person.name}")
    String name;
    @Value("#{11*22}")//@Value的优先级较@ConfigurationProperties高
    String password;
    Dog dog;//@Value不能获取复杂类型,故此处不可用
  1. @ImportResource 在导入Spring专属的**.xml配置文件,使其生效**
@ImportResource(locations = {"classpath:beans.xml"})//在主配置类中导入Spring的配置文件
@SpringBootApplication
public class QuickStartSpringbootapplicationApplication {

    public static void main(String[] args) {
        SpringApplication.run(QuickStartSpringbootapplicationApplication.class, args);
    }
}
  • Spring的配置文件示例(beans.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="helloService" class="com.springinitializer.quickstartspringbootapplication.service.HelloService"</bean>
  
</beans>
  1. SpringBoot推荐给容器中添加组件的方式:@Bean注解的Spring配置类 (相当于一个Spring配置文件,而且能自动生效)
/*
*配置类 = Spring配置文件+@ImportResource
*
* */
@Configuration//指明当前类为Spring配置类:相当于Spring配置文件
public class MyAppConfig {
    //@Bean标志此方法是一个组件,该组件的返回值添加到容器中,id默认为方法名
    @Bean
    public HelloService helloService(){
        System.out.println("第一个Spring配置类:");
        return new HelloService();
    }

4.配置文件占位符(${})

  1. 随机数
person.name=张三${random.uuid}
person.password=1234
person.dog.name=dog1

2.获取之前配置的值(若没有配置 : 冒号代表默认值)

person.password=1234${person.hello:hello}
person.dog.name=dog1${person.password} 

5.Profile文件

1.多Profile文件

实际开发时可能用到多套配置文件(开发、测试、上线),所以写多个.properties文件(默认为application.properties)
在这里插入图片描述

2.激活指定profile

​ 将以下代码写入application.properties

spring.profiles.active=dev

3.yml的多文档块(— 表示分块)

#---代表分块
#激活某一块,用如下的active:profile-name
server:
  port: 8081
spring:
  profiles:
    active: development
---
server:
  port: 8082
spring:
  profiles: development
---
server:
  port: 8083
spring:
  profiles: product

4.命令行激活

运行时,可以通过命令指定profile文件(–spring.profiles.active=dev)

java -jar XXX.jar --spring.profiles.active=dev;

【注】:运行jar包时才可以用

6.配置文件加载位置

  • Spring Boot默认到以下位置扫描application.properties 或者 application.yml作为Spring boot的默认配置文件

    • file:./config/
    • File:./
    • Classpath:/config/
    • Classpath:/

    【注】:以上按照优先级从高到低的顺序,所有位置的文件都会被加载,不同内容互补,相同

    高优先级配置会覆盖低优先级内容

    可以通过配置spring.config.location来改变默认配置

7.外部配置加载顺序

SpringBoot也可以从以下位置加载配置;优先级从高到低;不同内容互补,相同的高优先级覆盖低优先级

1.命令行参数

java -jar XXX.jar --server.port=8087 --server.context-path=/abc

2.加载顺序

  • 优先加载带profile
    • jar包外部带profile的文件(application-{profile}.properties或application.yml(带spring.profile))
    • jar包内部带profile的文件
  • 再加载不带profile
    • jar包外部不带profile
    • jar包内部不带profile

总结:1)先看带不带profile(带profile的优先级高)

​ 2)再看在jar包内外部(jar包外部配置文件优先级高)

【注】:将配置文件放到jar包外部(同一文件夹下),配置文件会自动被加载

8.自动配置原理

配置文件的属性参照

自动配置原理:

  1. SpringBoot启动时加载主配置类,开启自动配置@EnableAutoConfiguration
  2. 看我们需要的功能在SpringBoot中有没有默认写好的自动配置类
  3. 看该自动配置中有哪些组件
  4. 手动给自动配置类中添加组件时,会从properties中获取某些属性,我们就要在properties文件中指定这些属性的值

三、日志

1.日志框架

  • 市面上日志框架

    • JUL,JCL,Jboss-logging,logback,log4j,log4j2,slf4j…
    日志门面(日志的抽象层)日志实现
    JCL(Jakarta Commons Logging), SLF4j(Simple Logging Facade for Java), jboss-loggingLog4j, JUL(java.util.logging), Log4j2, Logback

    使用时左边选一个门面(抽象层),右边选一个实现;

    日志门面:SLF4j

    日志实现:Logback

  • SpringBoot:底层是Spring框架,而Spring框架默认是JCL;但是SpringBoot选用SLF4j和Logback

2.SLF4j使用

1.日志框架的使用问题

开发时,不应该直接调用日志的实现类,而是调用日志抽象层(日志门面)里的方法;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }

SLF4J的工作原理见下图:

在这里插入图片描述

每一个日志的实现框架都有自己的配置文件,使用SLF4J后,各个日志实现框架的配置文件保持不变

2.遗留问题

如何统一日志记录

当依赖其他框架(Spring(commons-logging),Hibernate(jobs-logging),MyBatis…)时,如何统一?

答案:用XXX-slf4j.jar来代替原有的jar包(如log4j-over-slf4j.jar 代替 log4j.jar)

在这里插入图片描述

3.将系统中所有的日志都统一到slf4j具体步骤

  1. 将系统中其他日志框架先移除
  2. 用XXX-slf4j.jar替换原有的jar包
  3. 导入slf4j其它的实现

3.SpringBoot的日志依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-logging</artifactId>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>

4.日志使用

1.默认配置

SpringBoot默认帮我们配置好了日志,代码示例如下:

//使用日志打印信息
    Logger logger = LoggerFactory.getLogger(getClass());
    @Test
    void contextLoads() {
        //System.out.println(person);
        //日志的级别,由低到高,可以调整输出的日志级别,spring默认输出info及以上的级别
        logger.trace("这是trace日志");
        logger.debug("这是debug日志");
        logger.info("这是info日志");
        logger.warn("这是warn日志");
        logger.error("这是error日志");
    }

Spring默认输出info及以上级别的日志信息,也可以自己配置日志输出级别(在*.properties 或 *.yml),yml文件配置示例:

  • 指定日志文件path
logging:
  level:
    root: trace
  file:
    path: /Users/wjtang/IdeaProjects
  • 指定日志文件name
logging:
  level:
    root: trace
  file:
    name: /Users/wjtang/IdeaProjects/testLog.log
  • 指定输出格式pattern
logging:
  level:
    root: trace
  file:
    name: /Users/wjtang/IdeaProjects/testLog.log
  pattern:
    console:
    file:

5.切换日志框架

可以按照slf4j的日志适配图,进行相关的切换;

四、Spring Boot与Web开发

1.使用SpringBoot;

  • 创建SpringBoot应用,选中我们需要的模块(web,SQL(MyBatis),noSQL(Redis,MongoDB))
  • SpringBoot默认将这些模块配置好
  • 自己编写业务代码

自动配置原理

该场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?…

XXXAutoConfiguration;//给容器中自动配置组件
XXX.properties;//配置文件

2.SpringBoot对静态资源的映射规则

1.webjars(http://www.webjars.org)

将用到的前端框架(jquery,BootStrap…)以maven的形式给出,比如此处引用jquery:

				<dependency>
            <groupId>org.webjars</groupId>
            <artifactId>jquery</artifactId>
            <version>3.5.1</version>
        </dependency>
  • 所有**/webjars/****相关的请求都去以下地址找:/META-INF/resources/webjars/

在这里插入图片描述

比如此处的jquery,访问地址为:localhost:8080/webjars/jquery/3.5.1/jquery.js

在这里插入图片描述

  • "/**"访问当前项目的任何静态资源,路径如下:
 "/META-INF/resources/"
 "classpath:/resources/"
 "classpath:/static/"
 "classpath:/public/
 "/"
  • 欢迎页面:静态资源文件夹(resources)下的所有index.html页面;被“/**”映射

  • 配置喜欢的图标(命名为favicon.icon):也放在resources下,/**/favicon.icon都可被访问

  • 自定义静态资源文件夹路径,在配置文件中添加如下配置:

spring.resources.static-locations=classpath:/hello,classpath:/atguigu

3.模版引擎

模版引擎(如JSP,Velocity,Freemarker,Thymeleaf…)的作用(将Data和html结合,生成最终的页面),如下图:

在这里插入图片描述

Thymeleaf (SpringBoot推荐),语法更简单,功能更强大;

1.引用Thymeleaf

				<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

2.Thymeleaf的使用&语法

public class ThymeleafProperties {
    private static final Charset DEFAULT_ENCODING;
    public static final String DEFAULT_PREFIX = "classpath:/templates/";//默认前缀(SpringMVC中的prefix)
    public static final String DEFAULT_SUFFIX = ".html";//默认后缀
    private boolean checkTemplate = true;
    private boolean checkTemplateLocation = true;
    private String prefix = "classpath:/templates/";//html文件默认存放的路径
    private String suffix = ".html";
    private String mode = "HTML";
    private Charset encoding;
  //只要将html文件放入classpath:/templates/中,thymeleaf就可以直接识别并渲染
1.使用方法
  • 导入thymeleaf的名称空间(在html中)
<html xmlns:th="http://www.thymeleaf.org">
  • 使用thymeleaf语法
<!--一个简单的使用thymeleaf的 html sample-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Test of thymeleaf</title>
</head>

<body>
成功!
<div th:text="${hello}"></div><!--设置div的文本内容-->
</body>
</html>

1)使用th来设置或替换

的各个属性,具体用法可在thymeleaf官方参考文档中找到

<!-- th:id 可以替换原有的 id="div01",th:text可设置这个<div>显示的文本-->
<div id="div01" class="myDiv" th:id="${hello}" th:class="${hello}" th:text="${hello}"></div>

2)表达式,在thymeleaf官方参考文档中(4.Standard Expression Syntax)可找到

Simple expressions:
Variable Expressions: ${...} //获取变量值;OGNL语法;
															功能:1)获取变量的值,调用方法
																	 2)获取内置的基本对象:Expression Basic Objects
																	  #ctx: the context object.
                                    #vars: the context variables.
                                    #locale: the context locale.
                                    #request: (only in Web Contexts) the HttpServletRequest object.
                                    #response: (only in Web Contexts) the HttpServletResponse object.
                                    #session: (only in Web Contexts) the HttpSession object.
                                    #servletContext: (only in Web Contexts) the ServletContext object.
                                    3)内置的工具对象:Expression Utility Objects
                                    #execInfo: information about the template being processed.
                                    #messages: methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
                                    #uris: methods for escaping parts of URLs/URIs
                                    #conversions: methods for executing the configured conversion service (if any).
                                    #dates: methods for java.util.Date objects: formatting, component extraction, etc.
                                    #calendars: analogous to #dates, but for java.util.Calendar objects.等等
                                
							
Selection Variable Expressions: *{...}
Message Expressions: #{...}
Link URL Expressions: @{...}
Fragment Expressions: ~{...}
Literals
Text literals: 'one text', 'Another one!',…
Number literals: 0, 34, 3.0, 12.3,…
Boolean literals: true, false
Null literal: null
Literal tokens: one, sometext, main,…
Text operations:
String concatenation: +
Literal substitutions: |The name is ${name}|
Arithmetic operations:
Binary operators: +, -, *, /, %
Minus sign (unary operator): -
Boolean operations:
Binary operators: and, or
Boolean negation (unary operator): !, not
Comparisons and equality:
Comparators: >, <, >=, <= (gt, lt, ge, le)
Equality operators: ==, != (eq, ne)
Conditional operators:
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
Special tokens:
No-Operation: _

4.对SpringMVC的自动配置

  • 当开发web应用(Developing Web Applications)时,SpingBoot对SpringMVC做了大量自动配置(WebMvcAutoConfiguration.class)
7.1.1. Spring MVC Auto-configuration
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.

The auto-configuration adds the following features on top of Spring’s defaults:

Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.

Support for serving static resources, including support for WebJars (covered later in this document)).

Automatic registration of Converter, GenericConverter, and Formatter beans.

Support for HttpMessageConverters (covered later in this document).

Automatic registration of MessageCodesResolver (covered later in this document).

Static index.html support.

Custom Favicon support (covered later in this document).

Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).

If you want to keep those Spring Boot MVC customizations and make more MVC customizations (interceptors, formatters, view controllers, and other features), you can add your own @Configuration class of type WebMvcConfigurer but without @EnableWebMvc. //1.在默认配置基础上扩展配置;
  //[注]:class of type WebMvcConfigurer 指这个class要继承WebMvcConfigurer

If you want to provide custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or ExceptionHandlerExceptionResolver, and still keep the Spring Boot MVC customizations, you can declare a bean of type WebMvcRegistrations and use it to provide custom instances of those components.//2.在默认配置基础上扩展配置

If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with @EnableWebMvc, or alternatively add your own @Configuration-annotated DelegatingWebMvcConfiguration as described in the Javadoc of @EnableWebMvc.//3.替换原有配置

【注】:以上配置都是从SpringBoot官方文档中找到的

  • 在默认配置基础上扩展配置,可以写一个SpringBoot配置类,示例如下:
/*
* 在默认配置基础上扩展配置
* */
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    //ctrl+o 找出当前可以重写的方法
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/atguigu").setViewName("/success");
    }
}

5.如何修改SpringBoot的默认配置

模式:

  • SpringBoot在自动配置很多组件时,会先看下容器中有没有用户自己配置的组件(@Bean,@Component),优先使用或合并使用用户配置;

  • 在SpringBoot中会有很多xxxConfiguration.class帮助我们进行扩展配置;

6.Restful

1.默认访问首页:

在controller中写入如下代码:

@RequestMapping({"/","/index.html"})
    public String index(){
        return "index";
    }

2.国际化

国际化:网页的语言根据浏览器语言自动改变 or 点击页面某个按钮切换语言;

  • SpringMVC方式

    • 编写国际化配置文件
    • 使用ResourceBundleMessageResource管理国际化资源文件
    • 在页面使用fmt:message取出国际化内容
  • SpringBoot方式

    • 编写国际化配置文件,抽取页面需要显示的国际化消息在这里插入图片描述

    • SpringBoot自动配置好了管理国际化配置文件的组件

    • application.properties文件中添加如下配置:

      spring.messages.basename=i18n.login
      
    • 页面获取国际化配置文件中的值,实现根据浏览器语言自动切换网页语言

    <form class="form-signin" action="dashboard.html">
    			<img class="mb-4" th:src="@{asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72">
    			<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
    			<label class="sr-only" th:text="#{login.username}">Username</label>
    			<input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus="">
    			<label class="sr-only" th:text="#{login.password}">Password</label>
    			<input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required="">
    			<div class="checkbox mb-3">
    				<label>
              <input type="checkbox" value="remember-me"/> [[#{login.remember}]] <!--这里特殊(这是个单选框),要注意-->
            </label>
    			</div>
    			<button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button>
    			<p class="mt-5 mb-3 text-muted">© 2017-2018</p>
    			<a class="btn btn-sm">中文</a>
    			<a class="btn btn-sm">English</a>
    		</form>
    		
    <!--	th:text="#{login.username},th:placeholder="#{login.password},[[#{login.remember}]] 实现了获取国际化配置文件的值		--> 
    
    • 点击网页按钮切换语言

1)在html中配置好切换语言按钮的 超链接&参数(如zh_CN,代表语言_国家)

			<a class="btn btn-sm" th:href="@{/login.html(l='zh_CN')}">中文</a>
			<a class="btn btn-sm" th:href="@{/login.html(l='en_US')}">English</a>
			<!-- 没有模版引擎时,参数:login.html@l=zh_CN
				 有模版引擎之后,参数:login.html(l='zh_CN')-->

2)写一个“语言解析类”,解析前端传来的参数如zh_CN:

package com.atguigu.springwebrestfulcrud.component;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/*
* 自己配置的"语言解析类"
* 实现点击网页按钮切换显示语言功能
* */
public class MyLocaleResolver implements LocaleResolver {
    @Override
    public Locale resolveLocale(HttpServletRequest httpServletRequest) {
        String languange = httpServletRequest.getParameter("l");//获取前端传来的参数,如zh_CN
        Locale locale = Locale.getDefault();//如果参数为空,使用浏览器默认语言
        if (!StringUtils.isEmpty(languange)){ //参数不空,则解析,并使用参数所代表的语言
            String split[] = languange.split("_");
            locale = new Locale(split[0],split[1]);

        }
        return locale; 
    }

    @Override
    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

    }
}

​ 3)在SpringBoot配置类中注册MyLocaleResolver(@Bean)

/在SpringBoot配置类中添加组件,加载我们自己配置的"语言解析类"
    @Bean
    public LocaleResolver localeResolver(){
        return  new MyLocaleResolver();
    }

3.登录

1)热部署
  • 关闭thymeleaf缓存(在application.properties中添加配置)
#禁用thymeleaf的缓存
spring.thymeleaf.cache=false
  • Ctrl+F9 / Command+Shift+F9 修改页面后,此快捷键可以重新编译
2)处理登录请求
@Controller
public class LoginController {
    //@RequestMapping(value = "/user/login",method = RequestMethod.POST) =  @PostMapping(value = "/user/login")
    @PostMapping(value = "/user/login")
    public String login(@RequestParam("username")String username,
                        @RequestParam("password")String password,
                        Map<String,Object> map){
        if(!StringUtils.isEmpty(username)&&password.equals("123456")){//登录成功返回主页
            return "dashboard";
        }else {
            map.put("msg","用户名密码错误");
            return "login";//登录失败,返回登录页面,并附带登录失败的信息msg
        }
    }

login.html中显示登录失败的信息(登录验证不通过时):

<!--登录失败时显示-->
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
3) css样式丢失的问题

首先,登录失败重返登录页面后,可能会css样式丢失

原因:路径问题

解决方案:加上一个/,则使用绝对路径,即localhost:8080+显示网页源代码中的路径;

如果还不行,转到下面的【注2】;

#th:href="@{asserts/css/signin.css} #加上/ ,变成下面的配置
th:href="@{/asserts/css/signin.css}

【注】:图标丢失也是一样的问题,一样的解决方案;

【注2】:显示网页源码;点击xxx.css文件;在浏览器地址栏直接修改地址,观察哪个地址可以访问到,做相应修改;

其次,提交登录后,进入主页面,主页面可能会css样式丢失;

  • 重定向处理
@Controller
public class LoginController {
    //@RequestMapping(value = "/user/login",method = RequestMethod.POST) =  @PostMapping(value = "/user/login")
    @PostMapping(value = "/user/login")
    public String login(@RequestParam("username")String username,
                        @RequestParam("password")String password,
                        Map<String,Object> map){
        if(!StringUtils.isEmpty(username)&&password.equals("123456")){
            //return "dashboard"; //登录成功返回主页
          	//------------------
            return "redirect:/main.html";//防止表单重复提交,可以重定向到主页
          //--------------------
          
        }else {
            map.put("msg","用户名密码错误");
            return "login"; //登录失败,返回登录页面,并附带登录失败的信息msg
            //return "redirect:/loginFail";
        }
    }
}
  • 在SpringBoot配置类(MyMvcConfig)加如下代码:
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
    //ctrl+o 找出当前可以重写的方法
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/atguigu").setViewName("/success");
      //---------------
        registry.addViewController("/main.html").setViewName("dashboard");
      //---------------
        //registry.addViewController("/loginFail").setViewName("login");
    }
4)拦截器
  • 在LoginController中添加 HttpSession session 并且将刚刚登录的用户放到session中
@Controller
public class LoginController {
    //@RequestMapping(value = "/user/login",method = RequestMethod.POST) =  @PostMapping(value = "/user/login")
    @PostMapping(value = "/user/login")
    public String login(@RequestParam("username")String username,
                        @RequestParam("password")String password,
                        Map<String,Object> map,
                        HttpSession session){
        if(!StringUtils.isEmpty(username)&&password.equals("123456")){
            //return "dashboard"; //登录成功返回主页
          
            session.setAttribute("loginUser",username);
          
            return "redirect:/main.html";//防止表单重复提交,可以重定向到主页
        }else {
            map.put("msg","用户名密码错误");
            return "login"; //登录失败,返回登录页面,并附带登录失败的信息msg
            //return "redirect:/loginFail";
        }
    }
}
  • 编写拦截器
/*
* 拦截器,
* 登录检查
* */
public class LoginHandlerInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Object user = request.getSession().getAttribute("loginUser");//从session中获取用户
        if (user == null){//未登录
            request.setAttribute("msg","没有权限登录");
            request.getRequestDispatcher("/login.html").forward(request,response);
            return false;
        }else {//已登录
            return true;
        }
    }
  • 注册拦截器
		/*
    * 注册拦截器,使之生效
    * 防止越过登录非法访问
    * */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**")//代表对所有路径进行拦截
        .excludePathPatterns("/login.html","/","/user/login","/asserts/**","/webjars/**");//有些路径和静态资源不拦截
    }

【注】:有些路径和静态资源不能拦截,否则连登录都无法完成;SpringBoot2.0默认对静态资源拦截,要手动使其不拦截需要的文件;

4.CRUD-用户列表功能

实验要求:

1) RestfulCRUD:CRUD满足restful风格;

URL:/资源名称/资源标识;通过“HTTP请求方式”来区分对资源CRUD操作

CRUDRestfulCRUD
查询getEmpemp-GET
添加addEmp?xxxemp-POST
修改updateEmp?id=xxx&name=xxxemp/{id}-PUT
删除deleteEmp?id=xxxemp/{id}-DELETE
2) 实验的请求架构
请求URL请求方式
查询所有员工empsGET
查询单个员工emp/{id}GET
来到添加页面empGET
添加员工empPOST
来到修改页面emp/{id}GET
修改员工信息empPUT
删除员工emp/{id}DELETE
3) 员工列表
抽取页面公共元素
<!DOCTYPE html>
#1.抽取公共片段
<html xmlns:th="http://www.thymeleaf.org">
  <body>
    <div th:fragment="copy">
      &copy; 2011 The Good Thymes Virtual Grocery
    </div>
  </body>
</html>

#2.引入公共片段
<body>
  ...
  <div th:insert="~{footer :: copy}"></div> #~{} 可以省略; footer是1.公共片段的文件名(footer.html)
</body>

【注】:样例代码 来自于 thymeleaf官网

示例如下:

#1.在dashboard.html,抽取公共片段topbar
<body>
		<nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
      
#2.在list.html中,引入公共片段
    <!--		引入抽取的topbar-->
		<!--		模版名:会使用thymeleaf的前后缀配置规则进行解析-->
		<div th:insert="~{dashboard::topbar}"></div>

#3.最终效果,点击下图1处,2不会变化(不做公共片段,则2会变化)

在这里插入图片描述

实际上,thymeleaf包含三种配置“页面公共元素”的方式

  • th:insert is the simplest: it will simply insert the specified fragment as the body of its host tag.
  • th:replace actually replaces its host tag with the specified fragment.
  • th:include is similar to th:insert, but instead of inserting the fragment it only inserts the contents of this fragment.

不用th:fragment实现引用公共元素

#1.指明公共元素 的id
...
<div id="copy-section">
  &copy; 2011 The Good Thymes Virtual Grocery
</div>
...

#2.在另一个页面引用这个 id
<body>
  ...
  <div th:insert="~{footer :: #copy-section}"></div>
</body>

【注】:样例代码 来自于 thymeleaf官网

示例如下:

#在dashboard.html中指定 侧边栏(公共元素)的id
				<!--				指明 侧边栏(公共元素)的id-->
				<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar">
         
#2.在list.html中 引用这个 id
         <!--				删除之前的 nav-->
         <!--				引入侧边栏,通过id的方式 #id-->
				<div th:replace="dashboard::#sidebar"></div>

最终效果如下:点击“员工管理”,侧边栏不发生变化(不做上面的操作则会变化)

在这里插入图片描述

动态高亮
  • 首先将dashboard的topbar和sidebar独立出来放到commons/bar.html中
<!--引入topbar-->
    <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0" th:fragment="topbar">
        <a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
        ...
  • 其次将dashboard.html中的topbar和sidebar替换为引用的方式;将其它引用到topbar和sidebar的位置作相应修改(list.html);
#1.在原topbar和sidebar的位置替换成引用的形式
<!--	引入topbar-->
		<div th:replace="commons/bar::topbar"></div>
<!--		引入侧边栏 sidebar-->
				<div th:replace="commons/bar::#sidebar"></div>


#2.其它用到topbar和sidebar的地方作修改(如此处的list.html)
<!--		引入抽取的topbar-->
<!--		模版名:会使用thymeleaf的前后缀配置规则进行解析-->
		<div th:replace="~{commons/bar::topbar}"></div>
<!--				引入侧边栏sidebar,通过id的方式 #id-->
				<div th:replace="commons/bar::#sidebar"></div>
  • 在bar.html的侧边栏(sidebar)添加如下判断
th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}" //此处对应于dashboard.html
th:class="${activeUri=='emps'?'nav-link active':'nav-link'}"  //此处对应于员工列表页面 list.html
  • 最后在对应的页面(dashboard.html ,list.html)添加上参数;
<div th:replace="commons/bar::#sidebar(activeUri='main.html')"></div>  //此处对应于dashboard.html
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>   //此处对应于员工列表页面 list.html
修改list.html样式
  • list.html展示的数据该成从后端获取来的(而不是写死在list.html中的)
#修改<tbody></tbody>中的数据项;
#emps是处理后段Controller传过来的
				<thead>
								<tr>
									<th>id</th>
									<th>lastName</th>
									<th>email</th>
									<th>gender</th>
									<th>department</th>
									<th>birth</th>
									<th>操作</th>
								</tr>
							</thead>
							<tbody>
								<tr th:each="emp:${emps}">
									<td th:text="${emp.id}"></td>
									<td th:text="${emp.lastName}"></td>
									<td th:text="${emp.email}"></td>
									<td th:text="${emp.gender}==0?'':''"></td>
									<td th:text="${emp.department.departmentName}"></td>
									<td th:text="${#dates.format(emp.birth,'dd-MM-yyyy HH:mm')}"></td>
									<td>
										<button class="btn btn-sm btn-primary">编辑</button>
										<button class="btn btn-sm btn-danger">删除</button>
									</td>
								</tr>
							</tbody>

页面效果展示:在这里插入图片描述

4)员工添加
员工添加页面
  • list.html的按钮添加超链接
<h2><a class="btn btn-success" href="emp" th:href="@{/emp}">员工添加</a></h2>
  • 为超链接写后台
@RequestMapping("/emp")
    public String toAddPage(Model model){
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("depts",departments);//查出所有部门id,传到add.html
      
        return "emp/add";//返回add.html
    }
  • 编写员工添加页面add.html
#主要是写好下面的form表单,其它的可以复制list.xml
<form th:action="@{/emp}" method="post">
						<!--发送put请求修改员工数据-->
						<!--
						1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的)
						2、页面创建一个post表单
						3、创建一个input项,name="_method";值就是我们指定的请求方式
						-->
						<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/>
						<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
						<div class="form-group">
							<label>LastName</label>
							<input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}">
						</div>
						<div class="form-group">
							<label>Email</label>
							<input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}">
						</div>
						<div class="form-group">
							<label>Gender</label><br/>
							<div class="form-check form-check-inline">
								<input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}">
								<label class="form-check-label"></label>
							</div>
							<div class="form-check form-check-inline">
								<input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}">
								<label class="form-check-label"></label>
							</div>
						</div>
						<div class="form-group">
							<label>department</label>
							<!--提交的是部门的id-->
							<select class="form-control" name="department.id">
								<option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option>
							</select>
						</div>
						<div class="form-group">
							<label>Birth</label>
							<input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}">
						</div>
						<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
					</form>
实现员工添加功能
  • 首先配置form表单提交的地址
<form th:action="@{/emp}" method="post">
  • 处理刚刚的地址(在Controller中)
@PostMapping("/emp")
    public String addEmp(Employee employee){//SpringMVC自动将 请求参数 封装到javaBean;(属性名name要一致)
        System.out.println("保存的员工信息:"+employee);
        employeeDao.save(employee);
        //添加完成,返回员工列表页面;redirect:重定向;forward:转发到某个地址
        return "redirect:/emps";
    }

【注】:SpringMVC自动将 form中的参数 封装到javaBean(此处的javaBean是形参);(属性名name要一致)

【注2】:form中有日期时,默认用/(2019/10/03);如果要改,在application.properties中配置;

 spring.mvc.format.date=yyyy-MM-dd
5)修改员工
修改员工页面
  • 仍使用add.html(添加员工页面)
  • 编写Controller方法,处理编辑按钮对应的链接
@GetMapping("/emp/{id}")
    public String toEditPage(@PathVariable("id") Integer id,Model model){
        Employee employee = employeeDao.get(id);
        model.addAttribute("emp",employee); //将查出来的用户放到model里面供前端使用

        //查出所有部门id,在页面显示
        Collection<Department> departments = departmentDao.getDepartments();
        model.addAttribute("depts",departments);

        return "/emp/add";//edit.html修改添加
  • 添加判断,实现员工数据在编辑页面的回显
<div class="form-group">
							<label>LastName</label>
							<input name="lastName" type="text" class="form-control" placeholder="zhangsan" 	
                     th:value="${emp!=null}?${emp.lastName}">
</div>
<div class="form-group">
							<label>Email</label>
							<input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" 
                     th:value="${emp!=null}?${emp.email}">
</div>
...
<button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button>
<!-- th:value="${emp!=null}?${emp.email}" 通过这个来判断-->
实现修改员工操作
<form th:action="@{/emp}" method="post">
						<!--发送put请求修改员工数据-->
						<!--
						1、SpringMVC中配置HiddenHttpMethodFilter;(配置:spring.mvc.hiddenmethod.filter.enabled=true)
						2、页面创建一个post表单
						3、创建一个input项,name="_method";值就是我们指定的请求方式(这里是put 代表修改)
						-->
			<input type="hidden" name="_method" value="put" th:if="${emp!=null}"/><!-- emp!=null,即修改员工时才生效 -->

  • 编写Controller方法处理put请求
		//PUT 修改
    @PutMapping("/emp")
    public String updateEmployee(Employee employee){
        System.out.println("修改的员工为"+employee);
        return "redirect:/emps";
    }
6) 删除员工
<!--
				1、SpringMVC中配置HiddenHttpMethodFilter;(配置:spring.mvc.hiddenmethod.filter.enabled=true)
				2、页面创建一个post表单
				3、创建一个input项,name="_method";值就是我们指定的请求方式(这里是delete 代表删除)
-->
<form th:action="@{/emp/}+${emp.id}" method="post">
  <input type="hidden" name="_method" value="delete"/>
  <button type="submit" class="btn btn-sm btn-danger">删除</button>
</form>
  • 编写Controller方法处理delete请求
		//DELETE 删除
    @DeleteMapping("/emp/{id}")
    public String deleteEmployee(@PathVariable("id") Integer id){
        employeeDao.delete(id);
        return "redirect:/emps";
    }

7.错误处理机制

1. SpringBoot默认错误处理机制

  • 返回一个默认的错误页面
  • 若是某些客户端(如postman),返回json

【注】:具体配置可参考ErrorMvcAutoConfiguration.class

2. 定制错误页面

1)如何定制错误页面
  • 有模版引擎;
    • 地址templates/error/状态码.html;
    • 支持模糊匹配,如4xx.html可以匹配所有4开头的状态码; 优先匹配精确的状态码
    • 页面能获取的信息(可从DefaultErrorAttributes.class找);
      • timestamp :时间戳
      • status :状态码
      • error : 错误提示
      • exception :异常
      • message : 异常消息
      • errors :JSR303数据校验的错误都在这里
  • 没有模版引擎
    • 地址 : static/error/状态码.html;
    • 支持模糊匹配
    • 变量无法获取(timestamp、status…)
  • template和static下都没有错误页面
    • 使用SpringBoot默认的错误页面

下面给出具体配置方法

  • 自定义Exception类

    package com.atguigu.springwebrestfulcrud.exception; //这里新建了exception文件夹
    
    public class UserNotExistException extends RuntimeException{
        public UserNotExistException(){
            super("用户不存在");
        }
    }
    
  • 调用上面的Exception类

    //@ResponseBody返回纯文本
    @ResponseBody
    @RequestMapping("/hello")
    public String hello(@RequestParam("user")String user){
        if(user.equals("aaa")){
            throw new UserNotExistException(); 
        }
        return "Hello Spring web!!!";
    }
    
  • 在html中写入需要输出的信息(此处为4xx.html)

    <main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
       <h1>status:[[${status}]]</h1>
       <h2>timestamp:[[${timestamp}]]</h2>
    </main>
    
  • 如果用到页面可获取的信息(如message , exception…)

    #添加如下配置
    server.error.include-exception=true
    server.error.include-message=always
    
  • 测试

    • 地址: http://localhost:8080/crud/hello?user=aaa

【注】:此时实现的是浏览器返回html,Postman返回json;其中html可以定制,但是json目前还不能定制;

2)如何定制错误json提示
  • 浏览器和客户端都返回json

    package com.atguigu.springwebrestfulcrud.controller;
    
    //自定义一个ExceptionHandler
    @ControllerAdvice
    public class MyExceptionHandler {
          //1.浏览器和客户端都返回json
          @ResponseBody
          @ExceptionHandler(UserNotExistException.class)
          public Map<String,Object> handleException(Exception e){
              Map<String,Object> map = new HashMap<>();
              map.put("code","user.notexist");
              map.put("message",e.getMessage());
              return map;
          }
    }
    
  • 自适应效果:浏览器html,客户端显示json;

    //2.实现自适应;浏览器返回html,客户端返回json
    @ExceptionHandler(UserNotExistException.class)
    public String handleException(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        //传入我们自己的错误状态码 4xx 5xx
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
      
      	//配合下一步
      	request.setAttribute("extra",map);//将需要显示的错误信息放到request中,供MyErrorAttributes调用
      
        return "forward:/error";//转发到/error
    }
    

【注】:此处的“自适应效果”,json还是不能定制;若要实现json的定制,需要自定义如下ErrorAttributes

package com.atguigu.springwebrestfulcrud.component;//此处放在component包下

//给容器中加入自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String,Object> map = super.getErrorAttributes(webRequest, options);
        map.put("company","atguigu");
        Map<String,Object> extra = (Map<String, Object>) webRequest.getAttribute("extra",0);//0代表从request域中获取数据
        map.put("extra",extra);
        return map;
    }

8.配置嵌入式Servlet容器

SpringBoot默认使用的是嵌入式的Servlet容器;

【注】:Tomcat就是一个Servlet容器

1. 定制和修改Servlet相关配置
  • 方法1:修改和server有关的配置

    server.servlet.context-path=/crud
    server.port=8080
    ...
    
  • 方法2:在配置类(@Configuration)编写一个EmbeddedServletContainerCustomizer(Spring 2.0版本以上用:WebServerFactoryCustomizer)

    @Bean
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> configurableWebServerFactory(){
        return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
            @Override
            public void customize(ConfigurableWebServerFactory factory) {
                factory.setPort(8083);
            }
        };
    }
    

【注】:建议使用方法1;方便快捷,并且在静态文件中,修改不需要重新编译,替换即可;

  • 注册Servlet三大组件(Servlet,Filter,Listener)

    //注册三大组件; 在@Configuration注解的Spring配置类下
    @Bean
    public ServletRegistrationBean myServlet(){
        ServletRegistrationBean registrationBean =
                new ServletRegistrationBean(new MyServlet(),"/myServlet");
        return registrationBean;
    }
    
    @Bean
    public FilterRegistrationBean myFilter(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new MyFilter());
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/myServlet"));
        return filterRegistrationBean;
    }
    
    @Bean
    public ServletListenerRegistrationBean myListener(){
        ServletListenerRegistrationBean servletListenerRegistrationBean
                = new ServletListenerRegistrationBean(new MyListener());
        return servletListenerRegistrationBean;
    }
    
    • 当然,这三大组件要重新定义一下才能实现定制

      package com.atguigu.springwebrestfulcrud.servlet;
      
      import javax.servlet.ServletException;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      
      public class MyServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              resp.getWriter().write("hello MyServlet");
          }
      
          @Override
          protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
              super.doGet(req, resp);
          }
      }
      
      package com.atguigu.springwebrestfulcrud.filter;
      
      import javax.servlet.*;
      import java.io.IOException;
      
      public class MyFilter implements Filter {
          @Override
          public void init(FilterConfig filterConfig) throws ServletException {
      
          }
      
          @Override
          public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
              System.out.println("My Filter Process!!!");
              filterChain.doFilter(servletRequest,servletResponse);
          }
      
          @Override
          public void destroy() {
      
          }
      }
      
      package com.atguigu.springwebrestfulcrud.listener;
      
      import javax.servlet.ServletContextEvent;
      import javax.servlet.ServletContextListener;
      
      public class MyListener implements ServletContextListener {
          @Override
          public void contextInitialized(ServletContextEvent sce) {
              System.out.println("contextInitialized...web应用启动");
          }
      
          @Override
          public void contextDestroyed(ServletContextEvent sce) {
              System.out.println("contextDestroyed...web应用销毁");
          }
      }
      
2. SpringBoot配置使用其它的Servlet容器
  • 首先排除tomcat的依赖(SpringBoot默认的Servlet容器就是Tomcat)

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions> <!--    排除tomcat ,为了使用其它servlet容器  -->
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
  • 添加其它Servlet容器的依赖(如:jetty,undertow)

    <!--添加jetty的依赖 ,jetty是一种servlet容器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
    
    <!--添加undertow的依赖 ,undertow是一种servlet容器-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
    
  

【注】:上面两个servlet容器选择一个即可

## 9. 使用外置的Servlet容器

- 嵌入式Servlet容器:jar;

  - 优点:简单、便携;
  - 默认不支持JSP,优化定制比较复杂;

- 外置的Servlet容器:war;

  - 使用外部Servlet容器时,首先要创建一个war包的web项目,然后将tomcat设置为provided,即外部提供;然后将tomcat部署好,即传统做法-->Edit Configuration;

    ```xml
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
  • 运行时,要运行war项目自带的ServletInitializer

    public class ServletInitializer extends SpringBootServletInitializer {
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            return application.sources(SpringBoot04WebJspApplication.class);
        }
    
    }
    

五、Docker

1、简介

Docker是一个开源的应用容器引擎(类似于虚拟机,或者说镜像),有了Docker,进入一个新系统后就不用一个一个地下载安装软件了,而是有一个镜像,这个镜像可以直接移植配置好了的各种软件;

2、核心概念

  • docker主机(Host):安装了Docker程序的机器(Docker直接安装在操作系统之上);
  • docker客户端(Client):连接docker主机进行操作,通过命令行或其它工具使用Docker;
  • docker仓库(Repository):用来保存各种打包好的软件镜像
  • docker镜像(Images):软件打包好的镜像;放在docker仓库中;

使用Docker的步骤

  1. 安装Docker
  2. 去Docker仓库找到这个软件对应的镜像;
  3. 使用Docker运行这个镜像,这个镜像就会生成一个Docker容器;
  4. 对容器的启动停止即对软件的启动停止

3、安装Docker(for Mac)

  1. Docker下载dmg文件;
  2. 拖入Applications安装;
  3. 测试是否安装成功(docker -v);
~ wjtang$ docker -v
Docker version 19.03.13, build 4484c46d9d

4、Docker常用命令

#利用Docker查找mysql镜像
~ wjtang$ docker search mysql
NAME                              DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
mysql                             MySQL is a widely used, open-source relation…   10159               [OK]                
mariadb                           MariaDB is a community-developed fork of MyS…   3740                [OK]                
mysql/mysql-server                Optimized MySQL Server Docker images. Create…   742                                     [OK]
...

#利用Docker下载mysql
~ wjtang$ docker pull mysql
Using default tag: latest
latest: Pulling from library/mysql
bb79b6b2107f: Downloading [===============================>                   ]  16.98MB/27.09MB
49e22f6fb9f7: Download complete 
842b1255668c: Download complete 
9f48d1f43000: Download complete 
...

#下载带版本号的mysql
~ wjtang$ docker pull mysql:5.5

#查看当前系统中的镜像
~ wjtang$ docker images
REPOSITORY               TAG                 IMAGE ID            CREATED             SIZE
docker/getting-started   latest              67a3629d4d71        10 days ago         27.2MB
mysql                    latest              db2b37ec6181        3 weeks ago         545MB

#删除镜像  ~ wjtang$ docker rmi image_id
#这里的 image_id 可以通过 “查看当前系统中的镜像”即docker images命令 查出来,例如要删除查出来的mysql
~ wjtang$ docker rmi db2b37ec6181
Untagged: mysql:latest
Untagged: mysql@sha256:8c17271df53ee3b843d6e16d46cff13f22c9c04d6982eb15a9a47bd5c9ac7e2d
Deleted: sha256:db2b37ec6181ee1f367363432f841bf3819d4a9f61d26e42ac16e5bd7ff2ec18
...

#运行镜像,运行后产生容器
#-d 代表daemon,即后台运行;mysql:latest中的latest指的是上一步查出来的TAG
~ wjtang$ docker run --name mysql_personal_name -d mysql:latest

#查看当前运行的docker容器
~ wjtang$ docker ps
CONTAINER ID        IMAGE                    COMMAND                  CREATED             STATUS              PORTS                NAMES
4cefb266d041        docker/getting-started   "/docker-entrypoint.…"   8 minutes ago       Up 8 minutes        0.0.0.0:80->80/tcp   hungry_haibt
#查看所有容器
~ wjtang$ docker ps -a

#启动容器,通过容器id
docker start 4cefb266d041

#停止运行中的容器
#通过上一步查出来的id来停止
~ wjtang$ docker stop 4cefb266d041

#端口映射
#主机端口:容器端口
docker run -d -p 8888:8080 tomcat

#更多命令参考
https://docs.docker.com/engine/reference/commandline/docker

5、Docker的容器操作

  • 软件镜像–>运行镜像–>产生容器(正在运行的软件)

【注】:这些步骤可参考 “4、Docker常用命令” 所演示的步骤

六、SpringBoot与数据访问

1. JDBC

1. 使用mysql

  • 配置好jdbc的参数
spring.datasource.username=root
spring.datasource.password=15152697858.*twj
#localhost:3306/mysql 其中的mysql是数据库名
spring.datasource.url=jdbc:mysql://localhost:3306/mysql
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  • 测试
package com.atguigu.springboot;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class SpringBoot06DataJdbcApplicationTests {
    @Autowired
    DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
        connection.close();
    }

}
  • DataSourceConfiguration.class配置了与JDBC相关的配置

2. 项目运行时,运行sql脚本

  • 新建一个sql脚本

    drop table if exists test;
    create table test(id varchar(10) , name varchar(10));
    
  • 配置sql脚本

    #配置sqlscript.sql所在的路径
    spring.datasource.schema=classpath:sqlscript.sql
    spring.datasource.initialization-mode=always
    
  • 运行测试

【注】:mysql的驱动版本要正确,否则项目运行不了

3. SpringBoot操纵数据库

package com.atguigu.springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.List;
import java.util.Map;

@Controller
public class HelloController {
    @Autowired
    JdbcTemplate jdbcTemplate; //SpringBoot内置JdbcTemplate用来查询数据库

    @ResponseBody
    @RequestMapping("/queryUsers")
    public Map<String,Object> map(){
        List<Map<String, Object>> list = jdbcTemplate.queryForList("select * from Users");
        return list.get(0);
    }

}

【注】:SpringBoot内置JdbcTemplate用来查询数据库;具体的数据库配置在配置文件中(application.properties等)

4. Druid作为数据源

  • 导入druid的依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.3</version>
</dependency>
  • 配置文件
#spring.datasource.schema=classpath:sqlscript.sql #先注释掉原本的数据源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

【注】:druid这一章节没有写全,以后用到的话再补充;

2. MyBatis

1. Spring Boot整合MyBatis

  • 首先要导入mybatis-spring-boot的整合jar包依赖

    <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.3</version>
    </dependency>
    
  • 建表,并写好javaBean

1) 使用MyBatis注解
  • 写好Mapper

    @Mapper
    public interface UsersMapper {
        @Select("select * from Users where id=#{id}")
        public Users getUsersById(int id);
    
        @Delete("delete from Users where id=#{id}")
        public int deleteUsersById(int id);
    
        @Insert("insert into Users(id,name,password,money) values(#{id},#{name},#{password},#{money})")
        public int insertUsers(Users users);
    
        @Update("update Users set name=#{name} where id=#{id}")
        public int updateUsers(Users users);
    }
    
  • 写好Controller

    @RestController
    public class UsersController {
        @Autowired
        UsersMapper usersMapper;
    
        @GetMapping("/Users/{id}")
        public Users getUsers(@PathVariable("id")int id){
            Users users = usersMapper.getUsersById(id);
            return users;
        }
    		
      	//@Options(useGeneratedKeys = true,keyProperty = "id") //使用自增主键
        @GetMapping("/insertUsers")
        public Users insertUsers(Users users){
            usersMapper.insertUsers(users);
            return users;
        }
    
        @GetMapping("/deleteUsers/{id}")
        public String deleteUsers(@PathVariable("id")int id){
            int count = usersMapper.deleteUsersById(id);
            return "删除了"+count+"行的用户";
        }
    }
    
2) 使用MyBatis非注解
  • 写好MyBatis配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <!--<environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="org/mybatis/example/BlogMapper.xml"/>
        </mappers>-->
      <!--注:environments 以及 mappers 都在全局配置文件(如application.properties)中进行配置-->
    </configuration>
    
  • 写好Mapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.atguigu.springboot.mapper.UsersMapper">
        <select id="getUsersById" resultType="com.atguigu.springboot.bean.Users">
        select * from Users where id = #{id}
      </select>
        
        <insert id="insertUsers" >
        insert into Users(id,name,password,money) values(#{id},#{name},#{password},#{money})
        </insert>
    </mapper>
    
  • 在全局配置文件(此处为application.properties)进行相关配置

    mybatis.config-location=classpath:mybatis/mybatis-config.xml
    mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
    #注:数据库相关配置在前面已经配置过了
    
  • 写好Mapper.java

    //非注解
    @Mapper
    public interface UsersMapper {
        public Users getUsersById(int id);
    
        public int insertUsers(Users users);
    }
    
  • 写Controller

    @RestController
    public class UsersController {
        @Autowired
        UsersMapper usersMapper;
    
        @GetMapping("/Users/{id}")
        public Users getUsers(@PathVariable("id")int id){
            Users users = usersMapper.getUsersById(id);
            return users;
        }
    
        //@Options(useGeneratedKeys = true,keyProperty = "id") //使用自增主键
        @GetMapping("/insertUsers")
        public Users insertUsers(Users users){
            usersMapper.insertUsers(users);
            return users;
        }
     }
    //注:非注解的Controller与注解的Controller没区别
    

2.SpringBoot整合JPA

  • 编写bean与数据库表映射(使用JPA相关注解)

    @Entity //告诉JPA这是一个实体类
    @Table(name="Users") //映射表名,省略则表名默认为类名
    public class Users {
    
      @Id  //标志主键
      @GeneratedValue(strategy = GenerationType.IDENTITY) //自增主键
      private int id;
    
      @Column(name = "name") //标志表中的某列,这个注解默认省略
      private String name;
      private String password;
      private String money;
    
    
      public int getId() {
        return id;
      }
    
      public void setId(int id) {
        this.id = id;
      }
    
    
      public String getName() {
        return name;
      }
    
      public void setName(String name) {
        this.name = name;
      }
    
    
      public String getPassword() {
        return password;
      }
    
      public void setPassword(String password) {
        this.password = password;
      }
    
    
      public String getMoney() {
        return money;
      }
    
      public void setMoney(String money) {
        this.money = money;
      }
    
    }
    
  • 编写Dao接口进行增删改查(这里的dao称为Repository)

    public interface UsersRepository extends JpaRepository<Users,Integer> { //Users为实体类,String为@Id(主键)的类型
        
    }
    
    //注: JpaRepository中内置了crud(增删改查),继承之后我们就不用写crud对应的sql语句了
    
  • 基本的配置

    spring:
      datasource:
        url: jdbc:mysql://localhost:3306/mysql
        username: root
        password: 15152697858.*twj
    
      jpa:
        hibernate:
          #ddl-auto 定义数据表的生成策略,update 没有则建表,有则更新
          ddl-auto: update
    
  • 编写Controller进行测试

    @RestController
    public class UsersController {
    
        @Autowired
        UsersRepository usersRepository;
    
        @GetMapping("/Users/{id}")
        public Users getUsers(@PathVariable("id")Integer id){
            Users users = usersRepository.findById(id).get();
            return users;
        }
    
        @GetMapping("/insertUsers")
        public Users insertUsers(Users users){
            Users newUsers = usersRepository.save(users);
            return newUsers;
        }
    }
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值