JavaWeb_16_SpringBoot


本文介绍SpringBoot框架。

1. 概述

前面几篇文章介绍了经典的框架Spring、SpringMVC以及MyBatis。那么为什么还会出现SpringBoot呢,之前的框架有什么缺点,SpringBoot做了哪些提升呢?

前面Spring和SpringMVC框架有以下特点:

都有容器的概念,在创建对象以及其他配置时,有大量的配置文件。而且大型项目需要很多的对象,所以配置文件有很多的标签等等。所以,大量的配置文件以及大量的标签,使得我们在使用时容易出错。

另外,Spring在集成第三方框架时也会需要配置文件。所以在集成外部框架的时候,也需要手动创建多个配置文件;并且需要掌握该框架的具体用法。

还有,事务等等都需要配置文件。

总体上说,Spring框架随着项目的增大,配置文件越来越臃肿和复杂,集成其他框架也不是很流畅。因此,开发了SpringBoot框架,该框架可以使得我们少写配置文件,尽快进入开发业务功能阶段。他可以把大多数框架以及第三方资源已经写好的配置,直接集成到项目中使用。SpringBoot可以实现Spring和SpringMVC的功能,而不再写太多的配置文件。常用的配置文件以及第三方库已经由该框架设置好了。SpringBoot就相当于是不需要配置文件的Spring以及MVC。

2. Xml和JavaConfig

2.1 什么是JavaConfig

通过Spring发现,xml是作为配置文件的形式出现的。主要用于集成一些框架,以及声明对象。而SpringBoot主要采用注解来创建对象,而不再使用配置文件。JavaConfig则是基于注解的具体实现方式。

JavaConfig是Spring提供的使用Java类来配置容器,这是配置Spring IOC容器的纯Java方法,即采用Java类来配置容器,替代原来的xml文件;在Java类中可以创建对象,将该对象放到Spring容器中,即注入到容器中。具有以下优点:

  1. 可以使用面向对象的方式,一个配置类可以继承配置类,可以重写方法。
  2. 避免繁琐的xml配置。

两种方法简单对比案例如下所示:

  1. 以原始xml配置文件方式创建对象。创建maven项目,添加spring-context依赖以及junit依赖。之后,创建实体类,创建xml配置文件(Spring)并在其中用bean标签声明实体类对象。并在测试类中,手动创建容器对象,并获取实体类对象。
  2. 以JavaConfig方式创建对象。创建maven项目,添加spring-context依赖以及junit依赖。创建实体类,创建JavaConfig类(用@Configuration注解修饰),在类中创建方法,方法中和Java编程一样,创建要注入的对象,方法的返回值是该对象,在类上面用@Bean修饰。并在测试类中,手动创建容器对象,并获取实体类对象。

可以看到,其实两者没什么区别,只是以不同的形式体现而已。JavaConfig则是以Java方式来做的,xml则是以配置文件方式来做的。

2.2 Xml配置容器

要声明的对象类,如实体类如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Student {

private String name;
private Integer age;
private String sex;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}

xml配置文件中声明对象。

1
2
3
4
5
6
<!-- 声明bean对象 -->
<bean id="myStudent" class="org.hianian.entity.Student">
<property name="name" value="李思" />
<property name="age" value="20" />
<property name="sex" value="女" />
</bean>

测试类如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void test01(){

String config = "beans.xml";

// 手动创建容器,以配置文件方式作为参数。
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);

// 从容器中获取指定的对象
Student student = (Student) ctx.getBean("myStudent");
System.out.println(student);
}

2.3 JavaConfig配置容器

注意,使用JavaConfig来配置容器,需要以下两个注解支持:

  1. @Configuration:放在类的上面,表示这个类作为配置文件使用;
  2. @Bean:用来声明对象,将该对象注入到容器中;

例子如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 这是一个类文件,可以有多个类
// 如果类是用@Configuration注解修饰,表明这个类用作配置文件(部分)使用。

@Configuration
public class SpringConfig {

// 这个方法的返回值是对象类型,且方法的上面是用@Bean注解修饰的。
// 满足上述两个条件,此时该方法的返回值对象就会被注入到容器中。相当于xml配置文件中的<bean>标签
// @Bean注解的作用是将该返回值对象注入到Spring容器中。
// 另外,@Bean可以指定该对象在容器的名称,如果不指定,那么就默认是方法名;

@Bean
public Student createStudent(){

Student s1 = new Student();
s1.setName("张三");
s1.setAge(25);
s1.setSex("男");

return s1;
}

@Bean(name = "wwStudent")
public Student makeStudent(){

Student s1 = new Student();
s1.setName("王五");
s1.setAge(23);
s1.setSex("男");

return s1;
}
}

注意,手动创建容器对象的时候,不再采用原始的ClassPathXmlApplicationContext方式,而是采用AnnotationConfigApplicationContext方式,参数为具体方法的反射类型Class类型。测试类如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 使用JavaConfig方式作为配置文件
*/
@Test
public void test02(){

// 创建容器对象,以JavaConfig注解对象作为参数
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

Student bean = (Student) ctx.getBean("createStudent");
System.out.println(bean);

System.out.println("-----------------");
bean = (Student) ctx.getBean("wwStudent");
System.out.println(bean);
}

2.4 @ImportResource注解

该注解是用来导入现有的xml配置,等同于xml文件的resources标签。比如想要使用JavaCofnig做容器配置,但是现在仍存在xml配置文件。后续只能创建一个容器,而创建容器的方法只能有一种,所以要么是JavaConfig,要么是xml。

如果采用JavaConfig,那么就需要在该文件中导入已有的xml。即采用@ImportResource注解。该注解用在JavaConfig类上面使用,有value和locations两个属性,都表明xml文件的位置,也就是类路径。此时,xml文件中的bean对象就被放入了容器中。

xml配置文件applicationContext.xml中的bean标签如下所示:

1
2
3
4
5
<bean id="myCat" class="org.hianian.entity.Cat">
<property name="cardId" value="2022" />
<property name="name" value="Tom" />
<property name="age" value="12" />
</bean>

JavaConfig中的注解如下所示:

1
2
3
4
5
@Configuration
@ImportResource(value = "classpath:applicationContext.xml")
public class SpringConfig {
...
}

2.5 @PropertyResource注解

除了xml配置文件,还有一些属性配置文件.properties,如数据库url密码等等。读取这些文件用于创建bean对象时给属性赋值。此时就需要读取属性配置文件,获取属性值并赋值。

该注解的用法如下:

  1. 在JavaConfig类上面使用@PropertyResource注解引入properties文件的位置。(这部分操作是使得JavaConfig读取到属性配置文件)
  2. 使用@Component注解和@Value(value=”${key}”)的方式获取properties文件中的key的属性值。(这部分操作是在实体类中,即以注解形式创建实体类对象。注意,此时还没创建,需要在容器中创建)
  3. 在JavaConfig类中,告诉容器,提取以注解形式创建的对象。即xml中的组件扫描器标签,此时可用@ComponentScan()注解来扫描。即扫描要创建的类。

为了简单起见,这里设置简单的配置文件,(这种方式似乎只能采用注解的形式创建对象),以注解形式给实体类对象赋值。案例如下所示:

属性配置文件config.properties如下所示:

1
2
tigerName=dongbei
tigerAge=8

实体类如下所示(因为采用注解方式创建对象,所以添加了注解):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component("tiger")
public class Tiger {

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

@Value("${tigerAge}")
private Integer age;

@Override
public String toString() {
return "Tiger{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

JavaConfig如下所示:

1
2
3
4
5
6
7
@Configuration
@ImportResource(value = "classpath:applicationContext.xml")
@PropertySource(value = "classpath:config.properties") // 仅仅是读取属性配置文件,用于以注解形式创建对象
@ComponentScan(basePackages = "org.hianian.entity") // 组件扫描器,即扫描@Component注解,扫描以注解形式创建的对象。需要指定包名
public class SpringConfig {
...
}

测试类如下所示:

1
2
3
4
5
6
7
8
9
@Test
public void test04(){

// 创建容器对象,以JavaConfig注解对象作为参数
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

Tiger bean = (Tiger) ctx.getBean("tiger");
System.out.println(bean);
}

2.6 小节

实际上,原始xml配置文件创建对象有两种方式,一种是bean标签,一种是注解并在xml文件中使用组件扫描器;上面前2个例子中介绍的是如何将xml中标签形式创建的对象替换为JavaConfig形式创建,以及采用@ImportResource注解形式将xml以bean标签创建的对象引入到JavaConfig中。

那么以注解形式创建对象,是在Java类中操作的,因此JavaConfig不需要对其转换。对于组件扫描器扫描,xml中的组件扫描器是标签,在JavaConfig中对应的注解是@ComponentScan(basePackages = "org.hianian.entity")。另外,xml中需要指明属性文件的位置,此时在JavaConfig中指明位置采用的是@PropertySource(value = "classpath:config.properties") 标签。

总体上说,JavaConfig提供了和xml配置文件相同的功能,即以类、方法和注解的形式等同于xml以及其标签。也就是说,JavaConfig仍然有两种方式创建对象:方法、注解。

3. SpringBoot入门

SpringBoot作为Spring全家桶的一员,可在官网Spring | Home中找到。有以下几个特点:

  • Create stand-alone Spring applications

    创建独立的Spring应用。

  • Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)

    内嵌常用的服务器(默认是Tomcat)。

  • Provide opinionated ‘starter’ dependencies to simplify your build configuration

    提供一个starter起步依赖,简化应用的配置。比如MyBatis等框架的依赖,这些基本的配置不再手动配置,只需要配置起步依赖即可。

  • Automatically configure Spring and 3rd party libraries whenever possible

    尽可能地自动配置Spring和第三方库用到的对象。

  • Provide production-ready features such as metrics, health checks, and externalized configuration

    提供了统计、健康检查、外部配置等功能。

  • Absolutely no code generation and no requirement for XML configuration

    不用生成代码,不用使用xml配置文件。

即可以简化Spring、SpringMVC的使用,核心还是IOC容器。

SpringBoot的参考文档:Spring Boot Reference Documentation;API为:Overview (Spring Boot 2.6.7 API)

3.1 SpringBoot项目的创建方式

SpringBoot项目有两种创建方式,一种是SpringBoot提供的初始化器创建(需要联网),一种是使用Maven来创建。

3.1.1 使用SpringBoot的初始化器创建项目

在IDEA中新建Module,左侧选择类别,在maven上面有一个Spring Initializer,这就是初始化器。有两个需要设置,一个是JDK,一个是URL,这个URL默认就是Spring官方提供的网址,该网址会引导创建基于SpringBoot的项目。(也可采用国内的镜像网址:http://start.springboot.io,或者在浏览器中输入该网址,手动生成并下载压缩包,然后导入开发工具中。)

springboot_001.png (1462×754) (gitee.io)

点击Next,此处设置模块的相关信息,如项目坐标。下面四个就是在pom文件中的描述信息。

springboot_002.png (1453×755) (gitee.io)

配置好信息后,点击Next,此处设置项目所需的依赖项(以前都是手动在pom文件中写坐标),这步其实就是上面说的自动配置。左侧第一栏就是依赖的类别,比如开发工具、Web项目的、SQL的、NoSQL的等等。(由于这是案例,所以可以不选,这里简单选了Web中的Spring Web,也就是SpringMVC,简单看一下)

springboot_003.png (1471×765) (gitee.io)

点击Next,此处即设置模块的位置。点击Finish,自动下载(注意,下面两个路径是本项目的存储路径,需要再设置一个子文件夹)。

springboot_004.png (1459×761) (gitee.io)

创建好后的目录结构如下所示:

springboot_005.png (417×538) (gitee.io)

其中

  1. .gitignore文件是Git的一个文件;(不需要过多关注)
  2. .iml文件是IDEA的一个文件;(不需要过多关注)
  3. HELP.md是一个描述帮助文件;(不需要过多关注)
  4. .mvn文件夹、mvnw以及mvnw.cmd是和maven有关的三个文件,相当于maven增强版的一个工具,和maven作用类似。
  5. pom.xml文件则是负责maven依赖的文件。

其中3和4中的文件,实际上是不需要的,可以删掉。

接下来就是src目录了,和Spring类似,也分为main和test子目录。

  1. main

    1. Java:项目程序源码
    2. resources
      1. static:存放静态资源:css、js、图片等等;
      2. templates:存放模板文件,类似jsp,用于显示数据;
      3. application.properties:SpringBoot重要的配置文件;
  2. test

    测试

查看pom.xml文件,可以发现有如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<!-- 本项目的父项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<!-- 在创建项目时设置的坐标 -->
<groupId>com.hianian</groupId>
<artifactId>springboot02</artifactId>
<version>1.0.0</version>

<!-- 项目描述信息 -->
<name>springboot02</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>

<!-- 基于SpringBoot的web的起步依赖,没有版本,表明采用父项目中规定的版本 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 基于SpringBoot的test依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<build>
<plugins>

<!-- SpringBoot项目用到的maven插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

查看maven依赖,可看到有相关的starter初始化器,并将常用的依赖spring等自动加入了。

3.1.2 使用maven创建项目

上面的方式需要使用网络,其实SpringBoot项目和普通的maven项目没什么区别。主要有以下两点区别:

  1. SpringBoot必须有父项目

    1
    2
    3
    4
    5
    6
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.8</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
  2. resources资源目录下有三个文件(夹):static、templates、application.properties。

因此,这两个区别我们可以手动实现。如果需要用到其他起步依赖,再手动添加即可,比如web依赖等等。

springboot_006.png (382×292) (gitee.io)

3.1.3 SpringBoot简单案例

这里使用SpringBoot开发web小案例。

首先采用上面任意一种方式创建SpringBoot项目。之后,在Java目录下创建Controller,代码如下所示:

1
2
3
4
5
6
7
8
9
@Controller
public class HelloSpringBoot {

@RequestMapping("/hello")
@ResponseBody
public String helloSpringBoot(){
return "欢迎使用SpringBoot";
}
}

(如果采用自带初始化器创建的项目会自带一个Application的Java类,该类由@SpringBootApplication注解修饰。如果采用maven创建,则需要手动创建一下该类。类名可自定义,但是无论是web项目还是其他项目,都是通过该类中的main方法来启动的。)在Application类文件中,运行main方法,启动本项目。代码如下所示:

1
2
3
4
5
6
7
8
@SpringBootApplication
public class Application {

public static void main(String[] args) {

SpringApplication.run(Application.class, args);
}
}

启动界面如下所示:

springboot_007.png (1870×739) (gitee.io)

在详细信息里面可看到:: Tomcat started on port(s): 8080 (http) with context path '',即该项目没有根路径,直接在浏览器中的端口后面输入上面控制器中的路径即可。如下所示:

springboot_008.png (616×184) (gitee.io)

可以看到,什么中央调度器啊、容器啥的都不再需要手动配置了,SpringBoot已经将这些内置好了。大大简化了项目的开发的前期准备过程,使得可以更加专注业务功能的实现。

3.1.4 @SpringBootApplication注解

通过案例可以看到,SpringBoot的核心似乎就是这个@SpringBootApplication注解,通过这个注解修饰的类中的主方法来启动整个项目。查看该注解的详细信息,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};

@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};

@AliasFor(
annotation = ComponentScan.class,
attribute = "nameGenerator"
)
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}

上面的元注解的作用分别如下:

  1. @Target({ElementType.TYPE}):表明SpringBootApplication注解修饰的位置
  2. @Retention(RetentionPolicy.RUNTIME):表明该注解保存的位置
  3. @Documented:表明该注解可被Javadoc生成文档
  4. @Inherited:表明该注解可继承

其余的三个注解是本注解的核心元注解,本注解是一个复合注解,由这三个注解完成

  1. @SpringBootConfiguration

    点击进去,可发现该注解有一个@Configuration元注解修饰,即本注解修饰的类可当做配置文件使用。(那么本注解修饰的子注解(@SpringBootApplication)修饰的类Application当然也可当做配置文件使用,和JavaConfig一样,可@Bean创建对象放入到容器中。)

  2. @EnableAutoConfiguration

    启用自动配置,即自动把常用的Java对象配置好,注入到Spring容器中。例如可以把MyBatis创建好放入到容器中。

  3. @ComponentScan

    组件扫描器,即找到注解,根据注解的功能创建对象(如@Component),给属性赋值(如@Value)等等。注意,仔细观察可发现,在2.5的例子中,用@Component创建对象需要用到组件扫描器;但是在3.1.3中,用@Controller创建对象却没有用组件扫描器。

    其实组件扫描器有一个默认规则:自动扫描所在类的所在包以及其子包中所有的类。即在3.2.3中,因为@SpringBootApplication注解包含了组件扫描器,并且所在类是在项目的主包中,且@Controller修饰类在子包中。因此此处不需要显式设置组件扫描器也可,因此可找到@Controller并创建对象。

总之,使用@SpringBootApplication有很多个作用,首先当前类可作为配置文件使用;另外可启动组件扫描器扫描当前类所在包以及子包中的所有类(创建对象);启动自动配置,把常见的框架所需的对象都自动创建好,放入容器中。

3.2 SpringBoot核心配置文件

application.properties文件是SpringBoot的核心配置文件。这种文件其实也可以是.yml结尾的文件。这两种格式文件在内容上是一致的,仅仅是格式上有区别。最开始使用的是.properties格式,现在主推的是.yml格式。

注意,当两个格式的配置文件同时存在时,使用的是properties配置文件。注意,名称必须为Application。

可以在该文件中设置项目的根路径以及开放的端口号。

3.2.1 properties配置文件

该文件中的格式是(k=v),#为注释。设置是上下文路径以及端口号如下所示:

1
2
3
4
5
# 设置端口号
server.port=8090

# 设置应用的路径
server.servlet.context-path=/myboot

其实还可以设置其他属性,输入server.之后可自动弹出很多个选项。

3.2.2 yml配置文件

yml是一种yaml格式的配置文件,主要采用一定的空格、换行等格式排版进行配置。yaml是一种直观的能够被计算机识别的数据序列化格式,容易被人类阅读,yaml类似于xml,但是语法比xml简洁很多,值与前面的冒号配置项必须要有一个空格,.yml也可换成.yaml

该文件中的格式是(k: v)。注意,冒号后面有空格。这种格式的文件表达的内容比较清晰。和.properties文件一样,在输入的时候,自动弹出并自动分级。

1
2
3
4
server:
port: 8070
servlet:
context-path: /myboot2

3.2.3 多环境配置

在实际开发的过程中,我们的项目会经历很多的阶段(开发——>测试——>上线),每个阶段的配置也会不同,例如:端口、上下文路径、数据库url以及账号密码等等。那么这个时候为了方便在不同的环境之间切换,SpringBoot提供了多环境配置。

而SpringBoot的核心配置就是前面说的application.yml/properties文件。因此可设置多个配置文件(每个配置文件对应一个环境),此时在不同的阶段使用不同的配置文件即可。这些配置文件的命名规则是:application-环境名称.yml/properties。如下所示:

1
2
3
4
5
6
7
# 开发阶段的配置文件 application-dev.yml

server:
servlet:
context-path: /dev

port: 8080
1
2
3
4
5
6
7
# 测试阶段的配置文件 application-test.yml

server:
servlet:
context-path: /test

port: 8090
1
2
3
4
5
6
7
# 线上运行阶段的配置文件 application-online.yml

server:
servlet:
context-path: /online

port: 8070

之后,再手动指定要读取的配置文件即可。因为默认会读取application.properties/yml中的内容,所以可在该文件中指定具体的配置文件。即使用spring.profiles.active=环境名称,注意,这个环境名称就是上述配置文件中杠后面的内容。如下所示:

1
2
3
4
5
6
7
8
# application.properties

# 激活使用哪个配置文件
# spring.profiles.active=dev

# spring.profiles.active=test

spring.profiles.active=online

3.2.4 SpringBoot自定义配置

SpringBoot的核心配置文件中,除了使用内置的配置项之外,我们还可以在自定义变量,然后采用下面的注解来读取变量值。

3.2.4.1 @Value注解

正如2.5中例子所示,@Value注解可以读取.properties文件中的key-value。因此@Value也可读取核心配置文件中的内容。application.properties文件中的内容如下所示:

1
2
3
4
5
6
7
8
9
10
# 配置端口号以及项目路径
server.port=8081
server.servlet.context-path=/springboot08

# 自定义变量
company.name=fuyun
company.website=www.hianian.com
company.address=北京丰台

site=www.baidu.com

采用@Value注解读取变量值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Controller
public class HelloController {

@Value("${server.port}")
private Integer port;

@Value("${server.servlet.context-path}")
private String contextPath;

@Value("${company.address}")
private String companyPath;

@Value("${site}")
private String site;

@RequestMapping("/doSome")
@ResponseBody
public String doSome(){

return this.port + "--->" + this.contextPath + "--->" + this.companyPath + "--->" + this.site;
}
}
3.2.4.2 @ConfigurationProperties注解

该注解用于将整个文件映射成一个Java对象,这时通过调用Java对象的属性即可获取该值。适用于自定义配置项比较多的情况。(因为如果仍然采用@Value方式的话,因为配置项比较多,就需要写多个@Value注解,容易出现错误。)

此时可设置一个Java类,类属性就是配置文件中想要读取的变量名,并设置set和get方法。在类上面加入@ConfigurationProperties注解,该注解有一个prefix前缀属性,表明本类中的属性用于匹配哪个前缀后面的子名。代码如下所示(配置文件中的内容仍然是上面的内容):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 设置前缀,即在配置文件中找company前缀的变量组,并依次赋值给同名属性
@ConfigurationProperties(prefix = "company")
@Component // 创建对象(个人感觉上面的配置注解就相当于是在属性上面加入了@Value注解)
public class companyInfo {

private String name;
private String website;
private String address;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getWebsite() {
return website;
}

public void setWebsite(String website) {
this.website = website;
}

public String getAddress() {
return address;
}

public void setAddress(String address) {
this.address = address;
}

@Override
public String toString() {
return "companyInfo{" +
"name='" + name + '\'' +
", website='" + website + '\'' +
", address='" + address + '\'' +
'}';
}
}

为了测试,这里采用@Component注解形式创建对象。在Controller中获取该对象,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Controller
public class HelloController {

// 以自动注入方式给属性赋值
@Resource
private companyInfo ci;

@RequestMapping("/doInfo")
@ResponseBody
public String doInfo(){

return this.ci.toString();
}
}

3.3 SpringBoot中使用JSP(了解)

其实使用SpringBoot框架的时候,不推荐使用JSP。该框架本身并不支持JSP,需要单独配置(底层SpringBoot使用的是内嵌的Tomcat,并不是原始的Tomcat)。框架提供了模板技术,即Thymeleaf模板作为视图使用。

使用JSP需要如下几个步骤:

  1. 加入一个处理jsp的依赖,负责编译jsp文件。

    1
    2
    3
    4
    <dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-jasper</artifactId>
    </dependency>

    可以不加版本,使用父依赖的版本。

  2. 如果需要使用servlet、jsp、jstl等其他功能,需要单独加入这些依赖。

  3. 创建一个用于存放jsp的目录,一般叫做webapp。在main目录下创建。

    因为SpringBoot默认不支持JSP,所以创建的目录后,也不支持创建jsp文件。注意看下面目录中的图标:

    springboot_010.png (518×286) (gitee.io)

    此时需要将webapp目录设置为web资源目录。如下所示,选择file — > project structure,之后选择指定的模块,点击Web文件夹,在右侧中下方点击加号,选择添加的webapp目录即可。再次观看该目录图标,发生了变化:

    springboot_009.png (1651×882) (gitee.io)

    springboot_011.png (511×225) (gitee.io)

    springboot_012.png (537×283) (gitee.io)

  4. pom.xml文件中指定jsp文件的编译后的存放目录,为META-INF/resources

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <build>

    <!-- 指定jsp编译后的存放目录 -->
    <resources>
    <resource>
    <!-- jsp文件原来的目录 -->
    <directory>src/main/webapp</directory>

    <!-- 指定编译后的存放目录 -->
    <targetPath>META-INF/resources</targetPath>

    <!--指定要处理的文件,因为jsp文件不只存在于webapp下,还有可能存在于其子目录下-->
    <!-- 即将该webapp目录下所有子文件都编译到resources下 -->
    <includes>
    <include>**/*.*</include>
    </includes>
    </resource>
    </resources>
    </build>
  5. 创建Controller,访问JSP。

    创建的JSP页面如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
    <title>Title</title>
    </head>
    <body>
    <h3>使用JSP显示Controller中的数据:${data}</h3>
    </body>
    </html>

    Controller如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Controller
    public class JspController {

    @RequestMapping("/jsp")
    public String doJsp(HttpServletRequest request){

    // 将数据放入到请求域中
    request.setAttribute("data", "SpringBoot中使用Jsp");

    // 返回视图的逻辑名称(底层会采用视图解析器来拼接的)
    return "index";
    }
    }
  6. application.properties文件中配置视图解析器。

    1
    2
    3
    4
    # 配置视图解析器
    # 此处的/表示 src/main/webapp
    spring.mvc.view.prefix=/
    spring.mvc.view.suffix=.jsp

启动服务器,访问页面如下所示:

springboot_013.png (763×168) (gitee.io)

编译后的目录如下所示,可看到,确实在META-INF/resources下生成了JSP文件。

springboot_014.png (533×664) (gitee.io)

3.4 SpringBoot中使用ApplicationContext

SpringBoot也是基于容器的概念。在Spring以及SpringMVC中,创建容器有两种方式,一种是手动在Java程序中创建;一种是以标签形式,如监听器等等,在配置文件中创建。

那么在SpringBoot中怎么创建容器以及怎么使用容器呢?查看项目主程序,代码如下所示:

1
2
3
4
5
6
7
8
@SpringBootApplication
public class Springboot09Application {

public static void main(String[] args) {
SpringApplication.run(Springboot09Application.class, args);
}

}

SpringApplication.run()方法就是启动这个项目的代码,查看源码,run方法是SpringApplication类的静态方法,返回值是ConfigurableApplicationContext接口。如下所示:

1
2
3
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}

这个接口继承了ApplicationContext接口,也就是说,run方法返回值就是应用域对象,也就是容器。

1
2
3
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
...
}

可以看到,实际上项目启动的时候,就会自动创建容器。我们可以接收获取容器对象,也可以不获取。

3.5 CommandLineRunner接口

在开发中可能会有这样的场景,需要在容器启动后执行一些内容。比如读取配置文件、数据库连接之类的。SpringBoot给我们提供了两个接口来帮助我们实现这种需求。这两个接口分别为CommandLineRunner和ApplicationRunner。它们的执行时机为容器启动完成的时候,自动执行run方法

这两个接口中有一个run方法,我们只需要实现这个方法即可。这两个接口的不同之处在于:ApplicationRunner中的run方法的参数为ApplicationArguments,而CommanLineRunner接口中run方法的参数为String数组。反编译后的源码如下所示:

1
2
3
public interface CommandLineRunner {
void run(String... args) throws Exception;
}
1
2
3
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}

这两个接口的效果是一样的,所以使用哪个接口都行,当我们需要在容器完成后执行一些自定义的操作时,可采用这两个接口来完成

本质上说,该接口中的方法是被创建容器对象语句SpringApplication.run()自动调用的,但是也是在创建容器对象之后调用的。所以说,本接口中的run方法是可以获取到容器中的对象的。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 实现CommandLineRunner接口
@SpringBootApplication
public class Springboot10Application implements CommandLineRunner {

@Resource
private HelloService helloService;

public static void main(String[] args) {

System.out.println("准备创建容器对象");

// 创建容器对象
SpringApplication.run(Springboot10Application.class, args);

System.out.println("容器对象创建之后");
}

// 实现CommandLineRunner接口中的run方法,用于在创建完容器之后(语句执行完之后),自动执行。执行完本方法之后,在继续执行
// main方法中创建容器对象之后的语句。也就是说,本方法实际上是被创建容器对象方法run调用的。
@Override
public void run(String... args) throws Exception {

// 测试是否可以调用容器中的对象
String hS = helloService.sayHello("李思");
System.out.println("调用容器中的对象:" + hS);

// 参数String.. args 实际上就是main方法中的参数String[] args
System.out.println("容器对象创建好后执行本方法");

// 自定义操作

}
}

结果如下所示,可以看到接口中的run方法中的内容,是比创建容器对象语句后的语句要先执行的。

springboot_015.png (650×647) (gitee.io)

4. SpringBoot和Web组件

上面只是讲解了SpringBoot的入门使用。因为SpringBoot是Spring和MVC的提升版,因此,可用于Web项目的开发。本节介绍SpringBoot中的Web组件使用。

4.1 SpringBoot中的拦截器

拦截器是SpringMVC中的一种对象,能拦截对Controller的请求。框架其实已经有了内置的拦截器,但是我们也可以自定义拦截器,实现对请求的预先处理。自定义拦截器有如下步骤:

  1. 自定义类实现SpringMVC框架中的HandlerInterceptor接口,有preHandlepostHandleafterCompletion三个抽象方法。
  2. 将上述自定义类,创建对象,在SpringMVC的配置文件中声明该拦截器对象,以及声明要拦截的URL路径。

上面的步骤是SpringMVC中的步骤,其实在SpringBoot中也适用。只不过,SpringBoot将SpringMVC中的配置文件xml改为了WebMvcConfigurer接口,自定义配置类,实现该接口,并调用接口中的方法即可。(本质上还是SpringBoot用Java类文件来代替xml配置文件。

WebMvcConfigurer接口的部分源码如下所示:

1
2
3
4
5
6
7
public interface WebMvcConfigurer {

// 添加拦截器,参数是一个拦截器注册器,只需要将拦截器添加到该对象中即可
default void addInterceptors(InterceptorRegistry registry) {
}

}

SpringBoot中使用拦截器的步骤如下所示:

  1. 自定义拦截器类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class LoginInterceptor implements HandlerInterceptor {

    // 实现该方法,即相当于拦截器

    /**
    *
    * @param request
    * @param response
    * @param handler 被拦截的控制器对象
    * @return 返回值是boolean类型,true表示能被Controller处理,即拦截器通过该请求。false表示拦截器将其过滤掉。
    * @throws Exception
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    // 为了简单起见,这里不进行业务处理,仅仅输出一句话,表示该方法执行了。
    System.out.println("执行了LoginInterceptor的preHandle方法");
    return true;
    }
    }
  2. 创建拦截器对象,并设置其拦截路径以及不拦截路径等等。(此设置是在Java类配置文件中设置,即将其作为配置信息。注意,拦截器只能通过实现WebMvcConfigurer接口的配置类对象来添加到容器中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 自定义类实现WebMvcConfigurer接口,相当于将拦截器添加到MVC容器中。
    // 即原始SpringMVC中配置文件中的内容,均移动到了WebMvcConfigurer这个接口的方法中。
    // 注意,这是类也是作为配置信息出现的,所以需要加Configuration注解,将本类作为配置文件,创建对象
    @Configuration
    public class MyAppConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

    // 创建拦截器对象
    HandlerInterceptor handlerInterceptor = new LoginInterceptor();

    // 将拦截器对象加入到注册器中,并声明要拦截的地址等等
    InterceptorRegistration interceptorRegistration = registry.addInterceptor(handlerInterceptor);

    String[] inter = {"/user/**"};
    String[] exclude = {"/user/login"};

    // 指定要拦截的地址
    InterceptorRegistration interceptorRegistration1 = interceptorRegistration.addPathPatterns(inter);

    // 指定不拦截的地址
    InterceptorRegistration interceptorRegistration2 = interceptorRegistration1.excludePathPatterns(exclude);

    }
    }
  3. 创建Controller,对上述拦截URL进行测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Controller
    public class BootController {

    @ResponseBody
    @RequestMapping("/user/**")
    public String userAccount() {
    return "访问user/**地址";
    }

    @ResponseBody
    @RequestMapping("/user/login")
    public String adminAccount() {
    return "访问user/login地址";
    }
    }

其实,在SpringBoot中使用拦截器,和在SpringMVC中类似,只不过将拦截器声明到容器中的方式不同罢了。在SpringBoot中的思想就是以JavaConfig类文件代替配置文件。

4.2 SpringBoot中使用Servlet

本节讲解在SpringBoot框架中使用Servlet对象。步骤如下:

  1. 创建Servlet类,继承HttpServlet,实现业务方法

  2. 注册Servlet类对象,让框架能够找到Servlet。

    注意,和拦截器一样,Servlet类对象也只能通过某个特殊的配置类对象添加到容器中。

代码如下所示:

  1. 自定义Servlet类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class MyServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    // 通过response对象返回给浏览器一句话。
    resp.setContentType("text/html;charset=utf-8");

    PrintWriter writer = resp.getWriter();

    writer.println("执行的是Servlet对象");

    writer.flush();
    writer.close();
    }
    }
  2. Servlet类对象配置类,通过该配置类文件,将Servlet类对象添加到容器中,并设置其映射的URL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Configuration
    public class MyConfig {

    // 定义方法,用来注册Servlet对象。
    @Bean // 该注解表示本方法的返回值对象可加入到容器中
    public ServletRegistrationBean servletRegistrationBean(){

    MyServlet ms = new MyServlet();

    // 参数是Servlet对象,以及该Servlet处理的访问URL
    // 第一种方式,在创建对象的时候,将参数直接传入
    // ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(ms, "/myServlet");

    // 第二种方式,无参创建对象,后续调用方法传入
    ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();

    servletRegistrationBean.setServlet(ms);

    servletRegistrationBean.addUrlMappings("/myServlet");

    return servletRegistrationBean;
    }
    }

运行主程序,测试URL即可。

4.3 SpringBoot中使用Filter

本节介绍在SpringBoot中使用Filter。步骤和Servlet类似:

  1. 创建自定义过滤器类,实现Filter接口,实现业务方法
  2. 注册过滤器类对象,让框架能够找到过滤器。

代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
// 自定义过滤器类
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

// 实现业务处理,
System.out.println("执行了MyFilter的doFilter方法");

// 将请求和响应穿过本过滤器链
filterChain.doFilter(servletRequest, servletResponse);
}
}

配置类文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class MyConfig {

@Bean
public FilterRegistrationBean filterRegistrationBean(){

MyFilter mf = new MyFilter();

FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();

// 设置过滤器对象,以及过滤的URL
filterRegistrationBean.setFilter(mf);
filterRegistrationBean.addUrlPatterns("/user/*");

return filterRegistrationBean;
}
}

Controller测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Controller
public class MyController {

@RequestMapping("/user/account")
@ResponseBody
public String doFilters(){

return "执行过滤器方法";
}

@RequestMapping("/query/asdf")
@ResponseBody
public String doFilters2(){

return "不执行过滤器方法";
}
}

4.4 字符集过滤器的使用

字符集过滤器CharacterEncodingFilter主要用于解决post请求中乱码的问题。这个过滤器是SpringMVC框架中提供的,所以说,字符集过滤器和上面的Filter是一样的操作步骤,在设置好字符集过滤器的参数后,采用FilterRegistration注册过滤器并设置其过滤的URL。

**除此之外,还需要在主配置文件applicaiton.properties中设置server.servlet.encoding.enabled=false**,这是因为SpringBoot默认已经配置了CharacterEncodingFilter,而server.servlet.encoding.enabled默认为真,表示使用的是框架内部的字符集过滤器。

过滤器代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class CharFilter {

@Bean
public FilterRegistrationBean filterRegistrationBean(){

// 创建过滤器对象,并设置属性:编码方式、是否对请求编码有效、是否对响应编码有效
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("utf-8");
filter.setForceRequestEncoding(true);
filter.setForceResponseEncoding(true);

FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.addUrlPatterns("/*");

return filterRegistrationBean;
}
}

主配置文件内容如下所示:

1
2
# 设置为false,表示不使用框架内部的字符集过滤器。
server.servlet.encoding.enabled=false

4.5 在application.properties文件中设置过滤器

上一节提到,框架本身设置了字符集过滤器。因此,我们可直接修改字符编码方式,而不再自己设置字符集过滤器。

1
2
3
4
5
server.servlet.encoding.enabled=true
server.servlet.encoding.charset=UTF-8

# 强制请求和响应都使用charset属性的值。
server.servlet.encoding.force=true

5. ORM操作MySQL

前面讲解了SpringBoot集成Web组件,本节讲解SpringBoot集成MyBatis。ORM其实在Spring中提到过,是一个模块,用于操作数据库,ORM是Object Relation Mapping的简写,即Java对象和数据表的映射。

在SpringBoot中使用MyBatis框架操作数据,需要以下步骤:

  1. 在SpringBoot项目中加入MyBatis起步依赖:完成MyBatis对象的自动配置,并将对象放在容器中;
  2. pom.xml指定把src/main/java目录中的xml等资源文件包含到classpath中(配置Resource插件);
  3. 创建实体类Student;
  4. 创建Dao接口StudentDao,创建一个查询学生的方法;
  5. 创建Dao接口对应的Mapper文件、xml文件,写SQL语句;
  6. 创建Service层对象,创建StudentService接口和它的实现类。调用Dao对象的方法,完成数据库的操作;
  7. 创建Controller对象,访问Service;
  8. application.properties文件,配置数据库的连接信息。

5.1 创建SpringBoot项目

  1. 创建项目并选取MyBatis起步依赖

创建步骤的流程和前面类似,只不过在选择依赖的时候,需要将SQL中的MyBatis Framework、MySQL Driver选上。创建好后的项目依赖如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<dependencies>

<!-- SpringBoot的web起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- mybatis的起步依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>

<!-- mysql的驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

具体Maven显示的依赖如下所示,可以看到起步依赖其实包含了很多必备的基础依赖,即起步依赖自动为我们添加了依赖:

springboot_016.png (894×458) (gitee.io)

  1. pom.xml文件中添加resources资源插件,将SQL语句xml文件编译到target目录的classes目录下。一定要做,否则找不到xml文件,不会创建代理对象,也就访问不到这个对象和方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <build>

    <!-- 设置指定目录下的资源文件编译到指定target目录下的classes目录下 -->
    <resources>
    <resource>
    <directory>src/main/java</directory>
    <includes>
    <include>**/*.xml</include>
    <!-- 此处,最好是**/*.*,或者添加一个额外的resource配置,否则似乎会覆盖掉默认resources目录下文件编译后的位置信息。 -->
    </includes>
    </resource>
    </resources>
    </build>
  2. 创建实体类User

    注意,属性名要和数据表中的字段名一致。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    public class User {

    private Integer no;
    private String login;
    private String password;
    private String name;

    public Integer getNo() {
    return no;
    }

    public void setNo(Integer no) {
    this.no = no;
    }

    public String getLogin() {
    return login;
    }

    public void setLogin(String login) {
    this.login = login;
    }

    public String getPassword() {
    return password;
    }

    public void setPassword(String password) {
    this.password = password;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    @Override
    public String toString() {
    return "user{" +
    "no=" + no +
    ", login='" + login + '\'' +
    ", password='" + password + '\'' +
    ", name='" + name + '\'' +
    '}';
    }
    }
  3. 创建实体类的Dao接口,并创建操作方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 在SpringBoot中使用UserDao接口,需要在接口上面加Mapper注解,即告诉SpringBoot,这是接口,创建该接口的代理对象。
    *
    * 其实整合MyBatis有多种方式,Mapper只是其中一种。
    */
    @Mapper
    public interface UserDao {

    // 简单类型作为参数,因此可采用MyBatis中的注解给参数命名
    User selectByNo(@Param("userNo")Integer no);
    }
  4. 创建Dao接口的Mapper映射文件,xml形式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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="org.hianian.dao.UserDao">

    <!-- 定义sql语句 -->
    <select id="selectByNo" resultType="org.hianian.entity.User">
    select no, login, password, name from t_user where no=#{userNo}
    </select>

    </mapper>
  5. 创建service层接口以及实现类,用于调用Dao接口,实现业务功能

    1
    2
    3
    4
    public interface UserService {

    User queryUser(Integer no);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 采用注解声明这是业务层对象,自动创建该对象
    @Service
    public class UserServiceImpl implements UserService {

    // 自动注入赋值
    @Resource
    private UserDao userDao;

    @Override
    public User queryUser(Integer no) {

    // 简单实现一下Service层调用Dao

    User user = userDao.selectByNo(no);
    return user;
    }
    }
  6. 创建Controller对象,为用户URL请求调用不同的service业务对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Controller
    public class UserController {

    // 采用自动注入,将该接口的实现类对象赋值给本属性。(该对象在实现类中已经采用@Service注解生成了)
    @Resource
    private UserService userService;

    @RequestMapping("/userQuery")
    @ResponseBody
    public String queryUser(Integer no){

    // 注意,参数no可以以URL中的参数携带过来。即localhost:8080/orm/userQuery?no=1

    User user = userService.queryUser(no);
    return user.toString();
    }
    }
  7. 配置application.properties文件,配置数据库连接信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    server.port=9001
    server.servlet.context-path=/orm

    # 配置数据库
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://ip:端口/数据库?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8

    spring.datasource.username=用户名
    spring.datasource.password=密码

测试结果如下所示:

springboot_017.png (810×123) (gitee.io)

5.2 @MapperScan

上述是SpringBoot使用MyBatis的基本步骤。其实和其他程序类似,只不过需要创建Mapper的代理对象。SpringBoot提供了以下方式创建Mapper代理对象。

  1. @Mapper注解:放在Dao接口的上面,每个Dao接口都需要放置这个注解。缺点就是随着Dao接口数量的增加而注解增加

    作用就是:通过Mapper注解,找到Dao接口,然后找到该接口的xml文件,进而执行对应的SQL语句。

  2. @MapperScan注解:扫描指定的Dao接口。放置在主启动类Application上面,并以basePackages属性形式提供Dao接口所在的包名。如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @SpringBootApplication
    @MapperScan(basePackages = "org.hianian.dao")
    public class Springboot16Application {

    public static void main(String[] args) {
    SpringApplication.run(Springboot16Application.class, args);
    }

    }

    作用就是:通过@MapperScan注解,找到指定的包,并扫描该包中所有的Dao接口以及对应的xml文件,进而执行对应的SQL语句。

5.3 mapper文件和Java代码分开管理

仔细观察项目目录可以发现,Dao接口和其映射文件xml是放在一个目录下的。为了方便,一般情况下一个目录中存放的文件应该是同一类型的,因此可将mapper文件和Java代码分开存放。一般情况下,可将mapper映射文件放置到resources目录中自定的子目录下。此时因为java目录下没有了要编译的除.java外的文件,因此pom.xml就不需要设置resources插件了。

但是此时,因为Dao和Mapper映射文件不在同一个目录下,此时SpringBoot中的@Mapper注解和@MapperScan注解在找到Dao文件后,怎么找到映射文件呢?需要在application.properties文件中配置目录。

注意,resources目录中所有的内容都会在编译之后放置到target目录下的classes目录中。因此在application.properties文件中设置目录的时候,需要指定的是编译之后的目录(即类路径classpath+自定义目录)。

此时,springboot通过两个注解,会找到Dao接口,然后通过主配置文件中的配置信息找到其映射文件,进而创建代理对象,执行指定的SQL语句方法。

1
mybatis.mapper-locations=classpath:resources下自定义的目录/*.xml

目录如下所示,这样的话,目录还是比较清晰的:

springboot_018.png (557×557) (gitee.io)

5.4 事务支持

提到数据库,肯定要有事务的。本节讲解SpringBoot中使用数据库中的事务。先回顾一下Spring中如何使用事务的:

  1. 管理事务的对象:事务管理器(接口,接口中有很多的实现类,即负责不同数据库访问技术的具体事务管理)

    如JDBC、MyBatis需要使用DataSourceTransactionManager。

  2. 声明式事务:和编程式事务不同,不需要在代码中编写事务代码;而是采用注解以及配置文件形式来声明事务的控制内容。

    主要控制事务的隔离级别、传播行为以及超时时间等等。

  3. 事务的处理方式有两种:

    1. Spring框架中的@Tranactional
    2. AspectJ框架

那么在SpringBoot框架中怎么使用事务呢?(上面两种方式均可在SpringBoot中使用,下面逐个介绍)

第一种方式,采用Spring框架中的自带事务注解@Transactional:

  1. 在业务方法的上面加入@Transactional注解,加入注解后,该方法就有事务功能了。
  2. 最好加上本注解:在主启动类的上面加入@EnableTransactionManagement,表示启用事务管理器。
  3. 备注:@Transactional注解默认使用库的隔离级别,传播行为为REQUIRED,传播时间为-1。可以通过属性自己设置这些性质。如果修饰的方法中出现了异常,那么方法内部,异常前面已经“执行”的数据库操作就会回滚。即添加了事务。

Controller中的业务方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping("/insert")
@ResponseBody
@Transactional
public String doInsert(String login, String password, String name) {

Integer row = userService.insertUser(login, password, name);

// 手动抛出运行时异常。在没有设置事务的情况下,下面发生了异常,但是上面的插入操作却执行了。
// 在设置了事务的情况下,插入操作没有执行(回滚了)。
int num = 10 / 0;

return "插入新记录:" + row;
}

主启动类:

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@MapperScan(basePackages = "org.hianian.dao")
@EnableTransactionManagement
public class Springboot18Application {

public static void main(String[] args) {
SpringApplication.run(Springboot18Application.class, args);
}

}

6. 接口架构风格——RESTful(了解)

注意,这里的接口指的并不是Java中的interface,而是应用程序接口,即API。也就是软件层面的接口,最直观的就是Java程序中各种库函数的调用,也可以是访问Servlet映射的URL,等等这些都是接口。从广义上说,API就是获取某种功能、服务的通道。

风格指的就是组织方式,即API的外在表现形式,比如Java语言中的API就是:类名/对象.方法名(),通过这种形式来获取功能。而网络URL的传统风格就是:协议://IP地址:端口号/资源名?参数名=值&参数名=值。而RESTful则是一种新型的风格。

6.1 认识REST

REST是Representational State Transfer的缩写,也被称为表现层状态转移。是一种互联网软件架构设计的风格,但它并不是标准,它只是提出了一组客户端和服务器交互时的架构理念和设计原则,基于这种理念和原则设计的接口可以更加简洁,更有层次。REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。

任何技术都可以实现这种理念,如果一个架构符合REST原则,就称它为RESTful架构。比如我们要访问一个http接口:http://localhost:8080/boot/order?id=1021&status=1,采用RESTful风格的地址为:http://localhost:8080/boot/order/1021/1。即REST风格是将URL中的额外参数以“/”分割。

REST中的要素:

  1. 用REST表示资源和对资源的操作。在互联网中,REST表示一个资源或者一个操作。
  2. 资源使用URL表示的,在互联网中,使用的图片,视频,文本,网页等等都是资源。
  3. 资源使用名词表示。

资源:

  1. 查询资源:观看资源,通过URL找到资源
  2. 创建资源:添加资源
  3. 更新资源:更新编辑
  4. 删除资源:去除

REST风格:资源使用URL表示,通过名词来表示资源。使用http中的动作(请求方式:GET、POST、PUT、DELETE)表示对资源的操作(分别对应:查、增、改、删)。但是浏览器不支持PUT和DELETE,所以需要其他的方式来实现(可以采用POST模拟)

6.2 RESTful的注解

SpringBoot已经支持了这种风格。因为REST风格和传统URL不同,参数的表现形式不一样,因此需要专门切分URL来获取请求的资源以及参数。

6.2.1 @PathVariable

从URL中获取数据

6.2.2 @PostMapping

支持的post请求方式,等同于@RequestMapping(method=RequestMethod.POST)

1
2
3
4
@GetMapping("/add/{name}/{value}")
public String addToRedis(@PathVariable("name") String name, @PathVariable("value") String value){
// 获取路径中的name、value
}

6.2.3 @DeleteMapping

支持的delete请求方式,等同于@RequestMapping(method=RequestMethod.DELETE)

6.2.4 @PutMapping

支持的put请求方式,等同于@RequestMapping(method=RequestMethod.PUT)

6.2.5 @GetMapping

支持的get请求方式,等同于@RequestMapping(method=RequestMethod.GET)

6.2.6 @RestController

复合注解,是@Controller和@ResponseBody的组合。在类上面加入此注解,表明该类中所有的方法都加入了@ResponseBody。

1
2
3
4
5
6
7
8
9
10
11
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}

6.3 总结

简单地说,REST风格其实就是规定了请求资源以及对资源处理的一种新型的URL风格。针对这种URL风格,制定了后台从URL获取资源路径和参数的方法。其实就是类似Servlet中的url以及get、post等方式获取参数等等。代码案例就不再演示了。

另外,这几个注解似乎和RequestMapping类似,只不过取URL中的参数的方式不同。

7. SpringBoot集成Redis

本节讲解SpringBoot集成Redis,以小案例为基础。

Redis是一个NoSQL数据库,常作为缓存使用。通过Redis客户端可以在程序中访问Redis数据。Java语言中使用的客户端有Jedis、lettuce、Redission。SpringBoot以及Spring中使用RedisTemplate(StringRedisTemplate)模板类操作Redis数据,这个工具类是简化了客户端,和客户端的功能一样。

Redis是一个中间件,是一个独立的服务器,可直接调用,完成某个功能,不需要像框架那样需要编码才能使用。

案例需求如下:

完善根据学生id查询学生的功能,先从Redis缓存中查找,如果找不到,再从数据库中查找,然后放到Redis缓存中。之后,如果再查找类似的数据,可直接在Redis缓存中找到,提升效率。

7.0 RedisTemplate类

该类提供了若干个属性对象用于操作Redis,源码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class RedisTemplate {

// 操作String类型
private final ValueOperations<K, V> valueOps = new DefaultValueOperations(this);

// 操作List类型
private final ListOperations<K, V> listOps = new DefaultListOperations(this);

// 操作set类型
private final SetOperations<K, V> setOps = new DefaultSetOperations(this);

private final StreamOperations<K, ?, ?> streamOps = new DefaultStreamOperations(this, ObjectHashMapper.getSharedInstance());

// 操作ZSet类型
private final ZSetOperations<K, V> zSetOps = new DefaultZSetOperations(this);
private final GeoOperations<K, V> geoOps = new DefaultGeoOperations(this);
private final HyperLogLogOperations<K, V> hllOps = new DefaultHyperLogLogOperations(this);
private final ClusterOperations<K, V> clusterOps = new DefaultClusterOperations(this);
}

另外,提供了方法来获取上述对象。

springboot_021.png (847×591) (gitee.io)

7.1 项目之前需要安装配置好Redis

安装好Redis,可以访问。

7.2 需求实现步骤

SpringBoot集成Redis需要以下步骤。首先新建一个SpringBoot项目,注意在选择依赖的时候,需要选择NoSQL中的Redis。

springboot_019.png (1449×734) (gitee.io)

7.2.1 pom.xml

查看pom.xml文件,已经添加了Redis的起步依赖,如下:

1
2
3
4
5
<!-- Redis的起步依赖,可直接在项目中使用工具类RedisTemplate(StringRedisTemplate) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

查看Maven中的依赖,可发现,这里Redis的起步依赖实际上使用的是lettuce客户端,也就是说,我们在程序中通过RedisTemplate(StringRedisTemplate)类访问Redis数据库,实际上调用的是lettuce客户端中的方法,即SpringBoot在客户端的层面上又封装了一层,使我们更加方便调用数据库

springboot_020.png (821×256) (gitee.io)

7.2.2 核心配置文件application.properties

在核心配置文件中配置Redis数据库信息。

1
2
3
4
5
6
7
server.port=9003
server.servlet.context-path=/myredis

# 配置Redis
spring.redis.host=localhost
spring.redis.port=6379
#spring.redis.password=123

7.2.3 创建RedisConotroller

这里为了方便,直接在Controller中进行数据访问。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@RestController
public class RedisController {


/**
* 声明SpringBoot提供的RedisTemplate模板类对象,采用注入方式赋值
*
* 该类可采用三种泛型方式: <String, String>、<Object, Object>、不写
*
* 属性名只能是redisTemplate。
*/
@Resource
private RedisTemplate redisTemplate;

// 添加数据到Redis
// 此方法不需要在注明@ResponseBody注解,因为@RestController已经注明
@GetMapping("/add/{name}/{value}")
public String addToRedis(@PathVariable("name") String name, @PathVariable("value") String value){

// 调用模板类的方法向数据库添加
// 获取操作String类型的对象
ValueOperations valueOperations = redisTemplate.opsForValue();

valueOperations.set(name, value);

return "向Redis添加String类型的数据" + ", name=" + name + ", value=" + value;
}

// 获取数据
@GetMapping("/get/{k}")
public String getToRedis(@PathVariable("k") String k){

// 获取操作String类型的对象
ValueOperations valueOperations = redisTemplate.opsForValue();

Object o = valueOperations.get(k);

return "向Redis获取key=" + k + "的数据为:" + o;
}
}

7.2.4 启动Redis服务

7.2.5 执行SpringBoot Application启动类

7.2.6 使用Postman发送请求

7.2.7 启动浏览器访问Controller

springboot_022.png (714×110) (gitee.io)

springboot_023.png (646×123) (gitee.io)

7.2.8 Redis客户端中查看数据

springboot_024.png (548×196) (gitee.io)

可以看到,在key的前面部分出现了编码,其实在使用模板对象的时候,可采用StringRedisTemplate类型,对象名为stringRedisTemplate。

7.3 RedisTemplate和StringRedisTemplate的区别

  1. StringRedisTemplate把key和value都是作为String处理,使用的是String的序列化,可读性较好;(同时也意味着,这种类型只能存储字符串类型数据,如果存储对象,那是不可能的。
  2. RedisTemplate把key和value是先经过了JDK序列化,key和value是序列化的内容,不能直接识别。

(Java)序列化:把对象转化为可传输的字节序列(二进制)过程称为序列化。

(Java)反序列化:把字节序列还原为对象的过程称为反序列化。

为什么需要序列化?

序列化的最终目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从IO流里面读出数据的时候,再以这种规则把对象还原回来(反序列化)。

序列化只是一种拆装组装对象的规则,形式有多种多样,比如现在常见的序列化方式有:JDK(不支持跨语言,方便,性能最差)、JSON、XML、Kryo(不支持跨语言,支持Java,性能最好)、Thrift等等。JDK是Java自带的序列化,就是将对象转换为二进制数据,即byte[]。JSON就是那种key、value的形式。

7.3.1 修改默认的序列化方式

设置RedisTemplate序列化方式,可单独设置key,也可单独设置value,也可同时设置二者的序列化方式。在方法中,使用该对象的前面,直接调用方法即可。

1
2
3
// 设置序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());

8. SpringBoot集成Dubbo

略。

9. SpringBoot打包

SpringBoot可以打包为war或者jar文件,以两种方式发布应用。

9.1 打包war包

以访问Controller,跳转到JSP页面这个简单案例为例,打包成war文件,并可以将该文件部署到独立的Tomcat中运行测试。

注意,SpringBoot中使用JSP需要加入处理JSP的依赖,见3.3。运行好没问题后,在pom.xml文件中的<build>标签中指定打包后的文件名称。

1
2
<!-- 指定打包后的文件名称 -->
<finalName>myboot</finalName>

注意,在SpringBoot中开发的项目,使用的是SpringBoot内嵌的Tomcat。因此,如果我们想要将其打包,并且能够运行在独立的Tomcat中,需要将项目的主启动类继承SpringBootServletInitializer,并重写configure方法。

SpringBootServletInitializer类就是相当于原来的web.xml文件的替代,使用了嵌入式的Servlet,默认是不支持JSP的。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class Springboot20Application extends SpringBootServletInitializer {

public static void main(String[] args) {
SpringApplication.run(Springboot20Application.class, args);
}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Springboot20Application.class);
}
}

注意,因为是打包成war,所以还需要在pom.xml文件中指定打包类型为war。

1
2
<!-- 指定打包类型为war -->
<packaging>war</packaging>

之后,采用maven工具,先clean一下,再package,可以发现在target目录中出现了打包后的myboot.war文件。将其放到Tomcat的webapp目录下即可(这里要注意,SpringBoot中的Tomcat插件版本和自己要部署的Tomcat的版本要一致,否则,有的目录可能不支持URL映射。)。

综上,打包war包,有如下步骤:

  1. 指定打包后的文件名称以及打包的文件类型
  2. 在主启动类中继承SpringBootServletInitializer类,并重写configure方法(目的是为了支持部署到外在的web服务器)
  3. maven clean、maven package
  4. 把war文件放到Tomcat等服务器的发布目录中,启动服务器即可。

9.2 打包jar包

打包jar包和war包类似,需要下面两步:

  1. 指定打包后的文件名称以及打包的文件类型
  2. 指定springboot-maven-plugin版本(1.4.2-RELEASE)
  3. maven clean、maven package
  4. 生成的jar包是一个可独立运行的springboot项目(程序),直接java -jar 文件名.jar即可。

9.3 war和jar区别

打包成war包,运行war包需要服务器支持才能运行,但是可以充分利用服务器的资源优势以及其他功能。

打包成jar包,可以独立运行,不依赖服务器,但是内嵌的服务器在功能上没有外部独立服务器丰富,所以在功能上可能有所欠缺。

10. SpringBoot集成Thymeleaf模板(了解)

10.1 概述

本质上说,JSP也是一种模板,因为它的内容框架是不变的,只是利用${变量值}从后端动态获取数据。

Thymeleaf是一个流行的模板引擎,该模板引擎采用Java语言开发。模板引擎是一个技术名词,是跨领域跨平台的概念。在Java语言体系下有模板引擎,在C#、PHP语言体系下也有模板引擎,甚至在JavaScript中也会用到模板引擎技术,Java生态下的模板引擎有Thymeleaf、Freemaker、Velocity、Beetl(国产)等。

Thymeleaf对网络环境下不存在严格的要求,既能用于Web环境下,也能用于非Web环境下。在非Web环境下,他能直接显示模板上的静态数据;在Web环境下,它能像JSP一样从后台接收数据并替换掉模板上的静态数据。它是基于HTML的,以HTML标签为载体,Thymeleaf要寄托在HTML标签下实现。

SpringBoot集成了Thymeleaf模板技术,并且SpringBoot官方也推荐使用Thymeleaf来替代JSP技术。注意,Thymeleaf只是一种模板技术,本身并不属于SpringBoot。SpringBoot只是很好地集成这种模板技术,作为前端页面的数据显示,在过去的Java Web开发中,我们往往会选择使用JSP去完成页面的动态渲染,但是JSP需要编译成Java才能运行,效率较低。

Thymeleaf的官方网站:Thymeleaf

Thymeleaf的官方手册:Tutorial: Using Thymeleaf

10.2 案例入门

新建SpringBoot项目,注意,选择依赖的时候,除了选web依赖,还需要选择Thymeleaf模板引擎

springboot_025.png (1468×750) (gitee.io)

其实项目整体上没什么区别,和前面的类似,只不过Controller返回结果的时候,返回的视图不再是jsp,而是模板文件(一般情况下是html)。

代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Controller
public class ThymeleafController {

@RequestMapping("/hello")
public String helloThymeleaf(HttpServletRequest servletRequest) {

// 添加数据到request作用域,模板引擎可以从request中获取数据
servletRequest.setAttribute("data", "欢迎使用Thymeleaf模板引擎!");

// 指定视图(模板引擎使用的页面(一般情况下是html))
// 和jsp一样,采用逻辑名称,在配置文件中声明视图解析器(已经默认设置好)
return "helloThymeleaf";
}

}

注意,模板文件放在resources/templates中。模板文件如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>helloThymeleaf.html</title>
</head>
<body>
<h3>使用Thymeleaf的例子</h3>

<p>想显示数据</p>

<!-- 从服务端请求域获取数据 -->
<p th:text="${data}">显示数据</p>
</body>
</html>

其中th:text="${data}"会在服务端获取数据,并替换“显示数据”内容,如果有data则会替换,没有则不会替换,单独打开这个html也会正常显示

配置文件中关于模板的常规设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 默认是true,即后续的请求模板会自动从缓存中读取。
# 在开发阶段,最好设为false,使得修改可以立即访问到。
spring.thymeleaf.cache=false

# 设置编码格式(默认为UTF-8)
spring.thymeleaf.encoding=UTF-8

# 设置模板的类型(默认为HTML)
spring.thymeleaf.mode=HTML

# 设置视图解析器(默认就是放置在了templates,且格式为.html)
spring.thymeleaf.prefix=classpath:templates/
spring.thymeleaf.suffix=.html

模板引擎的依赖为:

1
2
3
4
5
<!-- 模板引擎的起步依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

10.3 表达式

10.3.1 标准变量表达式

语法格式为:${key},作用是获取request作用域中key的值。在模板文件(以html为例)中,在html的标签中使用th:text="表达式",相当于标签的属性。代码如下所示:

1
2
3
4
5
6
7
8
9
<!-- 从服务端获取数据 -->
<!-- 标准变量表达式,获取普通值 -->
<p th:text="${data}">显示数据</p>

<p>获取对象数据,可采用 对象.属性 或者 对象.方法:</p>
<p th:text="${stu.name}">name</p>
<p th:text="${stu.age}">age</p>
<p th:text="${stu.height}">height</p>
<p th:text="${stu.getName()}">name</p>

10.3.2 选择变量表达式

语法格式为:*{key},作用是获取request作用域中key的值。注意,选择变量表达式不能单独用,需要和 th:object 这个属性一起使用。目的是简化获取对象的属性值。代码如下所示:

1
2
3
4
5
6
7
8
<h3>选择变量表达式</h3>
<!-- 选择变量表达式,获取普通值 -->
<p>获取对象数据:</p>
<div th:object="${stu}">
<p th:text="*{name}">name</p>
<p th:text="*{age}">age</p>
<p th:text="*{height}">height</p>
</div>

10.3.3 链接表达式(URL表达式)

语法为:@{URL},作用是用于链接、地址的显示。注意,比如加th,意味着采用Thymeleaf引擎来处理。代码如下所示:

1
2
3
4
5
6
7
8
<h3>链接表达式</h3>
<a th:href="@{http://www.baidu.com}">链接到百度</a>
<a th:href="@{/tpl/queryAccount}">相对地址,没有参数</a>
<a th:href="@{'/tpl/queryAccounts?id=' + ${stu.age}}">获取后台数据,并作为超链接中的参数</a>

<!-- 推荐下面的方法传参 -->
<a th:href="@{/tpl/queryAccounts(id=${stu.age})">传入单个参数</a>
<a th:href="@{/tpl/queryAccounts(name='lisi', age=${stu.age})">传入多个参数</a>

10.4 Thymeleaf属性

模板的属性就是html原有的属性,只不过前面加了一个th前缀。加了th的属性,表明需要经过模板引擎处理。不过加不加th,其标签作用是一样的,只不过加了th可以使用表达式,动态获取数据更新页面。

10.4.1 th:action

定义后台控制台的路径,类似form表单中的action,主要是结合URL表达式,获取动态变量,形成新的URL。

10.4.2 th:method

设置请求方式,如post、get等等。

10.4.3 th:href

定义超链接,主要是结合URL表达式,获取动态变量,形成新的URL。

10.4.4 th:src

用于外部资源引入,常与@{}表达式结合使用。

10.4.5 th:text

用于文本的显示,该属性显示的文本在标签体中,如果是文本框,数据会在文本框外显示。要想显示在文本框内,使用th:value

10.4.6 th:style

设置样式

10.4.7 th:each

这个属性非常常用,比如从后台传来一个对象集合,那么就可以使用该属性遍历输出结果。它与JSTL中的<c: forEach>类似,该属性既可以循环遍历List集合,也可以循环遍历Array数组以及Map。语法如下所示:

1
2
3
<div th:each="集合循环成员, 循环的状态变量: ${key}">
<p th:text=“${集合循环成员}”></p>
</div>

其中,循环集合成员和循环的状态变量,两个名称都是自定义的。“循环的状态变量”这个名称可以不写,默认是“集合循环成员Stat”,该对象表示循环的信息,有多个属性,比如当前循环的索引下标、循环体的长度等等。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping("/theach")
public String thEach(Model model) {

List<Student> list = new ArrayList<>();

list.add(new Student("刘备", 41, 180));
list.add(new Student("关羽", 40, 175));
list.add(new Student("张飞", 39, 178));
list.add(new Student("赵云", 37, 170));
list.add(new Student("黄忠", 60, 165));

model.addAttribute("myStudents", list);

return "each";
}
1
2
3
4
5
6
<h3>循环遍历List</h3>
<div th:each="stu, stuI: ${myStudents}">
<h4 th:text="${stu.name}"></h4>
<h4 th:text="${stu.age}"></h4>
<h4 th:text="${stu.height}"></h4>
</div>

从结果可以看出,实际上循环显示的是整个<div>块,即each属性所在的标签。

遍历数组和遍历List一样。对于Map,因为是多个<k,v>,所以说,集合循环成员可以被看成是一个<k,v>对象,属性为key、value。那么Map中的对象属性值,也就是集合循环成员.value.属性名。总体上和List大同小异,相当于外面又套了一层。

10.4.8 条件判断if

和Java语言中一样。语法为:th:if="布尔条件",条件为true,则显示标签体内容。还有一个类似的,th:unless="布尔条件",当条件为false则显示标签体内容。

1
2
3
4
5
<div th:if="10 > 0"> 当条件为真时显示本内容 </div>

<div th:unless=" 10 < 0"> 当条件为假时显示本内容</div>

<div th:if="${sex == 'm'}">性别是男</div>

注意,如果是表达式,则需要在花括号里面写布尔表达式。并不是先取值${sex},再判断,${sex} == ‘m’。

10.4.9 switch-case判断语句

类似Java中的switch-case。其中“*”表示以上都不匹配。

1
2
3
4
5
<div th:switch="${sex}">
<p th:case="m">显示男</p>
<p th:case="f">显示女</p>
<p th:case="*">未知</p>
</div>

10.4.10 th:inline

内联,有三个取值:text、javascript、none。

  1. 内联text

    可以让Thymeleaf表达式不依赖于html标签,直接使用[[表达式]],即可获取动态数据,要求在父级标签上加th:inline="text"属性,在使用表达式的时候,采用[[表达式]]直接获取数据。

    因为之前获取动态数据,必须在设置标签属性为th:text="表达式",必须设置text属性。可通过内联表达式,直接获取动态数据。

    1
    2
    3
    <div th:inline="text">
    <p>显示数据:[[${sex}]]</p>
    </div>

    备注:其实th:inline="text"可写可不写,直接[[表达式]]就行。

  2. 内联javascript

    通过该属性,可以在JS脚本中,获取服务器后台中的数据。注意,是在脚本中。

    1
    2
    3
    4
    5
    6
    7
    <script type="text/javascript" th:inline="javascript">
    var name = [[${name}]];
    var age = [[${age}]];
    var sex = [[${sex}]];

    alert("获取到的数据为:" + name + "===" + age + "===" + sex);
    </script>

10.5 字面量

字面量和Java中的字面值是一回事,就是真实存在的数据。

  1. 文本字面量

    用单引号包围的字符串为文本字面量

  2. 数字字面量

    就是数字。

  3. boolean字面量

    false,true

  4. null字面量

    null

10.6 字符串连接

字符串连接主要有两种方式,一种是加号,一种是双竖线。如下所示:

1
2
3
<p th:text="'我是' + ${name} + ',我所在的城市是' + ${city}">显示数据</p>

<p th:text="|我是${name},我所在的城市是${city}|">显示数据</p>

可以看到,第二种方式比较简单直观,不需要单引号,也不需要写加号。

10.7 运算符

运算符主要有以下几种:

  1. 算术运算

    +,-,*,/,%

  2. 关系运算

    >,<,>=,<=,==,!=,gt,lt,ge,le,eq,ne

  3. 三元运算符

    布尔表达式 ? 值1 : 值2

10.8 Thymeleaf基本对象

模板引擎提供了一组内置的对象Tutorial: Using Thymeleaf,这些内置的对象可以直接在模板中使用,比较常用的内置对象有:

  1. #request对象

    表示HttpServletRequest对象

  2. #session对象

    表示HttpSession对象

  3. session对象(是2的简写)

    表示HttpSession对象。简化版的对象,以Map的<k,v>形式存储对象的属性名(方法名)和属性值(方法返回值)。

这些是内置对象,可以在文件中直接使用。即可以在模板文件中直接使用上述域对象,获取域中的数据。

假设已经在上述域中添加了数据。在html模板中直接获取对象并调用方法获取数据。

1
2
3
4
5
6
7
<p>获取作用域中的数据</p>

<p th:text="${#request.getAttribute('key')}"></p>

<p th:text="${#session.getAttribute('key')}"></p>

<p th:text="${session.key}"></p>

可以看到,实际上10.3中的表达式只能获取到的是request作用域中的数据,即默认采用内置对象HttpServletRequest。当然,不仅仅是getAttribute()方法,其实有很多种方法,如getRequestURL()、getRequestURI(),这里不再一一举例。

10.9 Thymeleaf内置工具类对象

模板引擎提供了一组工具类对象Tutorial: Using Thymeleaf,可以在模板中直接使用这些对象提供的功能方法。常见的功能类对象有如下:

  1. #dates:主要是对后台传过来的时间对象进行操作,比如格式化,取年月日等等
  2. #numbers:主要是操作数字,以不同的格式对数字进行处理,比如以货币形式、以高精度形式等等。
  3. #strings:主要是操作字符串,比如字符串切割、格式化等等。
  4. #lists:主要是处理list集合,比如计算元素个数,判断是否包含某元素等等。

可直接在html模板文件中使用这些对象及其方法。

注意,有时候并不知道是否有数据。比如有一个Dog对象dog,属性有name。同时有一个Zoo对象zoo,里面有属性Dog。那么当我们在Thymeleaf模板文件中访问的时候,一般是这种形式:${zoo.dog.name}。但是有时候,dog可能没有添加到zoo中,即该属性为null,此时访问就会报错。为了使得程序更加鲁棒,可采用问号的形式,即当为null的时候,就不访问:${zoo.dog?.name},也可都加问号${zoo?.dog?.name?}

10.10 内容复用(自定义模板)

自定义模板是复用的行为。可以把一些内容,当做模板,多次重复使用。语法为th:fragment="模板自定义名称",表示当前属性所在的标签就是模板,可重复多次引用该模板。此时在其他的地方可直接引用该模板。

1
2
3
4
5
6
7
8
9
<!-- 文件名为test.html -->
<div th:fragment="head">
<P>
Java开发
</P>
<p>
www.baidu.com
</p>
</div>

引用模板语法有以下两种:

  1. ~{模板所在文件名称 :: 模板自定义名称}
  2. 模板所在文件名称 :: 模板自定义名称

使用模板有两个:包含模板(th:include)、插入模板(th:insert)

1
2
3
4
5
6
7
<!-- 以插入形式引用模板,插入形式就是将原模板全部插入到该属性所在标签的里面 -->
<div th:insert="~{test :: head}"></div>
<div th:insert="test :: head"></div>

<!-- 以包含形式引用模板,包含形式就是将原模板内容 替换了 该属性所在的标签 -->
<div th:include="~{test :: head}"></div>
<div th:include="test :: head"></div>

如果想引用整个模板文件:

1
2
3
4
<!-- test表示文件名,html表示全部文件,或者省略后面的html -->

<div th:include="test :: html"></div>
<div th:include="test"></div>

11. 总结

参考的教程,基本上就这些内容。感觉没什么新内容,SpringBoot只是以JavaConfig文件形式代替了xml文件(以及相应的注解),以及用起步依赖自动将相关依赖一起引入。

总体上,简化了对象的创建以及依赖的导入。下面复习一下用到的注解,这些注解有的是JDK中的,有的是Spring的,有的是SpringBoot的。

11.1 创建对象的注解

注解 描述
@Controller 放在类的上面,创建控制器对象,注入到容器中。
@RestController 放在类的上面,创建控制器对象,注入到容器中。是@Controller和@ResponseBody的复合注解,表名该类的所有方法的返回值都是数据,即都被@ResponseBody修饰。
@Service 放在业务层的实现类上面,创建Service对象,注入到容器中。
@Repository 放在持久层Dao层的实现类上面,创建Dao上面,放入到容器中。(很少使用,因为使用了MyBatis框架,Dao对象会通过代理生成的。)
@Component 放在类的上面,创建该类对象,注入到容器中。(和三层架构没关系,就是创建对象。)

11.2 赋值的注解

注解 描述
@Value 简单类型的复制,在属性上面使用,如@Value(“张三”)。或者获取属性文件中的变量值,如@Value(${server.port})。
@Autowired 引用类型自动注入,支持byName、byType,默认是byName。用来放在属性和构造方法的上面。
@Qualifier 给引用类型赋值,使用byName方式。
@Resource (JDK中的)实现引用类型自动注入,支持byName、byType,默认是byName。用来放在属性之上。

11.3 其他注解

注解 描述
@Configuration 放在类的上面,表示这是一个配置类,相当于配置文件。
@Bean 放在方法的上面,表示将该方法的返回值对象,注入到Spring容器中。
@ImportResource 加载其他的xml配置文件,将文件中的对象注入到Spring容器中。
@PropertySource 读取其他的properties属性配置文件
@ComponentScan 组件扫描器,指定报名,扫描注解
@ResponseBody 放在方法的上面,表示方法的返回值是数据,不是视图
@RequestBody 把请求体中的数据,读取出来,转为Java对象使用
@ControllerAdvice 用在类的上面,表示控制器增强,表示此类提供了方法,可以对Controller增强功能(如异常处理)。
@ExceptionHandler 处理异常,放在方法的上面,表示该方法是处理异常的。
@Transactional 处理事务,放在service实现类的public方法上面,表示此方法有事务。
@SpringBootApplication 放在启动类上面,是一个复合注解(@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan)
@Mapper 放在类的上面,让MyBatis找到接口,创建其代理对象。
@MapperScan 放在启动类的上面,扫描指定的包,把这个包中的所有接口都创建代理对象,并将对象注入到容器中。
@Param 放在dao接口的方法的形参上面,作为命名参数使用。

12. 备注

参考B站《动力节点》。


文章作者: 浮云
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 浮云 !
  目录